blob: 0dcf378ef9d1f6a8c64b8fd75152ea96867e7e61 [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()
355 .put("type", link.type().toString().toLowerCase())
356 .put("linkWidth", 2)
357 .put("src", link.src().deviceId().toString())
358 .put("srcPort", link.src().port().toString())
359 .put("dst", link.dst().deviceId().toString())
360 .put("dstPort", link.dst().port().toString());
361 String type = (event.type() == LINK_ADDED) ? "addLink" :
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800362 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
363 return envelope(type, 0, payload);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800364 }
365
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800366 // Produces a host event message to the client.
367 private ObjectNode hostMessage(HostEvent event) {
368 Host host = event.subject();
369 ObjectNode payload = mapper.createObjectNode()
370 .put("id", host.id().toString());
371 payload.set("cp", location(mapper, host.location()));
372 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
373 host.mac().toString()));
374 return payload;
375 }
376
377
378 // Returns device details response.
379 private ObjectNode deviceDetails(DeviceId deviceId, long sid) {
380 Device device = deviceService.getDevice(deviceId);
381 Annotations annot = device.annotations();
382 int portCount = deviceService.getPorts(deviceId).size();
383 return envelope("showDetails", sid,
384 json(deviceId.toString(),
385 device.type().toString().toLowerCase(),
386 new Prop("Name", annot.value("name")),
387 new Prop("Vendor", device.manufacturer()),
388 new Prop("H/W Version", device.hwVersion()),
389 new Prop("S/W Version", device.swVersion()),
390 new Prop("Serial Number", device.serialNumber()),
391 new Separator(),
392 new Prop("Latitude", annot.value("latitude")),
393 new Prop("Longitude", annot.value("longitude")),
394 new Prop("Ports", Integer.toString(portCount))));
395 }
396
397 // Returns host details response.
398 private ObjectNode hostDetails(HostId hostId, long sid) {
399 Host host = hostService.getHost(hostId);
400 Annotations annot = host.annotations();
401 return envelope("showDetails", sid,
402 json(hostId.toString(), "host",
403 new Prop("MAC", host.mac().toString()),
404 new Prop("IP", host.ipAddresses().toString()),
405 new Separator(),
406 new Prop("Latitude", annot.value("latitude")),
407 new Prop("Longitude", annot.value("longitude"))));
408 }
409
410 // Produces JSON property details.
411 private ObjectNode json(String id, String type, Prop... props) {
412 ObjectMapper mapper = new ObjectMapper();
413 ObjectNode result = mapper.createObjectNode()
414 .put("id", id).put("type", type);
415 ObjectNode pnode = mapper.createObjectNode();
416 ArrayNode porder = mapper.createArrayNode();
417 for (Prop p : props) {
418 porder.add(p.key);
419 pnode.put(p.key, p.value);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800420 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800421 result.set("propOrder", porder);
422 result.set("props", pnode);
423 return result;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800424 }
425
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800426 // Auxiliary key/value carrier.
427 private class Prop {
428 private final String key;
429 private final String value;
430
431 protected Prop(String key, String value) {
432 this.key = key;
433 this.value = value;
434 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800435 }
436
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800437 private class Separator extends Prop {
438 protected Separator() {
439 super("-", "");
440 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800441 }
442
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800443 private class InternalDeviceListener implements DeviceListener {
444 @Override
445 public void event(DeviceEvent event) {
446 sendMessage(deviceMessage(event));
447 }
448 }
449
450 private class InternalLinkListener implements LinkListener {
451 @Override
452 public void event(LinkEvent event) {
453 sendMessage(linkMessage(event));
454 }
455 }
456
457 private class InternalHostListener implements HostListener {
458 @Override
459 public void event(HostEvent event) {
460 sendMessage(hostMessage(event));
461 }
462 }
463
464 private class InternalMastershipListener implements MastershipListener {
465 @Override
466 public void event(MastershipEvent event) {
467
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800468 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800469 }
470}
471