ONOS-303 Added ability to add mult-source intent from GUI.
Fixed treatment of selection & hover modes.
Change-Id: Idf47b6a15b56ea96b9edaeeb034fad0f205af6e3
diff --git a/tools/package/debian/onos.conf b/tools/package/debian/onos.conf
index 888c02b..9891bd1 100644
--- a/tools/package/debian/onos.conf
+++ b/tools/package/debian/onos.conf
@@ -1,4 +1,4 @@
-description "Open Networking Operating System"
+description "Open Network Operating System"
author "ON.Lab"
start on (net-device-up
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 9562040..f65232e 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
@@ -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.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 @@
super(directory);
intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
- hostService, linkService);
+ hostService, linkService);
appId = directory.get(CoreService.class).registerApplication(APP_ID);
}
@@ -195,8 +201,11 @@
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 @@
}
}
+
// Creates host-to-host intent.
private void createHostIntent(ObjectNode event) {
ObjectNode payload = payload(event);
@@ -276,19 +286,66 @@
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());
- trafficEvent = event;
- intentService.submit(hostIntent);
+ HostToHostIntent intent =
+ new HostToHostIntent(appId, one, two,
+ DefaultTrafficSelector.builder().build(),
+ DefaultTrafficTreatment.builder().build());
+ startMonitoring(event);
+ intentService.submit(intent);
}
- private synchronized long startMonitoring(ObjectNode event) {
- if (trafficTask == null) {
- trafficEvent = event;
- trafficTask = new TrafficMonitor();
- timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
+ // 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(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) {
+ stopMonitoring();
+ }
+ trafficEvent = event;
+ trafficTask = new TrafficMonitor();
+ timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
return number(event, "sid");
}
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 50b3493..46c0d02 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -132,11 +132,11 @@
/* LINKS */
#topo svg .link {
- opacity: .7;
+ opacity: .9;
}
#topo svg .link.inactive {
- opacity: .2;
+ opacity: .5;
stroke-dasharray: 8 4;
}
@@ -285,7 +285,7 @@
padding: 2px 6px;
font-size: 9pt;
cursor: pointer;
- width: 50%;
+ width: 200px;
text-align: center;
/* theme specific... */
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index d58de15..cd30334 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -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 showAllTrafficAction() {
- cancelTraffic();
- sendMessage('requestAllTraffic', {});
- network.view.flash('All Traffic');
- }
function showDeviceLinkFlowsAction() {
- cancelTraffic();
- hoverMode = 2;
- showDeviceLinkFlows();
+ hoverMode = hoverModeFlows;
+ requestDeviceLinkFlows();
network.view.flash('Device Flows');
}
- function showDeviceLinkFlows() {
- // if nothing is hovered over, and nothing selected, send cancel request
+ function requestDeviceLinkFlows() {
+ if (validateSelectionContext()) {
+ var hoverId = (hoverMode === hoverModeFlows && hovered &&
+ (hovered.class === 'device')) ? hovered.id : '';
+ sendMessage('requestDeviceLinkFlows', {
+ ids: selectOrder,
+ hover: hoverId
+ });
+ }
+ }
+
+
+ function showAllTrafficAction() {
+ hoverMode = hoverModeAll;
+ requestAllTraffic();
+ network.view.flash('All Traffic');
+ }
+
+ function requestAllTraffic() {
+ sendMessage('requestAllTraffic', {});
+ }
+
+ 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);