GUI - Topologies - refactoring of ServicesBundle.
Jira: ONOS-6259
 - also fixed bug where edge links were being omitted.

Change-Id: I19ac83d09ce7930de7a927fb2754e0c5004705f2
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
index 28b761b..8ef929b 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
@@ -80,10 +80,12 @@
 
 
     private static final Mod MOD_PROT_PRIMARY = new Mod(PROT_PRIMARY);
-    private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET = ImmutableSet.of(MOD_PROT_PRIMARY);
+    private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET =
+            ImmutableSet.of(MOD_PROT_PRIMARY);
 
     private static final Mod MOD_PROT_BACKUP = new Mod(PROT_BACKUP);
-    private static final Set<Mod> PROTECTED_MOD_BACKUP_SET = ImmutableSet.of(MOD_PROT_BACKUP);
+    private static final Set<Mod> PROTECTED_MOD_BACKUP_SET =
+            ImmutableSet.of(MOD_PROT_BACKUP);
 
 
     /**
@@ -95,7 +97,7 @@
     }
 
     private final long trafficPeriod;
-    private final ServicesBundle servicesBundle;
+    private final ServicesBundle services;
     private final TopologyViewMessageHandler msgHandler;
 
     private final Timer timer = new Timer("topo-protected-intents");
@@ -108,16 +110,15 @@
     /**
      * Constructs a protected intent monitor.
      *
-     * @param trafficPeriod  traffic task period in ms
-     * @param servicesBundle bundle of services
-     * @param msgHandler     our message handler
+     * @param trafficPeriod traffic task period in ms
+     * @param services      bundle of services
+     * @param msgHandler    our message handler
      */
-    public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle servicesBundle,
+    public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle services,
                                   TopologyViewMessageHandler msgHandler) {
         this.trafficPeriod = trafficPeriod;
-        this.servicesBundle = servicesBundle;
+        this.services = services;
         this.msgHandler = msgHandler;
-
     }
 
     // =======================================================================
@@ -200,7 +201,7 @@
     private Highlights protectedIntentHighlights() {
         Highlights highlights = new Highlights();
         TrafficLinkMap linkMap = new TrafficLinkMap();
-        IntentService intentService = servicesBundle.intentService();
+        IntentService intentService = services.intent();
         if (selectedIntent != null) {
             List<Intent> installables = intentService.getInstallableIntents(selectedIntent.key());
 
@@ -224,12 +225,12 @@
                 Set<Link> backup = new LinkedHashSet<>();
 
                 Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
-                    .filter(FlowRuleIntent.class::isInstance)
-                    .map(FlowRuleIntent.class::cast)
-                    // only consider fwd links so that ants march in one direction
-                    // TODO: didn't help need further investigation.
-                    //.filter(i -> !i.resources().contains(marker("rev")))
-                    .collect(Collectors.groupingBy(this::isPrimary));
+                        .filter(FlowRuleIntent.class::isInstance)
+                        .map(FlowRuleIntent.class::cast)
+                        // only consider fwd links so that ants march in one direction
+                        // TODO: didn't help need further investigation.
+                        //.filter(i -> !i.resources().contains(marker("rev")))
+                        .collect(Collectors.groupingBy(this::isPrimary));
 
                 // walk primary
                 ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
@@ -284,8 +285,8 @@
      */
     private Set<Link> protectedIntentMultiLayer(ConnectPoint head, ConnectPoint tail) {
         List<Link> links = new LinkedList<>();
-        LinkService linkService = servicesBundle.linkService();
-        IntentService intentService = servicesBundle.intentService();
+        LinkService linkService = services.link();
+        IntentService intentService = services.intent();
 
         // Ingress cross connect link
         links.addAll(
@@ -336,9 +337,9 @@
     /**
      * Populate Links along the primary/backup path.
      *
-     * @param links link collection to populate [output]
-     * @param head head-end of primary/backup path
-     * @param tail tail-end of primary/backup path
+     * @param links   link collection to populate [output]
+     * @param head    head-end of primary/backup path
+     * @param tail    tail-end of primary/backup path
      * @param transit Intents if any
      */
     private void populateLinks(Set<Link> links,
@@ -359,11 +360,11 @@
                         .map(pn -> new ConnectPoint(fr.deviceId(), pn))
                         .orElse(null)
                 ).filter(Objects::nonNull)
-                .map(dst -> servicesBundle.linkService().getLink(head, dst))
+                .map(dst -> services.link().getLink(head, dst))
                 .filter(Objects::nonNull)
                 .findFirst()
                 // if there isn't one probably 1 hop to the tail
-                .orElse(servicesBundle.linkService().getLink(head, tail));
+                .orElse(services.link().getLink(head, tail));
 
         // add first link
         if (first != null) {
@@ -387,7 +388,7 @@
                         .map(pn -> new ConnectPoint(fr.deviceId(), pn))
                         .orElse(null)
                 ).filter(Objects::nonNull)
-                .map(src -> servicesBundle.linkService().getLink(src, tail))
+                .map(src -> services.link().getLink(src, tail))
                 .filter(Objects::nonNull)
                 .findFirst()
                 .ifPresent(links::add);
@@ -406,7 +407,7 @@
 
     // returns true if the backup path is the one where the traffic is currently flowing
     private boolean usingBackup(Set<Link> primary) {
-        Set<Link> activeLinks = Sets.newHashSet(servicesBundle.linkService().getActiveLinks());
+        Set<Link> activeLinks = Sets.newHashSet(services.link().getActiveLinks());
         return primary.isEmpty() || !activeLinks.containsAll(primary);
     }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index e20216d..1dbe3b1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -29,7 +29,6 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplicationId;
 import org.onosproject.event.Event;
-import org.onosproject.mastership.MastershipAdminService;
 import org.onosproject.mastership.MastershipEvent;
 import org.onosproject.mastership.MastershipListener;
 import org.onosproject.net.ConnectPoint;
@@ -62,7 +61,7 @@
 import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
-import org.onosproject.ui.impl.TrafficMonitor.Mode;
+import org.onosproject.ui.impl.TrafficMonitorBase.Mode;
 import org.onosproject.ui.topo.Highlights;
 import org.onosproject.ui.topo.NodeSelection;
 import org.onosproject.ui.topo.PropertyPanel;
@@ -83,9 +82,7 @@
 import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.HostId.hostId;
-import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
-import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED;
-import static org.onosproject.net.device.DeviceEvent.Type.PORT_STATS_UPDATED;
+import static org.onosproject.net.device.DeviceEvent.Type.*;
 import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
 import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
 import static org.onosproject.ui.JsonUtils.envelope;
@@ -204,8 +201,8 @@
     public void init(UiConnection connection, ServiceDirectory directory) {
         super.init(connection, directory);
         appId = directory.get(CoreService.class).registerApplication(MY_APP_ID);
-        traffic = new TrafficMonitor(TRAFFIC_PERIOD, servicesBundle, this);
-        protectedIntentMonitor = new ProtectedIntentMonitor(TRAFFIC_PERIOD, servicesBundle, this);
+        traffic = new TrafficMonitor(TRAFFIC_PERIOD, services, this);
+        protectedIntentMonitor = new ProtectedIntentMonitor(TRAFFIC_PERIOD, services, this);
     }
 
     @Override
@@ -398,7 +395,7 @@
 
         @Override
         public void process(ObjectNode payload) {
-            directory.get(MastershipAdminService.class).balanceRoles();
+            services.mastershipAdmin().balanceRoles();
         }
     }
 
@@ -424,7 +421,7 @@
                     .two(two)
                     .build();
 
-            intentService.submit(intent);
+            services.intent().submit(intent);
             if (overlayCache.isActive(TrafficOverlay.TRAFFIC_ID)) {
                 traffic.monitor(intent);
             }
@@ -446,17 +443,17 @@
 
             long longKey = Long.decode(stringKey);
             key = Key.of(longKey, applicId);
-            intent = intentService.getIntent(key);
+            intent = services.intent().getIntent(key);
 
             if (intent == null) {
                 // Intent might using string key, not long key
                 key = Key.of(stringKey, applicId);
-                intent = intentService.getIntent(key);
+                intent = services.intent().getIntent(key);
             }
         } catch (NumberFormatException ex) {
             // string key
             key = Key.of(stringKey, applicId);
-            intent = intentService.getIntent(key);
+            intent = services.intent().getIntent(key);
         }
 
         log.debug("Attempting to select intent by key={}", key);
@@ -481,9 +478,9 @@
             } else {
                 log.debug("Withdrawing / Purging intent {}", intent.key());
                 if (isIntentToBePurged(payload)) {
-                    intentService.purge(intent);
+                    services.intent().purge(intent);
                 } else {
-                    intentService.withdraw(intent);
+                    services.intent().withdraw(intent);
                 }
             }
         }
