Fix for ONOS-291. Highlighting intents in ONOS GUI for selected links.
Change-Id: I757aa40b96d92014fa2d720539da20dd309ec9b1
Showing
11 changed files
with
259 additions
and
52 deletions
... | @@ -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()) { | ... | ... |
-
Please register or login to post a comment