ONOS-6259: Topo2 - Implement server-side highlighting model
- (Still WIP)
- refactored trafficSummary() and related methods up into superclass
- introduced hook method for link aggregation

Change-Id: I0a993bbc5ba5f0e861214f8d06877dad3f7bc8ee
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 660ba72..1d5127d 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
@@ -19,7 +19,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import org.onosproject.incubator.net.PortStatisticsService.MetricType;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.ElementId;
@@ -41,7 +40,6 @@
 import org.onosproject.net.intent.OpticalPathIntent;
 import org.onosproject.net.intent.PathIntent;
 import org.onosproject.net.link.LinkService;
-import org.onosproject.net.statistic.Load;
 import org.onosproject.ui.impl.topo.util.IntentSelection;
 import org.onosproject.ui.impl.topo.util.ServicesBundle;
 import org.onosproject.ui.impl.topo.util.TopoIntentFilter;
@@ -68,8 +66,6 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static org.onosproject.incubator.net.PortStatisticsService.MetricType.BYTES;
-import static org.onosproject.incubator.net.PortStatisticsService.MetricType.PACKETS;
 import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
 import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.RELATED_INTENTS;
 import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT;
@@ -107,7 +103,7 @@
     // =======================================================================
     // === API ===
 
-    // monitor(Mode) is implemented in the super class
+    // monitor(Mode) is now implemented in the super class
 
     /**
      * Monitor for traffic data to be sent back to the web client, under
@@ -273,29 +269,8 @@
     // =======================================================================
     // === Generate messages in JSON object node format
 
-    private Highlights trafficSummary(StatsType type) {
-        Highlights highlights = new Highlights();
-
-        TrafficLinkMap linkMap = new TrafficLinkMap();
-        compileLinks(linkMap);
-        addEdgeLinks(linkMap);
-
-        for (TrafficLink tlink : linkMap.biLinks()) {
-            if (type == StatsType.FLOW_STATS) {
-                attachFlowLoad(tlink);
-            } else if (type == StatsType.PORT_STATS) {
-                attachPortLoad(tlink, BYTES);
-            } else if (type == StatsType.PORT_PACKET_STATS) {
-                attachPortLoad(tlink, PACKETS);
-            }
-
-            // we only want to report on links deemed to have traffic
-            if (tlink.hasTraffic()) {
-                highlights.add(tlink.highlight(type));
-            }
-        }
-        return highlights;
-    }
+    // NOTE: trafficSummary(StatsType) => Highlights
+    //        has been moved to the superclass
 
     // create highlights for links, showing flows for selected devices.
     private Highlights deviceLinkFlows() {
@@ -374,49 +349,6 @@
 
     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
-    private void compileLinks(TrafficLinkMap linkMap) {
-        services.link().getLinks().forEach(linkMap::add);
-    }
-
-    private void addEdgeLinks(TrafficLinkMap linkMap) {
-        services.host().getHosts().forEach(host -> {
-            linkMap.add(createEdgeLink(host, true));
-            linkMap.add(createEdgeLink(host, false));
-        });
-    }
-
-    private Load getLinkFlowLoad(Link link) {
-        if (link != null && link.src().elementId() instanceof DeviceId) {
-            return services.flowStats().load(link);
-        }
-        return null;
-    }
-
-    private void attachFlowLoad(TrafficLink link) {
-        link.addLoad(getLinkFlowLoad(link.one()));
-        link.addLoad(getLinkFlowLoad(link.two()));
-    }
-
-    private void attachPortLoad(TrafficLink link, MetricType metricType) {
-        // For bi-directional traffic links, use
-        // the max link rate of either direction
-        // (we choose 'one' since we know that is never null)
-        Link one = link.one();
-        Load egressSrc = services.portStats().load(one.src(), metricType);
-        Load egressDst = services.portStats().load(one.dst(), metricType);
-        link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0);
-    }
-
-    private Load maxLoad(Load a, Load b) {
-        if (a == null) {
-            return b;
-        }
-        if (b == null) {
-            return a;
-        }
-        return a.rate() > b.rate() ? a : b;
-    }
-
     // Counts all flow entries that egress on the links of the given device.
     private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
         // get the flows for the device
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
index dfedaf3..c59f2c5 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -17,15 +17,27 @@
 
 package org.onosproject.ui.impl;
 
+import org.onosproject.incubator.net.PortStatisticsService.MetricType;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.statistic.Load;
 import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.impl.topo.util.TrafficLink;
+import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
 import org.onosproject.ui.topo.AbstractTopoMonitor;
+import org.onosproject.ui.topo.Highlights;
 import org.onosproject.ui.topo.TopoUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashSet;
+import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 
+import static org.onosproject.incubator.net.PortStatisticsService.MetricType.BYTES;
+import static org.onosproject.incubator.net.PortStatisticsService.MetricType.PACKETS;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
 import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE;
 
 /**
@@ -36,7 +48,7 @@
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     // 4 Kilo Bytes as threshold
-    static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
+    protected static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
 
 
     /**
@@ -221,6 +233,163 @@
 
 
     // =======================================================================
+    // === Methods for computing traffic on links
+
+    /**
+     * Generates a {@link Highlights} object summarizing the traffic on the
+     * network, ready to be transmitted back to the client for display on
+     * the topology view.
+     *
+     * @param type the type of statistics to be displayed
+     * @return highlights, representing links to be labeled/colored
+     */
+    protected Highlights trafficSummary(TrafficLink.StatsType type) {
+        Highlights highlights = new Highlights();
+
+        // TODO: consider whether a map would be better...
+        Set<TrafficLink> linksWithTraffic = computeLinksWithTraffic(type);
+
+        Set<TrafficLink> aggregatedLinks = doAggregation(linksWithTraffic);
+
+        for (TrafficLink tlink : aggregatedLinks) {
+            highlights.add(tlink.highlight(type));
+        }
+        return highlights;
+    }
+
+    /**
+     * Generates a set of "traffic links" encapsulating information about the
+     * traffic on each link (that is deemed to have traffic).
+     *
+     * @param type the type of statistics to be displayed
+     * @return the set of links with traffic
+     */
+    protected Set<TrafficLink> computeLinksWithTraffic(TrafficLink.StatsType type) {
+        TrafficLinkMap linkMap = new TrafficLinkMap();
+        compileLinks(linkMap);
+        addEdgeLinks(linkMap);
+
+        Set<TrafficLink> linksWithTraffic = new HashSet<>();
+        // TODO: consider whether a map would be better...
+
+        for (TrafficLink tlink : linkMap.biLinks()) {
+            if (type == TrafficLink.StatsType.FLOW_STATS) {
+                attachFlowLoad(tlink);
+            } else if (type == TrafficLink.StatsType.PORT_STATS) {
+                attachPortLoad(tlink, BYTES);
+            } else if (type == TrafficLink.StatsType.PORT_PACKET_STATS) {
+                attachPortLoad(tlink, PACKETS);
+            }
+
+            // we only want to report on links deemed to have traffic
+            if (tlink.hasTraffic()) {
+                linksWithTraffic.add(tlink);
+            }
+        }
+        return linksWithTraffic;
+    }
+
+    /**
+     * Iterates across the set of links in the topology and generates the
+     * appropriate set of traffic links.
+     *
+     * @param linkMap link map to augment with traffic links
+     */
+    protected void compileLinks(TrafficLinkMap linkMap) {
+        services.link().getLinks().forEach(linkMap::add);
+    }
+
+    /**
+     * Iterates across the set of hosts in the topology and generates the
+     * appropriate set of traffic links for the edge links.
+     *
+     * @param linkMap link map to augment with traffic links
+     */
+    protected void addEdgeLinks(TrafficLinkMap linkMap) {
+        services.host().getHosts().forEach(host -> {
+            linkMap.add(createEdgeLink(host, true));
+            linkMap.add(createEdgeLink(host, false));
+        });
+    }
+
+    /**
+     * Processes the given traffic link to attach the "flow load" attributed
+     * to the underlying topology links.
+     *
+     * @param link the traffic link to process
+     */
+    protected void attachFlowLoad(TrafficLink link) {
+        link.addLoad(getLinkFlowLoad(link.one()));
+        link.addLoad(getLinkFlowLoad(link.two()));
+    }
+
+    /**
+     * Returns the load for the given link, as determined by the statistics
+     * service. May return null.
+     *
+     * @param link the link on which to look up the stats
+     * @return the corresponding load (or null)
+     */
+    protected Load getLinkFlowLoad(Link link) {
+        if (link != null && link.src().elementId() instanceof DeviceId) {
+            return services.flowStats().load(link);
+        }
+        return null;
+    }
+
+    /**
+     * Processes the given traffic link to attach the "port load" attributed
+     * to the underlying topology links, for the specified metric type (either
+     * bytes/sec or packets/sec).
+     *
+     * @param link       the traffic link to process
+     * @param metricType the metric type (bytes or packets)
+     */
+    protected void attachPortLoad(TrafficLink link, MetricType metricType) {
+        // For bi-directional traffic links, use
+        // the max link rate of either direction
+        // (we choose 'one' since we know that is never null)
+        Link one = link.one();
+        Load egressSrc = services.portStats().load(one.src(), metricType);
+        Load egressDst = services.portStats().load(one.dst(), metricType);
+        link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0);
+    }
+
+    /**
+     * Returns the load with the greatest rate.
+     *
+     * @param a load a
+     * @param b load b
+     * @return the larger of the two
+     */
+    protected Load maxLoad(Load a, Load b) {
+        if (a == null) {
+            return b;
+        }
+        if (b == null) {
+            return a;
+        }
+        return a.rate() > b.rate() ? a : b;
+    }
+
+
+    /**
+     * Subclasses (well, Traffic2Monitor really) can override this method and
+     * process the traffic links before generating the highlights object.
+     * In particular, links that roll up into "synthetic links" between
+     * regions should show aggregated data from the constituent links.
+     * <p>
+     * This default implementation does nothing.
+     *
+     * @param linksWithTraffic link data for all links
+     * @return transformed link data appropriate to the region display
+     */
+    protected Set<TrafficLink> doAggregation(Set<TrafficLink> linksWithTraffic) {
+        return linksWithTraffic;
+    }
+
+
+    // =======================================================================
     // === Background Task
 
     // Provides periodic update of traffic information to the client