@@ -501,7 +498,7 @@
                 log.warn("Unable to find intent from payload {}", payload);
             } else {
                 log.debug("Resubmitting intent {}", intent.key());
-                intentService.submit(intent);
+                services.intent().submit(intent);
             }
         }
     }
@@ -516,7 +513,7 @@
             // TODO: add protection against device ids and non-existent hosts.
             Set<HostId> src = getHostIds((ArrayNode) payload.path(SRC));
             HostId dst = hostId(string(payload, DST));
-            Host dstHost = hostService.getHost(dst);
+            Host dstHost = services.host().getHost(dst);
 
             Set<ConnectPoint> ingressPoints = getHostLocations(src);
 
@@ -534,7 +531,7 @@
                             .egressPoint(dstHost.location())
                             .build();
 
-            intentService.submit(intent);
+            services.intent().submit(intent);
             if (overlayCache.isActive(TrafficOverlay.TRAFFIC_ID)) {
                 traffic.monitor(intent);
             }
@@ -586,6 +583,12 @@
         }
     }
 
+    private NodeSelection makeNodeSelection(ObjectNode payload) {
+        return new NodeSelection(payload, services.device(), services.host(),
+                                 services.link());
+    }
+
+
     private final class ReqDevLinkFlows extends RequestHandler {
         private ReqDevLinkFlows() {
             super(REQ_DEV_LINK_FLOWS);
@@ -593,9 +596,7 @@
 
         @Override
         public void process(ObjectNode payload) {
-            NodeSelection nodeSelection =
-                    new NodeSelection(payload, deviceService, hostService, linkService);
-            traffic.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
+            traffic.monitor(Mode.DEV_LINK_FLOWS, makeNodeSelection(payload));
         }
     }
 
@@ -606,9 +607,7 @@
 
         @Override
         public void process(ObjectNode payload) {
-            NodeSelection nodeSelection =
-                    new NodeSelection(payload, deviceService, hostService, linkService);
-            traffic.monitor(Mode.RELATED_INTENTS, nodeSelection);
+            traffic.monitor(Mode.RELATED_INTENTS, makeNodeSelection(payload));
         }
     }
 
