ONOS-248 Added ability to visualize counts of device flows along egress links.
Change-Id: I4587c4a285025fb12e616391cdae91966d976c97
Showing
3 changed files
with
149 additions
and
5 deletions
... | @@ -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 | } | ... | ... |
-
Please register or login to post a comment