@@ -258,5 +427,4 @@
             }
         }
     }
-
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
index b30ff11..e577f45 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
@@ -40,11 +40,10 @@
 
     The original topology view message handler was broken into two classes
     TopologyViewMessageHandler, and TopologyViewMessageHandlerBase.
-    We do not need to follow that model necessarily. Starting with a
-    single class, and breaking it apart later if necessary.
 
-    Need to figure out the connection between this message handler and the
-    new way of doing things with UiTopoSession...
+    We do not need to follow that model necessarily. Instead, we have this
+    class and Topo2Jsonifier, which takes UiModel objects and renders them
+    as JSON objects.
 
  */
 
@@ -201,12 +200,12 @@
         @Override
         public void process(ObjectNode payload) {
             // client view has gone away; so shut down server-side processing
-            // TODO: implement...
 
             log.debug("topo2Stop: {}", payload);
+            // TODO: tell traffic monitor to stop monitoring...
+            //       this requires this handler to know about traffic handler!!
 
             // OLD CODE DID THE FOLLOWING...
-//            removeListeners();
 //            stopSummaryMonitoring();
 //            traffic.stopMonitoring();
         }
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 cb91e0d..0653832 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
@@ -19,9 +19,13 @@
 
 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.topo.Highlights;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Set;
+
 /**
  * Encapsulates the behavior of monitoring specific traffic patterns in the
  * Topology-2 view.
@@ -31,6 +35,7 @@
     private static final Logger log =
             LoggerFactory.getLogger(Traffic2Monitor.class);
 
+    // link back to our message handler (for outbound messages)
     private final Topo2TrafficMessageHandler msgHandler;
 
     /**
@@ -49,24 +54,32 @@
     @Override
     protected void sendAllFlowTraffic() {
         log.debug("TOPO-2-TRAFFIC: sendAllFlowTraffic");
+        Highlights h = trafficSummary(TrafficLink.StatsType.FLOW_STATS);
+
         // TODO
     }
 
     @Override
     protected void sendAllPortTrafficBits() {
         log.debug("TOPO-2-TRAFFIC: sendAllPortTrafficBits");
+        Highlights h = trafficSummary(TrafficLink.StatsType.PORT_STATS);
+
         // TODO
     }
 
     @Override
     protected void sendAllPortTrafficPackets() {
         log.debug("TOPO-2-TRAFFIC: sendAllPortTrafficPackets");
+        Highlights h = trafficSummary(TrafficLink.StatsType.PORT_PACKET_STATS);
+
         // TODO
     }
 
     @Override
     protected void sendClearHighlights() {
         log.debug("TOPO-2-TRAFFIC: sendClearHighlights");
+        Highlights h = new Highlights();
+
         // TODO
     }
 
@@ -83,4 +96,16 @@
     @Override
     protected void clearSelection() {
     }
+
+    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+    // -- link aggregation
+
+
+    @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;
+    }
 }