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