Thomas Vachuska

ONOS-303 Added ability to add mult-source intent from GUI.

Fixed treatment of selection & hover modes.

Change-Id: Idf47b6a15b56ea96b9edaeeb034fad0f205af6e3
description "Open Networking Operating System"
description "Open Network Operating System"
author "ON.Lab"
start on (net-device-up
......
......@@ -15,6 +15,7 @@
*/
package org.onlab.onos.gui;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.websocket.WebSocket;
......@@ -23,20 +24,25 @@ import org.onlab.onos.cluster.ClusterEventListener;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.core.ApplicationId;
import org.onlab.onos.core.CoreService;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
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.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostListener;
import org.onlab.onos.net.intent.HostToHostIntent;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentEvent;
import org.onlab.onos.net.intent.IntentListener;
import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkListener;
import org.onlab.osgi.ServiceDirectory;
......@@ -119,7 +125,7 @@ public class TopologyViewWebSocket
super(directory);
intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
hostService, linkService);
hostService, linkService);
appId = directory.get(CoreService.class).registerApplication(APP_ID);
}
......@@ -195,8 +201,11 @@ public class TopologyViewWebSocket
requestDetails(event);
} else if (type.equals("updateMeta")) {
updateMetaUi(event);
} else if (type.equals("addHostIntent")) {
createHostIntent(event);
} else if (type.equals("addMultiSourceIntent")) {
createMultiSourceIntent(event);
} else if (type.equals("requestTraffic")) {
requestTraffic(event);
......@@ -268,6 +277,7 @@ public class TopologyViewWebSocket
}
}
// Creates host-to-host intent.
private void createHostIntent(ObjectNode event) {
ObjectNode payload = payload(event);
......@@ -276,19 +286,66 @@ public class TopologyViewWebSocket
HostId one = hostId(string(payload, "one"));
HostId two = hostId(string(payload, "two"));
HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
HostToHostIntent intent =
new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
startMonitoring(event);
intentService.submit(intent);
}
// Creates multi-source-to-single-dest intent.
private void createMultiSourceIntent(ObjectNode event) {
ObjectNode payload = payload(event);
long id = number(event, "sid");
// TODO: add protection against device ids and non-existent hosts.
Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
HostId dst = hostId(string(payload, "dst"));
Host dstHost = hostService.getHost(dst);
Set<ConnectPoint> ingressPoints = getHostLocations(src);
// FIXME: clearly, this is not enough
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthDst(dstHost.mac()).build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
MultiPointToSinglePointIntent intent =
new MultiPointToSinglePointIntent(appId, selector, treatment,
ingressPoints, dstHost.location());
trafficEvent = event;
intentService.submit(hostIntent);
intentService.submit(intent);
}
private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
Set<ConnectPoint> points = new HashSet<>();
for (HostId hostId : hostIds) {
points.add(getHostLocation(hostId));
}
return points;
}
private HostLocation getHostLocation(HostId hostId) {
return hostService.getHost(hostId).location();
}
// Produces a list of host ids from the specified JSON array.
private Set<HostId> getHostIds(ArrayNode ids) {
Set<HostId> hostIds = new HashSet<>();
for (JsonNode id : ids) {
hostIds.add(hostId(id.asText()));
}
return hostIds;
}
private synchronized long startMonitoring(ObjectNode event) {
if (trafficTask == null) {
trafficEvent = event;
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
if (trafficTask != null) {
stopMonitoring();
}
trafficEvent = event;
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
return number(event, "sid");
}
......
......@@ -132,11 +132,11 @@ svg .node.host circle {
/* LINKS */
#topo svg .link {
opacity: .7;
opacity: .9;
}
#topo svg .link.inactive {
opacity: .2;
opacity: .5;
stroke-dasharray: 8 4;
}
......@@ -285,7 +285,7 @@ svg .node.host circle {
padding: 2px 6px;
font-size: 9pt;
cursor: pointer;
width: 50%;
width: 200px;
text-align: center;
/* theme specific... */
......
......@@ -72,9 +72,9 @@
topo: {
linkBaseColor: '#666',
linkInColor: '#66f',
linkInWidth: 14,
linkInWidth: 12,
linkOutColor: '#f00',
linkOutWidth: 14
linkOutWidth: 10
},
icons: {
w: 30,
......@@ -148,8 +148,7 @@
P: togglePorts,
U: [unpin, 'Unpin node'],
R: [resetZoomPan, 'Reset zoom/pan'],
H: [cycleHoverMode, 'Cycle hover mode'],
V: [showTrafficAction, 'Show traffic'],
V: [showTrafficAction, 'Show related traffic'],
A: [showAllTrafficAction, 'Show all traffic'],
F: [showDeviceLinkFlowsAction, 'Show device link flows'],
esc: handleEscape
......@@ -191,10 +190,13 @@
onosOrder = [],
oiBox,
oiShowMaster = false,
hoverModes = [ 'none', 'intents', 'flows'],
hoverMode = 0,
portLabelsOn = false;
var hoverModeAll = 1,
hoverModeFlows = 2,
hoverModeIntents = 3,
hoverMode = hoverModeFlows;
// D3 selections
var svg,
zoomPanContainer,
......@@ -327,14 +329,6 @@
});
}
function cycleHoverMode(view) {
hoverMode++;
if (hoverMode === hoverModes.length) {
hoverMode = 0;
}
view.flash('Mode: ' + hoverModes[hoverMode]);
}
function togglePorts(view) {
view.alert('togglePorts() callback')
}
......@@ -829,6 +823,14 @@
function getSelId(idx) {
return getSel(idx).obj.id;
}
function getSelIds(start, endOffset) {
var end = selectOrder.length - endOffset;
var ids = [];
selectOrder.slice(start, end).forEach(function (d) {
ids.push(getSelId(d));
});
return ids;
}
function allSelectionsClass(cls) {
for (var i=0, n=nSel(); i<n; i++) {
if (getSel(i).obj.class !== cls) {
......@@ -876,69 +878,92 @@
sendMessage('requestDetails', payload);
}
function addIntentAction() {
function addHostIntentAction() {
sendMessage('addHostIntent', {
one: getSelId(0),
two: getSelId(1),
ids: [ getSelId(0), getSelId(1) ]
one: selectOrder[0],
two: selectOrder[1],
ids: selectOrder
});
network.view.flash('Host-to-Host connectivity added');
network.view.flash('Host-to-Host flow added');
}
function showTrafficAction() {
cancelTraffic();
hoverMode = 1;
showSelectTraffic();
network.view.flash('Related Traffic');
function addMultiSourceIntentAction() {
sendMessage('addMultiSourceIntent', {
src: selectOrder.slice(0, selectOrder.length - 1),
dst: selectOrder[selectOrder.length - 1],
ids: selectOrder
});
network.view.flash('Multi-Source flow added');
}
function cancelTraffic() {
sendMessage('cancelTraffic', {});
}
function showSelectTraffic() {
// if nothing is hovered over, and nothing selected, send cancel request
if (!hovered && nSel() === 0) {
cancelTraffic();
return;
function requestTrafficForMode() {
if (hoverMode === hoverModeAll) {
requestAllTraffic();
} else if (hoverMode === hoverModeFlows) {
requestDeviceLinkFlows();
} else if (hoverMode === hoverModeIntents) {
requestSelectTraffic();
}
}
// NOTE: hover is only populated if "show traffic on hover" is
// toggled on, and the item hovered is a host or a device...
var hoverId = (trafficHover() && hovered &&
(hovered.class === 'host' || hovered.class === 'device'))
function showTrafficAction() {
hoverMode = hoverModeIntents;
requestSelectTraffic();
network.view.flash('Related Traffic');
}
function requestSelectTraffic() {
if (validateSelectionContext()) {
var hoverId = (hoverMode === hoverModeIntents && hovered &&
(hovered.class === 'host' || hovered.class === 'device'))
? hovered.id : '';
sendMessage('requestTraffic', {
ids: selectOrder,
hover: hoverId
});
sendMessage('requestTraffic', {
ids: selectOrder,
hover: hoverId
});
}
}
function showDeviceLinkFlowsAction() {
hoverMode = hoverModeFlows;
requestDeviceLinkFlows();
network.view.flash('Device Flows');
}
function requestDeviceLinkFlows() {
if (validateSelectionContext()) {
var hoverId = (hoverMode === hoverModeFlows && hovered &&
(hovered.class === 'device')) ? hovered.id : '';
sendMessage('requestDeviceLinkFlows', {
ids: selectOrder,
hover: hoverId
});
}
}
function showAllTrafficAction() {
cancelTraffic();
sendMessage('requestAllTraffic', {});
hoverMode = hoverModeAll;
requestAllTraffic();
network.view.flash('All Traffic');
}
function showDeviceLinkFlowsAction() {
cancelTraffic();
hoverMode = 2;
showDeviceLinkFlows();
network.view.flash('Device Flows');
function requestAllTraffic() {
sendMessage('requestAllTraffic', {});
}
function showDeviceLinkFlows() {
// if nothing is hovered over, and nothing selected, send cancel request
function validateSelectionContext() {
if (!hovered && nSel() === 0) {
cancelTraffic();
return;
return false;
}
var hoverId = (flowsHover() && hovered && hovered.class === 'device') ?
hovered.id : '';
sendMessage('requestDeviceLinkFlows', {
ids: selectOrder,
hover: hoverId
});
return true;
}
// TODO: these should be moved out to utility module.
......@@ -1547,20 +1572,12 @@
function nodeMouseOver(d) {
hovered = d;
if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
showSelectTraffic();
} else if (flowsHover() && (d.class === 'device')) {
showDeviceLinkFlows();
}
requestTrafficForMode();
}
function nodeMouseOut(d) {
hovered = null;
if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
showSelectTraffic();
} else if (flowsHover() && (d.class === 'device')) {
showDeviceLinkFlows();
}
requestTrafficForMode();
}
function addHostIcon(node, radius, iid) {
......@@ -2002,22 +2019,29 @@
function updateDetailPane() {
var nSel = selectOrder.length;
if (!nSel) {
detailPane.hide();
cancelTraffic();
emptySelect();
} else if (nSel === 1) {
singleSelect();
requestTrafficForMode();
} else {
multiSelect();
}
}
function emptySelect() {
detailPane.hide();
cancelTraffic();
}
function singleSelect() {
// NOTE: detail is shown from showDetails event callback
requestDetails();
// NOTE: detail pane will be shown from showDetails event callback
requestTrafficForMode();
}
function multiSelect() {
populateMultiSelect();
requestTrafficForMode();
}
function addSep(tbody) {
......@@ -2127,7 +2151,9 @@
addAction(detailPane, 'Show Related Traffic', showTrafficAction);
// if exactly two hosts are selected, also want 'add host intent'
if (nSel() === 2 && allSelectionsClass('host')) {
addAction(detailPane, 'Add Host-to-Host Intent', addIntentAction);
addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
} else if (nSel() >= 2 && allSelectionsClass('host')) {
addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
}
}
......@@ -2239,14 +2265,6 @@
return false;
}
function trafficHover() {
return hoverModes[hoverMode] === 'intents';
}
function flowsHover() {
return hoverModes[hoverMode] === 'flows';
}
function loadGlyphs(svg) {
var defs = svg.append('defs');
gly.defBird(defs);
......