ONOS-248 Added ability to visualize counts of device flows along egress links.
Change-Id: I4587c4a285025fb12e616391cdae91966d976c97
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
index 2c3f018..fdf6caa 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
@@ -35,10 +35,14 @@
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.Link;
+import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.Intent;
@@ -58,6 +62,8 @@
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -72,6 +78,7 @@
import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.HostId.hostId;
+import static org.onlab.onos.net.PortNumber.P0;
import static org.onlab.onos.net.PortNumber.portNumber;
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
@@ -446,6 +453,49 @@
return count;
}
+ // Counts all entries that egress on the given device links.
+ protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
+ List<FlowEntry> entries = new ArrayList<>();
+ Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
+ Set<Host> hosts = hostService.getConnectedHosts(deviceId);
+ Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
+ while (it.hasNext()) {
+ entries.add(it.next());
+ }
+
+ // Add all edge links to the set
+ if (hosts != null) {
+ for (Host host : hosts) {
+ links.add(new DefaultEdgeLink(host.providerId(),
+ new ConnectPoint(host.id(), P0),
+ host.location(), false));
+ }
+ }
+
+ Map<Link, Integer> counts = new HashMap<>();
+ for (Link link : links) {
+ counts.put(link, getEgressFlows(link, entries));
+ }
+ return counts;
+ }
+
+ // Counts all entries that egress on the link source port.
+ private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
+ int count = 0;
+ PortNumber out = link.src().port();
+ for (FlowEntry entry : entries) {
+ TrafficTreatment treatment = entry.treatment();
+ for (Instruction instruction : treatment.instructions()) {
+ if (instruction.type() == Instruction.Type.OUTPUT &&
+ ((OutputInstruction) instruction).port().equals(out)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+
// Returns host details response.
protected ObjectNode hostDetails(HostId hostId, long sid) {
Host host = hostService.getHost(hostId);
@@ -478,6 +528,34 @@
return envelope("showTraffic", sid, payload);
}
+ // Produces JSON message to trigger flow overview visualization
+ protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
+ ObjectNode payload = mapper.createObjectNode();
+ ArrayNode paths = mapper.createArrayNode();
+ payload.set("paths", paths);
+
+ for (Device device : devices) {
+ Map<Link, Integer> counts = getFlowCounts(device.id());
+ for (Link link : counts.keySet()) {
+ addLinkFlows(link, paths, counts.get(link));
+ }
+ }
+ return envelope("showTraffic", sid, payload);
+ }
+
+ private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
+ ObjectNode pathNode = mapper.createObjectNode();
+ ArrayNode linksNode = mapper.createArrayNode();
+ ArrayNode labels = mapper.createArrayNode();
+ boolean noFlows = count == null || count == 0;
+ pathNode.put("class", noFlows ? "secondary" : "primary");
+ pathNode.put("traffic", false);
+ pathNode.set("links", linksNode.add(compactLinkString(link)));
+ pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
+ (count == 1 ? " flow" : " flows"))));
+ paths.add(pathNode);
+ }
+
// Produces JSON message to trigger traffic visualization
protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
@@ -560,7 +638,7 @@
unit = B_UNIT;
}
DecimalFormat format = new DecimalFormat("#,###.##");
- return format.format(value) + " " + unit;
+ return format.format(value) + " " + unit;
}
private boolean isInfrastructureEgress(Link link) {
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
index 99e9aeb..104fa95 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
@@ -42,6 +42,7 @@
import org.onlab.osgi.ServiceDirectory;
import java.io.IOException;
+import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@@ -167,6 +168,7 @@
processMessage((ObjectNode) mapper.reader().readTree(data));
} catch (Exception e) {
log.warn("Unable to parse GUI request {} due to {}", data, e);
+ log.warn("Boom!!!!", e);
}
}
@@ -183,6 +185,8 @@
requestTraffic(event);
} else if (type.equals("requestAllTraffic")) {
requestAllTraffic(event);
+ } else if (type.equals("requestDeviceLinkFlows")) {
+ requestDeviceLinkFlows(event);
} else if (type.equals("cancelTraffic")) {
cancelTraffic(event);
}
@@ -263,6 +267,26 @@
sendMessage(trafficSummaryMessage(sid));
}
+ private void requestDeviceLinkFlows(ObjectNode event) {
+ ObjectNode payload = payload(event);
+ long sid = number(event, "sid");
+ monitorRequest = event;
+
+ // Get the set of selected hosts and their intents.
+ ArrayNode ids = (ArrayNode) payload.path("ids");
+ Set<Host> hosts = new HashSet<>();
+ Set<Device> devices = getDevices(ids);
+
+ // If there is a hover node, include it in the hosts and find intents.
+ String hover = string(payload, "hover");
+ Set<Intent> hoverIntents;
+ if (!isNullOrEmpty(hover)) {
+ addHover(hosts, devices, hover);
+ }
+ sendMessage(flowSummaryMessage(sid, devices));
+ }
+
+
// Subscribes for host traffic messages.
private synchronized void requestTraffic(ObjectNode event) {
ObjectNode payload = payload(event);
@@ -374,6 +398,8 @@
String type = string(monitorRequest, "event", "unknown");
if (type.equals("requestAllTraffic")) {
requestAllTraffic(monitorRequest);
+ } else if (type.equals("requestDeviceLinkFlows")) {
+ requestDeviceLinkFlows(monitorRequest);
} else {
requestTraffic(monitorRequest);
}
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index e9a8c1e..82f7194 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -143,6 +143,7 @@
H: toggleHover,
V: showTrafficAction,
A: showAllTrafficAction,
+ F: showDeviceLinkFlowsAction,
esc: handleEscape
};
@@ -179,7 +180,8 @@
onosOrder = [],
oiBox,
oiShowMaster = false,
- hoverEnabled = false,
+ hoverModes = [ 'none', 'intents', 'flows'],
+ hoverMode = 0,
portLabelsOn = false;
// D3 selections
@@ -314,7 +316,11 @@
}
function toggleHover(view) {
- hoverEnabled = !hoverEnabled;
+ hoverMode++;
+ if (hoverMode === hoverModes.length) {
+ hoverMode = 0;
+ }
+ console.log('Hover Mode:' + hoverMode + ': ' + hoverModes[hoverMode]);
}
function togglePorts(view) {
@@ -827,6 +833,12 @@
}
function showTrafficAction() {
+ // force intents hover mode
+ hoverMode = 1;
+ showSelectTraffic();
+ }
+
+ function showSelectTraffic() {
// if nothing is hovered over, and nothing selected, send cancel request
if (!hovered && nSel() === 0) {
sendMessage('cancelTraffic', {});
@@ -848,6 +860,25 @@
sendMessage('requestAllTraffic', {});
}
+ function showDeviceLinkFlowsAction() {
+ // force intents hover mode
+ hoverMode = 2;
+ showDeviceLinkFlows();
+ }
+
+ function showDeviceLinkFlows() {
+ // if nothing is hovered over, and nothing selected, send cancel request
+ if (!hovered && nSel() === 0) {
+ sendMessage('cancelTraffic', {});
+ return;
+ }
+ var hoverId = (flowsHover() && hovered && hovered.class === 'device') ?
+ hovered.id : '';
+ sendMessage('requestDeviceLinkFlows', {
+ ids: selectOrder,
+ hover: hoverId
+ });
+ }
// ==============================
// onos instance panel functions
@@ -1375,14 +1406,18 @@
function nodeMouseOver(d) {
hovered = d;
if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
- showTrafficAction();
+ showSelectTraffic();
+ } else if (flowsHover() && (d.class === 'device')) {
+ showDeviceLinkFlows();
}
}
function nodeMouseOut(d) {
hovered = null;
if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
- showTrafficAction();
+ showSelectTraffic();
+ } else if (flowsHover() && (d.class === 'device')) {
+ showDeviceLinkFlows();
}
}
@@ -2020,8 +2055,13 @@
}
function trafficHover() {
- return hoverEnabled;
+ return hoverModes[hoverMode] === 'intents';
}
+
+ function flowsHover() {
+ return hoverModes[hoverMode] === 'flows';
+ }
+
function toggleTrafficHover() {
showTrafficOnHover.classed('active', !trafficHover());
}