Thomas Vachuska

ONOS-245 Adding more polish and capability to the GUI.

Change-Id: I20cfd48f10de5f053d0c00dc1460d85d5c0d22de
1 +/*
2 + * Copyright 2014 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.onos.gui;
17 +
18 +import org.onlab.onos.net.ConnectPoint;
19 +import org.onlab.onos.net.Device;
20 +import org.onlab.onos.net.DeviceId;
21 +import org.onlab.onos.net.Host;
22 +import org.onlab.onos.net.HostId;
23 +import org.onlab.onos.net.Link;
24 +import org.onlab.onos.net.device.DeviceService;
25 +import org.onlab.onos.net.host.HostService;
26 +import org.onlab.onos.net.intent.HostToHostIntent;
27 +import org.onlab.onos.net.intent.Intent;
28 +import org.onlab.onos.net.intent.IntentService;
29 +import org.onlab.onos.net.intent.LinkCollectionIntent;
30 +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
31 +import org.onlab.onos.net.intent.OpticalConnectivityIntent;
32 +import org.onlab.onos.net.intent.PathIntent;
33 +import org.onlab.onos.net.intent.PointToPointIntent;
34 +import org.onlab.onos.net.link.LinkService;
35 +
36 +import java.util.HashSet;
37 +import java.util.List;
38 +import java.util.Set;
39 +
40 +import static org.onlab.onos.net.intent.IntentState.INSTALLED;
41 +
42 +/**
43 + * Auxiliary facility to query the intent service based on the specified
44 + * set of end-station hosts, edge points or infrastructure devices.
45 + */
46 +public class TopologyViewIntentFilter {
47 +
48 + private final IntentService intentService;
49 + private final DeviceService deviceService;
50 + private final HostService hostService;
51 + private final LinkService linkService;
52 +
53 + /**
54 + * Crreates an intent filter.
55 + *
56 + * @param intentService intent service reference
57 + * @param deviceService device service reference
58 + * @param hostService host service reference
59 + * @param linkService link service reference
60 + */
61 + TopologyViewIntentFilter(IntentService intentService,
62 + DeviceService deviceService,
63 + HostService hostService, LinkService linkService) {
64 + this.intentService = intentService;
65 + this.deviceService = deviceService;
66 + this.hostService = hostService;
67 + this.linkService = linkService;
68 + }
69 +
70 + /**
71 + * Finds all path (host-to-host or point-to-point) intents that pertains
72 + * to the given hosts.
73 + *
74 + * @param hosts set of hosts to query by
75 + * @param devices set of devices to query by
76 + * @return set of intents that 'match' all hosts and devices given
77 + */
78 + Set<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
79 + // Derive from this the set of edge connect points.
80 + Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
81 +
82 + // Iterate over all intents and produce a set that contains only those
83 + // intents that target all selected hosts or derived edge connect points.
84 + return getIntents(hosts, devices, edgePoints);
85 + }
86 +
87 +
88 + // Produces a set of edge points from the specified set of hosts.
89 + private Set<ConnectPoint> getEdgePoints(Set<Host> hosts) {
90 + Set<ConnectPoint> edgePoints = new HashSet<>();
91 + for (Host host : hosts) {
92 + edgePoints.add(host.location());
93 + }
94 + return edgePoints;
95 + }
96 +
97 + // Produces a set of intents that target all selected hosts, devices or connect points.
98 + private Set<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
99 + Set<ConnectPoint> edgePoints) {
100 + Set<Intent> intents = new HashSet<>();
101 + if (hosts.isEmpty() && devices.isEmpty()) {
102 + return intents;
103 + }
104 +
105 + Set<OpticalConnectivityIntent> opticalIntents = new HashSet<>();
106 +
107 + // Search through all intents and see if they are relevant to our search.
108 + for (Intent intent : intentService.getIntents()) {
109 + if (intentService.getIntentState(intent.id()) == INSTALLED) {
110 + boolean isRelevant = false;
111 + if (intent instanceof HostToHostIntent) {
112 + isRelevant = isIntentRelevantToHosts((HostToHostIntent) intent, hosts) &&
113 + isIntentRelevantToDevices(intent, devices);
114 + } else if (intent instanceof PointToPointIntent) {
115 + isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints) &&
116 + isIntentRelevantToDevices(intent, devices);
117 + } else if (intent instanceof MultiPointToSinglePointIntent) {
118 + isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints) &&
119 + isIntentRelevantToDevices(intent, devices);
120 + } else if (intent instanceof OpticalConnectivityIntent) {
121 + opticalIntents.add((OpticalConnectivityIntent) intent);
122 + }
123 + // TODO: add other intents, e.g. SinglePointToMultiPointIntent
124 +
125 + if (isRelevant) {
126 + intents.add(intent);
127 + }
128 + }
129 + }
130 +
131 + // As a second pass, try to link up any optical intents with the
132 + // packet-level ones.
133 + for (OpticalConnectivityIntent intent : opticalIntents) {
134 + if (isIntentRelevant(intent, intents) &&
135 + isIntentRelevantToDevices(intent, devices)) {
136 + intents.add(intent);
137 + }
138 + }
139 + return intents;
140 + }
141 +
142 + // Indicates whether the specified intent involves all of the given hosts.
143 + private boolean isIntentRelevantToHosts(HostToHostIntent intent, Set<Host> hosts) {
144 + for (Host host : hosts) {
145 + HostId id = host.id();
146 + // Bail if intent does not involve this host.
147 + if (!id.equals(intent.one()) && !id.equals(intent.two())) {
148 + return false;
149 + }
150 + }
151 + return true;
152 + }
153 +
154 + // Indicates whether the specified intent involves all of the given devices.
155 + private boolean isIntentRelevantToDevices(Intent intent, Set<Device> devices) {
156 + List<Intent> installables = intentService.getInstallableIntents(intent.id());
157 + for (Device device : devices) {
158 + if (!isIntentRelevantToDevice(installables, device)) {
159 + return false;
160 + }
161 + }
162 + return true;
163 + }
164 +
165 + // Indicates whether the specified intent involves the given device.
166 + private boolean isIntentRelevantToDevice(List<Intent> installables, Device device) {
167 + for (Intent installable : installables) {
168 + if (installable instanceof PathIntent) {
169 + PathIntent pathIntent = (PathIntent) installable;
170 + if (pathContainsDevice(pathIntent.path().links(), device.id())) {
171 + return true;
172 + }
173 + } else if (installable instanceof LinkCollectionIntent) {
174 + LinkCollectionIntent linksIntent = (LinkCollectionIntent) installable;
175 + if (pathContainsDevice(linksIntent.links(), device.id())) {
176 + return true;
177 + }
178 + }
179 + }
180 + return false;
181 + }
182 +
183 + // Indicates whether the specified intent involves the given device.
184 + private boolean pathContainsDevice(Iterable<Link> links, DeviceId id) {
185 + for (Link link : links) {
186 + if (link.src().elementId().equals(id) || link.dst().elementId().equals(id)) {
187 + return true;
188 + }
189 + }
190 + return false;
191 + }
192 +
193 + private boolean isIntentRelevant(PointToPointIntent intent, Set<ConnectPoint> edgePoints) {
194 + for (ConnectPoint point : edgePoints) {
195 + // Bail if intent does not involve this edge point.
196 + if (!point.equals(intent.egressPoint()) &&
197 + !point.equals(intent.ingressPoint())) {
198 + return false;
199 + }
200 + }
201 + return true;
202 + }
203 +
204 + // Indicates whether the specified intent involves all of the given edge points.
205 + private boolean isIntentRelevant(MultiPointToSinglePointIntent intent,
206 + Set<ConnectPoint> edgePoints) {
207 + for (ConnectPoint point : edgePoints) {
208 + // Bail if intent does not involve this edge point.
209 + if (!point.equals(intent.egressPoint()) &&
210 + !intent.ingressPoints().contains(point)) {
211 + return false;
212 + }
213 + }
214 + return true;
215 + }
216 +
217 + // Indicates whether the specified intent involves all of the given edge points.
218 + private boolean isIntentRelevant(OpticalConnectivityIntent opticalIntent,
219 + Set<Intent> intents) {
220 + Link ccSrc = getFirstLink(opticalIntent.getSrc(), false);
221 + Link ccDst = getFirstLink(opticalIntent.getDst(), true);
222 +
223 + for (Intent intent : intents) {
224 + List<Intent> installables = intentService.getInstallableIntents(intent.id());
225 + for (Intent installable : installables) {
226 + if (installable instanceof PathIntent) {
227 + List<Link> links = ((PathIntent) installable).path().links();
228 + if (links.size() == 3) {
229 + Link tunnel = links.get(1);
230 + if (tunnel.src().equals(ccSrc.src()) &&
231 + tunnel.dst().equals(ccDst.dst())) {
232 + return true;
233 + }
234 + }
235 + }
236 + }
237 + }
238 + return false;
239 + }
240 +
241 + private Link getFirstLink(ConnectPoint point, boolean ingress) {
242 + for (Link link : linkService.getLinks(point)) {
243 + if (point.equals(ingress ? link.src() : link.dst())) {
244 + return link;
245 + }
246 + }
247 + return null;
248 + }
249 +
250 +}
...@@ -35,7 +35,6 @@ import org.onlab.onos.net.Host; ...@@ -35,7 +35,6 @@ import org.onlab.onos.net.Host;
35 import org.onlab.onos.net.HostId; 35 import org.onlab.onos.net.HostId;
36 import org.onlab.onos.net.HostLocation; 36 import org.onlab.onos.net.HostLocation;
37 import org.onlab.onos.net.Link; 37 import org.onlab.onos.net.Link;
38 -import org.onlab.onos.net.Path;
39 import org.onlab.onos.net.device.DeviceEvent; 38 import org.onlab.onos.net.device.DeviceEvent;
40 import org.onlab.onos.net.device.DeviceService; 39 import org.onlab.onos.net.device.DeviceService;
41 import org.onlab.onos.net.host.HostEvent; 40 import org.onlab.onos.net.host.HostEvent;
...@@ -57,6 +56,7 @@ import org.slf4j.Logger; ...@@ -57,6 +56,7 @@ import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory; 56 import org.slf4j.LoggerFactory;
58 57
59 import java.text.DecimalFormat; 58 import java.text.DecimalFormat;
59 +import java.util.HashSet;
60 import java.util.Iterator; 60 import java.util.Iterator;
61 import java.util.List; 61 import java.util.List;
62 import java.util.Map; 62 import java.util.Map;
...@@ -68,6 +68,8 @@ import static com.google.common.base.Strings.isNullOrEmpty; ...@@ -68,6 +68,8 @@ import static com.google.common.base.Strings.isNullOrEmpty;
68 import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED; 68 import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
69 import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED; 69 import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
70 import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE; 70 import static org.onlab.onos.cluster.ControllerNode.State.ACTIVE;
71 +import static org.onlab.onos.net.DeviceId.deviceId;
72 +import static org.onlab.onos.net.HostId.hostId;
71 import static org.onlab.onos.net.PortNumber.portNumber; 73 import static org.onlab.onos.net.PortNumber.portNumber;
72 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; 74 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
73 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED; 75 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
...@@ -95,6 +97,8 @@ public abstract class TopologyViewMessages { ...@@ -95,6 +97,8 @@ public abstract class TopologyViewMessages {
95 private static final String KB_UNIT = "KB"; 97 private static final String KB_UNIT = "KB";
96 private static final String B_UNIT = "B"; 98 private static final String B_UNIT = "B";
97 99
100 + private static final String ANIMATED = "animated";
101 +
98 protected final ServiceDirectory directory; 102 protected final ServiceDirectory directory;
99 protected final ClusterService clusterService; 103 protected final ClusterService clusterService;
100 protected final DeviceService deviceService; 104 protected final DeviceService deviceService;
...@@ -196,6 +200,64 @@ public abstract class TopologyViewMessages { ...@@ -196,6 +200,64 @@ public abstract class TopologyViewMessages {
196 return event; 200 return event;
197 } 201 }
198 202
203 + // Produces a set of all hosts listed in the specified JSON array.
204 + protected Set<Host> getHosts(ArrayNode array) {
205 + Set<Host> hosts = new HashSet<>();
206 + if (array != null) {
207 + for (JsonNode node : array) {
208 + try {
209 + addHost(hosts, hostId(node.asText()));
210 + } catch (IllegalArgumentException e) {
211 + log.debug("Skipping ID {}", node.asText());
212 + }
213 + }
214 + }
215 + return hosts;
216 + }
217 +
218 + // Adds the specified host to the set of hosts.
219 + private void addHost(Set<Host> hosts, HostId hostId) {
220 + Host host = hostService.getHost(hostId);
221 + if (host != null) {
222 + hosts.add(host);
223 + }
224 + }
225 +
226 +
227 + // Produces a set of all devices listed in the specified JSON array.
228 + protected Set<Device> getDevices(ArrayNode array) {
229 + Set<Device> devices = new HashSet<>();
230 + if (array != null) {
231 + for (JsonNode node : array) {
232 + try {
233 + addDevice(devices, deviceId(node.asText()));
234 + } catch (IllegalArgumentException e) {
235 + log.debug("Skipping ID {}", node.asText());
236 + }
237 + }
238 + }
239 + return devices;
240 + }
241 +
242 + private void addDevice(Set<Device> devices, DeviceId deviceId) {
243 + Device device = deviceService.getDevice(deviceId);
244 + if (device != null) {
245 + devices.add(device);
246 + }
247 + }
248 +
249 + protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
250 + try {
251 + addHost(hosts, hostId(hover));
252 + } catch (IllegalArgumentException e) {
253 + try {
254 + addDevice(devices, deviceId(hover));
255 + } catch (IllegalArgumentException ne) {
256 + log.debug("Skipping ID {}", hover);
257 + }
258 + }
259 + }
260 +
199 // Produces a cluster instance message to the client. 261 // Produces a cluster instance message to the client.
200 protected ObjectNode instanceMessage(ClusterEvent event) { 262 protected ObjectNode instanceMessage(ClusterEvent event) {
201 ControllerNode node = event.subject(); 263 ControllerNode node = event.subject();
...@@ -382,16 +444,18 @@ public abstract class TopologyViewMessages { ...@@ -382,16 +444,18 @@ public abstract class TopologyViewMessages {
382 new Prop("Longitude", annot.value("longitude")))); 444 new Prop("Longitude", annot.value("longitude"))));
383 } 445 }
384 446
385 - // Produces a path payload to the client. 447 +
386 - protected ObjectNode pathMessage(Path path, String type) { 448 + // Produces JSON message to trigger traffic overview visualization
449 + protected ObjectNode trafficSummaryMessage(long sid) {
387 ObjectNode payload = mapper.createObjectNode(); 450 ObjectNode payload = mapper.createObjectNode();
388 - ArrayNode links = mapper.createArrayNode(); 451 + ArrayNode paths = mapper.createArrayNode();
389 - for (Link link : path.links()) { 452 + payload.set("paths", paths);
390 - links.add(compactLinkString(link)); 453 + for (Link link : linkService.getLinks()) {
454 + Set<Link> links = new HashSet<>();
455 + links.add(link);
456 + addPathTraffic(paths, "plain", "secondary", links);
391 } 457 }
392 - 458 + return envelope("showTraffic", sid, payload);
393 - payload.put("type", type).set("links", links);
394 - return payload;
395 } 459 }
396 460
397 461
...@@ -409,11 +473,14 @@ public abstract class TopologyViewMessages { ...@@ -409,11 +473,14 @@ public abstract class TopologyViewMessages {
409 for (Intent installable : installables) { 473 for (Intent installable : installables) {
410 String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type; 474 String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
411 if (installable instanceof PathIntent) { 475 if (installable instanceof PathIntent) {
412 - addPathTraffic(paths, cls, ((PathIntent) installable).path().links()); 476 + addPathTraffic(paths, cls, ANIMATED,
477 + ((PathIntent) installable).path().links());
413 } else if (installable instanceof LinkCollectionIntent) { 478 } else if (installable instanceof LinkCollectionIntent) {
414 - addPathTraffic(paths, cls, ((LinkCollectionIntent) installable).links()); 479 + addPathTraffic(paths, cls, ANIMATED,
480 + ((LinkCollectionIntent) installable).links());
415 } else if (installable instanceof OpticalPathIntent) { 481 } else if (installable instanceof OpticalPathIntent) {
416 - addPathTraffic(paths, cls, ((OpticalPathIntent) installable).path().links()); 482 + addPathTraffic(paths, cls, ANIMATED,
483 + ((OpticalPathIntent) installable).path().links());
417 } 484 }
418 485
419 } 486 }
...@@ -426,7 +493,8 @@ public abstract class TopologyViewMessages { ...@@ -426,7 +493,8 @@ public abstract class TopologyViewMessages {
426 493
427 // Adds the link segments (path or tree) associated with the specified 494 // Adds the link segments (path or tree) associated with the specified
428 // connectivity intent 495 // connectivity intent
429 - protected void addPathTraffic(ArrayNode paths, String type, Iterable<Link> links) { 496 + protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
497 + Iterable<Link> links) {
430 ObjectNode pathNode = mapper.createObjectNode(); 498 ObjectNode pathNode = mapper.createObjectNode();
431 ArrayNode linksNode = mapper.createArrayNode(); 499 ArrayNode linksNode = mapper.createArrayNode();
432 500
...@@ -445,7 +513,7 @@ public abstract class TopologyViewMessages { ...@@ -445,7 +513,7 @@ public abstract class TopologyViewMessages {
445 labels.add(label); 513 labels.add(label);
446 } 514 }
447 } 515 }
448 - pathNode.put("class", hasTraffic ? type + " animated" : type); 516 + pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
449 pathNode.put("traffic", hasTraffic); 517 pathNode.put("traffic", hasTraffic);
450 pathNode.set("links", linksNode); 518 pathNode.set("links", linksNode);
451 pathNode.set("labels", labels); 519 pathNode.set("labels", labels);
......
...@@ -135,28 +135,26 @@ svg .node.host circle { ...@@ -135,28 +135,26 @@ svg .node.host circle {
135 stroke-dasharray: 8 4; 135 stroke-dasharray: 8 4;
136 } 136 }
137 137
138 -#topo svg .link.primary {
139 - stroke: #ffA300;
140 - stroke-width: 4px;
141 -}
142 #topo svg .link.secondary { 138 #topo svg .link.secondary {
143 stroke: rgba(0,153,51,0.5); 139 stroke: rgba(0,153,51,0.5);
144 stroke-width: 3px; 140 stroke-width: 3px;
145 } 141 }
142 +#topo svg .link.primary {
143 + stroke: #ffA300;
144 + stroke-width: 4px;
145 +}
146 #topo svg .link.animated { 146 #topo svg .link.animated {
147 stroke: #ffA300; 147 stroke: #ffA300;
148 - Xstroke-width: 6px;
149 - Xstroke-dasharray: 8 8
150 } 148 }
151 149
152 -#topo svg .link.primary.optical {
153 - stroke: #74f;
154 - stroke-width: 6px;
155 -}
156 #topo svg .link.secondary.optical { 150 #topo svg .link.secondary.optical {
157 stroke: rgba(128,64,255,0.5); 151 stroke: rgba(128,64,255,0.5);
158 stroke-width: 4px; 152 stroke-width: 4px;
159 } 153 }
154 +#topo svg .link.primary.optical {
155 + stroke: #74f;
156 + stroke-width: 6px;
157 +}
160 #topo svg .link.animated.optical { 158 #topo svg .link.animated.optical {
161 stroke: #74f; 159 stroke: #74f;
162 stroke-width: 10px; 160 stroke-width: 10px;
...@@ -164,8 +162,6 @@ svg .node.host circle { ...@@ -164,8 +162,6 @@ svg .node.host circle {
164 } 162 }
165 163
166 #topo svg .linkLabel rect { 164 #topo svg .linkLabel rect {
167 - Xstroke: #ccc;
168 - Xstroke-width: 2px;
169 fill: #eee; 165 fill: #eee;
170 stroke: none; 166 stroke: none;
171 } 167 }
......
...@@ -141,6 +141,8 @@ ...@@ -141,6 +141,8 @@
141 U: unpin, 141 U: unpin,
142 R: resetZoomPan, 142 R: resetZoomPan,
143 H: toggleHover, 143 H: toggleHover,
144 + V: showTrafficAction,
145 + A: showAllTrafficAction,
144 esc: handleEscape 146 esc: handleEscape
145 }; 147 };
146 148
...@@ -832,8 +834,9 @@ ...@@ -832,8 +834,9 @@
832 } 834 }
833 835
834 // NOTE: hover is only populated if "show traffic on hover" is 836 // NOTE: hover is only populated if "show traffic on hover" is
835 - // toggled on, and the item hovered is a host... 837 + // toggled on, and the item hovered is a host or a device...
836 - var hoverId = (trafficHover() && hovered && hovered.class === 'host') 838 + var hoverId = (trafficHover() && hovered &&
839 + (hovered.class === 'host' || hovered.class === 'device'))
837 ? hovered.id : ''; 840 ? hovered.id : '';
838 sendMessage('requestTraffic', { 841 sendMessage('requestTraffic', {
839 ids: selectOrder, 842 ids: selectOrder,
...@@ -841,6 +844,10 @@ ...@@ -841,6 +844,10 @@
841 }); 844 });
842 } 845 }
843 846
847 + function showAllTrafficAction() {
848 + sendMessage('requestAllTraffic', {});
849 + }
850 +
844 851
845 // ============================== 852 // ==============================
846 // onos instance panel functions 853 // onos instance panel functions
...@@ -1367,14 +1374,14 @@ ...@@ -1367,14 +1374,14 @@
1367 1374
1368 function nodeMouseOver(d) { 1375 function nodeMouseOver(d) {
1369 hovered = d; 1376 hovered = d;
1370 - if (trafficHover() && d.class === 'host') { 1377 + if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
1371 showTrafficAction(); 1378 showTrafficAction();
1372 } 1379 }
1373 } 1380 }
1374 1381
1375 function nodeMouseOut(d) { 1382 function nodeMouseOut(d) {
1376 hovered = null; 1383 hovered = null;
1377 - if (trafficHover() && d.class === 'host') { 1384 + if (trafficHover() && (d.class === 'host' || d.class === 'device')) {
1378 showTrafficAction(); 1385 showTrafficAction();
1379 } 1386 }
1380 } 1387 }
......