ONOS-6259: Topo2 - Implement server-side highlighting model (WIP)
- added locType parameter to region-add command
- created RegionABC sample topology
- fixed possible NPE in Topo2Jsonifier.jsonClosedRegion()
- added "plain" sprite layout
- check for undefined sprite layout and log a warning
- updated logger.sh script
- fixed Topo2Model to have a reference to colleciton before initialization

Change-Id: Ie6af28516338f5d64576bf465373cb5df3dff52c
diff --git a/cli/src/main/java/org/onosproject/cli/net/RegionAddCommand.java b/cli/src/main/java/org/onosproject/cli/net/RegionAddCommand.java
index eff8fb5..db91db6 100644
--- a/cli/src/main/java/org/onosproject/cli/net/RegionAddCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/RegionAddCommand.java
@@ -39,10 +39,13 @@
         description = "Adds a new region.")
 public class RegionAddCommand extends AbstractShellCommand {
 
+    private static final String E_BAD_LOC_TYPE = "locType must be {geo|grid}";
+
+    private static final String GEO = "geo";
+    private static final String GRID = "grid";
     private static final String SLASH = "/";
 
-    private static final BiMap<String, Region.Type> REGION_TYPE_MAP
-            = HashBiMap.create();
+    private static final BiMap<String, Region.Type> REGION_TYPE_MAP = HashBiMap.create();
 
     static {
         for (Region.Type t : Region.Type.values()) {
@@ -63,15 +66,21 @@
             required = true, multiValued = false)
     String type = null;
 
-    @Argument(index = 3, name = "latitude", description = "Geo latitude",
+    @Argument(index = 3, name = "latOrY",
+            description = "Geo latitude / Grid y-coord",
             required = true, multiValued = false)
-    Double latitude = null;
+    Double latOrY = null;
 
-    @Argument(index = 4, name = "longitude", description = "Geo longitude",
+    @Argument(index = 4, name = "longOrX",
+            description = "Geo longitude / Grid x-coord",
             required = true, multiValued = false)
-    Double longitude = null;
+    Double longOrX = null;
 
-    @Argument(index = 5, name = "masters", description = "Region Master, a set " +
+    @Argument(index = 5, name = "locType", description = "Location type {geo|grid}",
+            required = false, multiValued = false)
+    String locType = GEO;
+
+    @Argument(index = 6, name = "masters", description = "Region Master, a set " +
             "of nodeIds should be split with '/' delimiter (e.g., 1 2 3 / 4 5 6)",
             required = true, multiValued = true)
     List<String> masterArgs = null;
@@ -81,6 +90,33 @@
         RegionAdminService service = get(RegionAdminService.class);
         RegionId regionId = RegionId.regionId(id);
 
+        NetworkConfigService cfgService = get(NetworkConfigService.class);
+        BasicRegionConfig cfg = cfgService.addConfig(regionId, BasicRegionConfig.class);
+        setConfigurationData(cfg);
+
+        List<Set<NodeId>> masters = parseMasterArgs();
+        service.createRegion(regionId, name, REGION_TYPE_MAP.get(type), masters);
+        print("Region successfully added.");
+    }
+
+    private void setConfigurationData(BasicRegionConfig cfg) {
+        cfg.name(name).locType(locType);
+
+        if (GEO.equals(locType)) {
+            cfg.latitude(latOrY).longitude(longOrX);
+
+        } else if (GRID.equals(locType)) {
+            cfg.gridY(latOrY).gridX(longOrX);
+
+        } else {
+            throw new IllegalArgumentException(E_BAD_LOC_TYPE);
+
+        }
+
+        cfg.apply();
+    }
+
+    private List<Set<NodeId>> parseMasterArgs() {
         List<Set<NodeId>> masters = Lists.newArrayList();
         Set<NodeId> nodeIds = Sets.newHashSet();
         for (String masterArg : masterArgs) {
@@ -92,15 +128,6 @@
             }
         }
         masters.add(nodeIds);
-
-        NetworkConfigService cfgService = get(NetworkConfigService.class);
-        BasicRegionConfig cfg = cfgService.addConfig(regionId, BasicRegionConfig.class);
-        cfg.name(name)
-                .latitude(latitude)
-                .longitude(longitude)
-                .apply();
-
-        service.createRegion(regionId, name, REGION_TYPE_MAP.get(type), masters);
-        print("Region successfully added.");
+        return masters;
     }
 }
\ No newline at end of file
diff --git a/tools/test/topos/regionabc-onos.py b/tools/test/topos/regionabc-onos.py
new file mode 100644
index 0000000..f2c8218
--- /dev/null
+++ b/tools/test/topos/regionabc-onos.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+
+from onosnet import run
+from regionabc import RegionABC
+
+run( RegionABC() )
diff --git a/tools/test/topos/regionabc.json b/tools/test/topos/regionabc.json
new file mode 100644
index 0000000..b5d15e0
--- /dev/null
+++ b/tools/test/topos/regionabc.json
@@ -0,0 +1,49 @@
+{
+  "devices": {
+    "of:0000000000000001": {
+      "basic": {
+        "name": "S1",
+        "gridX": 40,
+        "gridY": 20
+      }
+    },
+    "of:0000000000000002": {
+      "basic": {
+        "name": "S2",
+        "gridX": 40,
+        "gridY": 40
+      }
+    },
+
+    "of:0000000000000003": {
+      "basic": {
+        "name": "S3",
+        "gridX": 40,
+        "gridY": 20
+      }
+    },
+    "of:0000000000000004": {
+      "basic": {
+        "name": "S4",
+        "gridX": 40,
+        "gridY": 40
+      }
+    },
+
+    "of:0000000000000005": {
+      "basic": {
+        "name": "S5",
+        "gridX": 40,
+        "gridY": 20
+      }
+    },
+    "of:0000000000000006": {
+      "basic": {
+        "name": "S6",
+        "gridX": 40,
+        "gridY": 40
+      }
+    }
+
+  }
+}
diff --git a/tools/test/topos/regionabc.py b/tools/test/topos/regionabc.py
new file mode 100644
index 0000000..a26d3d3
--- /dev/null
+++ b/tools/test/topos/regionabc.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+"""
+      [1] ----- [3] ----- [5]
+       |   ____/ | \       |
+       |  /      |  \____  |
+       | /       |       \ |
+      [2] ----- [4] ----- [6]
+"""
+from mininet.topo import Topo
+
+class RegionABC( Topo ):
+    """Simple 6 switch example"""
+
+    def __init__( self ):
+        """Create a topology."""
+
+        # Initialize Topology
+        Topo.__init__( self )
+
+        # add nodes, switches first...
+        S1 = self.addSwitch( 's1' )
+        S2 = self.addSwitch( 's2' )
+        S3 = self.addSwitch( 's3' )
+        S4 = self.addSwitch( 's4' )
+        S5 = self.addSwitch( 's5' )
+        S6 = self.addSwitch( 's6' )
+
+        # ... and now hosts
+        S1_host = self.addHost( 'h1' )
+        S2_host = self.addHost( 'h2' )
+        S3_host = self.addHost( 'h3' )
+        S4_host = self.addHost( 'h4' )
+        S5_host  = self.addHost( 'h5' )
+        S6_host = self.addHost( 'h6' )
+
+        # add edges between switch and corresponding host
+        self.addLink( S1, S1_host )
+        self.addLink( S2, S2_host )
+        self.addLink( S3, S3_host )
+        self.addLink( S4, S4_host )
+        self.addLink( S5, S5_host )
+        self.addLink( S6, S6_host )
+
+        # add edges between switches as diagrammed above
+        self.addLink( S1, S2, bw=10, delay='1.0ms')
+        self.addLink( S1, S3, bw=10, delay='1.0ms')
+        self.addLink( S2, S3, bw=10, delay='1.0ms')
+        self.addLink( S2, S4, bw=10, delay='1.0ms')
+        self.addLink( S3, S4, bw=10, delay='1.0ms')
+        self.addLink( S3, S5, bw=10, delay='1.0ms')
+        self.addLink( S3, S6, bw=10, delay='1.0ms')
+        self.addLink( S4, S6, bw=10, delay='1.0ms')
+        self.addLink( S5, S6, bw=10, delay='1.0ms')
+
+topos = { 'regionabc': ( lambda: RegionABC() ) }
+
+if __name__ == '__main__':
+    from onosnet import run
+    run( RegionABC() )
diff --git a/tools/test/topos/regionabc.sh b/tools/test/topos/regionabc.sh
new file mode 100755
index 0000000..64e205a
--- /dev/null
+++ b/tools/test/topos/regionabc.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+#
+# A simple test topology of three regions, A, B, and C.
+#
+# Script Configuration:
+#
+# host     : the controller instance against which this script is run
+
+host=${1:-127.0.0.1}
+
+
+###------------------------------------------------------
+### Start by adding the three regions A, B, and C
+
+# region-add <region-id> <region-name> <region-type> \
+#   <lat/Y> <long/X> <locType> <region-master>
+
+onos ${host} <<-EOF
+
+# -- define regions
+region-add rA "Region A" LOGICAL_GROUP 30 20 grid ${host}
+region-add rB "Region B" LOGICAL_GROUP 30 40 grid ${host}
+region-add rC "Region C" LOGICAL_GROUP 30 60 grid ${host}
+
+# -- set peer locations
+region-add-peer-loc rA rB 40 70
+region-add-peer-loc rA rC 50 70
+
+region-add-peer-loc rB rA 30 10
+region-add-peer-loc rB rC 30 70
+
+region-add-peer-loc rC rA 10 10
+region-add-peer-loc rC rB 20 10
+
+EOF
+
+###------------------------------------------------------
+### Add layouts, associating backing regions, and optional parent
+
+# layout-add <layout-id> <bg-ref> \
+#   [ <region-id> <parent-layout-id> <scale> <offset-x> <offset-y> ]
+#
+
+onos ${host} <<-EOF
+
+# -- top level
+layout-add root +plain . . 1.0 0.0 0.0
+
+# -- layouts for top level regions
+layout-add lA +plain rA root 1.0 0.0 0.0
+layout-add lB +plain rB root 1.0 0.0 0.0
+layout-add lC +plain rC root 1.0 0.0 0.0
+
+# -- summary
+layouts
+EOF
+
+###------------------------------------------------------
+### Assign devices to each of their regions
+
+onos ${host} <<-EOF
+
+region-add-devices rA \
+    of:0000000000000001 \
+    of:0000000000000002 \
+
+region-add-devices rB \
+    of:0000000000000003 \
+    of:0000000000000004 \
+
+region-add-devices rC \
+    of:0000000000000005 \
+    of:0000000000000006 \
+
+EOF
+
+###------------------------------------------------------
+### Configure devices and hosts
+
+onos-netcfg ${host} regionabc.json
+
+
diff --git a/tools/test/topos/regions-bayarea-grid.sh b/tools/test/topos/regions-bayarea-grid.sh
index ba7ec95..03b2813 100755
--- a/tools/test/topos/regions-bayarea-grid.sh
+++ b/tools/test/topos/regions-bayarea-grid.sh
@@ -206,15 +206,16 @@
 
 ### Add regions and associate devices with them
 #
-# region-add <region-id> <region-name> <region-type> <lat/Y> <long/X> <region-master>
+# region-add <region-id> <region-name> <region-type> \
+#   <lat/Y> <long/X> <locType> <region-master>
 # region-add-devices <region-id> <device-id>...
 
 onos ${host} <<-EOF
 
-region-add c01 "San Francisco" DATA_CENTER 37.75394143914288 -122.45945851660800 ${host}
-region-add c02 "Palo Alto"     DATA_CENTER 37.45466637790734 -122.21838933304870 ${host}
-region-add c03 "San Jose"      DATA_CENTER 37.34425619809433 -121.94768095808017 ${host}
-region-add c04 "Fremont"      DATA_CENTER 37.54328280574901 -122.01205548699211 ${host}
+region-add c01 "San Francisco" DATA_CENTER 37.75394143914288 -122.45945851660800 geo ${host}
+region-add c02 "Palo Alto"     DATA_CENTER 37.45466637790734 -122.21838933304870 geo ${host}
+region-add c03 "San Jose"      DATA_CENTER 37.34425619809433 -121.94768095808017 geo ${host}
+region-add c04 "Fremont"      DATA_CENTER 37.54328280574901 -122.01205548699211 geo ${host}
 
 region-add-devices c01 \
     null:0000000000000001 \
diff --git a/tools/test/topos/regions-bayarea.sh b/tools/test/topos/regions-bayarea.sh
index ae0ef10..f37373f 100755
--- a/tools/test/topos/regions-bayarea.sh
+++ b/tools/test/topos/regions-bayarea.sh
@@ -206,15 +206,16 @@
 
 ### Add regions and associate devices with them
 #
-# region-add <region-id> <region-name> <region-type> <region-master>
+# region-add <region-id> <region-name> <region-type> \
+#   <lat/Y> <long/X> <locType> <region-master>
 # region-add-devices <region-id> <device-id>...
 
 onos ${host} <<-EOF
 
-region-add c01 SanFrancisco DATA_CENTER 37.75394143914288 -122.45945851660800 ${host}
-region-add c02 PaloAlto     DATA_CENTER 37.45466637790734 -122.21838933304870 ${host}
-region-add c03 SanJose      DATA_CENTER 37.34425619809433 -121.94768095808017 ${host}
-region-add c04 Fremont      DATA_CENTER 37.54328280574901 -122.01205548699211 ${host}
+region-add c01 SanFrancisco DATA_CENTER 37.75394143914288 -122.45945851660800 geo ${host}
+region-add c02 PaloAlto     DATA_CENTER 37.45466637790734 -122.21838933304870 geo ${host}
+region-add c03 SanJose      DATA_CENTER 37.34425619809433 -121.94768095808017 geo ${host}
+region-add c04 Fremont      DATA_CENTER 37.54328280574901 -122.01205548699211 geo ${host}
 
 region-add-devices c01 \
     null:0000000000000001 \
diff --git a/tools/test/topos/regions-europe.sh b/tools/test/topos/regions-europe.sh
index 58f2944..44e64fc 100755
--- a/tools/test/topos/regions-europe.sh
+++ b/tools/test/topos/regions-europe.sh
@@ -28,17 +28,18 @@
 ### Start by adding Country regions
 # Note that Long/Lat places region icon nicely in the country center
 
-# region-add <region-id> <region-name> <region-type> <lat/Y> <long/X> <region-master>
+# region-add <region-id> <region-name> <region-type> \
+#   <lat/Y> <long/X> <locType> <region-master>
 
 onos ${host} <<-EOF
 
-region-add rUK "United Kingdom" COUNTRY 52.206035 -1.310384 ${host}
-region-add rIT "Italy"   COUNTRY 44.447951  11.093161 ${host}
-region-add rFR "France"  COUNTRY 47.066264  2.711458 ${host}
-region-add rDE "Germany" COUNTRY 50.863152  9.761971 ${host}
-region-add rES "Spain"   COUNTRY 40.416704 -3.7035824 ${host}
+region-add rUK "United Kingdom" COUNTRY 52.206035 -1.310384 geo ${host}
+region-add rIT "Italy"   COUNTRY 44.447951  11.093161 geo ${host}
+region-add rFR "France"  COUNTRY 47.066264  2.711458 geo ${host}
+region-add rDE "Germany" COUNTRY 50.863152  9.761971 geo ${host}
+region-add rES "Spain"   COUNTRY 40.416704 -3.7035824 geo ${host}
 
-region-add rMilan "Milan" METRO 45.4654 9.1859 ${host}
+region-add rMilan "Milan" METRO 45.4654 9.1859 geo ${host}
 
 EOF
 
diff --git a/web/gui/logger.sh b/web/gui/logger.sh
index 53c3ddad..93a9048 100755
--- a/web/gui/logger.sh
+++ b/web/gui/logger.sh
@@ -3,16 +3,22 @@
 host=${1:-localhost}
 
 ### Set up debug log messages for classes we care about
+#
+# -- NOTE: leave commented out for checked-in source
+#          developer can uncomment locally
+
 onos ${host} <<-EOF
 
-log:set DEBUG org.onosproject.ui.impl.topo.Topo2ViewMessageHandler
-log:set DEBUG org.onosproject.ui.impl.topo.Topo2Jsonifier
-log:set DEBUG org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler
-log:set DEBUG org.onosproject.ui.impl.topo.Traffic2Monitor
+#log:set DEBUG org.onosproject.ui.impl.topo.Topo2ViewMessageHandler
+#log:set DEBUG org.onosproject.ui.impl.topo.Topo2Jsonifier
+#log:set DEBUG org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler
+#log:set DEBUG org.onosproject.ui.impl.topo.Traffic2Monitor
 
-log:set DEBUG org.onosproject.ui.impl.UiWebSocket
+#log:set DEBUG org.onosproject.ui.impl.UiWebSocket
 #log:set DEBUG org.onosproject.ui.impl.UiTopoSession
 
+#log:set DEBUG org.onosproject.ui.impl.topo.model.ModelCache
+
 log:list
 
 EOF
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
index c7a223f..da1f8e4 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
@@ -611,9 +611,11 @@
         //       all descendant subregions.
 
         Region r = region.backingRegion();
-        // this is location data, as injected via network configuration script
-        addGeoGridLocation(node, r);
-        addProps(node, r);
+        if (r != null) {
+            // add data injected via network configuration script
+            addGeoGridLocation(node, r);
+            addProps(node, r);
+        }
 
         // this may contain location data, as dragged by user
         // (which should take precedence, over configured data)
diff --git a/web/gui/src/main/webapp/app/fw/svg/sprite.js b/web/gui/src/main/webapp/app/fw/svg/sprite.js
index 6eafe66..b64f58d 100644
--- a/web/gui/src/main/webapp/app/fw/svg/sprite.js
+++ b/web/gui/src/main/webapp/app/fw/svg/sprite.js
@@ -215,9 +215,9 @@
 
     // Returns a layout "builder", which can be used to programmatically
     // define a layout.
-    function createLayout(id, w, h, grid) {
-        $log.debug('createLayout:', id, w, 'x', h, '(grid=' + grid + ')');
-        return layoutBuilder(id, w, h, grid);
+    function createLayout(id, w, h, opts) {
+        $log.debug('createLayout:', id, w, 'x', h, '(opts:', opts, ')');
+        return layoutBuilder(id, w, h, opts);
     }
 
     // Registers a sprite defined by the given object (JSON structure).
diff --git a/web/gui/src/main/webapp/app/fw/svg/spriteData.js b/web/gui/src/main/webapp/app/fw/svg/spriteData.js
index 1625a46..d970af9 100644
--- a/web/gui/src/main/webapp/app/fw/svg/spriteData.js
+++ b/web/gui/src/main/webapp/app/fw/svg/spriteData.js
@@ -77,6 +77,9 @@
             .addLabel('Segment Routing 2', 120, 10, {anchor: 'right'})
             .register();
 
+        ssApi.createLayout('plain', 80, 60)
+            .register();
+
         ssApi.dump();
         // ----------------------------------------------------------$$$
     }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
index efcd201..6da29c4 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
@@ -25,6 +25,7 @@
     function Model(attributes, collection) {
         this.attributes = {};
         this.set(angular.extend({}, attributes || {}), { silent: true });
+        this.collection = collection;
         this.initialize.apply(this, arguments);
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
index 1227c30..222e837 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
@@ -141,7 +141,8 @@
             // TODO: Inconsistent host id's (currentRegion and LinkLabel)
             var id = link.id.replace('/None/0', '/None').replace('-', '~'),
                 lab = t2rs.getLink(id);
-                // TODO: There's a bug in backend where link id is in reverse
+                // DONE: There's a bug in backend where link id is in reverse
+                //       This is fixed -- SDH
                 if (lab) {
                     t2lc.addLabel(LinkLabel, link, linkLabelsDOM, {
                         link: lab
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SpriteLayer.js b/web/gui/src/main/webapp/app/view/topo2/topo2SpriteLayer.js
index 98e890b..f53ccc6 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SpriteLayer.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SpriteLayer.js
@@ -31,9 +31,10 @@
 
     angular.module('ovTopo2')
         .factory('Topo2SpriteLayerService', [
-            'FnService', 'Topo2ViewController', 'SpriteService', 'ThemeService',
+            '$log', 'FnService',
+            'Topo2ViewController', 'SpriteService', 'ThemeService',
 
-            function (fs, ViewController, ss, ts) {
+            function ($log, fs, ViewController, ss, ts) {
 
                 var SpriteLayer = ViewController.extend({
 
@@ -50,13 +51,17 @@
                         this.container.selectAll("*").remove();
                         this.layout = ss.layout(id);
 
-                        this.width = this.layout.data.w;
-                        this.height = this.layout.data.h;
+                        if (this.layout) {
+                            this.width = this.layout.data.w;
+                            this.height = this.layout.data.h;
 
-                        this.renderLayout();
+                            this.renderLayout();
 
-                        if (fs.debugOn('sprite_grid')) {
-                            this.renderGrid();
+                            if (fs.debugOn('sprite_grid')) {
+                                this.renderGrid();
+                            }
+                        } else {
+                            $log.warn('no sprite layout registered:', id);
                         }
 
                         // Returns a promise for consistency with Topo2MapService