From 94f7daec3447d40b15d0dbfe9a26d2574393a4ba Mon Sep 17 00:00:00 2001 From: Simon Hunt Date: Wed, 26 Aug 2015 17:40:59 -0700 Subject: [PATCH] ONOS-2186 - GUI Topo Overlay - (WIP) - Showing traffic on selected intent now subdues other elements. - Augmented Highlights to allow for retrieval by ID. - Reparented HostHighlight and DeviceHighlight to NodeHighlight. - Added a few extra highlight unit tests. Change-Id: I0de1cefdcfda58a6fec6e90be5fe898d35aa1b37 --- .../onosproject/ui/topo/DeviceHighlight.java | 2 +- .../org/onosproject/ui/topo/Highlights.java | 80 +++++++++++++++---- .../onosproject/ui/topo/HostHighlight.java | 2 +- .../onosproject/ui/topo/NodeHighlight.java | 27 +++++++ .../onosproject/ui/topo/HighlightsTest.java | 61 ++++++++++++-- .../onosproject/ui/impl/TrafficMonitor.java | 42 ++++++++-- .../onosproject/ui/impl/topo/TopoJson.java | 8 +- .../main/webapp/app/view/topo/topoForce.js | 9 ++- .../main/webapp/app/view/topo/topoOverlay.js | 29 +++++-- 9 files changed, 214 insertions(+), 46 deletions(-) create mode 100644 core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java diff --git a/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java index fe1ecb2ab9..2985d3d48d 100644 --- a/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java +++ b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java @@ -20,7 +20,7 @@ package org.onosproject.ui.topo; /** * Denotes the highlighting to apply to a device. */ -public class DeviceHighlight extends AbstractHighlight { +public class DeviceHighlight extends NodeHighlight { public DeviceHighlight(String deviceId) { super(TopoElementType.DEVICE, deviceId); diff --git a/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java index 0700173771..be59c26bf8 100644 --- a/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java +++ b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java @@ -17,9 +17,10 @@ package org.onosproject.ui.topo; +import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; @@ -53,9 +54,9 @@ public class Highlights { } } - private final Set devices = new HashSet<>(); - private final Set hosts = new HashSet<>(); - private final Set links = new HashSet<>(); + private final Map devices = new HashMap<>(); + private final Map hosts = new HashMap<>(); + private final Map links = new HashMap<>(); private Amount subdueLevel = Amount.ZERO; @@ -67,7 +68,7 @@ public class Highlights { * @return self, for chaining */ public Highlights add(DeviceHighlight dh) { - devices.add(dh); + devices.put(dh.elementId(), dh); return this; } @@ -78,7 +79,7 @@ public class Highlights { * @return self, for chaining */ public Highlights add(HostHighlight hh) { - hosts.add(hh); + hosts.put(hh.elementId(), hh); return this; } @@ -89,7 +90,7 @@ public class Highlights { * @return self, for chaining */ public Highlights add(LinkHighlight lh) { - links.add(lh); + links.put(lh.elementId(), lh); return this; } @@ -106,30 +107,30 @@ public class Highlights { } /** - * Returns the set of device highlights. + * Returns the collection of device highlights. * * @return device highlights */ - public Set devices() { - return Collections.unmodifiableSet(devices); + public Collection devices() { + return Collections.unmodifiableCollection(devices.values()); } /** - * Returns the set of host highlights. + * Returns the collection of host highlights. * * @return host highlights */ - public Set hosts() { - return Collections.unmodifiableSet(hosts); + public Collection hosts() { + return Collections.unmodifiableCollection(hosts.values()); } /** - * Returns the set of link highlights. + * Returns the collection of link highlights. * * @return link highlights */ - public Set links() { - return Collections.unmodifiableSet(links); + public Collection links() { + return Collections.unmodifiableCollection(links.values()); } /** @@ -141,4 +142,49 @@ public class Highlights { public Amount subdueLevel() { return subdueLevel; } + + /** + * Returns the node highlight (device or host) for the given element + * identifier, or null if no match. + * + * @param id element identifier + * @return corresponding node highlight + */ + public NodeHighlight getNode(String id) { + NodeHighlight nh = devices.get(id); + return nh != null ? nh : hosts.get(id); + } + + /** + * Returns the device highlight for the given device identifier, + * or null if no match. + * + * @param id device identifier + * @return corresponding device highlight + */ + public DeviceHighlight getDevice(String id) { + return devices.get(id); + } + + /** + * Returns the host highlight for the given host identifier, + * or null if no match. + * + * @param id host identifier + * @return corresponding host highlight + */ + public HostHighlight getHost(String id) { + return hosts.get(id); + } + + /** + * Returns the link highlight for the given link identifier, + * or null if no match. + * + * @param id link identifier + * @return corresponding link highlight + */ + public LinkHighlight getLink(String id) { + return links.get(id); + } } diff --git a/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java index cb64e07aa3..76669a84af 100644 --- a/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java +++ b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java @@ -20,7 +20,7 @@ package org.onosproject.ui.topo; /** * Denotes the highlighting to apply to a host. */ -public class HostHighlight extends AbstractHighlight { +public class HostHighlight extends NodeHighlight { public HostHighlight(String hostId) { super(TopoElementType.HOST, hostId); diff --git a/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java new file mode 100644 index 0000000000..735f81668c --- /dev/null +++ b/core/api/src/main/java/org/onosproject/ui/topo/NodeHighlight.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.onosproject.ui.topo; + +/** + * Parent class of {@link DeviceHighlight} and {@link HostHighlight}. + */ +public abstract class NodeHighlight extends AbstractHighlight { + public NodeHighlight(TopoElementType type, String elementId) { + super(type, elementId); + } +} diff --git a/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java b/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java index ce2d2db89e..7d6dfe678b 100644 --- a/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java +++ b/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java @@ -17,28 +17,75 @@ package org.onosproject.ui.topo; +import org.junit.Before; import org.junit.Test; +import org.onosproject.ui.topo.Highlights.Amount; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * Unit tests for {@link Highlights}. */ public class HighlightsTest { - private Highlights hl; + private static final String DEV_1 = "dev-1"; + private static final String DEV_2 = "dev-2"; + private static final String HOST_A = "Host...A"; + + private Highlights highlights; + private DeviceHighlight dh1; + private DeviceHighlight dh2; + private HostHighlight hha; + + @Before + public void setUp() { + highlights = new Highlights(); + } @Test public void basic() { - hl = new Highlights(); + assertEquals("devices", 0, highlights.devices().size()); + assertEquals("hosts", 0, highlights.hosts().size()); + assertEquals("links", 0, highlights.links().size()); + assertEquals("sudue", Amount.ZERO, highlights.subdueLevel()); + } - assertEquals("devices", 0, hl.devices().size()); - assertEquals("hosts", 0, hl.hosts().size()); - assertEquals("links", 0, hl.links().size()); - assertEquals("sudue", Highlights.Amount.ZERO, hl.subdueLevel()); + @Test + public void coupleOfDevices() { + dh1 = new DeviceHighlight(DEV_1); + dh2 = new DeviceHighlight(DEV_2); + + highlights.add(dh1); + highlights.add(dh2); + assertTrue("missing dh1", highlights.devices().contains(dh1)); + assertTrue("missing dh2", highlights.devices().contains(dh2)); + } + + @Test + public void alternateSubdue() { + highlights.subdueAllElse(Amount.MINIMALLY); + assertEquals("wrong level", Amount.MINIMALLY, highlights.subdueLevel()); + } + + @Test + public void highlightRetrieval() { + dh1 = new DeviceHighlight(DEV_1); + hha = new HostHighlight(HOST_A); + highlights.add(dh1) + .add(hha); + + assertNull("dev as host", highlights.getHost(DEV_1)); + assertNull("host as dev", highlights.getDevice(HOST_A)); + + assertEquals("missed dev as dev", dh1, highlights.getDevice(DEV_1)); + assertEquals("missed dev as node", dh1, highlights.getNode(DEV_1)); + + assertEquals("missed host as host", hha, highlights.getHost(HOST_A)); + assertEquals("missed host as node", hha, highlights.getNode(HOST_A)); } // NOTE: further unit tests involving the Highlights class are done // in TopoJsonTest. - } diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java index 2dc8e44e8e..4dcaeb1bb3 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java @@ -20,7 +20,9 @@ package org.onosproject.ui.impl; import com.google.common.collect.ImmutableList; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; import org.onosproject.net.Host; +import org.onosproject.net.HostId; import org.onosproject.net.Link; import org.onosproject.net.PortNumber; import org.onosproject.net.flow.FlowEntry; @@ -40,8 +42,12 @@ import org.onosproject.ui.impl.topo.TopoIntentFilter; import org.onosproject.ui.impl.topo.TrafficLink; import org.onosproject.ui.impl.topo.TrafficLink.StatsType; import org.onosproject.ui.impl.topo.TrafficLinkMap; +import org.onosproject.ui.topo.DeviceHighlight; import org.onosproject.ui.topo.Highlights; +import org.onosproject.ui.topo.Highlights.Amount; +import org.onosproject.ui.topo.HostHighlight; import org.onosproject.ui.topo.LinkHighlight.Flavor; +import org.onosproject.ui.topo.NodeHighlight; import org.onosproject.ui.topo.NodeSelection; import org.onosproject.ui.topo.TopoUtils; import org.slf4j.Logger; @@ -59,9 +65,7 @@ import java.util.Timer; import java.util.TimerTask; import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; -import static org.onosproject.ui.impl.TrafficMonitor.Mode.IDLE; -import static org.onosproject.ui.impl.TrafficMonitor.Mode.RELATED_INTENTS; -import static org.onosproject.ui.impl.TrafficMonitor.Mode.SELECTED_INTENT; +import static org.onosproject.ui.impl.TrafficMonitor.Mode.*; /** * Encapsulates the behavior of monitoring specific traffic patterns. @@ -442,6 +446,7 @@ public class TrafficMonitor { current.id(), selectedIntents.index(), selectedIntents.size()); highlightIntentLinksWithTraffic(highlights, primary); + highlights.subdueAllElse(Amount.MINIMALLY); } return highlights; } @@ -540,19 +545,20 @@ public class TrafficMonitor { TrafficLinkMap linkMap = new TrafficLinkMap(); // NOTE: highlight secondary first, then primary, so that links shared // by intents are colored correctly ("last man wins") - createTrafficLinks(linkMap, secondary, Flavor.SECONDARY_HIGHLIGHT, false); - createTrafficLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, false); + createTrafficLinks(highlights, linkMap, secondary, Flavor.SECONDARY_HIGHLIGHT, false); + createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, false); colorLinks(highlights, linkMap); } private void highlightIntentLinksWithTraffic(Highlights highlights, Set primary) { TrafficLinkMap linkMap = new TrafficLinkMap(); - createTrafficLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, true); + createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, true); colorLinks(highlights, linkMap); } - private void createTrafficLinks(TrafficLinkMap linkMap, Set intents, + private void createTrafficLinks(Highlights highlights, + TrafficLinkMap linkMap, Set intents, Flavor flavor, boolean showTraffic) { for (Intent intent : intents) { List installables = servicesBundle.intentService() @@ -573,11 +579,33 @@ public class TrafficMonitor { boolean isOptical = intent instanceof OpticalConnectivityIntent; processLinks(linkMap, links, flavor, isOptical, showTraffic); + updateHighlights(highlights, links); } } } } + private void updateHighlights(Highlights highlights, Iterable links) { + for (Link link : links) { + ensureNodePresent(highlights, link.src().elementId()); + ensureNodePresent(highlights, link.dst().elementId()); + } + } + + private void ensureNodePresent(Highlights highlights, ElementId eid) { + String id = eid.toString(); + NodeHighlight nh = highlights.getNode(id); + if (nh == null) { + if (eid instanceof DeviceId) { + nh = new DeviceHighlight(id); + highlights.add((DeviceHighlight) nh); + } else if (eid instanceof HostId) { + nh = new HostHighlight(id); + highlights.add((HostHighlight) nh); + } + } + } + // Extracts links from the specified flow rule intent resources private Collection linkResources(Intent installable) { ImmutableList.Builder builder = ImmutableList.builder(); diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java index 0a12e1ca40..8b4b354094 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java @@ -90,13 +90,13 @@ public final class TopoJson { } private static ObjectNode json(DeviceHighlight dh) { - // TODO: implement this once we know what a device highlight looks like - return objectNode(); + return objectNode() + .put(ID, dh.elementId()); } private static ObjectNode json(HostHighlight hh) { - // TODO: implement this once we know what a host highlight looks like - return objectNode(); + return objectNode() + .put(ID, hh.elementId()); } private static ObjectNode json(LinkHighlight lh) { diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js index 555fa50328..dbe8f9f5aa 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoForce.js +++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js @@ -242,6 +242,10 @@ // ======================== + function nodeById(id) { + return lu[id]; + } + function makeNodeKey(node1, node2) { return node1 + '-' + node2; } @@ -515,10 +519,10 @@ }); } - function unsuppressLink(id, less) { + function unsuppressLink(key, less) { var cls = supAmt(less); link.each(function (n) { - if (n.id === id) { + if (n.key === key) { n.el.classed(cls, false); } }); @@ -922,6 +926,7 @@ clearLinkTrafficStyle: clearLinkTrafficStyle, removeLinkLabels: removeLinkLabels, findLinkById: tms.findLinkById, + findNodeById: nodeById, updateLinks: updateLinks, updateNodes: updateNodes, supLayers: suppressLayers, diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js index 4a432e08b2..6bd7762a89 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js +++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js @@ -301,11 +301,12 @@ clearLinkTrafficStyle() removeLinkLabels() findLinkById( id ) + findNodeById( id ) updateLinks() updateNodes() supLayers( bool, [less] ) unsupNode( id, [less] ) - unsupLink( id, [less] ) + unsupLink( key, [less] ) */ // TODO: clear node highlighting @@ -322,16 +323,30 @@ api.supLayers(false, true); } - // TODO: device and host highlights + data.hosts.forEach(function (host) { + var hdata = api.findNodeById(host.id); + if (hdata && !hdata.el.empty()) { + api.unsupNode(hdata.id, less); + // TODO: further highlighting? + } + }); - data.links.forEach(function (lnk) { - var ldata = api.findLinkById(lnk.id), - lab = lnk.label, + data.devices.forEach(function (device) { + var ddata = api.findNodeById(device.id); + if (ddata && !ddata.el.empty()) { + api.unsupNode(ddata.id, less); + // TODO: further highlighting? + } + }); + + data.links.forEach(function (link) { + var ldata = api.findLinkById(link.id), + lab = link.label, units, portcls, magnitude; if (ldata && !ldata.el.empty()) { - api.unsupLink(ldata.id, less); - ldata.el.classed(lnk.css, true); + api.unsupLink(ldata.key, less); + ldata.el.classed(link.css, true); ldata.label = lab; // inject additional styling for port-based traffic