Thomas Vachuska

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

Fixed treatment of selection & hover modes.

Change-Id: Idf47b6a15b56ea96b9edaeeb034fad0f205af6e3
1 -description "Open Networking Operating System" 1 +description "Open Network Operating System"
2 author "ON.Lab" 2 author "ON.Lab"
3 3
4 start on (net-device-up 4 start on (net-device-up
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onlab.onos.gui; 16 package org.onlab.onos.gui;
17 17
18 +import com.fasterxml.jackson.databind.JsonNode;
18 import com.fasterxml.jackson.databind.node.ArrayNode; 19 import com.fasterxml.jackson.databind.node.ArrayNode;
19 import com.fasterxml.jackson.databind.node.ObjectNode; 20 import com.fasterxml.jackson.databind.node.ObjectNode;
20 import org.eclipse.jetty.websocket.WebSocket; 21 import org.eclipse.jetty.websocket.WebSocket;
...@@ -23,20 +24,25 @@ import org.onlab.onos.cluster.ClusterEventListener; ...@@ -23,20 +24,25 @@ import org.onlab.onos.cluster.ClusterEventListener;
23 import org.onlab.onos.cluster.ControllerNode; 24 import org.onlab.onos.cluster.ControllerNode;
24 import org.onlab.onos.core.ApplicationId; 25 import org.onlab.onos.core.ApplicationId;
25 import org.onlab.onos.core.CoreService; 26 import org.onlab.onos.core.CoreService;
27 +import org.onlab.onos.net.ConnectPoint;
26 import org.onlab.onos.net.Device; 28 import org.onlab.onos.net.Device;
27 import org.onlab.onos.net.Host; 29 import org.onlab.onos.net.Host;
28 import org.onlab.onos.net.HostId; 30 import org.onlab.onos.net.HostId;
31 +import org.onlab.onos.net.HostLocation;
29 import org.onlab.onos.net.Link; 32 import org.onlab.onos.net.Link;
30 import org.onlab.onos.net.device.DeviceEvent; 33 import org.onlab.onos.net.device.DeviceEvent;
31 import org.onlab.onos.net.device.DeviceListener; 34 import org.onlab.onos.net.device.DeviceListener;
32 import org.onlab.onos.net.flow.DefaultTrafficSelector; 35 import org.onlab.onos.net.flow.DefaultTrafficSelector;
33 import org.onlab.onos.net.flow.DefaultTrafficTreatment; 36 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
37 +import org.onlab.onos.net.flow.TrafficSelector;
38 +import org.onlab.onos.net.flow.TrafficTreatment;
34 import org.onlab.onos.net.host.HostEvent; 39 import org.onlab.onos.net.host.HostEvent;
35 import org.onlab.onos.net.host.HostListener; 40 import org.onlab.onos.net.host.HostListener;
36 import org.onlab.onos.net.intent.HostToHostIntent; 41 import org.onlab.onos.net.intent.HostToHostIntent;
37 import org.onlab.onos.net.intent.Intent; 42 import org.onlab.onos.net.intent.Intent;
38 import org.onlab.onos.net.intent.IntentEvent; 43 import org.onlab.onos.net.intent.IntentEvent;
39 import org.onlab.onos.net.intent.IntentListener; 44 import org.onlab.onos.net.intent.IntentListener;
45 +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
40 import org.onlab.onos.net.link.LinkEvent; 46 import org.onlab.onos.net.link.LinkEvent;
41 import org.onlab.onos.net.link.LinkListener; 47 import org.onlab.onos.net.link.LinkListener;
42 import org.onlab.osgi.ServiceDirectory; 48 import org.onlab.osgi.ServiceDirectory;
...@@ -119,7 +125,7 @@ public class TopologyViewWebSocket ...@@ -119,7 +125,7 @@ public class TopologyViewWebSocket
119 super(directory); 125 super(directory);
120 126
121 intentFilter = new TopologyViewIntentFilter(intentService, deviceService, 127 intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
122 - hostService, linkService); 128 + hostService, linkService);
123 appId = directory.get(CoreService.class).registerApplication(APP_ID); 129 appId = directory.get(CoreService.class).registerApplication(APP_ID);
124 } 130 }
125 131
...@@ -195,8 +201,11 @@ public class TopologyViewWebSocket ...@@ -195,8 +201,11 @@ public class TopologyViewWebSocket
195 requestDetails(event); 201 requestDetails(event);
196 } else if (type.equals("updateMeta")) { 202 } else if (type.equals("updateMeta")) {
197 updateMetaUi(event); 203 updateMetaUi(event);
204 +
198 } else if (type.equals("addHostIntent")) { 205 } else if (type.equals("addHostIntent")) {
199 createHostIntent(event); 206 createHostIntent(event);
207 + } else if (type.equals("addMultiSourceIntent")) {
208 + createMultiSourceIntent(event);
200 209
201 } else if (type.equals("requestTraffic")) { 210 } else if (type.equals("requestTraffic")) {
202 requestTraffic(event); 211 requestTraffic(event);
...@@ -268,6 +277,7 @@ public class TopologyViewWebSocket ...@@ -268,6 +277,7 @@ public class TopologyViewWebSocket
268 } 277 }
269 } 278 }
270 279
280 +
271 // Creates host-to-host intent. 281 // Creates host-to-host intent.
272 private void createHostIntent(ObjectNode event) { 282 private void createHostIntent(ObjectNode event) {
273 ObjectNode payload = payload(event); 283 ObjectNode payload = payload(event);
...@@ -276,19 +286,66 @@ public class TopologyViewWebSocket ...@@ -276,19 +286,66 @@ public class TopologyViewWebSocket
276 HostId one = hostId(string(payload, "one")); 286 HostId one = hostId(string(payload, "one"));
277 HostId two = hostId(string(payload, "two")); 287 HostId two = hostId(string(payload, "two"));
278 288
279 - HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two, 289 + HostToHostIntent intent =
280 - DefaultTrafficSelector.builder().build(), 290 + new HostToHostIntent(appId, one, two,
281 - DefaultTrafficTreatment.builder().build()); 291 + DefaultTrafficSelector.builder().build(),
292 + DefaultTrafficTreatment.builder().build());
293 + startMonitoring(event);
294 + intentService.submit(intent);
295 + }
296 +
297 + // Creates multi-source-to-single-dest intent.
298 + private void createMultiSourceIntent(ObjectNode event) {
299 + ObjectNode payload = payload(event);
300 + long id = number(event, "sid");
301 + // TODO: add protection against device ids and non-existent hosts.
302 + Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
303 + HostId dst = hostId(string(payload, "dst"));
304 + Host dstHost = hostService.getHost(dst);
305 +
306 + Set<ConnectPoint> ingressPoints = getHostLocations(src);
307 +
308 + // FIXME: clearly, this is not enough
309 + TrafficSelector selector = DefaultTrafficSelector.builder()
310 + .matchEthDst(dstHost.mac()).build();
311 + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
312 +
313 + MultiPointToSinglePointIntent intent =
314 + new MultiPointToSinglePointIntent(appId, selector, treatment,
315 + ingressPoints, dstHost.location());
282 trafficEvent = event; 316 trafficEvent = event;
283 - intentService.submit(hostIntent); 317 + intentService.submit(intent);
284 } 318 }
285 319
320 + private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
321 + Set<ConnectPoint> points = new HashSet<>();
322 + for (HostId hostId : hostIds) {
323 + points.add(getHostLocation(hostId));
324 + }
325 + return points;
326 + }
327 +
328 + private HostLocation getHostLocation(HostId hostId) {
329 + return hostService.getHost(hostId).location();
330 + }
331 +
332 + // Produces a list of host ids from the specified JSON array.
333 + private Set<HostId> getHostIds(ArrayNode ids) {
334 + Set<HostId> hostIds = new HashSet<>();
335 + for (JsonNode id : ids) {
336 + hostIds.add(hostId(id.asText()));
337 + }
338 + return hostIds;
339 + }
340 +
341 +
286 private synchronized long startMonitoring(ObjectNode event) { 342 private synchronized long startMonitoring(ObjectNode event) {
287 - if (trafficTask == null) { 343 + if (trafficTask != null) {
288 - trafficEvent = event; 344 + stopMonitoring();
289 - trafficTask = new TrafficMonitor();
290 - timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
291 } 345 }
346 + trafficEvent = event;
347 + trafficTask = new TrafficMonitor();
348 + timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
292 return number(event, "sid"); 349 return number(event, "sid");
293 } 350 }
294 351
......
...@@ -132,11 +132,11 @@ svg .node.host circle { ...@@ -132,11 +132,11 @@ svg .node.host circle {
132 /* LINKS */ 132 /* LINKS */
133 133
134 #topo svg .link { 134 #topo svg .link {
135 - opacity: .7; 135 + opacity: .9;
136 } 136 }
137 137
138 #topo svg .link.inactive { 138 #topo svg .link.inactive {
139 - opacity: .2; 139 + opacity: .5;
140 stroke-dasharray: 8 4; 140 stroke-dasharray: 8 4;
141 } 141 }
142 142
...@@ -285,7 +285,7 @@ svg .node.host circle { ...@@ -285,7 +285,7 @@ svg .node.host circle {
285 padding: 2px 6px; 285 padding: 2px 6px;
286 font-size: 9pt; 286 font-size: 9pt;
287 cursor: pointer; 287 cursor: pointer;
288 - width: 50%; 288 + width: 200px;
289 text-align: center; 289 text-align: center;
290 290
291 /* theme specific... */ 291 /* theme specific... */
......
...@@ -72,9 +72,9 @@ ...@@ -72,9 +72,9 @@
72 topo: { 72 topo: {
73 linkBaseColor: '#666', 73 linkBaseColor: '#666',
74 linkInColor: '#66f', 74 linkInColor: '#66f',
75 - linkInWidth: 14, 75 + linkInWidth: 12,
76 linkOutColor: '#f00', 76 linkOutColor: '#f00',
77 - linkOutWidth: 14 77 + linkOutWidth: 10
78 }, 78 },
79 icons: { 79 icons: {
80 w: 30, 80 w: 30,
...@@ -148,8 +148,7 @@ ...@@ -148,8 +148,7 @@
148 P: togglePorts, 148 P: togglePorts,
149 U: [unpin, 'Unpin node'], 149 U: [unpin, 'Unpin node'],
150 R: [resetZoomPan, 'Reset zoom/pan'], 150 R: [resetZoomPan, 'Reset zoom/pan'],
151 - H: [cycleHoverMode, 'Cycle hover mode'], 151 + V: [showTrafficAction, 'Show related traffic'],
152 - V: [showTrafficAction, 'Show traffic'],
153 A: [showAllTrafficAction, 'Show all traffic'], 152 A: [showAllTrafficAction, 'Show all traffic'],
154 F: [showDeviceLinkFlowsAction, 'Show device link flows'], 153 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
155 esc: handleEscape 154 esc: handleEscape
...@@ -191,10 +190,13 @@ ...@@ -191,10 +190,13 @@
191 onosOrder = [], 190 onosOrder = [],
192 oiBox, 191 oiBox,
193 oiShowMaster = false, 192 oiShowMaster = false,
194 - hoverModes = [ 'none', 'intents', 'flows'],
195 - hoverMode = 0,
196 portLabelsOn = false; 193 portLabelsOn = false;
197 194
195 + var hoverModeAll = 1,
196 + hoverModeFlows = 2,
197 + hoverModeIntents = 3,
198 + hoverMode = hoverModeFlows;
199 +
198 // D3 selections 200 // D3 selections
199 var svg, 201 var svg,
200 zoomPanContainer, 202 zoomPanContainer,
...@@ -327,14 +329,6 @@ ...@@ -327,14 +329,6 @@
327 }); 329 });
328 } 330 }
329 331
330 - function cycleHoverMode(view) {
331 - hoverMode++;
332 - if (hoverMode === hoverModes.length) {
333 - hoverMode = 0;
334 - }
335 - view.flash('Mode: ' + hoverModes[hoverMode]);
336 - }
337 -
338 function togglePorts(view) { 332 function togglePorts(view) {
339 view.alert('togglePorts() callback') 333 view.alert('togglePorts() callback')
340 } 334 }
...@@ -829,6 +823,14 @@ ...@@ -829,6 +823,14 @@
829 function getSelId(idx) { 823 function getSelId(idx) {
830 return getSel(idx).obj.id; 824 return getSel(idx).obj.id;
831 } 825 }
826 + function getSelIds(start, endOffset) {
827 + var end = selectOrder.length - endOffset;
828 + var ids = [];
829 + selectOrder.slice(start, end).forEach(function (d) {
830 + ids.push(getSelId(d));
831 + });
832 + return ids;
833 + }
832 function allSelectionsClass(cls) { 834 function allSelectionsClass(cls) {
833 for (var i=0, n=nSel(); i<n; i++) { 835 for (var i=0, n=nSel(); i<n; i++) {
834 if (getSel(i).obj.class !== cls) { 836 if (getSel(i).obj.class !== cls) {
...@@ -876,69 +878,92 @@ ...@@ -876,69 +878,92 @@
876 sendMessage('requestDetails', payload); 878 sendMessage('requestDetails', payload);
877 } 879 }
878 880
879 - function addIntentAction() { 881 + function addHostIntentAction() {
880 sendMessage('addHostIntent', { 882 sendMessage('addHostIntent', {
881 - one: getSelId(0), 883 + one: selectOrder[0],
882 - two: getSelId(1), 884 + two: selectOrder[1],
883 - ids: [ getSelId(0), getSelId(1) ] 885 + ids: selectOrder
884 }); 886 });
885 - network.view.flash('Host-to-Host connectivity added'); 887 + network.view.flash('Host-to-Host flow added');
886 } 888 }
887 889
888 - function showTrafficAction() { 890 + function addMultiSourceIntentAction() {
889 - cancelTraffic(); 891 + sendMessage('addMultiSourceIntent', {
890 - hoverMode = 1; 892 + src: selectOrder.slice(0, selectOrder.length - 1),
891 - showSelectTraffic(); 893 + dst: selectOrder[selectOrder.length - 1],
892 - network.view.flash('Related Traffic'); 894 + ids: selectOrder
895 + });
896 + network.view.flash('Multi-Source flow added');
893 } 897 }
894 898
899 +
895 function cancelTraffic() { 900 function cancelTraffic() {
896 sendMessage('cancelTraffic', {}); 901 sendMessage('cancelTraffic', {});
897 } 902 }
898 903
899 - function showSelectTraffic() { 904 + function requestTrafficForMode() {
900 - // if nothing is hovered over, and nothing selected, send cancel request 905 + if (hoverMode === hoverModeAll) {
901 - if (!hovered && nSel() === 0) { 906 + requestAllTraffic();
902 - cancelTraffic(); 907 + } else if (hoverMode === hoverModeFlows) {
903 - return; 908 + requestDeviceLinkFlows();
909 + } else if (hoverMode === hoverModeIntents) {
910 + requestSelectTraffic();
904 } 911 }
912 + }
905 913
906 - // NOTE: hover is only populated if "show traffic on hover" is 914 + function showTrafficAction() {
907 - // toggled on, and the item hovered is a host or a device... 915 + hoverMode = hoverModeIntents;
908 - var hoverId = (trafficHover() && hovered && 916 + requestSelectTraffic();
909 - (hovered.class === 'host' || hovered.class === 'device')) 917 + network.view.flash('Related Traffic');
918 + }
919 +
920 + function requestSelectTraffic() {
921 + if (validateSelectionContext()) {
922 + var hoverId = (hoverMode === hoverModeIntents && hovered &&
923 + (hovered.class === 'host' || hovered.class === 'device'))
910 ? hovered.id : ''; 924 ? hovered.id : '';
911 - sendMessage('requestTraffic', { 925 + sendMessage('requestTraffic', {
912 - ids: selectOrder, 926 + ids: selectOrder,
913 - hover: hoverId 927 + hover: hoverId
914 - }); 928 + });
929 + }
915 } 930 }
916 931
932 +
933 + function showDeviceLinkFlowsAction() {
934 + hoverMode = hoverModeFlows;
935 + requestDeviceLinkFlows();
936 + network.view.flash('Device Flows');
937 + }
938 +
939 + function requestDeviceLinkFlows() {
940 + if (validateSelectionContext()) {
941 + var hoverId = (hoverMode === hoverModeFlows && hovered &&
942 + (hovered.class === 'device')) ? hovered.id : '';
943 + sendMessage('requestDeviceLinkFlows', {
944 + ids: selectOrder,
945 + hover: hoverId
946 + });
947 + }
948 + }
949 +
950 +
917 function showAllTrafficAction() { 951 function showAllTrafficAction() {
918 - cancelTraffic(); 952 + hoverMode = hoverModeAll;
919 - sendMessage('requestAllTraffic', {}); 953 + requestAllTraffic();
920 network.view.flash('All Traffic'); 954 network.view.flash('All Traffic');
921 } 955 }
922 956
923 - function showDeviceLinkFlowsAction() { 957 + function requestAllTraffic() {
924 - cancelTraffic(); 958 + sendMessage('requestAllTraffic', {});
925 - hoverMode = 2;
926 - showDeviceLinkFlows();
927 - network.view.flash('Device Flows');
928 } 959 }
929 960
930 - function showDeviceLinkFlows() { 961 + function validateSelectionContext() {
931 - // if nothing is hovered over, and nothing selected, send cancel request
932 if (!hovered && nSel() === 0) { 962 if (!hovered && nSel() === 0) {
933 cancelTraffic(); 963 cancelTraffic();
934 - return; 964 + return false;
935 } 965 }
936 - var hoverId = (flowsHover() && hovered && hovered.class === 'device') ? 966 + return true;
937 - hovered.id : '';
938 - sendMessage('requestDeviceLinkFlows', {
939 - ids: selectOrder,
940 - hover: hoverId
941 - });
942 } 967 }
943 968
944 // TODO: these should be moved out to utility module. 969 // TODO: these should be moved out to utility module.
...@@ -1547,20 +1572,12 @@ ...@@ -1547,20 +1572,12 @@
1547 1572
1548 function nodeMouseOver(d) { 1573 function nodeMouseOver(d) {
1549 hovered = d; 1574 hovered = d;
1550 - if (trafficHover() && (d.class === 'host' || d.class === 'device')) { 1575 + requestTrafficForMode();
1551 - showSelectTraffic();
1552 - } else if (flowsHover() && (d.class === 'device')) {
1553 - showDeviceLinkFlows();
1554 - }
1555 } 1576 }
1556 1577
1557 function nodeMouseOut(d) { 1578 function nodeMouseOut(d) {
1558 hovered = null; 1579 hovered = null;
1559 - if (trafficHover() && (d.class === 'host' || d.class === 'device')) { 1580 + requestTrafficForMode();
1560 - showSelectTraffic();
1561 - } else if (flowsHover() && (d.class === 'device')) {
1562 - showDeviceLinkFlows();
1563 - }
1564 } 1581 }
1565 1582
1566 function addHostIcon(node, radius, iid) { 1583 function addHostIcon(node, radius, iid) {
...@@ -2002,22 +2019,29 @@ ...@@ -2002,22 +2019,29 @@
2002 function updateDetailPane() { 2019 function updateDetailPane() {
2003 var nSel = selectOrder.length; 2020 var nSel = selectOrder.length;
2004 if (!nSel) { 2021 if (!nSel) {
2005 - detailPane.hide(); 2022 + emptySelect();
2006 - cancelTraffic();
2007 } else if (nSel === 1) { 2023 } else if (nSel === 1) {
2008 singleSelect(); 2024 singleSelect();
2025 + requestTrafficForMode();
2009 } else { 2026 } else {
2010 multiSelect(); 2027 multiSelect();
2011 } 2028 }
2012 } 2029 }
2013 2030
2031 + function emptySelect() {
2032 + detailPane.hide();
2033 + cancelTraffic();
2034 + }
2035 +
2014 function singleSelect() { 2036 function singleSelect() {
2037 + // NOTE: detail is shown from showDetails event callback
2015 requestDetails(); 2038 requestDetails();
2016 - // NOTE: detail pane will be shown from showDetails event callback 2039 + requestTrafficForMode();
2017 } 2040 }
2018 2041
2019 function multiSelect() { 2042 function multiSelect() {
2020 populateMultiSelect(); 2043 populateMultiSelect();
2044 + requestTrafficForMode();
2021 } 2045 }
2022 2046
2023 function addSep(tbody) { 2047 function addSep(tbody) {
...@@ -2127,7 +2151,9 @@ ...@@ -2127,7 +2151,9 @@
2127 addAction(detailPane, 'Show Related Traffic', showTrafficAction); 2151 addAction(detailPane, 'Show Related Traffic', showTrafficAction);
2128 // if exactly two hosts are selected, also want 'add host intent' 2152 // if exactly two hosts are selected, also want 'add host intent'
2129 if (nSel() === 2 && allSelectionsClass('host')) { 2153 if (nSel() === 2 && allSelectionsClass('host')) {
2130 - addAction(detailPane, 'Add Host-to-Host Intent', addIntentAction); 2154 + addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);
2155 + } else if (nSel() >= 2 && allSelectionsClass('host')) {
2156 + addAction(detailPane, 'Create Multi-Source Flow', addMultiSourceIntentAction);
2131 } 2157 }
2132 } 2158 }
2133 2159
...@@ -2239,14 +2265,6 @@ ...@@ -2239,14 +2265,6 @@
2239 return false; 2265 return false;
2240 } 2266 }
2241 2267
2242 - function trafficHover() {
2243 - return hoverModes[hoverMode] === 'intents';
2244 - }
2245 -
2246 - function flowsHover() {
2247 - return hoverModes[hoverMode] === 'flows';
2248 - }
2249 -
2250 function loadGlyphs(svg) { 2268 function loadGlyphs(svg) {
2251 var defs = svg.append('defs'); 2269 var defs = svg.append('defs');
2252 gly.defBird(defs); 2270 gly.defBird(defs);
......