@@ -723,7 +722,7 @@
 
     // Sends all controller nodes to the client as node-added messages.
     private void sendAllInstances(String messageType) {
-        List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
+        List<ControllerNode> nodes = new ArrayList<>(services.cluster().getNodes());
         nodes.sort(NODE_COMPARATOR);
         for (ControllerNode node : nodes) {
             sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
@@ -734,13 +733,13 @@
     // Sends all devices to the client as device-added messages.
     private void sendAllDevices() {
         // Send optical first, others later for layered rendering
-        for (Device device : deviceService.getDevices()) {
+        for (Device device : services.device().getDevices()) {
             if ((device.type() == Device.Type.ROADM) ||
                     (device.type() == Device.Type.OTN)) {
                 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
             }
         }
-        for (Device device : deviceService.getDevices()) {
+        for (Device device : services.device().getDevices()) {
             if ((device.type() != Device.Type.ROADM) &&
                     (device.type() != Device.Type.OTN)) {
                 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
@@ -751,12 +750,12 @@
     // Sends all links to the client as link-added messages.
     private void sendAllLinks() {
         // Send optical first, others later for layered rendering
-        for (Link link : linkService.getLinks()) {
+        for (Link link : services.link().getLinks()) {
             if (link.type() == Link.Type.OPTICAL) {
                 sendMessage(composeLinkMessage(new LinkEvent(LINK_ADDED, link)));
             }
         }
-        for (Link link : linkService.getLinks()) {
+        for (Link link : services.link().getLinks()) {
             if (link.type() != Link.Type.OPTICAL) {
                 sendMessage(composeLinkMessage(new LinkEvent(LINK_ADDED, link)));
             }
@@ -789,7 +788,7 @@
 
     // Sends all hosts to the client as host-added messages.
     private void sendAllHosts() {
-        for (Host host : hostService.getHosts()) {
+        for (Host host : services.host().getHosts()) {
             sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
         }
     }
@@ -803,7 +802,7 @@
     }
 
     private HostLocation getHostLocation(HostId hostId) {
-        return hostService.getHost(hostId).location();
+        return services.host().getHost(hostId).location();
     }
 
     // Produces a list of host ids from the specified JSON array.
@@ -838,26 +837,26 @@
     // Adds all internal listeners.
     private synchronized void addListeners() {
         listenersRemoved = false;
-        clusterService.addListener(clusterListener);
-        mastershipService.addListener(mastershipListener);
-        deviceService.addListener(deviceListener);
-        linkService.addListener(linkListener);
-        hostService.addListener(hostListener);
-        intentService.addListener(intentListener);
-        flowService.addListener(flowListener);
+        services.cluster().addListener(clusterListener);
+        services.mastership().addListener(mastershipListener);
+        services.device().addListener(deviceListener);
+        services.link().addListener(linkListener);
+        services.host().addListener(hostListener);
+        services.intent().addListener(intentListener);
+        services.flow().addListener(flowListener);
     }
 
     // Removes all internal listeners.
     private synchronized void removeListeners() {
         if (!listenersRemoved) {
             listenersRemoved = true;
-            clusterService.removeListener(clusterListener);
-            mastershipService.removeListener(mastershipListener);
-            deviceService.removeListener(deviceListener);
-            linkService.removeListener(linkListener);
-            hostService.removeListener(hostListener);
-            intentService.removeListener(intentListener);
-            flowService.removeListener(flowListener);
+            services.cluster().removeListener(clusterListener);
+            services.mastership().removeListener(mastershipListener);
+            services.device().removeListener(deviceListener);
+            services.link().removeListener(linkListener);
+            services.host().removeListener(hostListener);
+            services.intent().removeListener(intentListener);
+            services.flow().removeListener(flowListener);
         }
     }
 
@@ -879,7 +878,7 @@
         public void event(MastershipEvent event) {
             msgSender.execute(() -> {
                 sendAllInstances(UPDATE_INSTANCE);
-                Device device = deviceService.getDevice(event.subject());
+                Device device = services.device().getDevice(event.subject());
                 if (device != null) {
                     sendMessage(deviceMessage(new DeviceEvent(DEVICE_UPDATED, device)));
                 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 7fb5d95..24b1773 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -22,15 +22,11 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.util.DefaultHashMap;
 import org.onosproject.cluster.ClusterEvent;
-import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.core.CoreService;
-import org.onosproject.incubator.net.PortStatisticsService;
 import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
 import org.onosproject.incubator.net.tunnel.Tunnel;
-import org.onosproject.incubator.net.tunnel.TunnelService;
-import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.Annotated;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.Annotations;
@@ -45,18 +41,11 @@
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.Link;
 import org.onosproject.net.device.DeviceEvent;
-import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.FlowEntry;
-import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.host.HostEvent;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.link.LinkEvent;
-import org.onosproject.net.link.LinkService;
 import org.onosproject.net.provider.ProviderId;
-import org.onosproject.net.statistic.StatisticService;
 import org.onosproject.net.topology.Topology;
-import org.onosproject.net.topology.TopologyService;
 import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiMessageHandler;
@@ -73,7 +62,6 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static org.onosproject.net.PortNumber.portNumber;
 import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
@@ -120,25 +108,6 @@
     private static final ProviderId PID =
             new ProviderId("core", "org.onosproject.core", true);
 
-    protected static final String SHOW_HIGHLIGHTS = "showHighlights";
-
-    protected ServiceDirectory directory;
-    protected ClusterService clusterService;
-    protected DeviceService deviceService;
-    protected LinkService linkService;
-    protected HostService hostService;
-    protected MastershipService mastershipService;
-    protected IntentService intentService;
-    protected FlowRuleService flowService;
-    protected StatisticService flowStatsService;
-    protected PortStatisticsService portStatsService;
-    protected TopologyService topologyService;
-    protected TunnelService tunnelService;
-
-    protected ServicesBundle servicesBundle;
-
-    private String version;
-
     // TODO: extract into an external & durable state; good enough for now and demo
     private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
 
@@ -151,27 +120,21 @@
         return Collections.unmodifiableMap(metaUi);
     }
 
+
+    protected ServicesBundle services;
+
+    private String version;
+
+
     @Override
     public void init(UiConnection connection, ServiceDirectory directory) {
         super.init(connection, directory);
-        this.directory = checkNotNull(directory, "Directory cannot be null");
-        clusterService = directory.get(ClusterService.class);
-        deviceService = directory.get(DeviceService.class);
-        linkService = directory.get(LinkService.class);
-        hostService = directory.get(HostService.class);
-        mastershipService = directory.get(MastershipService.class);
-        intentService = directory.get(IntentService.class);
-        flowService = directory.get(FlowRuleService.class);
-        flowStatsService = directory.get(StatisticService.class);
-        portStatsService = directory.get(PortStatisticsService.class);
-        topologyService = directory.get(TopologyService.class);
-        tunnelService = directory.get(TunnelService.class);
+        services = new ServicesBundle(directory);
+        setVersionString(directory);
+    }
 
-        servicesBundle = new ServicesBundle(intentService, deviceService,
-                                            hostService, linkService,
-                                            flowService,
-                                            flowStatsService, portStatsService);
-
+    // Creates a palatable version string to display on the summary panel
+    private void setVersionString(ServiceDirectory directory) {
         String ver = directory.get(CoreService.class).version().toString();
         version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
     }
@@ -220,13 +183,13 @@
     // Produces a cluster instance message to the client.
     protected ObjectNode instanceMessage(ClusterEvent event, String msgType) {
         ControllerNode node = event.subject();
-        int switchCount = mastershipService.getDevicesOf(node.id()).size();
+        int switchCount = services.mastership().getDevicesOf(node.id()).size();
         ObjectNode payload = objectNode()
                 .put("id", node.id().toString())
                 .put("ip", node.ip().toString())
-                .put("online", clusterService.getState(node.id()).isActive())
-                .put("ready", clusterService.getState(node.id()).isReady())
-                .put("uiAttached", node.equals(clusterService.getLocalNode()))
+                .put("online", services.cluster().getState(node.id()).isActive())
+                .put("ready", services.cluster().getState(node.id()).isReady())
+                .put("uiAttached", node.equals(services.cluster().getLocalNode()))
                 .put("switches", switchCount);
 
         ArrayNode labels = arrayNode();
@@ -251,7 +214,7 @@
         ObjectNode payload = objectNode()
                 .put("id", device.id().toString())
                 .put("type", devType)
-                .put("online", deviceService.isAvailable(device.id()))
+                .put("online", services.device().isAvailable(device.id()))
                 .put("master", master(device.id()));
 
         // Generate labels: id, chassis id, no-label, optional-name
@@ -331,7 +294,7 @@
 
     // Returns the name of the master node for the specified device id.
     private String master(DeviceId deviceId) {
-        NodeId master = mastershipService.getMasterFor(deviceId);
+        NodeId master = services.mastership().getMasterFor(deviceId);
         return master != null ? master.toString() : "";
     }
 
@@ -387,64 +350,62 @@
 
     // Returns property panel model for summary response.
     protected PropertyPanel summmaryMessage() {
-        Topology topology = topologyService.currentTopology();
+        Topology topology = services.topology().currentTopology();
 
         return new PropertyPanel("ONOS Summary", "node")
-            .addProp(Properties.VERSION, version)
-            .addSeparator()
-            .addProp(Properties.DEVICES, deviceService.getDeviceCount())
-            .addProp(Properties.LINKS, topology.linkCount())
-            .addProp(Properties.HOSTS, hostService.getHostCount())
-            .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
-            .addSeparator()
-            .addProp(Properties.INTENTS, intentService.getIntentCount())
-            .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
-            .addProp(Properties.FLOWS, flowService.getFlowRuleCount());
+                .addProp(Properties.VERSION, version)
+                .addSeparator()
+                .addProp(Properties.DEVICES, services.device().getDeviceCount())
+                .addProp(Properties.LINKS, topology.linkCount())
+                .addProp(Properties.HOSTS, services.host().getHostCount())
+                .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
+                .addSeparator()
+                .addProp(Properties.INTENTS, services.intent().getIntentCount())
+                .addProp(Properties.TUNNELS, services.tunnel().tunnelCount())
+                .addProp(Properties.FLOWS, services.flow().getFlowRuleCount());
     }
 
     // Returns property panel model for device details response.
     protected PropertyPanel deviceDetails(DeviceId deviceId) {
-        Device device = deviceService.getDevice(deviceId);
+        Device device = services.device().getDevice(deviceId);
         Annotations annot = device.annotations();
         String name = annot.value(AnnotationKeys.NAME);
-        int portCount = deviceService.getPorts(deviceId).size();
+        int portCount = services.device().getPorts(deviceId).size();
         int flowCount = getFlowCount(deviceId);
         int tunnelCount = getTunnelCount(deviceId);
 
         String title = isNullOrEmpty(name) ? deviceId.toString() : name;
         String typeId = device.type().toString().toLowerCase();
 
-        PropertyPanel pp = new PropertyPanel(title, typeId)
-            .id(deviceId.toString())
+        return new PropertyPanel(title, typeId)
+                .id(deviceId.toString())
 
-            .addProp(Properties.URI, deviceId.toString())
-            .addProp(Properties.VENDOR, device.manufacturer())
-            .addProp(Properties.HW_VERSION, device.hwVersion())
-            .addProp(Properties.SW_VERSION, device.swVersion())
-            .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
-            .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
-            .addSeparator()
+                .addProp(Properties.URI, deviceId.toString())
+                .addProp(Properties.VENDOR, device.manufacturer())
+                .addProp(Properties.HW_VERSION, device.hwVersion())
+                .addProp(Properties.SW_VERSION, device.swVersion())
+                .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
+                .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
+                .addSeparator()
 
-            .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
-            .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
-            .addSeparator()
+                .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+                .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
+                .addSeparator()
 
-            .addProp(Properties.PORTS, portCount)
-            .addProp(Properties.FLOWS, flowCount)
-            .addProp(Properties.TUNNELS, tunnelCount)
+                .addProp(Properties.PORTS, portCount)
+                .addProp(Properties.FLOWS, flowCount)
+                .addProp(Properties.TUNNELS, tunnelCount)
 
-            .addButton(CoreButtons.SHOW_DEVICE_VIEW)
-            .addButton(CoreButtons.SHOW_FLOW_VIEW)
-            .addButton(CoreButtons.SHOW_PORT_VIEW)
-            .addButton(CoreButtons.SHOW_GROUP_VIEW)
-            .addButton(CoreButtons.SHOW_METER_VIEW);
-
-        return pp;
+                .addButton(CoreButtons.SHOW_DEVICE_VIEW)
+                .addButton(CoreButtons.SHOW_FLOW_VIEW)
+                .addButton(CoreButtons.SHOW_PORT_VIEW)
+                .addButton(CoreButtons.SHOW_GROUP_VIEW)
+                .addButton(CoreButtons.SHOW_METER_VIEW);
     }
 
     protected int getFlowCount(DeviceId deviceId) {
         int count = 0;
-        for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
+        for (FlowEntry flowEntry : services.flow().getFlowEntries(deviceId)) {
             count++;
         }
         return count;
@@ -452,7 +413,7 @@
 
     protected int getTunnelCount(DeviceId deviceId) {
         int count = 0;
-        Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
+        Collection<Tunnel> tunnels = services.tunnel().queryAllTunnels();
         for (Tunnel tunnel : tunnels) {
             //Only OpticalTunnelEndPoint has a device
             if (!(tunnel.src() instanceof OpticalTunnelEndPoint) ||
@@ -476,7 +437,7 @@
 
     // Returns host details response.
     protected PropertyPanel hostDetails(HostId hostId) {
-        Host host = hostService.getHost(hostId);
+        Host host = services.host().getHost(hostId);
         Annotations annot = host.annotations();
         String type = annot.value(AnnotationKeys.TYPE);
         String name = annot.value(AnnotationKeys.NAME);
@@ -485,17 +446,14 @@
         String title = isNullOrEmpty(name) ? hostId.toString() : name;
         String typeId = isNullOrEmpty(type) ? "endstation" : type;
 
-        PropertyPanel pp = new PropertyPanel(title, typeId)
-            .id(hostId.toString())
-            .addProp(Properties.MAC, host.mac())
-            .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
-            .addProp(Properties.VLAN, "-1".equals(vlan) ? "none" : vlan)
-            .addSeparator()
-            .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
-            .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
-
-        // Potentially add button descriptors here
-        return pp;
+        return new PropertyPanel(title, typeId)
+                .id(hostId.toString())
+                .addProp(Properties.MAC, host.mac())
+                .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
+                .addProp(Properties.VLAN, "-1".equals(vlan) ? "none" : vlan)
+                .addSeparator()
+                .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+                .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
     }
 
 }
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 cc03524..660ba72 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
@@ -18,6 +18,7 @@
 package org.onosproject.ui.impl;
 
 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;
@@ -47,7 +48,6 @@
 import org.onosproject.ui.impl.topo.util.TrafficLink;
 import org.onosproject.ui.impl.topo.util.TrafficLink.StatsType;
 import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
-import org.onosproject.ui.topo.AbstractTopoMonitor;
 import org.onosproject.ui.topo.DeviceHighlight;
 import org.onosproject.ui.topo.Highlights;
 import org.onosproject.ui.topo.Highlights.Amount;
@@ -55,7 +55,6 @@
 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;
 import org.slf4j.LoggerFactory;
 
@@ -67,50 +66,25 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
 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.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.TrafficMonitorBase.Mode.RELATED_INTENTS;
+import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT;
 
 /**
  * Encapsulates the behavior of monitoring specific traffic patterns.
  */
-public class TrafficMonitor extends AbstractTopoMonitor {
-
-    // 4 Kilo Bytes as threshold
-    private static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
+public class TrafficMonitor extends TrafficMonitorBase {
 
     private static final Logger log =
             LoggerFactory.getLogger(TrafficMonitor.class);
 
-    /**
-     * Designates the different modes of operation.
-     */
-    public enum Mode {
-        IDLE,
-        ALL_FLOW_TRAFFIC_BYTES,
-        ALL_PORT_TRAFFIC_BIT_PS,
-        ALL_PORT_TRAFFIC_PKT_PS,
-        DEV_LINK_FLOWS,
-        RELATED_INTENTS,
-        SELECTED_INTENT
-    }
-
-    private final long trafficPeriod;
-    private final ServicesBundle servicesBundle;
     private final TopologyViewMessageHandler msgHandler;
     private final TopoIntentFilter intentFilter;
 
-    private final Timer timer = new Timer("topo-traffic");
-
-    private TimerTask trafficTask = null;
-    private Mode mode = IDLE;
     private NodeSelection selectedNodes = null;
     private IntentSelection selectedIntents = null;
 
@@ -124,8 +98,7 @@
      */
     public TrafficMonitor(long trafficPeriod, ServicesBundle servicesBundle,
                           TopologyViewMessageHandler msgHandler) {
-        this.trafficPeriod = trafficPeriod;
-        this.servicesBundle = servicesBundle;
+        super(trafficPeriod, servicesBundle);
         this.msgHandler = msgHandler;
 
         intentFilter = new TopoIntentFilter(servicesBundle);
@@ -134,56 +107,7 @@
     // =======================================================================
     // === API ===
 
-    /**
-     * Monitor for traffic data to be sent back to the web client, under
-     * the given mode. This causes a background traffic task to be
-     * scheduled to repeatedly compute and transmit the appropriate traffic
-     * data to the client.
-     * <p>
-     * The monitoring mode is expected to be one of:
-     * <ul>
-     * <li>ALL_FLOW_TRAFFIC_BYTES</li>
-     * <li>ALL_PORT_TRAFFIC_BIT_PS</li>
-     * <li>ALL_PORT_TRAFFIC_PKT_PS</li>
-     * <li>SELECTED_INTENT</li>
-     * </ul>
-     *
-     * @param mode monitoring mode
-     */
-    public synchronized void monitor(Mode mode) {
-        log.debug("monitor: {}", mode);
-        this.mode = mode;
-
-        switch (mode) {
-            case ALL_FLOW_TRAFFIC_BYTES:
-                clearSelection();
-                scheduleTask();
-                sendAllFlowTraffic();
-                break;
-
-            case ALL_PORT_TRAFFIC_BIT_PS:
-                clearSelection();
-                scheduleTask();
-                sendAllPortTraffic(StatsType.PORT_STATS);
-                break;
-
-            case ALL_PORT_TRAFFIC_PKT_PS:
-                clearSelection();
-                scheduleTask();
-                sendAllPortTraffic(StatsType.PORT_PACKET_STATS);
-                break;
-
-            case SELECTED_INTENT:
-                scheduleTask();
-                sendSelectedIntentTraffic();
-                break;
-
-            default:
-                log.debug("Unexpected call to monitor({})", mode);
-                clearAll();
-                break;
-        }
-    }
+    // monitor(Mode) is implemented in the super class
 
     /**
      * Monitor for traffic data to be sent back to the web client, under
@@ -211,7 +135,7 @@
             case DEV_LINK_FLOWS:
                 // only care about devices (not hosts)
                 if (selectedNodes.devicesWithHover().isEmpty()) {
-                    sendClearAll();
+                    clearAll();
                 } else {
                     scheduleTask();
                     sendDeviceLinkFlows();
@@ -220,11 +144,11 @@
 
             case RELATED_INTENTS:
                 if (selectedNodes.none()) {
-                    sendClearAll();
+                    clearAll();
                 } else {
                     selectedIntents = new IntentSelection(selectedNodes, intentFilter);
                     if (selectedIntents.none()) {
-                        sendClearAll();
+                        clearAll();
                     } else {
                         sendSelectedIntents();
                     }
@@ -295,83 +219,57 @@
         }
     }
 
-    /**
-     * Stop all traffic monitoring.
-     */
-    public synchronized void stopMonitoring() {
-        log.debug("STOP monitoring");
-        if (mode != IDLE) {
-            sendClearAll();
-        }
-    }
-
-
     // =======================================================================
-    // === Helper methods ===
+    // === Abstract method implementations ===
 
-    private void sendClearAll() {
-        clearAll();
-        sendClearHighlights();
-    }
-
-    private void clearAll() {
-        this.mode = IDLE;
-        clearSelection();
-        cancelTask();
-    }
-
-    private void clearSelection() {
-        selectedNodes = null;
-        selectedIntents = null;
-    }
-
-    private synchronized void scheduleTask() {
-        if (trafficTask == null) {
-            log.debug("Starting up background traffic task...");
-            trafficTask = new TrafficUpdateTask();
-            timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
-        } else {
-            log.debug("(traffic task already running)");
-        }
-    }
-
-    private synchronized void cancelTask() {
-        if (trafficTask != null) {
-            trafficTask.cancel();
-            trafficTask = null;
-        }
-    }
-
-    private void sendAllFlowTraffic() {
+    @Override
+    protected void sendAllFlowTraffic() {
         log.debug("sendAllFlowTraffic");
         msgHandler.sendHighlights(trafficSummary(StatsType.FLOW_STATS));
     }
 
-    private void sendAllPortTraffic(StatsType t) {
-        log.debug("sendAllPortTraffic: {}", t);
-        msgHandler.sendHighlights(trafficSummary(t));
+    @Override
+    protected void sendAllPortTrafficBits() {
+        log.debug("sendAllPortTrafficBits");
+        msgHandler.sendHighlights(trafficSummary(StatsType.PORT_STATS));
     }
 
-    private void sendDeviceLinkFlows() {
+    @Override
+    protected void sendAllPortTrafficPackets() {
+        log.debug("sendAllPortTrafficPackets");
+        msgHandler.sendHighlights(trafficSummary(StatsType.PORT_PACKET_STATS));
+    }
+
+    @Override
+    protected void sendDeviceLinkFlows() {
         log.debug("sendDeviceLinkFlows: {}", selectedNodes);
         msgHandler.sendHighlights(deviceLinkFlows());
     }
 
-    private void sendSelectedIntents() {
-        log.debug("sendSelectedIntents: {}", selectedIntents);
-        msgHandler.sendHighlights(intentGroup());
-    }
-
-    private void sendSelectedIntentTraffic() {
+    @Override
+    protected void sendSelectedIntentTraffic() {
         log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
         msgHandler.sendHighlights(intentTraffic());
     }
 
-    private void sendClearHighlights() {
+    @Override
+    protected void sendClearHighlights() {
         log.debug("sendClearHighlights");
         msgHandler.sendHighlights(new Highlights());
     }
 
+    @Override
+    protected void clearSelection() {
+        selectedNodes = null;
+        selectedIntents = null;
+    }
+
+
+    private void sendSelectedIntents() {
+        log.debug("sendSelectedIntents: {}", selectedIntents);
+        msgHandler.sendHighlights(intentGroup());
+    }
+
     // =======================================================================
     // === Generate messages in JSON object node format
 
@@ -450,7 +348,7 @@
                 allBut.remove(current);
                 secondary = allBut;
                 log.debug("Highlight intent: {} ([{}] of {})",
-                        current.id(), selectedIntents.index(), count);
+                          current.id(), selectedIntents.index(), count);
             }
 
             highlightIntentLinks(highlights, primary, secondary);
@@ -466,7 +364,7 @@
             Set<Intent> primary = new HashSet<>();
             primary.add(current);
             log.debug("Highlight traffic for intent: {} ([{}] of {})",
-                    current.id(), selectedIntents.index(), selectedIntents.size());
+                      current.id(), selectedIntents.index(), selectedIntents.size());
 
             highlightIntentLinksWithTraffic(highlights, primary);
             highlights.subdueAllElse(Amount.MINIMALLY);
@@ -477,11 +375,11 @@
     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
     private void compileLinks(TrafficLinkMap linkMap) {
-        servicesBundle.linkService().getLinks().forEach(linkMap::add);
+        services.link().getLinks().forEach(linkMap::add);
     }
 
     private void addEdgeLinks(TrafficLinkMap linkMap) {
-        servicesBundle.hostService().getHosts().forEach(host -> {
+        services.host().getHosts().forEach(host -> {
             linkMap.add(createEdgeLink(host, true));
             linkMap.add(createEdgeLink(host, false));
         });
@@ -489,7 +387,7 @@
 
     private Load getLinkFlowLoad(Link link) {
         if (link != null && link.src().elementId() instanceof DeviceId) {
-            return servicesBundle.flowStatsService().load(link);
+            return services.flowStats().load(link);
         }
         return null;
     }
@@ -504,8 +402,8 @@
         // the max link rate of either direction
         // (we choose 'one' since we know that is never null)
         Link one = link.one();
-        Load egressSrc = servicesBundle.portStatsService().load(one.src(), metricType);
-        Load egressDst = servicesBundle.portStatsService().load(one.dst(), metricType);
+        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);
     }
 
@@ -523,14 +421,14 @@
     private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
         // get the flows for the device
         List<FlowEntry> entries = new ArrayList<>();
-        for (FlowEntry flowEntry : servicesBundle.flowService().getFlowEntries(deviceId)) {
+        for (FlowEntry flowEntry : services.flow().getFlowEntries(deviceId)) {
             entries.add(flowEntry);
         }
 
         // get egress links from device, and include edge links
-        Set<Link> links = new HashSet<>(servicesBundle.linkService()
-                .getDeviceEgressLinks(deviceId));
-        Set<Host> hosts = servicesBundle.hostService().getConnectedHosts(deviceId);
+        Set<Link> links = new HashSet<>(services.link()
+                                                .getDeviceEgressLinks(deviceId));
+        Set<Host> hosts = services.host().getConnectedHosts(deviceId);
         if (hosts != null) {
             for (Host host : hosts) {
                 links.add(createEdgeLink(host, false));
@@ -582,7 +480,7 @@
                                     TrafficLinkMap linkMap, Set<Intent> intents,
                                     Flavor flavor, boolean showTraffic) {
         for (Intent intent : intents) {
-            List<Intent> installables = servicesBundle.intentService()
+            List<Intent> installables = services.intent()
                     .getInstallableIntents(intent.key());
             Iterable<Link> links = null;
             if (installables != null) {
@@ -596,8 +494,8 @@
                         // Add cross connect links
                         if (intent instanceof OpticalConnectivityIntent) {
                             OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
-                            LinkService linkService = servicesBundle.linkService();
-                            DeviceService deviceService = servicesBundle.deviceService();
+                            LinkService linkService = services.link();
+                            DeviceService deviceService = services.device();
                             l.addAll(linkService.getDeviceIngressLinks(ocIntent.getSrc().deviceId()).stream()
                                     .filter(i ->
                                             deviceService.getDevice(i.src().deviceId()).type() == Device.Type.SWITCH)
@@ -609,13 +507,19 @@
                         }
                         links = l;
                     } else if (installable instanceof FlowObjectiveIntent) {
-                        links = addEdgeLinksIfNeeded(intent, linkResources(installable));
+                        links = linkResources(installable);
                     } else if (installable instanceof LinkCollectionIntent) {
                         links = ((LinkCollectionIntent) installable).links();
                     } else if (installable instanceof OpticalPathIntent) {
                         links = ((OpticalPathIntent) installable).path().links();
                     }
 
+                    if (links == null) {
+                        links = Lists.newArrayList();
+                    }
+
+                    links = addEdgeLinksIfNeeded(intent, Lists.newArrayList(links));
+
                     boolean isOptical = intent instanceof OpticalConnectivityIntent;
                     processLinks(linkMap, links, flavor, isOptical, showTraffic);
                     updateHighlights(highlights, links);
@@ -629,8 +533,8 @@
         if (parentIntent instanceof HostToHostIntent) {
             links = new HashSet<>(links);
             HostToHostIntent h2h = (HostToHostIntent) parentIntent;
-            Host h1 = servicesBundle.hostService().getHost(h2h.one());
-            Host h2 = servicesBundle.hostService().getHost(h2h.two());
+            Host h1 = services.host().getHost(h2h.one());
+            Host h2 = services.host().getHost(h2h.two());
             links.add(createEdgeLink(h1, true));
             links.add(createEdgeLink(h2, true));
         }
@@ -688,42 +592,4 @@
         }
     }
 
-    // =======================================================================
-    // === Background Task
-
-    // Provides periodic update of traffic information to the client
-    private class TrafficUpdateTask extends TimerTask {
-        @Override
-        public void run() {
-            try {
-                switch (mode) {
-                    case ALL_FLOW_TRAFFIC_BYTES:
-                        sendAllFlowTraffic();
-                        break;
-                    case ALL_PORT_TRAFFIC_BIT_PS:
-                        sendAllPortTraffic(StatsType.PORT_STATS);
-                        break;
-                    case ALL_PORT_TRAFFIC_PKT_PS:
-                        sendAllPortTraffic(StatsType.PORT_PACKET_STATS);
-                        break;
-                    case DEV_LINK_FLOWS:
-                        sendDeviceLinkFlows();
-                        break;
-                    case SELECTED_INTENT:
-                        sendSelectedIntentTraffic();
-                        break;
-
-                    default:
-                        // RELATED_INTENTS and IDLE modes should never invoke
-                        // the background task, but if they do, they have
-                        // nothing to do
-                        break;
-                }
-
-            } catch (Exception e) {
-                log.warn("Unable to process traffic task due to {}", e.getMessage());
-                log.warn("Boom!", e);
-            }
-        }
-    }
 }
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
new file mode 100644
index 0000000..dfedaf3
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java
@@ -0,0 +1,262 @@
+/*
+ * 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;
+
+import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.topo.AbstractTopoMonitor;
+import org.onosproject.ui.topo.TopoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE;
+
+/**
+ * Base superclass for traffic monitor (both 'classic' and 'topo2' versions).
+ */
+public abstract class TrafficMonitorBase extends AbstractTopoMonitor {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // 4 Kilo Bytes as threshold
+    static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO;
+
+
+    /**
+     * Designates the different modes of operation.
+     */
+    public enum Mode {
+        IDLE,
+        ALL_FLOW_TRAFFIC_BYTES,
+        ALL_PORT_TRAFFIC_BIT_PS,
+        ALL_PORT_TRAFFIC_PKT_PS,
+        DEV_LINK_FLOWS,
+        RELATED_INTENTS,
+        SELECTED_INTENT
+    }
+
+    /**
+     * Number of milliseconds between invocations of sending traffic data.
+     */
+    protected final long trafficPeriod;
+
+    /**
+     * Holds references to services.
+     */
+    protected final ServicesBundle services;
+
+    /**
+     * Current operating mode.
+     */
+    protected Mode mode = Mode.IDLE;
+
+    private final Timer timer;
+    private TimerTask trafficTask = null;
+
+    /**
+     * Constructs the monitor, initializing the task period and
+     * services bundle reference.
+     *
+     * @param trafficPeriod  traffic task period in ms
+     * @param servicesBundle bundle of services
+     */
+    protected TrafficMonitorBase(long trafficPeriod,
+                                 ServicesBundle servicesBundle) {
+        this.trafficPeriod = trafficPeriod;
+        this.services = servicesBundle;
+        timer = new Timer("uiTopo-" + getClass().getSimpleName());
+    }
+
+    /**
+     * Initiates monitoring of traffic for a given mode.
+     * This causes a background traffic task to be
+     * scheduled to repeatedly compute and transmit the appropriate traffic
+     * data to the client.
+     * <p>
+     * The monitoring mode is expected to be one of:
+     * <ul>
+     * <li>ALL_FLOW_TRAFFIC_BYTES</li>
+     * <li>ALL_PORT_TRAFFIC_BIT_PS</li>
+     * <li>ALL_PORT_TRAFFIC_PKT_PS</li>
+     * <li>SELECTED_INTENT</li>
+     * </ul>
+     *
+     * @param mode the monitoring mode
+     */
+    public synchronized void monitor(Mode mode) {
+        this.mode = mode;
+
+        switch (mode) {
+
+            case ALL_FLOW_TRAFFIC_BYTES:
+                clearSelection();
+                scheduleTask();
+                sendAllFlowTraffic();
+                break;
+
+            case ALL_PORT_TRAFFIC_BIT_PS:
+                clearSelection();
+                scheduleTask();
+                sendAllPortTrafficBits();
+                break;
+
+            case ALL_PORT_TRAFFIC_PKT_PS:
+                clearSelection();
+                scheduleTask();
+                sendAllPortTrafficPackets();
+                break;
+
+            case SELECTED_INTENT:
+                sendSelectedIntentTraffic();
+                scheduleTask();
+                break;
+
+            default:
+                log.warn("Unexpected call to monitor({})", mode);
+                clearAll();
+                break;
+        }
+    }
+
+    /**
+     * Subclass should compile and send appropriate highlights data showing
+     * flow traffic (bytes on links).
+     */
+    protected abstract void sendAllFlowTraffic();
+
+    /**
+     * Subclass should compile and send appropriate highlights data showing
+     * bits per second, as computed using port stats.
+     */
+    protected abstract void sendAllPortTrafficBits();
+
+    /**
+     * Subclass should compile and send appropriate highlights data showing
+     * packets per second, as computed using port stats.
+     */
+    protected abstract void sendAllPortTrafficPackets();
+
+    /**
+     * Subclass should compile and send appropriate highlights data showing
+     * number of flows traversing links for the "selected" device(s).
+     */
+    protected abstract void sendDeviceLinkFlows();
+
+    /**
+     * Subclass should compile and send appropriate highlights data showing
+     * traffic traversing links for the "selected" intent.
+     */
+    protected abstract void sendSelectedIntentTraffic();
+
+    /**
+     * Subclass should send a "clear highlights" event.
+     */
+    protected abstract void sendClearHighlights();
+
+    /**
+     * Subclasses should clear any selection state.
+     */
+    protected abstract void clearSelection();
+
+    /**
+     * Sets the mode to IDLE, clears the selection, cancels the background
+     * task, and sends a clear highlights event to the client.
+     */
+    protected void clearAll() {
+        this.mode = Mode.IDLE;
+        clearSelection();
+        cancelTask();
+        sendClearHighlights();
+    }
+
+    /**
+     * Schedules the background monitor task to run.
+     */
+    protected synchronized void scheduleTask() {
+        if (trafficTask == null) {
+            log.debug("Starting up background traffic task...");
+            trafficTask = new TrafficUpdateTask();
+            timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
+        } else {
+            log.debug("(traffic task already running)");
+        }
+    }
+
+    /**
+     * Cancels the background monitor task.
+     */
+    protected synchronized void cancelTask() {
+        if (trafficTask != null) {
+            trafficTask.cancel();
+            trafficTask = null;
+        }
+    }
+
+    /**
+     * Stops monitoring. (Invokes {@link #clearAll}, if not idle).
+     */
+    public synchronized void stopMonitoring() {
+        log.debug("STOP monitoring");
+        if (mode != IDLE) {
+            clearAll();
+        }
+    }
+
+
+    // =======================================================================
+    // === Background Task
+
+    // Provides periodic update of traffic information to the client
+    private class TrafficUpdateTask extends TimerTask {
+        @Override
+        public void run() {
+            try {
+                switch (mode) {
+                    case ALL_FLOW_TRAFFIC_BYTES:
+                        sendAllFlowTraffic();
+                        break;
+                    case ALL_PORT_TRAFFIC_BIT_PS:
+                        sendAllPortTrafficBits();
+                        break;
+                    case ALL_PORT_TRAFFIC_PKT_PS:
+                        sendAllPortTrafficPackets();
+                        break;
+                    case DEV_LINK_FLOWS:
+                        sendDeviceLinkFlows();
+                        break;
+                    case SELECTED_INTENT:
+                        sendSelectedIntentTraffic();
+                        break;
+
+                    default:
+                        // RELATED_INTENTS and IDLE modes should never invoke
+                        // the background task, but if they do, they have
+                        // nothing to do
+                        break;
+                }
+
+            } catch (Exception e) {
+                log.warn("Unable to process traffic task due to {}", e.getMessage());
+                log.warn("Boom!", e);
+            }
+        }
+    }
+
+}
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 8a6ec8b..6b72e89 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
@@ -23,6 +23,7 @@
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.impl.topo.util.ServicesBundle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,13 +43,25 @@
     // === Outbound event identifiers
     private static final String HIGHLIGHTS = "topo2Highlights";
 
+
+    private static final long TRAFFIC_PERIOD = 5000;
+
 //    private UiTopoSession topoSession;
 //    private Topo2Jsonifier t2json;
 
+    protected ServicesBundle services;
+    private String version;
+
+
+    private Traffic2Monitor traffic;
+
+
     @Override
     public void init(UiConnection connection, ServiceDirectory directory) {
         super.init(connection, directory);
 
+        services = new ServicesBundle(directory);
+
         // get the topo session from the UiWebSocket
 //        topoSession = ((UiWebSocket) connection).topoSession();
 //        t2json = new Topo2Jsonifier(directory, connection.userName());
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
new file mode 100644
index 0000000..7aedc48
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import org.onosproject.ui.impl.TrafficMonitorBase;
+import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Encapsulates the behavior of monitoring specific traffic patterns in the
+ * Topology-2 view.
+ */
+public class Traffic2Monitor extends TrafficMonitorBase {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(Traffic2Monitor.class);
+
+    /**
+     * Constructs a traffic monitor.
+     *
+     * @param trafficPeriod traffic task period in ms
+     * @param servicesBundle bundle of services
+     */
+    public Traffic2Monitor(long trafficPeriod, ServicesBundle servicesBundle) {
+        super(trafficPeriod, servicesBundle);
+    }
+
+    @Override
+    protected void sendAllFlowTraffic() {
+        // TODO
+    }
+
+    @Override
+    protected void sendAllPortTrafficBits() {
+        // TODO
+    }
+
+    @Override
+    protected void sendAllPortTrafficPackets() {
+        // TODO
+    }
+
+    @Override
+    protected void sendDeviceLinkFlows() {
+        // NOTE: currently this monitor holds no state - nothing to do
+    }
+
+    @Override
+    protected void sendSelectedIntentTraffic() {
+        // NOTE: currently this monitor holds no state - nothing to do
+    }
+
+    @Override
+    protected void sendClearHighlights() {
+        // TODO
+    }
+
+    @Override
+    protected void clearSelection() {
+        // NOTE: currently this monitor holds no state - nothing to do
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
index 13bf1c2..aa20990 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java
@@ -16,63 +16,83 @@
 
 package org.onosproject.ui.impl.topo.util;
 
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.cluster.ClusterService;
 import org.onosproject.incubator.net.PortStatisticsService;
+import org.onosproject.incubator.net.tunnel.TunnelService;
+import org.onosproject.mastership.MastershipAdminService;
+import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.intent.IntentService;
 import org.onosproject.net.link.LinkService;
 import org.onosproject.net.statistic.StatisticService;
+import org.onosproject.net.topology.TopologyService;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * A bundle of services that the topology view requires to get its job done.
+ * A bundle of services that the topology view(s) require to get the job done.
  */
 public class ServicesBundle {
 
-    private final IntentService intentService;
-    private final DeviceService deviceService;
-    private final HostService hostService;
-    private final LinkService linkService;
-    private final FlowRuleService flowService;
-    private final StatisticService flowStatsService;
-    private final PortStatisticsService portStatsService;
+    private ClusterService clusterService;
+
+    private TopologyService topologyService;
+    private DeviceService deviceService;
+    private HostService hostService;
+    private LinkService linkService;
+    private TunnelService tunnelService;
+
+    private MastershipService mastershipService;
+    private MastershipAdminService mastershipAdminService;
+    private IntentService intentService;
+    private FlowRuleService flowService;
+    private StatisticService flowStatsService;
+    private PortStatisticsService portStatsService;
+
 
     /**
-     * Creates the services bundle.
+     * Creates the services bundle, from the given directly.
      *
-     * @param intentService     intent service reference
-     * @param deviceService     device service reference
-     * @param hostService       host service reference
-     * @param linkService       link service reference
-     * @param flowService       flow service reference
-     * @param flowStatsService  flow statistics service reference
-     * @param portStatsService  port statistics service reference
+     * @param directory service directory
      */
-    public ServicesBundle(IntentService intentService,
-                          DeviceService deviceService,
-                          HostService hostService,
-                          LinkService linkService,
-                          FlowRuleService flowService,
-                          StatisticService flowStatsService,
-                          PortStatisticsService portStatsService) {
-        this.intentService = checkNotNull(intentService);
-        this.deviceService = checkNotNull(deviceService);
-        this.hostService = checkNotNull(hostService);
-        this.linkService = checkNotNull(linkService);
-        this.flowService = checkNotNull(flowService);
-        this.flowStatsService = checkNotNull(flowStatsService);
-        this.portStatsService = checkNotNull(portStatsService);
+    public ServicesBundle(ServiceDirectory directory) {
+        checkNotNull(directory, "Directory cannot be null");
+
+        clusterService = directory.get(ClusterService.class);
+
+        topologyService = directory.get(TopologyService.class);
+        deviceService = directory.get(DeviceService.class);
+        hostService = directory.get(HostService.class);
+        linkService = directory.get(LinkService.class);
+        tunnelService = directory.get(TunnelService.class);
+
+        mastershipService = directory.get(MastershipService.class);
+        mastershipAdminService = directory.get(MastershipAdminService.class);
+        intentService = directory.get(IntentService.class);
+        flowService = directory.get(FlowRuleService.class);
+        flowStatsService = directory.get(StatisticService.class);
+        portStatsService = directory.get(PortStatisticsService.class);
     }
 
     /**
-     * Returns a reference to the intent service.
+     * Returns a reference to the cluster service.
      *
-     * @return intent service reference
+     * @return cluster service reference
      */
-    public IntentService intentService() {
-        return intentService;
+    public ClusterService cluster() {
+        return clusterService;
+    }
+
+    /**
+     * Returns a reference to the topology service.
+     *
+     * @return topology service reference
+     */
+    public TopologyService topology() {
+        return topologyService;
     }
 
     /**
@@ -80,7 +100,7 @@
      *
      * @return device service reference
      */
-    public DeviceService deviceService() {
+    public DeviceService device() {
         return deviceService;
     }
 
@@ -89,7 +109,7 @@
      *
      * @return host service reference
      */
-    public HostService hostService() {
+    public HostService host() {
         return hostService;
     }
 
@@ -98,16 +118,52 @@
      *
      * @return link service reference
      */
-    public LinkService linkService() {
+    public LinkService link() {
         return linkService;
     }
 
     /**
+     * Returns a reference to the tunnel service.
+     *
+     * @return tunnel service reference
+     */
+    public TunnelService tunnel() {
+        return tunnelService;
+    }
+
+    /**
+     * Returns a reference to the mastership service.
+     *
+     * @return mastership service reference
+     */
+    public MastershipService mastership() {
+        return mastershipService;
+    }
+
+    /**
+     * Returns a reference to the mastership admin service.
+     *
+     * @return mastership admin service reference
+     */
+    public MastershipAdminService mastershipAdmin() {
+        return mastershipAdminService;
+    }
+
+    /**
+     * Returns a reference to the intent service.
+     *
+     * @return intent service reference
+     */
+    public IntentService intent() {
+        return intentService;
+    }
+
+    /**
      * Returns a reference to the flow rule service.
      *
      * @return flow service reference
      */
-    public FlowRuleService flowService() {
+    public FlowRuleService flow() {
         return flowService;
     }
 
@@ -116,7 +172,7 @@
      *
      * @return flow statistics service reference
      */
-    public StatisticService flowStatsService() {
+    public StatisticService flowStats() {
         return flowStatsService;
     }
 
@@ -125,7 +181,7 @@
      *
      * @return port statistics service reference
      */
-    public PortStatisticsService portStatsService() {
+    public PortStatisticsService portStats() {
         return portStatsService;
     }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TopoIntentFilter.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TopoIntentFilter.java
index e43fbf2..1dbe8f3 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TopoIntentFilter.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/TopoIntentFilter.java
@@ -21,9 +21,7 @@
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.Link;
-import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.host.HostService;
 import org.onosproject.net.intent.FlowObjectiveIntent;
 import org.onosproject.net.intent.FlowRuleIntent;
 import org.onosproject.net.intent.HostToHostIntent;
@@ -52,8 +50,6 @@
 public class TopoIntentFilter {
 
     private final IntentService intentService;
-    private final DeviceService deviceService;
-    private final HostService hostService;
     private final LinkService linkService;
 
     /**
@@ -62,19 +58,17 @@
      * @param services service references bundle
      */
     public TopoIntentFilter(ServicesBundle services) {
-        this.intentService = services.intentService();
-        this.deviceService = services.deviceService();
-        this.hostService = services.hostService();
-        this.linkService = services.linkService();
+        this.intentService = services.intent();
+        this.linkService = services.link();
     }
 
     /**
      * Finds all path (host-to-host or point-to-point) intents that pertain
      * to the given hosts and devices.
      *
-     * @param hosts         set of hosts to query by
-     * @param devices       set of devices to query by
-     * @param links       set of links to query by
+     * @param hosts   set of hosts to query by
+     * @param devices set of devices to query by
+     * @param links   set of links to query by
      * @return set of intents that 'match' all hosts, devices and links given
      */
     public List<Intent> findPathIntents(Set<Host> hosts,