ONOS-6259: Topo2 - Implement server-side highlighting model
- NOTE: Still WIP
- Implement doAggregation() in Traffic2Monitor.
- Plumb through call to get relevantSynthLinks().
- Create UiLinkId from LinkKey.
- Add reference to original UiLink in the UiSynthLink, (so we can use as a key later).
- TrafficLink enhancements:
-- Implement equals/hashCode
-- add a copy constructor
-- add mergeStats() method
-- add stats accessor methods
Change-Id: I693626971b3511b842e80cee7fcd2a252087597f
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2TrafficMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2TrafficMessageHandler.java
index ce7175b..687764d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2TrafficMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2TrafficMessageHandler.java
@@ -24,12 +24,16 @@
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.TrafficMonitorBase.Mode;
+import org.onosproject.ui.impl.UiWebSocket;
import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.model.topo.UiLinkId;
+import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.topo.Highlights;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
+import java.util.Map;
import static org.onosproject.ui.topo.TopoJson.topo2HighlightsMessage;
@@ -44,9 +48,6 @@
private static final String REQUEST_ALL_TRAFFIC = "topo2RequestAllTraffic";
private static final String CANCEL_TRAFFIC = "topo2CancelTraffic";
- // === Outbound event identifiers
- private static final String HIGHLIGHTS = "topo2Highlights";
-
// field values
private static final String TRAFFIC_TYPE = "trafficType";
private static final String FLOW_STATS_BYTES = "flowStatsBytes";
@@ -56,13 +57,9 @@
// configuration parameters
private static final long TRAFFIC_PERIOD = 5000;
-// private UiTopoSession topoSession;
-// private Topo2Jsonifier t2json;
-
protected ServicesBundle services;
- private String version;
-
+ private UiTopoSession topoSession;
private Traffic2Monitor traffic;
@@ -71,13 +68,8 @@
super.init(connection, directory);
services = new ServicesBundle(directory);
-
traffic = new Traffic2Monitor(TRAFFIC_PERIOD, services, this);
-
- // get the topo session from the UiWebSocket
-// topoSession = ((UiWebSocket) connection).topoSession();
-// t2json = new Topo2Jsonifier(directory, connection.userName());
-
+ topoSession = ((UiWebSocket) connection).topoSession();
}
@Override
@@ -104,6 +96,16 @@
sendMessage(topo2HighlightsMessage(highlights));
}
+ /**
+ * Asks the topo session for the relevant synth links for current region.
+ * The returned map is keyed by "original" link.
+ *
+ * @return synth link map
+ */
+ Map<UiLinkId, UiSynthLink> retrieveRelevantSynthLinks() {
+ return topoSession.relevantSynthLinks();
+ }
+
// ==================================================================
private final class Topo2AllTraffic extends RequestHandler {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
index 8f056fa..60e7e83 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
@@ -20,10 +20,15 @@
import org.onosproject.ui.impl.TrafficMonitorBase;
import org.onosproject.ui.impl.topo.util.ServicesBundle;
import org.onosproject.ui.impl.topo.util.TrafficLink;
+import org.onosproject.ui.model.topo.UiLinkId;
+import org.onosproject.ui.model.topo.UiSynthLink;
import org.onosproject.ui.topo.Highlights;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -95,9 +100,34 @@
@Override
protected Set<TrafficLink> doAggregation(Set<TrafficLink> linksWithTraffic) {
- // TODO: figure out how to aggregate the link data...
log.debug("Need to aggregate {} links", linksWithTraffic.size());
- return linksWithTraffic;
+ // first, retrieve from the shared topology model those synth links that
+ // are part of the region currently being viewed by the user...
+ Map<UiLinkId, UiSynthLink> synthLinkMap =
+ msgHandler.retrieveRelevantSynthLinks();
+
+ // NOTE: compute Set<TrafficLink> which represents the consolidated links
+
+ Map<UiLinkId, TrafficLink> mappedByUiLinkId = new HashMap<>();
+
+ for (TrafficLink tl : linksWithTraffic) {
+ UiLinkId tlid = UiLinkId.uiLinkId(tl.key());
+ UiSynthLink sl = synthLinkMap.get(tlid);
+ if (sl != null) {
+ UiLinkId aggrid = sl.link().id();
+ TrafficLink aggregated = mappedByUiLinkId.get(aggrid);
+ if (aggregated == null) {
+ aggregated = new TrafficLink(tl);
+ mappedByUiLinkId.put(aggrid, aggregated);
+ } else {
+ aggregated.mergeStats(tl);
+ }
+ }
+ }
+
+ Set<TrafficLink> result = new HashSet<>();
+ result.addAll(mappedByUiLinkId.values());
+ return result;
}
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
index 3a3f141..2a02ffb 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
@@ -19,6 +19,7 @@
import org.onosproject.net.region.RegionId;
import org.onosproject.ui.UiTopoLayoutService;
import org.onosproject.ui.impl.UiWebSocket;
+import org.onosproject.ui.model.topo.UiLinkId;
import org.onosproject.ui.model.topo.UiModelEvent;
import org.onosproject.ui.impl.topo.model.UiModelListener;
import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
@@ -33,6 +34,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -280,4 +282,14 @@
UiTopoLayout layout = layoutService.getLayout(r);
setCurrentLayout(layout);
}
+
+ /**
+ * Returns synthetic links that are in the current region, mapped by
+ * original link ID.
+ *
+ * @return map of synth links
+ */
+ public Map<UiLinkId, UiSynthLink> relevantSynthLinks() {
+ return sharedModel.relevantSynthLinks(currentLayout.regionId());
+ }
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
index ec07673..fa426e1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
@@ -49,8 +49,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
@@ -553,6 +555,14 @@
return uiTopology.findSynthLinks(regionId);
}
+ Map<UiLinkId, UiSynthLink> relevantSynthLinks(RegionId regionId) {
+ Map<UiLinkId, UiSynthLink> result = new HashMap<>();
+ for (UiSynthLink sl : getSynthLinks(regionId)) {
+ result.put(sl.original().id(), sl);
+ }
+ return result;
+ }
+
/**
* Refreshes the internal state.
*/
@@ -569,11 +579,6 @@
services.region().getRegions().forEach(r -> {
RegionId rid = r.id();
-
-// BasicRegionConfig rcfg = cfgService.getConfig(rid, BasicRegionConfig.class);
-// services.netcfg() ...
- // TODO: figure out how to include peer-location data in UiRegion instance
-
UiRegion region = uiTopology.findRegion(rid);
if (region != null) {
reconcileDevicesAndHostsWithRegion(allDevices, allHosts, rid, region);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
index 6500ae3..183ccd6 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/UiSharedTopologyModel.java
@@ -67,6 +67,7 @@
import org.onosproject.ui.model.topo.UiDevice;
import org.onosproject.ui.model.topo.UiDeviceLink;
import org.onosproject.ui.model.topo.UiHost;
+import org.onosproject.ui.model.topo.UiLinkId;
import org.onosproject.ui.model.topo.UiModelEvent;
import org.onosproject.ui.model.topo.UiRegion;
import org.onosproject.ui.model.topo.UiSynthLink;
@@ -74,6 +75,7 @@
import org.slf4j.LoggerFactory;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -302,6 +304,17 @@
return cache.getSynthLinks(regionId);
}
+ /**
+ * Returns the synthetic links associated with the specified region,
+ * mapped by original link id.
+ *
+ * @param regionId region ID
+ * @return map of synthetic links for that region
+ */
+ public Map<UiLinkId, UiSynthLink> relevantSynthLinks(RegionId regionId) {
+ return cache.relevantSynthLinks(regionId);
+ }
+
// =====================================================================
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
index 166a83a..f2643c6 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TrafficLink.java
@@ -29,6 +29,7 @@
import java.util.HashSet;
import java.util.Set;
+import static com.google.common.base.MoreObjects.toStringHelper;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
@@ -70,6 +71,91 @@
}
/**
+ * Copy constructor.
+ *
+ * @param copy the instance to copy
+ */
+ public TrafficLink(TrafficLink copy) {
+ super(copy.key(), copy.one());
+ setOther(copy.two());
+ bytes = copy.bytes;
+ rate = copy.rate;
+ flows = copy.flows;
+ taggedFlavor = copy.taggedFlavor;
+ hasTraffic = copy.hasTraffic;
+ isOptical = copy.isOptical;
+ antMarch = copy.antMarch;
+ mods.addAll(copy.mods);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ TrafficLink that = (TrafficLink) o;
+
+ return bytes == that.bytes && rate == that.rate &&
+ flows == that.flows && hasTraffic == that.hasTraffic &&
+ isOptical == that.isOptical && antMarch == that.antMarch &&
+ taggedFlavor == that.taggedFlavor && mods.equals(that.mods);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (bytes ^ (bytes >>> 32));
+ result = 31 * result + (int) (rate ^ (rate >>> 32));
+ result = 31 * result + (int) (flows ^ (flows >>> 32));
+ result = 31 * result + taggedFlavor.hashCode();
+ result = 31 * result + (hasTraffic ? 1 : 0);
+ result = 31 * result + (isOptical ? 1 : 0);
+ result = 31 * result + (antMarch ? 1 : 0);
+ result = 31 * result + mods.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("key", key())
+ .add("bytes", bytes)
+ .add("rate", rate)
+ .add("flows", flows)
+ .toString();
+ }
+
+ /**
+ * Returns the count of bytes.
+ *
+ * @return the byte count
+ */
+ public long bytes() {
+ return bytes;
+ }
+
+ /**
+ * Returns the rate.
+ *
+ * @return the rate
+ */
+ public long rate() {
+ return rate;
+ }
+
+ /**
+ * Returns the flows.
+ *
+ * @return flow count
+ */
+ public long flows() {
+ return flows;
+ }
+
+ /**
* Sets the optical flag to the given value.
*
* @param b true if an optical link
@@ -149,6 +235,18 @@
this.flows += count;
}
+ /**
+ * Merges the load recorded on the given traffic link into this one.
+ *
+ * @param other the other traffic link
+ */
+ public void mergeStats(TrafficLink other) {
+ this.bytes += other.bytes;
+ this.rate += other.rate;
+ this.flows += other.flows;
+ }
+
+
@Override
public LinkHighlight highlight(Enum<?> type) {
StatsType statsType = (StatsType) type;
diff --git a/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java
new file mode 100644
index 0000000..2afa8a0
--- /dev/null
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/topo/util/TrafficLinkTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017-present 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.impl.topo.util;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultEdgeLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.LinkKey;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.statistic.DefaultLoad;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.ui.impl.AbstractUiImplTest;
+import org.onosproject.ui.topo.TopoUtils;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Unit tests for {@link TrafficLink}.
+ */
+public class TrafficLinkTest extends AbstractUiImplTest {
+
+ private static final DeviceId D1 = deviceId("1");
+ private static final DeviceId D2 = deviceId("2");
+ private static final PortNumber P1 = portNumber(1);
+ private static final PortNumber P2 = portNumber(2);
+
+ private static final ConnectPoint SRC1 = new ConnectPoint(D1, P1);
+ private static final ConnectPoint DST1 = new ConnectPoint(D2, P1);
+ private static final ConnectPoint DST2 = new ConnectPoint(D2, P2);
+
+ private static final LinkKey X = LinkKey.linkKey(SRC1, DST2);
+
+
+ private TrafficLink createALink() {
+ Link linkIngress = DefaultEdgeLink.createEdgeLink(SRC1, true);
+ LinkKey key = TopoUtils.canonicalLinkKey(checkNotNull(linkIngress));
+ TrafficLink tl = new TrafficLink(key, linkIngress);
+ Link linkEgress = DefaultEdgeLink.createEdgeLink(SRC1, false);
+ tl.setOther(linkEgress);
+ return tl;
+ }
+
+ @Test
+ public void basic() {
+ title("basic");
+
+ TrafficLink tl = createALink();
+ Load bigLoad = new DefaultLoad(2000, 0);
+ tl.addLoad(bigLoad);
+ print(tl);
+ assertEquals("bad bytes value", 2000, tl.bytes());
+ // NOTE: rate is bytes / period (10 seconds)
+ assertEquals("bad rate value", 200, tl.rate());
+ // this load does not represent flows
+ assertEquals("bad flow count", 0, tl.flows());
+ }
+
+ @Test
+ public void copyConstructor() {
+ title("copy-constructor");
+ TrafficLink tlOrig = createALink();
+ TrafficLink tlCopy = new TrafficLink(tlOrig);
+ assertEquals("not copied correctly (1)", tlOrig, tlCopy);
+
+ tlOrig.addLoad(new DefaultLoad(2000, 0));
+ tlCopy = new TrafficLink(tlOrig);
+ assertEquals("not copied correctly (2)", tlOrig, tlCopy);
+
+ tlOrig = createALink();
+ tlOrig.addFlows(345);
+ tlCopy = new TrafficLink(tlOrig);
+ assertEquals("not copied correctly (3)", tlOrig, tlCopy);
+ }
+
+ @Test
+ public void mergeStatsBytes() {
+ title("mergeStatsBytes");
+ TrafficLink tla = createALink();
+ tla.addLoad(new DefaultLoad(2000, 0));
+ print(tla);
+
+ TrafficLink tlb = createALink();
+ tlb.addLoad(new DefaultLoad(3000, 0));
+ print(tlb);
+
+ tla.mergeStats(tlb);
+ print(tla);
+ assertEquals("mergedBytes", 5000, tla.bytes());
+ assertEquals("mergeRate", 500, tla.rate());
+ }
+
+ @Test
+ public void mergeStatsFlows() {
+ title("mergeStatsFlows");
+ TrafficLink tla = createALink();
+ tla.addFlows(9);
+ print(tla);
+
+ TrafficLink tlb = createALink();
+ tlb.addFlows(16);
+ print(tlb);
+
+ tla.mergeStats(tlb);
+ print(tla);
+ assertEquals("mergedFlows", 25, tla.flows());
+ }
+}