Prince Pereira

Fix for ONOS-291. Highlighting intents in ONOS GUI for selected links.

Change-Id: I757aa40b96d92014fa2d720539da20dd309ec9b1
...@@ -19,11 +19,14 @@ package org.onosproject.ui.topo; ...@@ -19,11 +19,14 @@ package org.onosproject.ui.topo;
19 import com.fasterxml.jackson.databind.JsonNode; 19 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.node.ArrayNode; 20 import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.ObjectNode; 21 import com.fasterxml.jackson.databind.node.ObjectNode;
22 +import org.onosproject.net.ConnectPoint;
22 import org.onosproject.net.Device; 23 import org.onosproject.net.Device;
23 import org.onosproject.net.Element; 24 import org.onosproject.net.Element;
24 import org.onosproject.net.Host; 25 import org.onosproject.net.Host;
26 +import org.onosproject.net.Link;
25 import org.onosproject.net.device.DeviceService; 27 import org.onosproject.net.device.DeviceService;
26 import org.onosproject.net.host.HostService; 28 import org.onosproject.net.host.HostService;
29 +import org.onosproject.net.link.LinkService;
27 import org.onosproject.ui.JsonUtils; 30 import org.onosproject.ui.JsonUtils;
28 import org.slf4j.Logger; 31 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory; 32 import org.slf4j.LoggerFactory;
...@@ -33,11 +36,12 @@ import java.util.HashSet; ...@@ -33,11 +36,12 @@ import java.util.HashSet;
33 import java.util.Set; 36 import java.util.Set;
34 37
35 import static com.google.common.base.Strings.isNullOrEmpty; 38 import static com.google.common.base.Strings.isNullOrEmpty;
39 +import static org.onosproject.net.ConnectPoint.deviceConnectPoint;
36 import static org.onosproject.net.DeviceId.deviceId; 40 import static org.onosproject.net.DeviceId.deviceId;
37 import static org.onosproject.net.HostId.hostId; 41 import static org.onosproject.net.HostId.hostId;
38 42
39 /** 43 /**
40 - * Encapsulates a selection of devices and/or hosts from the topology view. 44 + * Encapsulates a selection of devices, hosts and links from the topology view.
41 */ 45 */
42 public class NodeSelection { 46 public class NodeSelection {
43 47
...@@ -46,31 +50,38 @@ public class NodeSelection { ...@@ -46,31 +50,38 @@ public class NodeSelection {
46 50
47 private static final String IDS = "ids"; 51 private static final String IDS = "ids";
48 private static final String HOVER = "hover"; 52 private static final String HOVER = "hover";
53 + private static final String LINK_ID_DELIM = "-";
49 54
50 private final DeviceService deviceService; 55 private final DeviceService deviceService;
51 private final HostService hostService; 56 private final HostService hostService;
57 + private final LinkService linkService;
52 58
53 private final Set<String> ids; 59 private final Set<String> ids;
54 private final String hover; 60 private final String hover;
55 61
56 private final Set<Device> devices = new HashSet<>(); 62 private final Set<Device> devices = new HashSet<>();
57 private final Set<Host> hosts = new HashSet<>(); 63 private final Set<Host> hosts = new HashSet<>();
64 + private final Set<Link> links = new HashSet<>();
58 private Element hovered; 65 private Element hovered;
59 66
60 /** 67 /**
61 * Creates a node selection entity, from the given payload, using the 68 * Creates a node selection entity, from the given payload, using the
62 - * supplied device and host services. Note that if a device or host was 69 + * supplied link, device and host services. Note that if a link, device
63 - * hovered over by the mouse, it is available via {@link #hovered()}. 70 + * or host was hovered over by the mouse, it is available
71 + * via {@link #hovered()}.
64 * 72 *
65 * @param payload message payload 73 * @param payload message payload
66 * @param deviceService device service 74 * @param deviceService device service
67 * @param hostService host service 75 * @param hostService host service
76 + * @param linkService link service
68 */ 77 */
69 public NodeSelection(ObjectNode payload, 78 public NodeSelection(ObjectNode payload,
70 DeviceService deviceService, 79 DeviceService deviceService,
71 - HostService hostService) { 80 + HostService hostService,
81 + LinkService linkService) {
72 this.deviceService = deviceService; 82 this.deviceService = deviceService;
73 this.hostService = hostService; 83 this.hostService = hostService;
84 + this.linkService = linkService;
74 85
75 ids = extractIds(payload); 86 ids = extractIds(payload);
76 hover = extractHover(payload); 87 hover = extractHover(payload);
...@@ -82,8 +93,9 @@ public class NodeSelection { ...@@ -82,8 +93,9 @@ public class NodeSelection {
82 setHoveredElement(); 93 setHoveredElement();
83 } 94 }
84 95
85 - // now go find the devices and hosts that are in the selection list 96 + // now go find the links, devices and hosts that are in the selection list
86 - Set<String> unmatched = findDevices(ids); 97 + Set<String> unmatched = findLinks(ids);
98 + unmatched = findDevices(unmatched);
87 unmatched = findHosts(unmatched); 99 unmatched = findHosts(unmatched);
88 if (unmatched.size() > 0) { 100 if (unmatched.size() > 0) {
89 log.debug("Skipping unmatched IDs {}", unmatched); 101 log.debug("Skipping unmatched IDs {}", unmatched);
...@@ -101,6 +113,15 @@ public class NodeSelection { ...@@ -101,6 +113,15 @@ public class NodeSelection {
101 } 113 }
102 114
103 /** 115 /**
116 + * Returns a view of the selected links (hover not included).
117 + *
118 + * @return selected links
119 + */
120 + public Set<Link> links() {
121 + return Collections.unmodifiableSet(links);
122 + }
123 +
124 + /**
104 * Returns a view of the selected devices, including the hovered device 125 * Returns a view of the selected devices, including the hovered device
105 * if there was one. 126 * if there was one.
106 * 127 *
...@@ -144,7 +165,24 @@ public class NodeSelection { ...@@ -144,7 +165,24 @@ public class NodeSelection {
144 } 165 }
145 166
146 /** 167 /**
147 - * Returns the element (host or device) over which the mouse was hovering, 168 + * Returns a view of the selected links, including the hovered link
169 + * if thee was one.
170 + *
171 + * @return selected (plus hovered) links
172 + */
173 + public Set<Link> linksWithHover() {
174 + Set<Link> withHover;
175 + if (hovered != null && hovered instanceof Link) {
176 + withHover = new HashSet<>(links);
177 + withHover.add((Link) hovered);
178 + } else {
179 + withHover = links;
180 + }
181 + return Collections.unmodifiableSet(withHover);
182 + }
183 +
184 + /**
185 + * Returns the element (link, host or device) over which the mouse was hovering,
148 * or null. 186 * or null.
149 * 187 *
150 * @return element hovered over 188 * @return element hovered over
...@@ -159,7 +197,7 @@ public class NodeSelection { ...@@ -159,7 +197,7 @@ public class NodeSelection {
159 * @return true if nothing selected 197 * @return true if nothing selected
160 */ 198 */
161 public boolean none() { 199 public boolean none() {
162 - return devices().size() == 0 && hosts().size() == 0; 200 + return devices().isEmpty() && hosts().isEmpty() && links().isEmpty();
163 } 201 }
164 202
165 @Override 203 @Override
...@@ -169,6 +207,7 @@ public class NodeSelection { ...@@ -169,6 +207,7 @@ public class NodeSelection {
169 ", hover='" + hover + '\'' + 207 ", hover='" + hover + '\'' +
170 ", #devices=" + devices.size() + 208 ", #devices=" + devices.size() +
171 ", #hosts=" + hosts.size() + 209 ", #hosts=" + hosts.size() +
210 + ", #links=" + links.size() +
172 '}'; 211 '}';
173 } 212 }
174 213
...@@ -248,4 +287,34 @@ public class NodeSelection { ...@@ -248,4 +287,34 @@ public class NodeSelection {
248 } 287 }
249 return unmatched; 288 return unmatched;
250 } 289 }
290 +
291 + private Set<String> findLinks(Set<String> ids) {
292 + Set<String> unmatched = new HashSet<>();
293 + ConnectPoint cpSrc, cpDst;
294 + Link link;
295 +
296 + for (String id : ids) {
297 + try {
298 + String[] connectPoints = id.split(LINK_ID_DELIM);
299 + if (connectPoints.length != 2) {
300 + unmatched.add(id);
301 + continue;
302 + }
303 +
304 + cpSrc = deviceConnectPoint(connectPoints[0]);
305 + cpDst = deviceConnectPoint(connectPoints[1]);
306 + link = linkService.getLink(cpSrc, cpDst);
307 +
308 + if (link != null) {
309 + links.add(link);
310 + } else {
311 + unmatched.add(id);
312 + }
313 +
314 + } catch (Exception e) {
315 + unmatched.add(id);
316 + }
317 + }
318 + return unmatched;
319 + }
251 } 320 }
......
...@@ -21,18 +21,24 @@ import com.fasterxml.jackson.databind.node.ArrayNode; ...@@ -21,18 +21,24 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.ObjectNode; 21 import com.fasterxml.jackson.databind.node.ObjectNode;
22 import com.google.common.collect.ImmutableSet; 22 import com.google.common.collect.ImmutableSet;
23 import org.junit.Test; 23 import org.junit.Test;
24 +import org.onosproject.net.ConnectPoint;
24 import org.onosproject.net.DefaultDevice; 25 import org.onosproject.net.DefaultDevice;
25 import org.onosproject.net.DefaultHost; 26 import org.onosproject.net.DefaultHost;
27 +import org.onosproject.net.DefaultLink;
26 import org.onosproject.net.Device; 28 import org.onosproject.net.Device;
27 import org.onosproject.net.DeviceId; 29 import org.onosproject.net.DeviceId;
28 import org.onosproject.net.Host; 30 import org.onosproject.net.Host;
29 import org.onosproject.net.HostId; 31 import org.onosproject.net.HostId;
32 +import org.onosproject.net.Link;
30 import org.onosproject.net.device.DeviceService; 33 import org.onosproject.net.device.DeviceService;
31 import org.onosproject.net.device.DeviceServiceAdapter; 34 import org.onosproject.net.device.DeviceServiceAdapter;
32 import org.onosproject.net.host.HostService; 35 import org.onosproject.net.host.HostService;
33 import org.onosproject.net.host.HostServiceAdapter; 36 import org.onosproject.net.host.HostServiceAdapter;
37 +import org.onosproject.net.link.LinkService;
38 +import org.onosproject.net.link.LinkServiceAdapter;
34 39
35 import static org.junit.Assert.*; 40 import static org.junit.Assert.*;
41 +import static org.onosproject.net.Link.Type.DIRECT;
36 42
37 /** 43 /**
38 * Unit tests for {@link NodeSelection}. 44 * Unit tests for {@link NodeSelection}.
...@@ -51,20 +57,31 @@ public class NodeSelectionTest { ...@@ -51,20 +57,31 @@ public class NodeSelectionTest {
51 } 57 }
52 } 58 }
53 59
60 + private static class FakeLink extends DefaultLink {
61 + FakeLink(ConnectPoint src, ConnectPoint dst) {
62 + super(null, src, dst, DIRECT, Link.State.ACTIVE);
63 + }
64 + }
65 +
54 private final ObjectMapper mapper = new ObjectMapper(); 66 private final ObjectMapper mapper = new ObjectMapper();
55 67
56 private static final String IDS = "ids"; 68 private static final String IDS = "ids";
57 private static final String HOVER = "hover"; 69 private static final String HOVER = "hover";
58 70
59 - private static final DeviceId DEVICE_1_ID = DeviceId.deviceId("Device-1"); 71 + private static final DeviceId DEVICE_1_ID = DeviceId.deviceId("Device1");
60 - private static final DeviceId DEVICE_2_ID = DeviceId.deviceId("Device-2"); 72 + private static final DeviceId DEVICE_2_ID = DeviceId.deviceId("Device2");
61 private static final HostId HOST_A_ID = HostId.hostId("aa:aa:aa:aa:aa:aa/1"); 73 private static final HostId HOST_A_ID = HostId.hostId("aa:aa:aa:aa:aa:aa/1");
62 private static final HostId HOST_B_ID = HostId.hostId("bb:bb:bb:bb:bb:bb/2"); 74 private static final HostId HOST_B_ID = HostId.hostId("bb:bb:bb:bb:bb:bb/2");
75 + private static final String LINK_1_ID = "Device1/1-Device2/2";
76 + private static final ConnectPoint CP_SRC = ConnectPoint.deviceConnectPoint("Device1/1");
77 + private static final ConnectPoint CP_DST = ConnectPoint.deviceConnectPoint("Device2/2");
63 78
64 private static final Device DEVICE_1 = new FakeDevice(DEVICE_1_ID); 79 private static final Device DEVICE_1 = new FakeDevice(DEVICE_1_ID);
65 private static final Device DEVICE_2 = new FakeDevice(DEVICE_2_ID); 80 private static final Device DEVICE_2 = new FakeDevice(DEVICE_2_ID);
66 private static final Host HOST_A = new FakeHost(HOST_A_ID); 81 private static final Host HOST_A = new FakeHost(HOST_A_ID);
67 private static final Host HOST_B = new FakeHost(HOST_B_ID); 82 private static final Host HOST_B = new FakeHost(HOST_B_ID);
83 + private static final Link LINK_A = new FakeLink(CP_SRC, CP_DST);
84 + private static final Link LINK_B = new FakeLink(CP_DST, CP_SRC);
68 85
69 // ================== 86 // ==================
70 // == FAKE SERVICES 87 // == FAKE SERVICES
...@@ -94,8 +111,21 @@ public class NodeSelectionTest { ...@@ -94,8 +111,21 @@ public class NodeSelectionTest {
94 } 111 }
95 } 112 }
96 113
114 + private static class FakeLinks extends LinkServiceAdapter {
115 + @Override
116 + public Link getLink(ConnectPoint src, ConnectPoint dst) {
117 + if (CP_SRC.equals(src) && CP_DST.equals(dst)) {
118 + return LINK_A;
119 + } else if (CP_SRC.equals(dst) && CP_DST.equals(src)) {
120 + return LINK_B;
121 + }
122 + return null;
123 + }
124 + }
125 +
97 private DeviceService deviceService = new FakeDevices(); 126 private DeviceService deviceService = new FakeDevices();
98 private HostService hostService = new FakeHosts(); 127 private HostService hostService = new FakeHosts();
128 + private LinkService linkService = new FakeLinks();
99 129
100 private NodeSelection ns; 130 private NodeSelection ns;
101 131
...@@ -108,7 +138,7 @@ public class NodeSelectionTest { ...@@ -108,7 +138,7 @@ public class NodeSelectionTest {
108 } 138 }
109 139
110 private NodeSelection createNodeSelection(ObjectNode payload) { 140 private NodeSelection createNodeSelection(ObjectNode payload) {
111 - return new NodeSelection(payload, deviceService, hostService); 141 + return new NodeSelection(payload, deviceService, hostService, linkService);
112 } 142 }
113 143
114 // selection JSON payload creation methods 144 // selection JSON payload creation methods
...@@ -134,6 +164,13 @@ public class NodeSelectionTest { ...@@ -134,6 +164,13 @@ public class NodeSelectionTest {
134 ids.add(HOST_A_ID.toString()); 164 ids.add(HOST_A_ID.toString());
135 return payload; 165 return payload;
136 } 166 }
167 + private ObjectNode oneLinkSelected() {
168 + ObjectNode payload = objectNode();
169 + ArrayNode ids = arrayNode();
170 + payload.set(IDS, ids);
171 + ids.add(LINK_1_ID.toString());
172 + return payload;
173 + }
137 174
138 private ObjectNode twoHostsOneDeviceSelected() { 175 private ObjectNode twoHostsOneDeviceSelected() {
139 ObjectNode payload = objectNode(); 176 ObjectNode payload = objectNode();
...@@ -204,6 +241,21 @@ public class NodeSelectionTest { ...@@ -204,6 +241,21 @@ public class NodeSelectionTest {
204 } 241 }
205 242
206 @Test 243 @Test
244 + public void oneLink() {
245 + ns = createNodeSelection(oneLinkSelected());
246 + assertEquals("unexpected devices", 0, ns.devices().size());
247 + assertEquals("unexpected devices w/hover", 0, ns.devicesWithHover().size());
248 + assertEquals("unexpected hosts", 0, ns.hosts().size());
249 + assertEquals("unexpected hosts w/hover", 0, ns.hostsWithHover().size());
250 + assertEquals("missing link", 1, ns.links().size());
251 + assertTrue("missing link A", ns.links().contains(LINK_A));
252 + assertEquals("missing link w/hover", 1, ns.linksWithHover().size());
253 + assertTrue("missing link A w/hover", ns.linksWithHover().contains(LINK_A));
254 + assertFalse("unexpected selection", ns.none());
255 + assertNull("hover?", ns.hovered());
256 + }
257 +
258 + @Test
207 public void twoHostsOneDevice() { 259 public void twoHostsOneDevice() {
208 ns = createNodeSelection(twoHostsOneDeviceSelected()); 260 ns = createNodeSelection(twoHostsOneDeviceSelected());
209 assertEquals("missing device", 1, ns.devices().size()); 261 assertEquals("missing device", 1, ns.devices().size());
......
...@@ -534,7 +534,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { ...@@ -534,7 +534,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
534 @Override 534 @Override
535 public void process(long sid, ObjectNode payload) { 535 public void process(long sid, ObjectNode payload) {
536 NodeSelection nodeSelection = 536 NodeSelection nodeSelection =
537 - new NodeSelection(payload, deviceService, hostService); 537 + new NodeSelection(payload, deviceService, hostService, linkService);
538 traffic.monitor(Mode.DEV_LINK_FLOWS, nodeSelection); 538 traffic.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
539 } 539 }
540 } 540 }
...@@ -547,7 +547,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { ...@@ -547,7 +547,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
547 @Override 547 @Override
548 public void process(long sid, ObjectNode payload) { 548 public void process(long sid, ObjectNode payload) {
549 NodeSelection nodeSelection = 549 NodeSelection nodeSelection =
550 - new NodeSelection(payload, deviceService, hostService); 550 + new NodeSelection(payload, deviceService, hostService, linkService);
551 traffic.monitor(Mode.RELATED_INTENTS, nodeSelection); 551 traffic.monitor(Mode.RELATED_INTENTS, nodeSelection);
552 } 552 }
553 } 553 }
......
...@@ -49,7 +49,10 @@ public class IntentSelection { ...@@ -49,7 +49,10 @@ public class IntentSelection {
49 */ 49 */
50 public IntentSelection(NodeSelection nodes, TopoIntentFilter filter) { 50 public IntentSelection(NodeSelection nodes, TopoIntentFilter filter) {
51 this.nodes = nodes; 51 this.nodes = nodes;
52 - intents = filter.findPathIntents(nodes.hostsWithHover(), nodes.devicesWithHover()); 52 + intents = filter.findPathIntents(
53 + nodes.hostsWithHover(),
54 + nodes.devicesWithHover(),
55 + nodes.linksWithHover());
53 if (intents.size() == 1) { 56 if (intents.size() == 1) {
54 index = 0; // pre-select a single intent 57 index = 0; // pre-select a single intent
55 } 58 }
......
...@@ -74,9 +74,12 @@ public class TopoIntentFilter { ...@@ -74,9 +74,12 @@ public class TopoIntentFilter {
74 * 74 *
75 * @param hosts set of hosts to query by 75 * @param hosts set of hosts to query by
76 * @param devices set of devices to query by 76 * @param devices set of devices to query by
77 - * @return set of intents that 'match' all hosts and devices given 77 + * @param links set of links to query by
78 + * @return set of intents that 'match' all hosts, devices and links given
78 */ 79 */
79 - public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) { 80 + public List<Intent> findPathIntents(Set<Host> hosts,
81 + Set<Device> devices,
82 + Set<Link> links) {
80 // start with all intents 83 // start with all intents
81 Iterable<Intent> sourceIntents = intentService.getIntents(); 84 Iterable<Intent> sourceIntents = intentService.getIntents();
82 85
...@@ -85,7 +88,7 @@ public class TopoIntentFilter { ...@@ -85,7 +88,7 @@ public class TopoIntentFilter {
85 88
86 // Iterate over all intents and produce a set that contains only those 89 // Iterate over all intents and produce a set that contains only those
87 // intents that target all selected hosts or derived edge connect points. 90 // intents that target all selected hosts or derived edge connect points.
88 - return getIntents(hosts, devices, edgePoints, sourceIntents); 91 + return getIntents(hosts, devices, links, edgePoints, sourceIntents);
89 } 92 }
90 93
91 94
...@@ -98,12 +101,12 @@ public class TopoIntentFilter { ...@@ -98,12 +101,12 @@ public class TopoIntentFilter {
98 return edgePoints; 101 return edgePoints;
99 } 102 }
100 103
101 - // Produces a list of intents that target all selected hosts, devices or connect points. 104 + // Produces a list of intents that target all selected hosts, devices, links or connect points.
102 - private List<Intent> getIntents(Set<Host> hosts, Set<Device> devices, 105 + private List<Intent> getIntents(Set<Host> hosts, Set<Device> devices, Set<Link> links,
103 Set<ConnectPoint> edgePoints, 106 Set<ConnectPoint> edgePoints,
104 Iterable<Intent> sourceIntents) { 107 Iterable<Intent> sourceIntents) {
105 List<Intent> intents = new ArrayList<>(); 108 List<Intent> intents = new ArrayList<>();
106 - if (hosts.isEmpty() && devices.isEmpty()) { 109 + if (hosts.isEmpty() && devices.isEmpty() && links.isEmpty()) {
107 return intents; 110 return intents;
108 } 111 }
109 112
...@@ -115,13 +118,13 @@ public class TopoIntentFilter { ...@@ -115,13 +118,13 @@ public class TopoIntentFilter {
115 boolean isRelevant = false; 118 boolean isRelevant = false;
116 if (intent instanceof HostToHostIntent) { 119 if (intent instanceof HostToHostIntent) {
117 isRelevant = isIntentRelevantToHosts((HostToHostIntent) intent, hosts) && 120 isRelevant = isIntentRelevantToHosts((HostToHostIntent) intent, hosts) &&
118 - isIntentRelevantToDevices(intent, devices); 121 + isIntentRelevantToDevices(intent, devices) && isIntentRelevantToLinks(intent, links);
119 } else if (intent instanceof PointToPointIntent) { 122 } else if (intent instanceof PointToPointIntent) {
120 isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints) && 123 isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints) &&
121 - isIntentRelevantToDevices(intent, devices); 124 + isIntentRelevantToDevices(intent, devices) && isIntentRelevantToLinks(intent, links);
122 } else if (intent instanceof MultiPointToSinglePointIntent) { 125 } else if (intent instanceof MultiPointToSinglePointIntent) {
123 isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints) && 126 isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints) &&
124 - isIntentRelevantToDevices(intent, devices); 127 + isIntentRelevantToDevices(intent, devices) && isIntentRelevantToLinks(intent, links);
125 } else if (intent instanceof OpticalConnectivityIntent) { 128 } else if (intent instanceof OpticalConnectivityIntent) {
126 opticalIntents.add((OpticalConnectivityIntent) intent); 129 opticalIntents.add((OpticalConnectivityIntent) intent);
127 } 130 }
...@@ -167,6 +170,17 @@ public class TopoIntentFilter { ...@@ -167,6 +170,17 @@ public class TopoIntentFilter {
167 return true; 170 return true;
168 } 171 }
169 172
173 + // Indicates whether the specified intent involves all of the given links.
174 + private boolean isIntentRelevantToLinks(Intent intent, Iterable<Link> links) {
175 + List<Intent> installables = intentService.getInstallableIntents(intent.key());
176 + for (Link link : links) {
177 + if (!isIntentRelevantToLink(installables, link)) {
178 + return false;
179 + }
180 + }
181 + return true;
182 + }
183 +
170 // Indicates whether the specified intent involves the given device. 184 // Indicates whether the specified intent involves the given device.
171 private boolean isIntentRelevantToDevice(List<Intent> installables, Device device) { 185 private boolean isIntentRelevantToDevice(List<Intent> installables, Device device) {
172 if (installables != null) { 186 if (installables != null) {
...@@ -196,6 +210,38 @@ public class TopoIntentFilter { ...@@ -196,6 +210,38 @@ public class TopoIntentFilter {
196 return false; 210 return false;
197 } 211 }
198 212
213 + // Indicates whether the specified intent involves the given link.
214 + private boolean isIntentRelevantToLink(List<Intent> installables, Link link) {
215 + Link reverseLink = linkService.getLink(link.dst(), link.src());
216 +
217 + if (installables != null) {
218 + for (Intent installable : installables) {
219 + if (installable instanceof PathIntent) {
220 + PathIntent pathIntent = (PathIntent) installable;
221 + return pathIntent.path().links().contains(link) ||
222 + pathIntent.path().links().contains(reverseLink);
223 +
224 + } else if (installable instanceof FlowRuleIntent) {
225 + FlowRuleIntent flowRuleIntent = (FlowRuleIntent) installable;
226 + return flowRuleIntent.resources().contains(link) ||
227 + flowRuleIntent.resources().contains(reverseLink);
228 +
229 + } else if (installable instanceof FlowObjectiveIntent) {
230 + FlowObjectiveIntent objectiveIntent = (FlowObjectiveIntent) installable;
231 + return objectiveIntent.resources().contains(link) ||
232 + objectiveIntent.resources().contains(reverseLink);
233 +
234 + } else if (installable instanceof LinkCollectionIntent) {
235 + LinkCollectionIntent linksIntent = (LinkCollectionIntent) installable;
236 + return linksIntent.links().contains(link) ||
237 + linksIntent.links().contains(reverseLink);
238 +
239 + }
240 + }
241 + }
242 + return false;
243 + }
244 +
199 // Indicates whether the specified links involve the given device. 245 // Indicates whether the specified links involve the given device.
200 private boolean pathContainsDevice(Iterable<Link> links, DeviceId id) { 246 private boolean pathContainsDevice(Iterable<Link> links, DeviceId id) {
201 for (Link link : links) { 247 for (Link link : links) {
......
...@@ -192,7 +192,7 @@ ...@@ -192,7 +192,7 @@
192 // else if we have node selections, deselect them all 192 // else if we have node selections, deselect them all
193 // (work already done) 193 // (work already done)
194 194
195 - } else if (tls.deselectLink()) { 195 + } else if (tls.deselectAllLinks()) {
196 // else if we have a link selected, deselect it 196 // else if we have a link selected, deselect it
197 // (work already done) 197 // (work already done)
198 198
......
...@@ -971,7 +971,7 @@ ...@@ -971,7 +971,7 @@
971 node: function () { return node; }, 971 node: function () { return node; },
972 zoomingOrPanning: zoomingOrPanning, 972 zoomingOrPanning: zoomingOrPanning,
973 updateDeviceColors: td3.updateDeviceColors, 973 updateDeviceColors: td3.updateDeviceColors,
974 - deselectLink: tls.deselectLink 974 + deselectAllLinks: tls.deselectAllLinks
975 }; 975 };
976 } 976 }
977 977
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
31 network, 31 network,
32 showPorts = true, // enable port highlighting by default 32 showPorts = true, // enable port highlighting by default
33 enhancedLink = null, // the link over which the mouse is hovering 33 enhancedLink = null, // the link over which the mouse is hovering
34 - selectedLink = null; // the link which is currently selected 34 + selectedLinks = {}; // the links which are already selected
35 35
36 // SVG elements; 36 // SVG elements;
37 var svg; 37 var svg;
...@@ -210,25 +210,33 @@ ...@@ -210,25 +210,33 @@
210 210
211 function selectLink(ldata) { 211 function selectLink(ldata) {
212 // if the new link is same as old link, do nothing 212 // if the new link is same as old link, do nothing
213 - if (selectedLink && ldata && selectedLink.key === ldata.key) return; 213 + if (d3.event.shiftKey && ldata.el.classed('selected')) {
214 - 214 + unselLink(ldata);
215 - // make sure no nodes are selected 215 + return;
216 - tss.deselectAll(); 216 + }
217 - 217 +
218 - // first, unenhance the currently enhanced link 218 + if (d3.event.shiftKey && !ldata.el.classed('selected')) {
219 - if (selectedLink) { 219 + selLink(ldata);
220 - unselLink(selectedLink); 220 + return;
221 - } 221 + }
222 - selectedLink = ldata; 222 +
223 - if (selectedLink) { 223 + tss.deselectAll();
224 - selLink(selectedLink); 224 +
225 - } 225 + if (!ldata.el.classed('selected')) {
226 + selLink(ldata);
227 + return;
228 + }
229 +
230 + if (ldata.el.classed('selected')) {
231 + unselLink(ldata);
232 + }
226 } 233 }
227 234
228 function unselLink(d) { 235 function unselLink(d) {
229 // guard against link element not set 236 // guard against link element not set
230 if (d.el) { 237 if (d.el) {
231 d.el.classed('selected', false); 238 d.el.classed('selected', false);
239 + delete selectedLinks[d.key];
232 } 240 }
233 } 241 }
234 242
...@@ -237,6 +245,7 @@ ...@@ -237,6 +245,7 @@
237 if (!d.el) return; 245 if (!d.el) return;
238 246
239 d.el.classed('selected', true); 247 d.el.classed('selected', true);
248 + selectedLinks[d.key] = {key : d};
240 249
241 tps.displayLink(d, tov.hooks.modifyLinkData); 250 tps.displayLink(d, tov.hooks.modifyLinkData);
242 tps.displaySomething(); 251 tps.displaySomething();
...@@ -252,6 +261,9 @@ ...@@ -252,6 +261,9 @@
252 261
253 function mouseClickHandler() { 262 function mouseClickHandler() {
254 var mp, link, node; 263 var mp, link, node;
264 + if (!d3.event.shiftKey) {
265 + deselectAllLinks();
266 + }
255 267
256 if (!tss.clickConsumed()) { 268 if (!tss.clickConsumed()) {
257 mp = getLogicalMousePosition(this); 269 mp = getLogicalMousePosition(this);
...@@ -262,6 +274,7 @@ ...@@ -262,6 +274,7 @@
262 } else { 274 } else {
263 link = computeNearestLink(mp); 275 link = computeNearestLink(mp);
264 selectLink(link); 276 selectLink(link);
277 + tss.selectObject(link);
265 } 278 }
266 } 279 }
267 } 280 }
...@@ -285,13 +298,15 @@ ...@@ -285,13 +298,15 @@
285 return on; 298 return on;
286 } 299 }
287 300
288 - function deselectLink() { 301 + function deselectAllLinks() {
289 - if (selectedLink) { 302 +
290 - unselLink(selectedLink); 303 + if (Object.keys(selectedLinks).length > 0) {
291 - selectedLink = null; 304 + network.links.forEach(function (d) {
292 - return true; 305 + if (selectedLinks[d.key]) {
306 + unselLink(d);
307 + }
308 + });
293 } 309 }
294 - return false;
295 } 310 }
296 311
297 // ========================== 312 // ==========================
...@@ -333,7 +348,7 @@ ...@@ -333,7 +348,7 @@
333 initLink: initLink, 348 initLink: initLink,
334 destroyLink: destroyLink, 349 destroyLink: destroyLink,
335 togglePorts: togglePorts, 350 togglePorts: togglePorts,
336 - deselectLink: deselectLink 351 + deselectAllLinks: deselectAllLinks
337 }; 352 };
338 }]); 353 }]);
339 }()); 354 }());
......
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
264 table = detail.appendBody('table'), 264 table = detail.appendBody('table'),
265 tbody = table.append('tbody'); 265 tbody = table.append('tbody');
266 266
267 - title.text('Selected Nodes'); 267 + title.text('Selected Items');
268 ids.forEach(function (d, i) { 268 ids.forEach(function (d, i) {
269 addProp(tbody, i+1, d); 269 addProp(tbody, i+1, d);
270 }); 270 });
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
31 node() // get ref to D3 selection of nodes 31 node() // get ref to D3 selection of nodes
32 zoomingOrPanning( ev ) 32 zoomingOrPanning( ev )
33 updateDeviceColors( [dev] ) 33 updateDeviceColors( [dev] )
34 - deselectLink() 34 + deselectAllLinks()
35 */ 35 */
36 36
37 // internal state 37 // internal state
...@@ -106,12 +106,27 @@ ...@@ -106,12 +106,27 @@
106 } 106 }
107 }); 107 });
108 } 108 }
109 - if (!n) return; 109 +
110 + if (obj.class === 'link') {
111 +
112 + if (selections[obj.key]) {
113 + deselectObject(obj.key);
114 + } else {
115 + selections[obj.key] = { obj: obj, el: el };
116 + selectOrder.push(obj.key);
117 + }
118 +
119 + updateDetail();
120 + return;
121 + }
122 +
123 + if (!n) {
124 + return;
125 + }
110 126
111 if (nodeEv) { 127 if (nodeEv) {
112 consumeClick = true; 128 consumeClick = true;
113 } 129 }
114 - api.deselectLink();
115 130
116 if (ev.shiftKey && n.classed('selected')) { 131 if (ev.shiftKey && n.classed('selected')) {
117 deselectObject(obj.id); 132 deselectObject(obj.id);
...@@ -196,6 +211,11 @@ ...@@ -196,6 +211,11 @@
196 211
197 function singleSelect() { 212 function singleSelect() {
198 var data = getSel(0).obj; 213 var data = getSel(0).obj;
214 +
215 + //the link details are already taken care of in topoLink.js
216 + if (data.class === 'link') {
217 + return;
218 + }
199 requestDetails(data); 219 requestDetails(data);
200 // NOTE: detail panel is shown as a response to receiving 220 // NOTE: detail panel is shown as a response to receiving
201 // a 'showDetails' event from the server. See 'showDetails' 221 // a 'showDetails' event from the server. See 'showDetails'
......
...@@ -75,8 +75,10 @@ ...@@ -75,8 +75,10 @@
75 var hov = api.hovered(); 75 var hov = api.hovered();
76 76
77 function hoverValid() { 77 function hoverValid() {
78 - return hoverMode === 'intents' && 78 + return hoverMode === 'intents' && hov && (
79 - hov && (hov.class === 'host' || hov.class === 'device'); 79 + hov.class === 'host' ||
80 + hov.class === 'device' ||
81 + hov.class === 'link');
80 } 82 }
81 83
82 if (api.somethingSelected()) { 84 if (api.somethingSelected()) {
......