blob: bcee363f933233b10e705863305a3116f84013e7 [file] [log] [blame]
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001/*
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 */
16package org.onlab.onos.gui;
17
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080022import org.eclipse.jetty.websocket.WebSocket;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080023import org.onlab.onos.mastership.MastershipEvent;
24import org.onlab.onos.mastership.MastershipListener;
25import org.onlab.onos.mastership.MastershipService;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080026import org.onlab.onos.net.Annotations;
27import org.onlab.onos.net.Device;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080028import org.onlab.onos.net.DeviceId;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080029import org.onlab.onos.net.Host;
30import org.onlab.onos.net.HostId;
31import org.onlab.onos.net.HostLocation;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080032import org.onlab.onos.net.Link;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080033import org.onlab.onos.net.Path;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080034import org.onlab.onos.net.device.DeviceEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080035import org.onlab.onos.net.device.DeviceListener;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080036import org.onlab.onos.net.device.DeviceService;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080037import org.onlab.onos.net.host.HostEvent;
38import org.onlab.onos.net.host.HostListener;
39import org.onlab.onos.net.host.HostService;
40import org.onlab.onos.net.intent.IntentId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080041import org.onlab.onos.net.link.LinkEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080042import org.onlab.onos.net.link.LinkListener;
43import org.onlab.onos.net.link.LinkService;
44import org.onlab.onos.net.topology.PathService;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080045import org.onlab.osgi.ServiceDirectory;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080046import org.onlab.packet.IpAddress;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080047
48import java.io.IOException;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080049import java.util.HashMap;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080050import java.util.Iterator;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080051import java.util.Map;
52import java.util.Set;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080053
Thomas Vachuska690e5f62014-11-09 08:26:47 -080054import static com.google.common.base.Preconditions.checkNotNull;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080055import static org.onlab.onos.net.DeviceId.deviceId;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080056import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080057import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
58import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
59import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
60import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
61
Thomas Vachuska7d638d32014-11-07 10:24:43 -080062/**
63 * Web socket capable of interacting with the GUI topology view.
64 */
Thomas Vachuska690e5f62014-11-09 08:26:47 -080065public class TopologyWebSocket implements WebSocket.OnTextMessage {
Thomas Vachuska7d638d32014-11-07 10:24:43 -080066
67 private final ServiceDirectory directory;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080068
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080069 private final ObjectMapper mapper = new ObjectMapper();
70
Thomas Vachuska7d638d32014-11-07 10:24:43 -080071 private Connection connection;
72
Thomas Vachuska690e5f62014-11-09 08:26:47 -080073 private final DeviceService deviceService;
74 private final LinkService linkService;
75 private final HostService hostService;
76 private final MastershipService mastershipService;
77
78 private final DeviceListener deviceListener = new InternalDeviceListener();
79 private final LinkListener linkListener = new InternalLinkListener();
80 private final HostListener hostListener = new InternalHostListener();
81 private final MastershipListener mastershipListener = new InternalMastershipListener();
82
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080083 // TODO: extract into an external & durable state; good enough for now and demo
84 private static Map<String, ObjectNode> metaUi = new HashMap<>();
85
86 private static final String COMPACT = "%s/%s-%s/%s";
87
88
Thomas Vachuska7d638d32014-11-07 10:24:43 -080089 /**
90 * Creates a new web-socket for serving data to GUI topology view.
91 *
92 * @param directory service directory
93 */
94 public TopologyWebSocket(ServiceDirectory directory) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -080095 this.directory = checkNotNull(directory, "Directory cannot be null");
Thomas Vachuska7d638d32014-11-07 10:24:43 -080096 deviceService = directory.get(DeviceService.class);
Thomas Vachuska690e5f62014-11-09 08:26:47 -080097 linkService = directory.get(LinkService.class);
98 hostService = directory.get(HostService.class);
99 mastershipService = directory.get(MastershipService.class);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800100 }
101
102 @Override
103 public void onOpen(Connection connection) {
104 this.connection = connection;
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800105 deviceService.addListener(deviceListener);
106 linkService.addListener(linkListener);
107 hostService.addListener(hostListener);
108 mastershipService.addListener(mastershipListener);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800109
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800110 sendAllDevices();
111 sendAllLinks();
112 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800113
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800114 private void sendAllDevices() {
115 for (Device device : deviceService.getDevices()) {
116 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
117 }
118 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800119
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800120 private void sendAllLinks() {
121 for (Link link : linkService.getLinks()) {
122 sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800123 }
124 }
125
126 @Override
127 public void onClose(int closeCode, String message) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800128 deviceService.removeListener(deviceListener);
129 linkService.removeListener(linkListener);
130 hostService.removeListener(hostListener);
131 mastershipService.removeListener(mastershipListener);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800132 }
133
134 @Override
135 public void onMessage(String data) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800136 try {
137 ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800138 String type = string(event, "event", "unknown");
139 if (type.equals("showDetails")) {
140 showDetails(event);
141 } else if (type.equals("updateMeta")) {
142 updateMetaInformation(event);
143 } else if (type.equals("requestPath")) {
144 sendPath(event);
145 } else if (type.equals("requestTraffic")) {
146 sendTraffic(event);
147 } else if (type.equals("cancelTraffic")) {
148 cancelTraffic(event);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800149 }
150 } catch (IOException e) {
151 System.out.println("Received: " + data);
152 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800153 }
154
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800155 // Sends the specified data to the client.
156 private void sendMessage(ObjectNode data) {
157 try {
158 connection.sendMessage(data.toString());
159 } catch (IOException e) {
160 e.printStackTrace();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800161 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800162 }
163
164 // Retrieves the payload from the specified event.
165 private ObjectNode payload(ObjectNode event) {
166 return (ObjectNode) event.path("payload");
167 }
168
169 // Returns the specified node property as a number
170 private long number(ObjectNode node, String name) {
171 return node.path(name).asLong();
172 }
173
174 // Returns the specified node property as a string.
175 private String string(ObjectNode node, String name) {
176 return node.path(name).asText();
177 }
178
179 // Returns the specified node property as a string.
180 private String string(ObjectNode node, String name, String defaultValue) {
181 return node.path(name).asText(defaultValue);
182 }
183
184 // Returns the specified set of IP addresses as a string.
185 private String ip(Set<IpAddress> ipAddresses) {
186 Iterator<IpAddress> it = ipAddresses.iterator();
187 return it.hasNext() ? it.next().toString() : "unknown";
188 }
189
190 // Encodes the specified host location into a JSON object.
191 private ObjectNode location(ObjectMapper mapper, HostLocation location) {
192 return mapper.createObjectNode()
193 .put("device", location.deviceId().toString())
194 .put("port", location.port().toLong());
195 }
196
197 // Encodes the specified list of labels a JSON array.
198 private ArrayNode labels(ObjectMapper mapper, String... labels) {
199 ArrayNode json = mapper.createArrayNode();
200 for (String label : labels) {
201 json.add(label);
202 }
203 return json;
204 }
205
206 // Produces JSON structure from annotations.
207 private JsonNode props(Annotations annotations) {
208 ObjectNode props = mapper.createObjectNode();
209 for (String key : annotations.keys()) {
210 props.put(key, annotations.value(key));
211 }
212 return props;
213 }
214
215 // Produces a log message event bound to the client.
216 private ObjectNode message(String severity, long id, String message) {
217 return envelope("message", id,
218 mapper.createObjectNode()
219 .put("severity", severity)
220 .put("message", message));
221 }
222
223 // Puts the payload into an envelope and returns it.
224 private ObjectNode envelope(String type, long sid, ObjectNode payload) {
225 ObjectNode event = mapper.createObjectNode();
226 event.put("event", type);
227 if (sid > 0) {
228 event.put("sid", sid);
229 }
230 event.set("payload", payload);
231 return event;
232 }
233
234 // Sends back device or host details.
235 private void showDetails(ObjectNode event) {
236 ObjectNode payload = payload(event);
237 String type = string(payload, "type", "unknown");
238 if (type.equals("device")) {
239 sendMessage(deviceDetails(deviceId(string(payload, "id")),
240 number(event, "sid")));
241 } else if (type.equals("host")) {
242 sendMessage(hostDetails(hostId(string(payload, "id")),
243 number(event, "sid")));
244 }
245 }
246
247 // Updates device/host meta information.
248 private void updateMetaInformation(ObjectNode event) {
249 ObjectNode payload = payload(event);
250 metaUi.put(string(payload, "id"), payload);
251 }
252
253 // Sends path message.
254 private void sendPath(ObjectNode event) {
255 ObjectNode payload = payload(event);
256 long id = number(event, "sid");
257 DeviceId one = deviceId(string(payload, "one"));
258 DeviceId two = deviceId(string(payload, "two"));
259
260 ObjectNode response = findPath(one, two);
261 if (response != null) {
262 sendMessage(envelope("showPath", id, response));
263 } else {
264 sendMessage(message("warn", id, "No path found"));
265 }
266 }
267
268 // Sends traffic message.
269 private void sendTraffic(ObjectNode event) {
270 ObjectNode payload = payload(event);
271 long id = number(event, "sid");
272 IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
273
274 if (payload != null) {
275 payload.put("traffic", true);
276 sendMessage(envelope("showPath", id, payload));
277 } else {
278 sendMessage(message("warn", id, "No path found"));
279 }
280 }
281
282 // Cancels sending traffic messages.
283 private void cancelTraffic(ObjectNode event) {
284 // TODO: implement this
285 }
286
287 // Finds the path between the specified devices.
288 private ObjectNode findPath(DeviceId one, DeviceId two) {
289 PathService pathService = directory.get(PathService.class);
290 Set<Path> paths = pathService.getPaths(one, two);
291 if (paths.isEmpty()) {
292 return null;
293 } else {
294 return pathMessage(paths.iterator().next());
295 }
296 }
297
298 // Produces a path message to the client.
299 private ObjectNode pathMessage(Path path) {
300 ObjectNode payload = mapper.createObjectNode();
301 ArrayNode links = mapper.createArrayNode();
302 for (Link link : path.links()) {
303 links.add(compactLinkString(link));
304 }
305
306 payload.set("links", links);
307 return payload;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800308 }
309
310 /**
311 * Returns a compact string representing the given link.
312 *
313 * @param link infrastructure link
314 * @return formatted link string
315 */
316 public static String compactLinkString(Link link) {
317 return String.format(COMPACT, link.src().deviceId(), link.src().port(),
318 link.dst().deviceId(), link.dst().port());
319 }
320
321
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800322 // Produces a link event message to the client.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800323 private ObjectNode deviceMessage(DeviceEvent event) {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800324 Device device = event.subject();
325 ObjectNode payload = mapper.createObjectNode()
326 .put("id", device.id().toString())
327 .put("type", device.type().toString().toLowerCase())
328 .put("online", deviceService.isAvailable(device.id()));
329
330 // Generate labels: id, chassis id, no-label, optional-name
331 ArrayNode labels = mapper.createArrayNode();
332 labels.add(device.id().toString());
333 labels.add(device.chassisId().toString());
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800334 labels.add(""); // compact no-label view
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800335 labels.add(device.annotations().value("name"));
336
337 // Add labels, props and stuff the payload into envelope.
338 payload.set("labels", labels);
339 payload.set("props", props(device.annotations()));
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800340
341 ObjectNode meta = metaUi.get(device.id().toString());
342 if (meta != null) {
343 payload.set("metaUi", meta);
344 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800345
346 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
347 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800348 return envelope(type, 0, payload);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800349 }
350
351 // Produces a link event message to the client.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800352 private ObjectNode linkMessage(LinkEvent event) {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800353 Link link = event.subject();
354 ObjectNode payload = mapper.createObjectNode()
Thomas Vachuska70c16ad2014-11-09 11:19:44 -0800355 .put("id", compactLinkString(link))
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800356 .put("type", link.type().toString().toLowerCase())
357 .put("linkWidth", 2)
358 .put("src", link.src().deviceId().toString())
359 .put("srcPort", link.src().port().toString())
360 .put("dst", link.dst().deviceId().toString())
361 .put("dstPort", link.dst().port().toString());
362 String type = (event.type() == LINK_ADDED) ? "addLink" :
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800363 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
364 return envelope(type, 0, payload);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800365 }
366
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800367 // Produces a host event message to the client.
368 private ObjectNode hostMessage(HostEvent event) {
369 Host host = event.subject();
370 ObjectNode payload = mapper.createObjectNode()
371 .put("id", host.id().toString());
372 payload.set("cp", location(mapper, host.location()));
373 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
374 host.mac().toString()));
375 return payload;
376 }
377
378
379 // Returns device details response.
380 private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
381 Device device = deviceService.getDevice(deviceId);
382 Annotations annot = device.annotations();
383 int portCount = deviceService.getPorts(deviceId).size();
384 return envelope("showDetails", sid,
385 json(deviceId.toString(),
386 device.type().toString().toLowerCase(),
387 new Prop("Name", annot.value("name")),
388 new Prop("Vendor", device.manufacturer()),
389 new Prop("H/W Version", device.hwVersion()),
390 new Prop("S/W Version", device.swVersion()),
391 new Prop("Serial Number", device.serialNumber()),
392 new Separator(),
393 new Prop("Latitude", annot.value("latitude")),
394 new Prop("Longitude", annot.value("longitude")),
395 new Prop("Ports", Integer.toString(portCount))));
396 }
397
398 // Returns host details response.
399 private ObjectNode hostDetails(HostId hostId, long sid) {
400 Host host = hostService.getHost(hostId);
401 Annotations annot = host.annotations();
402 return envelope("showDetails", sid,
403 json(hostId.toString(), "host",
404 new Prop("MAC", host.mac().toString()),
405 new Prop("IP", host.ipAddresses().toString()),
406 new Separator(),
407 new Prop("Latitude", annot.value("latitude")),
408 new Prop("Longitude", annot.value("longitude"))));
409 }
410
411 // Produces JSON property details.
412 private ObjectNode json(String id, String type, Prop... props) {
413 ObjectMapper mapper = new ObjectMapper();
414 ObjectNode result = mapper.createObjectNode()
415 .put("id", id).put("type", type);
416 ObjectNode pnode = mapper.createObjectNode();
417 ArrayNode porder = mapper.createArrayNode();
418 for (Prop p : props) {
419 porder.add(p.key);
420 pnode.put(p.key, p.value);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800421 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800422 result.set("propOrder", porder);
423 result.set("props", pnode);
424 return result;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800425 }
426
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800427 // Auxiliary key/value carrier.
428 private class Prop {
429 private final String key;
430 private final String value;
431
432 protected Prop(String key, String value) {
433 this.key = key;
434 this.value = value;
435 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800436 }
437
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800438 private class Separator extends Prop {
439 protected Separator() {
440 super("-", "");
441 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800442 }
443
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800444 private class InternalDeviceListener implements DeviceListener {
445 @Override
446 public void event(DeviceEvent event) {
447 sendMessage(deviceMessage(event));
448 }
449 }
450
451 private class InternalLinkListener implements LinkListener {
452 @Override
453 public void event(LinkEvent event) {
454 sendMessage(linkMessage(event));
455 }
456 }
457
458 private class InternalHostListener implements HostListener {
459 @Override
460 public void event(HostEvent event) {
461 sendMessage(hostMessage(event));
462 }
463 }
464
465 private class InternalMastershipListener implements MastershipListener {
466 @Override
467 public void event(MastershipEvent event) {
468
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800469 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800470 }
471}
472