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;
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.Logger;
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.ClusterEvent.Type.INSTANCE_REMOVED;
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 @@ public abstract class TopologyViewMessages {
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 @@ public abstract class TopologyViewMessages {
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 @@ public abstract class TopologyViewMessages {
unit = B_UNIT;
}
DecimalFormat format = new DecimalFormat("#,###.##");
return format.format(value) + " " + unit;
return format.format(value) + " " + unit;
}
private boolean isInfrastructureEgress(Link link) {
......
......@@ -42,6 +42,7 @@ import org.onlab.onos.net.link.LinkListener;
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 @@ public class TopologyViewWebSocket
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 @@ public class TopologyViewWebSocket
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 @@ public class TopologyViewWebSocket
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 @@ public class TopologyViewWebSocket
String type = string(monitorRequest, "event", "unknown");
if (type.equals("requestAllTraffic")) {
requestAllTraffic(monitorRequest);
} else if (type.equals("requestDeviceLinkFlows")) {
requestDeviceLinkFlows(monitorRequest);
} else {
requestTraffic(monitorRequest);
}
......
......@@ -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());
}
......