Thomas Vachuska

Refactored topo view server-side for better separation of concerns.

Added addInstance/removeInstance/updateInstance messages.

Change-Id: If312d2401597a89cb18be51a784f62157059aebf
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.onos.gui;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.ObjectMapper;
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
21 +import com.fasterxml.jackson.databind.node.ObjectNode;
22 +import org.onlab.onos.cluster.ClusterEvent;
23 +import org.onlab.onos.cluster.ClusterService;
24 +import org.onlab.onos.cluster.ControllerNode;
25 +import org.onlab.onos.cluster.NodeId;
26 +import org.onlab.onos.mastership.MastershipService;
27 +import org.onlab.onos.net.Annotations;
28 +import org.onlab.onos.net.ConnectPoint;
29 +import org.onlab.onos.net.DefaultEdgeLink;
30 +import org.onlab.onos.net.Device;
31 +import org.onlab.onos.net.DeviceId;
32 +import org.onlab.onos.net.EdgeLink;
33 +import org.onlab.onos.net.Host;
34 +import org.onlab.onos.net.HostId;
35 +import org.onlab.onos.net.HostLocation;
36 +import org.onlab.onos.net.Link;
37 +import org.onlab.onos.net.Path;
38 +import org.onlab.onos.net.device.DeviceEvent;
39 +import org.onlab.onos.net.device.DeviceService;
40 +import org.onlab.onos.net.host.HostEvent;
41 +import org.onlab.onos.net.host.HostService;
42 +import org.onlab.onos.net.intent.IntentService;
43 +import org.onlab.onos.net.link.LinkEvent;
44 +import org.onlab.onos.net.link.LinkService;
45 +import org.onlab.onos.net.provider.ProviderId;
46 +import org.onlab.osgi.ServiceDirectory;
47 +import org.onlab.packet.IpAddress;
48 +
49 +import java.util.Iterator;
50 +import java.util.Map;
51 +import java.util.Set;
52 +import java.util.concurrent.ConcurrentHashMap;
53 +
54 +import static com.google.common.base.Preconditions.checkNotNull;
55 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
56 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
57 +import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
58 +import static org.onlab.onos.net.PortNumber.portNumber;
59 +import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
60 +import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
61 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
62 +import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
63 +import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
64 +import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
65 +
66 +/**
67 + * Facility for creating messages bound for the topology viewer.
68 + */
69 +public abstract class TopologyMessages {
70 +
71 + private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
72 + private static final String COMPACT = "%s/%s-%s/%s";
73 +
74 + protected final ServiceDirectory directory;
75 + protected final ClusterService clusterService;
76 + protected final DeviceService deviceService;
77 + protected final LinkService linkService;
78 + protected final HostService hostService;
79 + protected final MastershipService mastershipService;
80 + protected final IntentService intentService;
81 +
82 + protected final ObjectMapper mapper = new ObjectMapper();
83 +
84 + // TODO: extract into an external & durable state; good enough for now and demo
85 + private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
86 +
87 + /**
88 + * Creates a messaging facility for creating messages for topology viewer.
89 + *
90 + * @param directory service directory
91 + */
92 + protected TopologyMessages(ServiceDirectory directory) {
93 + this.directory = checkNotNull(directory, "Directory cannot be null");
94 + clusterService = directory.get(ClusterService.class);
95 + deviceService = directory.get(DeviceService.class);
96 + linkService = directory.get(LinkService.class);
97 + hostService = directory.get(HostService.class);
98 + mastershipService = directory.get(MastershipService.class);
99 + intentService = directory.get(IntentService.class);
100 + }
101 +
102 + // Retrieves the payload from the specified event.
103 + protected ObjectNode payload(ObjectNode event) {
104 + return (ObjectNode) event.path("payload");
105 + }
106 +
107 + // Returns the specified node property as a number
108 + protected long number(ObjectNode node, String name) {
109 + return node.path(name).asLong();
110 + }
111 +
112 + // Returns the specified node property as a string.
113 + protected String string(ObjectNode node, String name) {
114 + return node.path(name).asText();
115 + }
116 +
117 + // Returns the specified node property as a string.
118 + protected String string(ObjectNode node, String name, String defaultValue) {
119 + return node.path(name).asText(defaultValue);
120 + }
121 +
122 + // Returns the specified set of IP addresses as a string.
123 + private String ip(Set<IpAddress> ipAddresses) {
124 + Iterator<IpAddress> it = ipAddresses.iterator();
125 + return it.hasNext() ? it.next().toString() : "unknown";
126 + }
127 +
128 + // Produces JSON structure from annotations.
129 + private JsonNode props(Annotations annotations) {
130 + ObjectNode props = mapper.createObjectNode();
131 + for (String key : annotations.keys()) {
132 + props.put(key, annotations.value(key));
133 + }
134 + return props;
135 + }
136 +
137 + // Produces an informational log message event bound to the client.
138 + protected ObjectNode info(long id, String message) {
139 + return message("info", id, message);
140 + }
141 +
142 + // Produces a warning log message event bound to the client.
143 + protected ObjectNode warning(long id, String message) {
144 + return message("warning", id, message);
145 + }
146 +
147 + // Produces an error log message event bound to the client.
148 + protected ObjectNode error(long id, String message) {
149 + return message("error", id, message);
150 + }
151 +
152 + // Produces a log message event bound to the client.
153 + private ObjectNode message(String severity, long id, String message) {
154 + return envelope("message", id,
155 + mapper.createObjectNode()
156 + .put("severity", severity)
157 + .put("message", message));
158 + }
159 +
160 + // Puts the payload into an envelope and returns it.
161 + protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
162 + ObjectNode event = mapper.createObjectNode();
163 + event.put("event", type);
164 + if (sid > 0) {
165 + event.put("sid", sid);
166 + }
167 + event.set("payload", payload);
168 + return event;
169 + }
170 +
171 + // Produces a cluster instance message to the client.
172 + protected ObjectNode instanceMessage(ClusterEvent event) {
173 + ControllerNode node = event.subject();
174 + ObjectNode payload = mapper.createObjectNode()
175 + .put("id", node.id().toString())
176 + .put("online", clusterService.getState(node.id()) == ACTIVE);
177 +
178 + ArrayNode labels = mapper.createArrayNode();
179 + labels.add(node.id().toString());
180 + labels.add(node.ip().toString());
181 +
182 + // Add labels, props and stuff the payload into envelope.
183 + payload.set("labels", labels);
184 + addMetaUi(node.id().toString(), payload);
185 +
186 + String type = (event.type() == INSTANCE_ADDED) ? "addInstance" :
187 + ((event.type() == INSTANCE_REMOVED) ? "removeInstance" : "updateInstance");
188 + return envelope(type, 0, payload);
189 + }
190 +
191 + // Produces a device event message to the client.
192 + protected ObjectNode deviceMessage(DeviceEvent event) {
193 + Device device = event.subject();
194 + ObjectNode payload = mapper.createObjectNode()
195 + .put("id", device.id().toString())
196 + .put("type", device.type().toString().toLowerCase())
197 + .put("online", deviceService.isAvailable(device.id()))
198 + .put("master", mastershipService.getMasterFor(device.id()).toString());
199 +
200 + // Generate labels: id, chassis id, no-label, optional-name
201 + ArrayNode labels = mapper.createArrayNode();
202 + labels.add(device.id().toString());
203 + labels.add(device.chassisId().toString());
204 + labels.add(""); // compact no-label view
205 + labels.add(device.annotations().value("name"));
206 +
207 + // Add labels, props and stuff the payload into envelope.
208 + payload.set("labels", labels);
209 + payload.set("props", props(device.annotations()));
210 + addMetaUi(device.id().toString(), payload);
211 +
212 + String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
213 + ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
214 + return envelope(type, 0, payload);
215 + }
216 +
217 + // Produces a link event message to the client.
218 + protected ObjectNode linkMessage(LinkEvent event) {
219 + Link link = event.subject();
220 + ObjectNode payload = mapper.createObjectNode()
221 + .put("id", compactLinkString(link))
222 + .put("type", link.type().toString().toLowerCase())
223 + .put("linkWidth", 2)
224 + .put("src", link.src().deviceId().toString())
225 + .put("srcPort", link.src().port().toString())
226 + .put("dst", link.dst().deviceId().toString())
227 + .put("dstPort", link.dst().port().toString());
228 + String type = (event.type() == LINK_ADDED) ? "addLink" :
229 + ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
230 + return envelope(type, 0, payload);
231 + }
232 +
233 + // Produces a host event message to the client.
234 + protected ObjectNode hostMessage(HostEvent event) {
235 + Host host = event.subject();
236 + ObjectNode payload = mapper.createObjectNode()
237 + .put("id", host.id().toString())
238 + .put("ingress", compactLinkString(edgeLink(host, true)))
239 + .put("egress", compactLinkString(edgeLink(host, false)));
240 + payload.set("cp", location(mapper, host.location()));
241 + payload.set("labels", labels(mapper, ip(host.ipAddresses()),
242 + host.mac().toString()));
243 + payload.set("props", props(host.annotations()));
244 + addMetaUi(host.id().toString(), payload);
245 +
246 + String type = (event.type() == HOST_ADDED) ? "addHost" :
247 + ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
248 + return envelope(type, 0, payload);
249 + }
250 +
251 + // Encodes the specified host location into a JSON object.
252 + private ObjectNode location(ObjectMapper mapper, HostLocation location) {
253 + return mapper.createObjectNode()
254 + .put("device", location.deviceId().toString())
255 + .put("port", location.port().toLong());
256 + }
257 +
258 + // Encodes the specified list of labels a JSON array.
259 + private ArrayNode labels(ObjectMapper mapper, String... labels) {
260 + ArrayNode json = mapper.createArrayNode();
261 + for (String label : labels) {
262 + json.add(label);
263 + }
264 + return json;
265 + }
266 +
267 + // Generates an edge link from the specified host location.
268 + private EdgeLink edgeLink(Host host, boolean ingress) {
269 + return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
270 + host.location(), ingress);
271 + }
272 +
273 + // Adds meta UI information for the specified object.
274 + private void addMetaUi(String id, ObjectNode payload) {
275 + ObjectNode meta = metaUi.get(id);
276 + if (meta != null) {
277 + payload.set("metaUi", meta);
278 + }
279 + }
280 +
281 + // Updates meta UI information for the specified object.
282 + protected void updateMetaUi(ObjectNode event) {
283 + ObjectNode payload = payload(event);
284 + metaUi.put(string(payload, "id"), payload);
285 + }
286 +
287 + // Returns device details response.
288 + protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
289 + Device device = deviceService.getDevice(deviceId);
290 + Annotations annot = device.annotations();
291 + int portCount = deviceService.getPorts(deviceId).size();
292 + NodeId master = mastershipService.getMasterFor(device.id());
293 + return envelope("showDetails", sid,
294 + json(deviceId.toString(),
295 + device.type().toString().toLowerCase(),
296 + new Prop("Name", annot.value("name")),
297 + new Prop("Vendor", device.manufacturer()),
298 + new Prop("H/W Version", device.hwVersion()),
299 + new Prop("S/W Version", device.swVersion()),
300 + new Prop("Serial Number", device.serialNumber()),
301 + new Separator(),
302 + new Prop("Latitude", annot.value("latitude")),
303 + new Prop("Longitude", annot.value("longitude")),
304 + new Prop("Ports", Integer.toString(portCount)),
305 + new Separator(),
306 + new Prop("Master", master.toString())));
307 + }
308 +
309 + // Returns host details response.
310 + protected ObjectNode hostDetails(HostId hostId, long sid) {
311 + Host host = hostService.getHost(hostId);
312 + Annotations annot = host.annotations();
313 + return envelope("showDetails", sid,
314 + json(hostId.toString(), "host",
315 + new Prop("MAC", host.mac().toString()),
316 + new Prop("IP", host.ipAddresses().toString()),
317 + new Separator(),
318 + new Prop("Latitude", annot.value("latitude")),
319 + new Prop("Longitude", annot.value("longitude"))));
320 + }
321 +
322 +
323 + // Produces a path message to the client.
324 + protected ObjectNode pathMessage(Path path) {
325 + ObjectNode payload = mapper.createObjectNode();
326 + ArrayNode links = mapper.createArrayNode();
327 + for (Link link : path.links()) {
328 + links.add(compactLinkString(link));
329 + }
330 +
331 + payload.set("links", links);
332 + return payload;
333 + }
334 +
335 + // Produces compact string representation of a link.
336 + private static String compactLinkString(Link link) {
337 + return String.format(COMPACT, link.src().elementId(), link.src().port(),
338 + link.dst().elementId(), link.dst().port());
339 + }
340 +
341 + // Produces JSON property details.
342 + private ObjectNode json(String id, String type, Prop... props) {
343 + ObjectMapper mapper = new ObjectMapper();
344 + ObjectNode result = mapper.createObjectNode()
345 + .put("id", id).put("type", type);
346 + ObjectNode pnode = mapper.createObjectNode();
347 + ArrayNode porder = mapper.createArrayNode();
348 + for (Prop p : props) {
349 + porder.add(p.key);
350 + pnode.put(p.key, p.value);
351 + }
352 + result.set("propOrder", porder);
353 + result.set("props", pnode);
354 + return result;
355 + }
356 +
357 + // Auxiliary key/value carrier.
358 + private class Prop {
359 + public final String key;
360 + public final String value;
361 +
362 + protected Prop(String key, String value) {
363 + this.key = key;
364 + this.value = value;
365 + }
366 + }
367 +
368 + // Auxiliary properties separator
369 + private class Separator extends Prop {
370 + protected Separator() {
371 + super("-", "");
372 + }
373 + }
374 +
375 +}
...@@ -15,158 +15,94 @@ ...@@ -15,158 +15,94 @@
15 */ 15 */
16 package org.onlab.onos.gui; 16 package org.onlab.onos.gui;
17 17
18 -import com.fasterxml.jackson.databind.JsonNode;
19 -import com.fasterxml.jackson.databind.ObjectMapper;
20 -import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.ObjectNode; 18 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import org.eclipse.jetty.websocket.WebSocket; 19 import org.eclipse.jetty.websocket.WebSocket;
20 +import org.onlab.onos.cluster.ClusterEvent;
21 +import org.onlab.onos.cluster.ClusterEventListener;
22 +import org.onlab.onos.cluster.ControllerNode;
23 import org.onlab.onos.core.ApplicationId; 23 import org.onlab.onos.core.ApplicationId;
24 import org.onlab.onos.core.CoreService; 24 import org.onlab.onos.core.CoreService;
25 import org.onlab.onos.mastership.MastershipEvent; 25 import org.onlab.onos.mastership.MastershipEvent;
26 import org.onlab.onos.mastership.MastershipListener; 26 import org.onlab.onos.mastership.MastershipListener;
27 -import org.onlab.onos.mastership.MastershipService;
28 -import org.onlab.onos.net.Annotations;
29 -import org.onlab.onos.net.ConnectPoint;
30 -import org.onlab.onos.net.DefaultEdgeLink;
31 import org.onlab.onos.net.Device; 27 import org.onlab.onos.net.Device;
32 -import org.onlab.onos.net.DeviceId;
33 -import org.onlab.onos.net.ElementId;
34 import org.onlab.onos.net.Host; 28 import org.onlab.onos.net.Host;
35 import org.onlab.onos.net.HostId; 29 import org.onlab.onos.net.HostId;
36 -import org.onlab.onos.net.HostLocation;
37 import org.onlab.onos.net.Link; 30 import org.onlab.onos.net.Link;
38 import org.onlab.onos.net.Path; 31 import org.onlab.onos.net.Path;
39 import org.onlab.onos.net.device.DeviceEvent; 32 import org.onlab.onos.net.device.DeviceEvent;
40 import org.onlab.onos.net.device.DeviceListener; 33 import org.onlab.onos.net.device.DeviceListener;
41 -import org.onlab.onos.net.device.DeviceService;
42 import org.onlab.onos.net.flow.DefaultTrafficSelector; 34 import org.onlab.onos.net.flow.DefaultTrafficSelector;
43 import org.onlab.onos.net.flow.DefaultTrafficTreatment; 35 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
44 import org.onlab.onos.net.host.HostEvent; 36 import org.onlab.onos.net.host.HostEvent;
45 import org.onlab.onos.net.host.HostListener; 37 import org.onlab.onos.net.host.HostListener;
46 -import org.onlab.onos.net.host.HostService;
47 import org.onlab.onos.net.intent.HostToHostIntent; 38 import org.onlab.onos.net.intent.HostToHostIntent;
48 import org.onlab.onos.net.intent.Intent; 39 import org.onlab.onos.net.intent.Intent;
49 import org.onlab.onos.net.intent.IntentEvent; 40 import org.onlab.onos.net.intent.IntentEvent;
50 import org.onlab.onos.net.intent.IntentId; 41 import org.onlab.onos.net.intent.IntentId;
51 import org.onlab.onos.net.intent.IntentListener; 42 import org.onlab.onos.net.intent.IntentListener;
52 -import org.onlab.onos.net.intent.IntentService;
53 import org.onlab.onos.net.intent.PathIntent; 43 import org.onlab.onos.net.intent.PathIntent;
54 import org.onlab.onos.net.link.LinkEvent; 44 import org.onlab.onos.net.link.LinkEvent;
55 import org.onlab.onos.net.link.LinkListener; 45 import org.onlab.onos.net.link.LinkListener;
56 -import org.onlab.onos.net.link.LinkService;
57 -import org.onlab.onos.net.provider.ProviderId;
58 -import org.onlab.onos.net.topology.PathService;
59 import org.onlab.osgi.ServiceDirectory; 46 import org.onlab.osgi.ServiceDirectory;
60 -import org.onlab.packet.IpAddress;
61 47
62 import java.io.IOException; 48 import java.io.IOException;
63 -import java.util.Iterator;
64 import java.util.List; 49 import java.util.List;
65 import java.util.Map; 50 import java.util.Map;
66 -import java.util.Set;
67 import java.util.concurrent.ConcurrentHashMap; 51 import java.util.concurrent.ConcurrentHashMap;
68 52
69 -import static com.google.common.base.Preconditions.checkNotNull; 53 +import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
70 import static org.onlab.onos.net.DeviceId.deviceId; 54 import static org.onlab.onos.net.DeviceId.deviceId;
71 import static org.onlab.onos.net.HostId.hostId; 55 import static org.onlab.onos.net.HostId.hostId;
72 -import static org.onlab.onos.net.PortNumber.portNumber;
73 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; 56 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
74 -import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
75 import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED; 57 import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
76 -import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
77 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; 58 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
78 -import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
79 59
80 /** 60 /**
81 * Web socket capable of interacting with the GUI topology view. 61 * Web socket capable of interacting with the GUI topology view.
82 */ 62 */
83 -public class TopologyWebSocket implements WebSocket.OnTextMessage { 63 +public class TopologyWebSocket
64 + extends TopologyMessages implements WebSocket.OnTextMessage {
84 65
85 private static final String APP_ID = "org.onlab.onos.gui"; 66 private static final String APP_ID = "org.onlab.onos.gui";
86 - private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
87 67
88 private final ApplicationId appId; 68 private final ApplicationId appId;
89 - private final ServiceDirectory directory;
90 -
91 - private final ObjectMapper mapper = new ObjectMapper();
92 69
93 private Connection connection; 70 private Connection connection;
94 71
95 - private final DeviceService deviceService; 72 + private final ClusterEventListener clusterListener = new InternalClusterListener();
96 - private final LinkService linkService;
97 - private final HostService hostService;
98 - private final MastershipService mastershipService;
99 - private final IntentService intentService;
100 -
101 private final DeviceListener deviceListener = new InternalDeviceListener(); 73 private final DeviceListener deviceListener = new InternalDeviceListener();
102 private final LinkListener linkListener = new InternalLinkListener(); 74 private final LinkListener linkListener = new InternalLinkListener();
103 private final HostListener hostListener = new InternalHostListener(); 75 private final HostListener hostListener = new InternalHostListener();
104 private final MastershipListener mastershipListener = new InternalMastershipListener(); 76 private final MastershipListener mastershipListener = new InternalMastershipListener();
105 private final IntentListener intentListener = new InternalIntentListener(); 77 private final IntentListener intentListener = new InternalIntentListener();
106 78
107 - // TODO: extract into an external & durable state; good enough for now and demo
108 - private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
109 -
110 // Intents that are being monitored for the GUI 79 // Intents that are being monitored for the GUI
111 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>(); 80 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
112 81
113 - private static final String COMPACT = "%s/%s-%s/%s";
114 -
115 -
116 /** 82 /**
117 * Creates a new web-socket for serving data to GUI topology view. 83 * Creates a new web-socket for serving data to GUI topology view.
118 * 84 *
119 * @param directory service directory 85 * @param directory service directory
120 */ 86 */
121 public TopologyWebSocket(ServiceDirectory directory) { 87 public TopologyWebSocket(ServiceDirectory directory) {
122 - this.directory = checkNotNull(directory, "Directory cannot be null"); 88 + super(directory);
123 - deviceService = directory.get(DeviceService.class);
124 - linkService = directory.get(LinkService.class);
125 - hostService = directory.get(HostService.class);
126 - mastershipService = directory.get(MastershipService.class);
127 - intentService = directory.get(IntentService.class);
128 -
129 appId = directory.get(CoreService.class).registerApplication(APP_ID); 89 appId = directory.get(CoreService.class).registerApplication(APP_ID);
130 } 90 }
131 91
132 @Override 92 @Override
133 public void onOpen(Connection connection) { 93 public void onOpen(Connection connection) {
134 this.connection = connection; 94 this.connection = connection;
135 - deviceService.addListener(deviceListener); 95 + addListeners();
136 - linkService.addListener(linkListener);
137 - hostService.addListener(hostListener);
138 - mastershipService.addListener(mastershipListener);
139 - intentService.addListener(intentListener);
140 96
97 + sendAllInstances();
141 sendAllDevices(); 98 sendAllDevices();
142 sendAllLinks(); 99 sendAllLinks();
143 sendAllHosts(); 100 sendAllHosts();
144 } 101 }
145 102
146 - private void sendAllHosts() {
147 - for (Host host : hostService.getHosts()) {
148 - sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
149 - }
150 - }
151 -
152 - private void sendAllDevices() {
153 - for (Device device : deviceService.getDevices()) {
154 - sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
155 - }
156 - }
157 -
158 - private void sendAllLinks() {
159 - for (Link link : linkService.getLinks()) {
160 - sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
161 - }
162 - }
163 -
164 @Override 103 @Override
165 public void onClose(int closeCode, String message) { 104 public void onClose(int closeCode, String message) {
166 - deviceService.removeListener(deviceListener); 105 + removeListeners();
167 - linkService.removeListener(linkListener);
168 - hostService.removeListener(hostListener);
169 - mastershipService.removeListener(mastershipListener);
170 } 106 }
171 107
172 @Override 108 @Override
...@@ -177,7 +113,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -177,7 +113,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
177 if (type.equals("showDetails")) { 113 if (type.equals("showDetails")) {
178 showDetails(event); 114 showDetails(event);
179 } else if (type.equals("updateMeta")) { 115 } else if (type.equals("updateMeta")) {
180 - updateMetaInformation(event); 116 + updateMetaUi(event);
181 } else if (type.equals("requestPath")) { 117 } else if (type.equals("requestPath")) {
182 createHostIntent(event); 118 createHostIntent(event);
183 } else if (type.equals("requestTraffic")) { 119 } else if (type.equals("requestTraffic")) {
...@@ -200,74 +136,32 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -200,74 +136,32 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
200 } 136 }
201 } 137 }
202 138
203 - // Retrieves the payload from the specified event. 139 + // Sends all controller nodes to the client as node-added messages.
204 - private ObjectNode payload(ObjectNode event) { 140 + private void sendAllInstances() {
205 - return (ObjectNode) event.path("payload"); 141 + for (ControllerNode node : clusterService.getNodes()) {
206 - } 142 + sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
207 -
208 - // Returns the specified node property as a number
209 - private long number(ObjectNode node, String name) {
210 - return node.path(name).asLong();
211 - }
212 -
213 - // Returns the specified node property as a string.
214 - private String string(ObjectNode node, String name) {
215 - return node.path(name).asText();
216 - }
217 -
218 - // Returns the specified node property as a string.
219 - private String string(ObjectNode node, String name, String defaultValue) {
220 - return node.path(name).asText(defaultValue);
221 } 143 }
222 -
223 - // Returns the specified set of IP addresses as a string.
224 - private String ip(Set<IpAddress> ipAddresses) {
225 - Iterator<IpAddress> it = ipAddresses.iterator();
226 - return it.hasNext() ? it.next().toString() : "unknown";
227 - }
228 -
229 - // Encodes the specified host location into a JSON object.
230 - private ObjectNode location(ObjectMapper mapper, HostLocation location) {
231 - return mapper.createObjectNode()
232 - .put("device", location.deviceId().toString())
233 - .put("port", location.port().toLong());
234 } 144 }
235 145
236 - // Encodes the specified list of labels a JSON array. 146 + // Sends all devices to the client as device-added messages.
237 - private ArrayNode labels(ObjectMapper mapper, String... labels) { 147 + private void sendAllDevices() {
238 - ArrayNode json = mapper.createArrayNode(); 148 + for (Device device : deviceService.getDevices()) {
239 - for (String label : labels) { 149 + sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
240 - json.add(label);
241 } 150 }
242 - return json;
243 } 151 }
244 152
245 - // Produces JSON structure from annotations. 153 + // Sends all links to the client as link-added messages.
246 - private JsonNode props(Annotations annotations) { 154 + private void sendAllLinks() {
247 - ObjectNode props = mapper.createObjectNode(); 155 + for (Link link : linkService.getLinks()) {
248 - for (String key : annotations.keys()) { 156 + sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
249 - props.put(key, annotations.value(key));
250 - }
251 - return props;
252 } 157 }
253 -
254 - // Produces a log message event bound to the client.
255 - private ObjectNode message(String severity, long id, String message) {
256 - return envelope("message", id,
257 - mapper.createObjectNode()
258 - .put("severity", severity)
259 - .put("message", message));
260 } 158 }
261 159
262 - // Puts the payload into an envelope and returns it. 160 + // Sends all hosts to the client as host-added messages.
263 - private ObjectNode envelope(String type, long sid, ObjectNode payload) { 161 + private void sendAllHosts() {
264 - ObjectNode event = mapper.createObjectNode(); 162 + for (Host host : hostService.getHosts()) {
265 - event.put("event", type); 163 + sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
266 - if (sid > 0) {
267 - event.put("sid", sid);
268 } 164 }
269 - event.set("payload", payload);
270 - return event;
271 } 165 }
272 166
273 // Sends back device or host details. 167 // Sends back device or host details.
...@@ -283,12 +177,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -283,12 +177,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
283 } 177 }
284 } 178 }
285 179
286 - // Updates device/host meta information.
287 - private void updateMetaInformation(ObjectNode event) {
288 - ObjectNode payload = payload(event);
289 - metaUi.put(string(payload, "id"), payload);
290 - }
291 -
292 // Creates host-to-host intent. 180 // Creates host-to-host intent.
293 private void createHostIntent(ObjectNode event) { 181 private void createHostIntent(ObjectNode event) {
294 ObjectNode payload = payload(event); 182 ObjectNode payload = payload(event);
...@@ -314,7 +202,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -314,7 +202,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
314 payload.put("traffic", true); 202 payload.put("traffic", true);
315 sendMessage(envelope("showPath", id, payload)); 203 sendMessage(envelope("showPath", id, payload));
316 } else { 204 } else {
317 - sendMessage(message("warn", id, "No path found")); 205 + sendMessage(warning(id, "No path found"));
318 } 206 }
319 } 207 }
320 208
...@@ -323,178 +211,35 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -323,178 +211,35 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
323 // TODO: implement this 211 // TODO: implement this
324 } 212 }
325 213
326 - // Finds the path between the specified devices.
327 - private ObjectNode findPath(DeviceId one, DeviceId two) {
328 - PathService pathService = directory.get(PathService.class);
329 - Set<Path> paths = pathService.getPaths(one, two);
330 - if (paths.isEmpty()) {
331 - return null;
332 - } else {
333 - return pathMessage(paths.iterator().next());
334 - }
335 - }
336 214
337 - // Produces a path message to the client. 215 + // Adds all internal listeners.
338 - private ObjectNode pathMessage(Path path) { 216 + private void addListeners() {
339 - ObjectNode payload = mapper.createObjectNode(); 217 + clusterService.addListener(clusterListener);
340 - ArrayNode links = mapper.createArrayNode(); 218 + deviceService.addListener(deviceListener);
341 - for (Link link : path.links()) { 219 + linkService.addListener(linkListener);
342 - links.add(compactLinkString(link)); 220 + hostService.addListener(hostListener);
221 + mastershipService.addListener(mastershipListener);
222 + intentService.addListener(intentListener);
343 } 223 }
344 224
345 - payload.set("links", links); 225 + // Removes all internal listeners.
346 - return payload; 226 + private void removeListeners() {
227 + clusterService.removeListener(clusterListener);
228 + deviceService.removeListener(deviceListener);
229 + linkService.removeListener(linkListener);
230 + hostService.removeListener(hostListener);
231 + mastershipService.removeListener(mastershipListener);
347 } 232 }
348 233
349 - /** 234 + // Cluster event listener.
350 - * Returns a compact string representing the given link. 235 + private class InternalClusterListener implements ClusterEventListener {
351 - * 236 + @Override
352 - * @param link infrastructure link 237 + public void event(ClusterEvent event) {
353 - * @return formatted link string 238 + sendMessage(instanceMessage(event));
354 - */
355 - public static String compactLinkString(Link link) {
356 - return String.format(COMPACT, link.src().elementId(), link.src().port(),
357 - link.dst().elementId(), link.dst().port());
358 - }
359 -
360 -
361 - // Produces a link event message to the client.
362 - private ObjectNode deviceMessage(DeviceEvent event) {
363 - Device device = event.subject();
364 - ObjectNode payload = mapper.createObjectNode()
365 - .put("id", device.id().toString())
366 - .put("type", device.type().toString().toLowerCase())
367 - .put("online", deviceService.isAvailable(device.id()));
368 -
369 - // Generate labels: id, chassis id, no-label, optional-name
370 - ArrayNode labels = mapper.createArrayNode();
371 - labels.add(device.id().toString());
372 - labels.add(device.chassisId().toString());
373 - labels.add(""); // compact no-label view
374 - labels.add(device.annotations().value("name"));
375 -
376 - // Add labels, props and stuff the payload into envelope.
377 - payload.set("labels", labels);
378 - payload.set("props", props(device.annotations()));
379 - addMetaUi(device.id(), payload);
380 -
381 - String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
382 - ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
383 - return envelope(type, 0, payload);
384 - }
385 -
386 - // Produces a link event message to the client.
387 - private ObjectNode linkMessage(LinkEvent event) {
388 - Link link = event.subject();
389 - ObjectNode payload = mapper.createObjectNode()
390 - .put("id", compactLinkString(link))
391 - .put("type", link.type().toString().toLowerCase())
392 - .put("linkWidth", 2)
393 - .put("src", link.src().deviceId().toString())
394 - .put("srcPort", link.src().port().toString())
395 - .put("dst", link.dst().deviceId().toString())
396 - .put("dstPort", link.dst().port().toString());
397 - String type = (event.type() == LINK_ADDED) ? "addLink" :
398 - ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
399 - return envelope(type, 0, payload);
400 - }
401 -
402 - // Produces a host event message to the client.
403 - private ObjectNode hostMessage(HostEvent event) {
404 - Host host = event.subject();
405 - ObjectNode payload = mapper.createObjectNode()
406 - .put("id", host.id().toString())
407 - .put("ingress", compactLinkString(edgeLink(host, true)))
408 - .put("egress", compactLinkString(edgeLink(host, false)));
409 - payload.set("cp", location(mapper, host.location()));
410 - payload.set("labels", labels(mapper, ip(host.ipAddresses()),
411 - host.mac().toString()));
412 - payload.set("props", props(host.annotations()));
413 - addMetaUi(host.id(), payload);
414 -
415 - String type = (event.type() == HOST_ADDED) ? "addHost" :
416 - ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
417 - return envelope(type, 0, payload);
418 - }
419 -
420 - private DefaultEdgeLink edgeLink(Host host, boolean ingress) {
421 - return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
422 - host.location(), ingress);
423 - }
424 -
425 - private void addMetaUi(ElementId id, ObjectNode payload) {
426 - ObjectNode meta = metaUi.get(id.toString());
427 - if (meta != null) {
428 - payload.set("metaUi", meta);
429 - }
430 - }
431 -
432 -
433 - // Returns device details response.
434 - private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
435 - Device device = deviceService.getDevice(deviceId);
436 - Annotations annot = device.annotations();
437 - int portCount = deviceService.getPorts(deviceId).size();
438 - return envelope("showDetails", sid,
439 - json(deviceId.toString(),
440 - device.type().toString().toLowerCase(),
441 - new Prop("Name", annot.value("name")),
442 - new Prop("Vendor", device.manufacturer()),
443 - new Prop("H/W Version", device.hwVersion()),
444 - new Prop("S/W Version", device.swVersion()),
445 - new Prop("Serial Number", device.serialNumber()),
446 - new Separator(),
447 - new Prop("Latitude", annot.value("latitude")),
448 - new Prop("Longitude", annot.value("longitude")),
449 - new Prop("Ports", Integer.toString(portCount))));
450 - }
451 -
452 - // Returns host details response.
453 - private ObjectNode hostDetails(HostId hostId, long sid) {
454 - Host host = hostService.getHost(hostId);
455 - Annotations annot = host.annotations();
456 - return envelope("showDetails", sid,
457 - json(hostId.toString(), "host",
458 - new Prop("MAC", host.mac().toString()),
459 - new Prop("IP", host.ipAddresses().toString()),
460 - new Separator(),
461 - new Prop("Latitude", annot.value("latitude")),
462 - new Prop("Longitude", annot.value("longitude"))));
463 - }
464 -
465 - // Produces JSON property details.
466 - private ObjectNode json(String id, String type, Prop... props) {
467 - ObjectMapper mapper = new ObjectMapper();
468 - ObjectNode result = mapper.createObjectNode()
469 - .put("id", id).put("type", type);
470 - ObjectNode pnode = mapper.createObjectNode();
471 - ArrayNode porder = mapper.createArrayNode();
472 - for (Prop p : props) {
473 - porder.add(p.key);
474 - pnode.put(p.key, p.value);
475 - }
476 - result.set("propOrder", porder);
477 - result.set("props", pnode);
478 - return result;
479 - }
480 -
481 - // Auxiliary key/value carrier.
482 - private class Prop {
483 - private final String key;
484 - private final String value;
485 -
486 - protected Prop(String key, String value) {
487 - this.key = key;
488 - this.value = value;
489 - }
490 - }
491 -
492 - private class Separator extends Prop {
493 - protected Separator() {
494 - super("-", "");
495 } 239 }
496 } 240 }
497 241
242 + // Device event listener.
498 private class InternalDeviceListener implements DeviceListener { 243 private class InternalDeviceListener implements DeviceListener {
499 @Override 244 @Override
500 public void event(DeviceEvent event) { 245 public void event(DeviceEvent event) {
...@@ -502,6 +247,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -502,6 +247,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
502 } 247 }
503 } 248 }
504 249
250 + // Link event listener.
505 private class InternalLinkListener implements LinkListener { 251 private class InternalLinkListener implements LinkListener {
506 @Override 252 @Override
507 public void event(LinkEvent event) { 253 public void event(LinkEvent event) {
...@@ -509,6 +255,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -509,6 +255,7 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
509 } 255 }
510 } 256 }
511 257
258 + // Host event listener.
512 private class InternalHostListener implements HostListener { 259 private class InternalHostListener implements HostListener {
513 @Override 260 @Override
514 public void event(HostEvent event) { 261 public void event(HostEvent event) {
...@@ -516,13 +263,15 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -516,13 +263,15 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
516 } 263 }
517 } 264 }
518 265
266 + // Mastership event listener.
519 private class InternalMastershipListener implements MastershipListener { 267 private class InternalMastershipListener implements MastershipListener {
520 @Override 268 @Override
521 public void event(MastershipEvent event) { 269 public void event(MastershipEvent event) {
522 - 270 + // TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same?
523 } 271 }
524 } 272 }
525 273
274 + // Intent event listener.
526 private class InternalIntentListener implements IntentListener { 275 private class InternalIntentListener implements IntentListener {
527 @Override 276 @Override
528 public void event(IntentEvent event) { 277 public void event(IntentEvent event) {
...@@ -539,5 +288,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage { ...@@ -539,5 +288,6 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage {
539 } 288 }
540 } 289 }
541 } 290 }
291 +
542 } 292 }
543 293
......