ONOS-248 Added ability to visualize counts of device flows along egress links.
Change-Id: I4587c4a285025fb12e616391cdae91966d976c97
Showing
3 changed files
with
150 additions
and
6 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) { |
| ... | @@ -560,7 +638,7 @@ public abstract class TopologyViewMessages { | ... | @@ -560,7 +638,7 @@ public abstract class TopologyViewMessages { |
| 560 | unit = B_UNIT; | 638 | unit = B_UNIT; |
| 561 | } | 639 | } |
| 562 | DecimalFormat format = new DecimalFormat("#,###.##"); | 640 | DecimalFormat format = new DecimalFormat("#,###.##"); |
| 563 | - return format.format(value) + " " + unit; | 641 | + return format.format(value) + " " + unit; |
| 564 | } | 642 | } |
| 565 | 643 | ||
| 566 | private boolean isInfrastructureEgress(Link link) { | 644 | private boolean isInfrastructureEgress(Link link) { | ... | ... |
| ... | @@ -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