Thomas Vachuska

ONOS-248 Added ability to visualize counts of device flows along egress links.

Change-Id: I4587c4a285025fb12e616391cdae91966d976c97
...@@ -35,10 +35,14 @@ import org.onlab.onos.net.Host; ...@@ -35,10 +35,14 @@ import org.onlab.onos.net.Host;
35 import org.onlab.onos.net.HostId; 35 import org.onlab.onos.net.HostId;
36 import org.onlab.onos.net.HostLocation; 36 import org.onlab.onos.net.HostLocation;
37 import org.onlab.onos.net.Link; 37 import org.onlab.onos.net.Link;
38 +import org.onlab.onos.net.PortNumber;
38 import org.onlab.onos.net.device.DeviceEvent; 39 import org.onlab.onos.net.device.DeviceEvent;
39 import org.onlab.onos.net.device.DeviceService; 40 import org.onlab.onos.net.device.DeviceService;
40 import org.onlab.onos.net.flow.FlowEntry; 41 import org.onlab.onos.net.flow.FlowEntry;
41 import org.onlab.onos.net.flow.FlowRuleService; 42 import org.onlab.onos.net.flow.FlowRuleService;
43 +import org.onlab.onos.net.flow.TrafficTreatment;
44 +import org.onlab.onos.net.flow.instructions.Instruction;
45 +import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
42 import org.onlab.onos.net.host.HostEvent; 46 import org.onlab.onos.net.host.HostEvent;
43 import org.onlab.onos.net.host.HostService; 47 import org.onlab.onos.net.host.HostService;
44 import org.onlab.onos.net.intent.Intent; 48 import org.onlab.onos.net.intent.Intent;
...@@ -58,6 +62,8 @@ import org.slf4j.Logger; ...@@ -58,6 +62,8 @@ import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory; 62 import org.slf4j.LoggerFactory;
59 63
60 import java.text.DecimalFormat; 64 import java.text.DecimalFormat;
65 +import java.util.ArrayList;
66 +import java.util.HashMap;
61 import java.util.HashSet; 67 import java.util.HashSet;
62 import java.util.Iterator; 68 import java.util.Iterator;
63 import java.util.List; 69 import java.util.List;
...@@ -72,6 +78,7 @@ import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED; ...@@ -72,6 +78,7 @@ import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
72 import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE; 78 import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
73 import static org.onlab.onos.net.DeviceId.deviceId; 79 import static org.onlab.onos.net.DeviceId.deviceId;
74 import static org.onlab.onos.net.HostId.hostId; 80 import static org.onlab.onos.net.HostId.hostId;
81 +import static org.onlab.onos.net.PortNumber.P0;
75 import static org.onlab.onos.net.PortNumber.portNumber; 82 import static org.onlab.onos.net.PortNumber.portNumber;
76 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; 83 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
77 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED; 84 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
...@@ -446,6 +453,49 @@ public abstract class TopologyViewMessages { ...@@ -446,6 +453,49 @@ public abstract class TopologyViewMessages {
446 return count; 453 return count;
447 } 454 }
448 455
456 + // Counts all entries that egress on the given device links.
457 + protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
458 + List<FlowEntry> entries = new ArrayList<>();
459 + Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
460 + Set<Host> hosts = hostService.getConnectedHosts(deviceId);
461 + Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
462 + while (it.hasNext()) {
463 + entries.add(it.next());
464 + }
465 +
466 + // Add all edge links to the set
467 + if (hosts != null) {
468 + for (Host host : hosts) {
469 + links.add(new DefaultEdgeLink(host.providerId(),
470 + new ConnectPoint(host.id(), P0),
471 + host.location(), false));
472 + }
473 + }
474 +
475 + Map<Link, Integer> counts = new HashMap<>();
476 + for (Link link : links) {
477 + counts.put(link, getEgressFlows(link, entries));
478 + }
479 + return counts;
480 + }
481 +
482 + // Counts all entries that egress on the link source port.
483 + private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
484 + int count = 0;
485 + PortNumber out = link.src().port();
486 + for (FlowEntry entry : entries) {
487 + TrafficTreatment treatment = entry.treatment();
488 + for (Instruction instruction : treatment.instructions()) {
489 + if (instruction.type() == Instruction.Type.OUTPUT &&
490 + ((OutputInstruction) instruction).port().equals(out)) {
491 + count++;
492 + }
493 + }
494 + }
495 + return count;
496 + }
497 +
498 +
449 // Returns host details response. 499 // Returns host details response.
450 protected ObjectNode hostDetails(HostId hostId, long sid) { 500 protected ObjectNode hostDetails(HostId hostId, long sid) {
451 Host host = hostService.getHost(hostId); 501 Host host = hostService.getHost(hostId);
...@@ -478,6 +528,34 @@ public abstract class TopologyViewMessages { ...@@ -478,6 +528,34 @@ public abstract class TopologyViewMessages {
478 return envelope("showTraffic", sid, payload); 528 return envelope("showTraffic", sid, payload);
479 } 529 }
480 530
531 + // Produces JSON message to trigger flow overview visualization
532 + protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
533 + ObjectNode payload = mapper.createObjectNode();
534 + ArrayNode paths = mapper.createArrayNode();
535 + payload.set("paths", paths);
536 +
537 + for (Device device : devices) {
538 + Map<Link, Integer> counts = getFlowCounts(device.id());
539 + for (Link link : counts.keySet()) {
540 + addLinkFlows(link, paths, counts.get(link));
541 + }
542 + }
543 + return envelope("showTraffic", sid, payload);
544 + }
545 +
546 + private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
547 + ObjectNode pathNode = mapper.createObjectNode();
548 + ArrayNode linksNode = mapper.createArrayNode();
549 + ArrayNode labels = mapper.createArrayNode();
550 + boolean noFlows = count == null || count == 0;
551 + pathNode.put("class", noFlows ? "secondary" : "primary");
552 + pathNode.put("traffic", false);
553 + pathNode.set("links", linksNode.add(compactLinkString(link)));
554 + pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
555 + (count == 1 ? " flow" : " flows"))));
556 + paths.add(pathNode);
557 + }
558 +
481 559
482 // Produces JSON message to trigger traffic visualization 560 // Produces JSON message to trigger traffic visualization
483 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) { 561 protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
......
...@@ -42,6 +42,7 @@ import org.onlab.onos.net.link.LinkListener; ...@@ -42,6 +42,7 @@ import org.onlab.onos.net.link.LinkListener;
42 import org.onlab.osgi.ServiceDirectory; 42 import org.onlab.osgi.ServiceDirectory;
43 43
44 import java.io.IOException; 44 import java.io.IOException;
45 +import java.util.HashSet;
45 import java.util.Set; 46 import java.util.Set;
46 import java.util.Timer; 47 import java.util.Timer;
47 import java.util.TimerTask; 48 import java.util.TimerTask;
...@@ -167,6 +168,7 @@ public class TopologyViewWebSocket ...@@ -167,6 +168,7 @@ public class TopologyViewWebSocket
167 processMessage((ObjectNode) mapper.reader().readTree(data)); 168 processMessage((ObjectNode) mapper.reader().readTree(data));
168 } catch (Exception e) { 169 } catch (Exception e) {
169 log.warn("Unable to parse GUI request {} due to {}", data, e); 170 log.warn("Unable to parse GUI request {} due to {}", data, e);
171 + log.warn("Boom!!!!", e);
170 } 172 }
171 } 173 }
172 174
...@@ -183,6 +185,8 @@ public class TopologyViewWebSocket ...@@ -183,6 +185,8 @@ public class TopologyViewWebSocket
183 requestTraffic(event); 185 requestTraffic(event);
184 } else if (type.equals("requestAllTraffic")) { 186 } else if (type.equals("requestAllTraffic")) {
185 requestAllTraffic(event); 187 requestAllTraffic(event);
188 + } else if (type.equals("requestDeviceLinkFlows")) {
189 + requestDeviceLinkFlows(event);
186 } else if (type.equals("cancelTraffic")) { 190 } else if (type.equals("cancelTraffic")) {
187 cancelTraffic(event); 191 cancelTraffic(event);
188 } 192 }
...@@ -263,6 +267,26 @@ public class TopologyViewWebSocket ...@@ -263,6 +267,26 @@ public class TopologyViewWebSocket
263 sendMessage(trafficSummaryMessage(sid)); 267 sendMessage(trafficSummaryMessage(sid));
264 } 268 }
265 269
270 + private void requestDeviceLinkFlows(ObjectNode event) {
271 + ObjectNode payload = payload(event);
272 + long sid = number(event, "sid");
273 + monitorRequest = event;
274 +
275 + // Get the set of selected hosts and their intents.
276 + ArrayNode ids = (ArrayNode) payload.path("ids");
277 + Set<Host> hosts = new HashSet<>();
278 + Set<Device> devices = getDevices(ids);
279 +
280 + // If there is a hover node, include it in the hosts and find intents.
281 + String hover = string(payload, "hover");
282 + Set<Intent> hoverIntents;
283 + if (!isNullOrEmpty(hover)) {
284 + addHover(hosts, devices, hover);
285 + }
286 + sendMessage(flowSummaryMessage(sid, devices));
287 + }
288 +
289 +
266 // Subscribes for host traffic messages. 290 // Subscribes for host traffic messages.
267 private synchronized void requestTraffic(ObjectNode event) { 291 private synchronized void requestTraffic(ObjectNode event) {
268 ObjectNode payload = payload(event); 292 ObjectNode payload = payload(event);
...@@ -374,6 +398,8 @@ public class TopologyViewWebSocket ...@@ -374,6 +398,8 @@ public class TopologyViewWebSocket
374 String type = string(monitorRequest, "event", "unknown"); 398 String type = string(monitorRequest, "event", "unknown");
375 if (type.equals("requestAllTraffic")) { 399 if (type.equals("requestAllTraffic")) {
376 requestAllTraffic(monitorRequest); 400 requestAllTraffic(monitorRequest);
401 + } else if (type.equals("requestDeviceLinkFlows")) {
402 + requestDeviceLinkFlows(monitorRequest);
377 } else { 403 } else {
378 requestTraffic(monitorRequest); 404 requestTraffic(monitorRequest);
379 } 405 }
......
...@@ -143,6 +143,7 @@ ...@@ -143,6 +143,7 @@
143 H: toggleHover, 143 H: toggleHover,
144 V: showTrafficAction, 144 V: showTrafficAction,
145 A: showAllTrafficAction, 145 A: showAllTrafficAction,
146 + F: showDeviceLinkFlowsAction,
146 esc: handleEscape 147 esc: handleEscape
147 }; 148 };
148 149
...@@ -179,7 +180,8 @@ ...@@ -179,7 +180,8 @@
179 onosOrder = [], 180 onosOrder = [],
180 oiBox, 181 oiBox,
181 oiShowMaster = false, 182 oiShowMaster = false,
182 - hoverEnabled = false, 183 + hoverModes = [ 'none', 'intents', 'flows'],
184 + hoverMode = 0,
183 portLabelsOn = false; 185 portLabelsOn = false;
184 186
185 // D3 selections 187 // D3 selections
...@@ -314,7 +316,11 @@ ...@@ -314,7 +316,11 @@
314 } 316 }
315 317
316 function toggleHover(view) { 318 function toggleHover(view) {
317 - hoverEnabled = !hoverEnabled; 319 + hoverMode++;
320 + if (hoverMode === hoverModes.length) {
321 + hoverMode = 0;
322 + }
323 + console.log('Hover Mode:' + hoverMode + ': ' + hoverModes[hoverMode]);
318 } 324 }
319 325
320 function togglePorts(view) { 326 function togglePorts(view) {
...@@ -827,6 +833,12 @@ ...@@ -827,6 +833,12 @@
827 } 833 }
828 834
829 function showTrafficAction() { 835 function showTrafficAction() {
836 + // force intents hover mode
837 + hoverMode = 1;
838 + showSelectTraffic();
839 + }
840 +
841 + function showSelectTraffic() {
830 // if nothing is hovered over, and nothing selected, send cancel request 842 // if nothing is hovered over, and nothing selected, send cancel request
831 if (!hovered && nSel() === 0) { 843 if (!hovered && nSel() === 0) {
832 sendMessage('cancelTraffic', {}); 844 sendMessage('cancelTraffic', {});
...@@ -848,6 +860,25 @@ ...@@ -848,6 +860,25 @@
848 sendMessage('requestAllTraffic', {}); 860 sendMessage('requestAllTraffic', {});
849 } 861 }
850 862
863 + function showDeviceLinkFlowsAction() {
864 + // force intents hover mode
865 + hoverMode = 2;
866 + showDeviceLinkFlows();
867 + }
868 +
869 + function showDeviceLinkFlows() {
870 + // if nothing is hovered over, and nothing selected, send cancel request
871 + if (!hovered && nSel() === 0) {
872 + sendMessage('cancelTraffic', {});
873 + return;
874 + }
875 + var hoverId = (flowsHover() && hovered && hovered.class === 'device') ?
876 + hovered.id : '';
877 + sendMessage('requestDeviceLinkFlows', {
878 + ids: selectOrder,
879 + hover: hoverId
880 + });
881 + }
851 882
852 // ============================== 883 // ==============================
853 // onos instance panel functions 884 // onos instance panel functions
...@@ -1375,14 +1406,18 @@ ...@@ -1375,14 +1406,18 @@
1375 function nodeMouseOver(d) { 1406 function nodeMouseOver(d) {
1376 hovered = d; 1407 hovered = d;
1377 if (trafficHover() && (d.class === 'host' || d.class === 'device')) { 1408 if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
1378 - showTrafficAction(); 1409 + showSelectTraffic();
1410 + } else if (flowsHover() && (d.class === 'device')) {
1411 + showDeviceLinkFlows();
1379 } 1412 }
1380 } 1413 }
1381 1414
1382 function nodeMouseOut(d) { 1415 function nodeMouseOut(d) {
1383 hovered = null; 1416 hovered = null;
1384 if (trafficHover() && (d.class === 'host' || d.class === 'device')) { 1417 if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
1385 - showTrafficAction(); 1418 + showSelectTraffic();
1419 + } else if (flowsHover() && (d.class === 'device')) {
1420 + showDeviceLinkFlows();
1386 } 1421 }
1387 } 1422 }
1388 1423
...@@ -2020,8 +2055,13 @@ ...@@ -2020,8 +2055,13 @@
2020 } 2055 }
2021 2056
2022 function trafficHover() { 2057 function trafficHover() {
2023 - return hoverEnabled; 2058 + return hoverModes[hoverMode] === 'intents';
2059 + }
2060 +
2061 + function flowsHover() {
2062 + return hoverModes[hoverMode] === 'flows';
2024 } 2063 }
2064 +
2025 function toggleTrafficHover() { 2065 function toggleTrafficHover() {
2026 showTrafficOnHover.classed('active', !trafficHover()); 2066 showTrafficOnHover.classed('active', !trafficHover());
2027 } 2067 }
......