[ONOS-6854] refactor bmv2-demo.py

Change-Id: I9b7460b15f6664363f2ff0b16110e2b3bc4dedeb
diff --git a/tools/dev/mininet/bmv2.py b/tools/dev/mininet/bmv2.py
index 5071c88..c4e27d5 100644
--- a/tools/dev/mininet/bmv2.py
+++ b/tools/dev/mininet/bmv2.py
@@ -23,7 +23,8 @@
     instanceCount = 0
 
     def __init__(self, name, json=None, debugger=False, loglevel="warn", elogger=False,
-                 persistent=False, grpcPort=None, thriftPort=None, netcfg=True, **kwargs):
+                 persistent=False, grpcPort=None, thriftPort=None, netcfg=True,
+                 pipeconfId="", **kwargs):
         Switch.__init__(self, name, **kwargs)
         self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
         self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
@@ -40,12 +41,25 @@
         self.persistent = persistent
         self.netcfg = netcfg
         self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
+        self.pipeconfId = pipeconfId
         if persistent:
             self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
             self.cmd("touch %s" % self.exectoken)
         # Store thrift port for future uses.
         self.cmd("echo %d > /tmp/bmv2-%d-grpc-port" % (self.grpcPort, self.deviceId))
 
+        if 'longitude' in kwargs:
+            self.longitude = kwargs['longitude']
+        else:
+            self.longitude = None
+
+        if 'latitude' in kwargs:
+            self.latitude = kwargs['latitude']
+        else:
+            self.latitude = None
+
+        self.onosDeviceId = "device:bmv2:%d" % self.deviceId
+
     @classmethod
     def pickUnusedPort(cls):
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -63,15 +77,7 @@
         r = re.search(r"src (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", ipRouteOut)
         return r.group(1) if r else None
 
-    def doOnosNetcfg(self, controllerIP):
-        """
-        Notifies ONOS about the new device via Netcfg.
-        """
-        srcIP = self.getSourceIp(controllerIP)
-        if not srcIP:
-            warn("WARN: unable to get device IP address, won't do onos-netcfg")
-            return
-        onosDeviceId = "bmv2:%s" % self.deviceId
+    def getDeviceConfig(self, srcIP):
         portData = {}
         portId = 1
         for intfName in self.intfNames():
@@ -87,25 +93,44 @@
             }
             portId += 1
 
+        basicCfg = {
+            "driver": "bmv2"
+        }
+
+        if self.longitude and self.latitude:
+            basicCfg["longitude"] = self.longitude
+            basicCfg["latitude"] = self.latitude
+
+        cfgData = {
+                "generalprovider": {
+                    "p4runtime": {
+                        "ip": srcIP,
+                        "port": self.grpcPort,
+                        "deviceId": self.deviceId,
+                        "deviceKeyId": "p4runtime:%s" % self.onosDeviceId
+                    }
+                },
+                "piPipeconf": {
+                    "piPipeconfId": self.pipeconfId
+                },
+                "basic": basicCfg,
+                "ports": portData
+        }
+
+        return cfgData
+
+    def doOnosNetcfg(self, controllerIP):
+        """
+        Notifies ONOS about the new device via Netcfg.
+        """
+        srcIP = self.getSourceIp(controllerIP)
+        if not srcIP:
+            warn("WARN: unable to get device IP address, won't do onos-netcfg")
+            return
+
         cfgData = {
             "devices": {
-                "device:%s" % onosDeviceId: {
-                    "generalprovider": {
-                        "p4runtime": {
-                            "ip": srcIP,
-                            "port": self.grpcPort,
-                            "deviceId": self.deviceId,
-                            "deviceKeyId": "p4runtime:%s" % onosDeviceId
-                        }
-                    },
-                    "piPipeconf": {
-                        "piPipeconfId": ""
-                    },
-                    "basic": {
-                        "driver": "bmv2"
-                    },
-                    "ports": portData
-                }
+                self.onosDeviceId: self.getDeviceConfig(srcIP)
             }
         }
         with open(self.netcfgfile, 'w') as fp:
diff --git a/tools/test/topos/bmv2-demo-cfg.json b/tools/test/topos/bmv2-demo-cfg.json
deleted file mode 100644
index 6c745b5..0000000
--- a/tools/test/topos/bmv2-demo-cfg.json
+++ /dev/null
@@ -1,162 +0,0 @@
-{
-  "apps": {
-    "org.onosproject.core": {
-      "core": {
-        "linkDiscoveryMode": "STRICT"
-      }
-    }
-  },
-  "devices": {
-    "bmv2:192.168.123.4:9090#11": {
-      "basic": {
-        "name": "bmv2:11",
-        "latitude": 40,
-        "longitude": -107
-      }
-    },
-    "bmv2:192.168.123.4:9091#12": {
-      "basic": {
-        "name": "bmv2:12",
-        "latitude": 40,
-        "longitude": -99
-      }
-    },
-    "bmv2:192.168.123.4:9092#13": {
-      "basic": {
-        "name": "bmv2:13",
-        "latitude": 40,
-        "longitude": -91
-      }
-    },
-    "bmv2:192.168.123.4:9093#21": {
-      "basic": {
-        "name": "bmv2:21",
-        "latitude": 46,
-        "longitude": -107
-      }
-    },
-    "bmv2:192.168.123.4:9094#22": {
-      "basic": {
-        "name": "bmv2:22",
-        "latitude": 46,
-        "longitude": -99
-      }
-    },
-    "bmv2:192.168.123.4:9095#23": {
-      "basic": {
-        "name": "bmv2:23",
-        "latitude": 46,
-        "longitude": -91
-      }
-    }
-  },
-  "links": {
-    "bmv2:192.168.123.4:9090#11/1-bmv2:192.168.123.4:9093#21/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9093#21/1-bmv2:192.168.123.4:9090#11/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9090#11/2-bmv2:192.168.123.4:9093#21/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9093#21/2-bmv2:192.168.123.4:9090#11/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9090#11/3-bmv2:192.168.123.4:9094#22/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9094#22/1-bmv2:192.168.123.4:9090#11/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9090#11/4-bmv2:192.168.123.4:9095#23/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9095#23/1-bmv2:192.168.123.4:9090#11/4": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9091#12/1-bmv2:192.168.123.4:9093#21/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9093#21/3-bmv2:192.168.123.4:9091#12/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9091#12/2-bmv2:192.168.123.4:9094#22/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9094#22/2-bmv2:192.168.123.4:9091#12/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9091#12/3-bmv2:192.168.123.4:9094#22/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9094#22/3-bmv2:192.168.123.4:9091#12/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9091#12/4-bmv2:192.168.123.4:9095#23/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9095#23/2-bmv2:192.168.123.4:9091#12/4": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9092#13/1-bmv2:192.168.123.4:9093#21/4": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9093#21/4-bmv2:192.168.123.4:9092#13/1": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9092#13/2-bmv2:192.168.123.4:9094#22/4": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9094#22/4-bmv2:192.168.123.4:9092#13/2": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9092#13/3-bmv2:192.168.123.4:9095#23/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9095#23/3-bmv2:192.168.123.4:9092#13/3": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9092#13/4-bmv2:192.168.123.4:9095#23/4": {
-      "basic": {}
-    },
-    "bmv2:192.168.123.4:9095#23/4-bmv2:192.168.123.4:9092#13/4": {
-      "basic": {}
-    }
-  },
-  "hosts": {
-    "00:00:00:00:00:01/-1": {
-      "basic": {
-        "locations": ["bmv2:192.168.123.4:9090#11/5"],
-        "ips": [
-          "10.0.0.1"
-        ],
-        "name": "h1",
-        "latitude": 36,
-        "longitude": -107
-      }
-    },
-    "00:00:00:00:00:02/-1": {
-      "basic": {
-        "locations": ["bmv2:192.168.123.4:9091#12/5"],
-        "ips": [
-          "10.0.0.2"
-        ],
-        "name": "h2",
-        "latitude": 36,
-        "longitude": -99
-      }
-    },
-    "00:00:00:00:00:03/-1": {
-      "basic": {
-        "locations": ["bmv2:192.168.123.4:9092#13/5"],
-        "ips": [
-          "10.0.0.3"
-        ],
-        "name": "h3",
-        "latitude": 36,
-        "longitude": -91
-      }
-    }
-  }
-}
diff --git a/tools/test/topos/bmv2-demo.py b/tools/test/topos/bmv2-demo.py
old mode 100644
new mode 100755
index 646c7353..a2aeaa6
--- a/tools/test/topos/bmv2-demo.py
+++ b/tools/test/topos/bmv2-demo.py
@@ -2,8 +2,18 @@
 
 import os
 import sys
+import json
 import argparse
 
+TEMP_NETCFG_FILE = '/tmp/bmv2-demo-cfg.json'
+BASE_LONGITUDE = -115
+SWITCH_BASE_LATITUDE = 25
+HOST_BASE_LATITUDE = 28
+BASE_SHIFT = 8
+VLAN_NONE = -1
+DEFAULT_SW_BW = 50
+DEFAULT_HOST_BW = 25
+
 if 'ONOS_ROOT' not in os.environ:
     print "Environment var $ONOS_ROOT not set"
     exit()
@@ -28,47 +38,52 @@
 from mininet.log import setLogLevel
 from mininet.net import Mininet
 from mininet.node import RemoteController, Host
-from mininet.topo import Topo, SingleSwitchTopo
-
+from mininet.topo import Topo
 
 class ClosTopo(Topo):
     "2 stage Clos topology"
 
-    def __init__(self, **opts):
+    def __init__(self, pipeconfId="", **opts):
         # Initialize topology and default options
         Topo.__init__(self, **opts)
 
         bmv2SwitchIds = ["s11", "s12", "s13", "s21", "s22", "s23"]
-
         bmv2Switches = {}
 
-        tport = 9090
         for switchId in bmv2SwitchIds:
+            deviceId=int(switchId[1:])
+            # Use first number in device id to calculate latitude (row number)
+            latitude = SWITCH_BASE_LATITUDE + (deviceId // 10) * BASE_SHIFT
+
+            # Use second number in device id to calculate longitude (column number)
+            longitude = BASE_LONGITUDE + (deviceId % 10) * BASE_SHIFT
             bmv2Switches[switchId] = self.addSwitch(switchId,
                                                     cls=ONOSBmv2Switch,
                                                     loglevel="warn",
-                                                    deviceId=int(switchId[1:]),
-                                                    thriftPort=tport)
-            tport += 1
+                                                    deviceId=deviceId,
+                                                    netcfg=False,
+                                                    longitude=longitude,
+                                                    latitude=latitude,
+                                                    pipeconfId=pipeconfId)
 
         for i in (1, 2, 3):
             for j in (1, 2, 3):
                 if i == j:
                     # 2 links
                     self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
-                                 cls=TCLink, bw=50)
+                                 cls=TCLink, bw=DEFAULT_SW_BW)
                     self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
-                                 cls=TCLink, bw=50)
+                                 cls=TCLink, bw=DEFAULT_SW_BW)
                 else:
                     self.addLink(bmv2Switches["s1%d" % i], bmv2Switches["s2%d" % j],
-                                 cls=TCLink, bw=50)
+                                 cls=TCLink, bw=DEFAULT_SW_BW)
 
         for hostId in (1, 2, 3):
             host = self.addHost("h%d" % hostId,
                                 cls=DemoHost,
                                 ip="10.0.0.%d/24" % hostId,
                                 mac='00:00:00:00:00:%02x' % hostId)
-            self.addLink(host, bmv2Switches["s1%d" % hostId], cls=TCLink, bw=22)
+            self.addLink(host, bmv2Switches["s1%d" % hostId], cls=TCLink, bw=DEFAULT_HOST_BW)
 
 
 class DemoHost(Host):
@@ -82,10 +97,8 @@
     def config(self, **params):
         r = super(Host, self).config(**params)
 
-        self.defaultIntf().rename("eth0")
-
         for off in ["rx", "tx", "sg"]:
-            cmd = "/sbin/ethtool --offload eth0 %s off" % off
+            cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf(), off)
             self.cmd(cmd)
 
         # disable IPv6
@@ -130,17 +143,83 @@
     def getCmdBg(self, cmd, logfile="/dev/null"):
         return "{} > {} 2>&1 &".format(cmd, logfile)
 
+def generateNetcfg(onosIp, net):
+    netcfg = { 'devices': {}, 'links': {}, 'hosts': {}}
+    # Device configs
+    for sw in net.switches:
+        srcIp = sw.getSourceIp(onosIp)
+        netcfg['devices'][sw.onosDeviceId] = sw.getDeviceConfig(srcIp)
+
+    hostLocations = {}
+    # Link configs
+    for link in net.links:
+        switchPort = link.intf1.name.split('-')
+        sw1Name = switchPort[0] # s11
+        port1Name = switchPort[1] # eth0
+        port1 = port1Name[3:]
+        switchPort = link.intf2.name.split('-')
+        sw2Name = switchPort[0]
+        port2Name = switchPort[1]
+        port2 = port2Name[3:]
+        sw1 = net[sw1Name]
+        sw2 = net[sw2Name]
+        if isinstance(sw1, Host):
+            # record host location and ignore it
+            # e.g. {'h1': 'device:bmv2:11'}
+            hostLocations[sw1.name] = '%s/%s' % (sw2.onosDeviceId, port2)
+            continue
+
+        if isinstance(sw2, Host):
+            # record host location and ignore it
+            # e.g. {'h1': 'device:bmv2:11'}
+            hostLocations[sw2.name] = '%s/%s' % (sw1.onosDeviceId, port1)
+            continue
+
+        linkId = '%s/%s-%s/%s' % (sw1.onosDeviceId, port1, sw2.onosDeviceId, port2)
+        netcfg['links'][linkId] = {
+            'basic': {
+                'type': 'DIRECT',
+                'bandwidth': 50
+            }
+        }
+
+    # Host configs
+    longitude = BASE_LONGITUDE
+    for host in net.hosts:
+        longitude = longitude + BASE_SHIFT
+        hostDefaultIntf = host.defaultIntf()
+        hostMac = host.MAC(hostDefaultIntf)
+        hostIp = host.IP(hostDefaultIntf)
+        hostId = '%s/%d' % (hostMac, VLAN_NONE)
+        location = hostLocations[host.name]
+
+        # use host Id to generate host location
+        hostConfig = {
+            'basic': {
+                'locations': [location],
+                'ips': [hostIp],
+                'name': host.name,
+                'latitude': HOST_BASE_LATITUDE,
+                'longitude': longitude
+            }
+        }
+        netcfg['hosts'][hostId] = hostConfig
+
+    print "Writing network config to %s" % TEMP_NETCFG_FILE
+    with open(TEMP_NETCFG_FILE, 'w') as tempFile:
+        json.dump(netcfg, tempFile)
 
 def main(args):
-    topo = ClosTopo()
-
+    setLogLevel('debug')
     if not args.onos_ip:
         controller = ONOSCluster('c0', 3)
         onosIp = controller.nodes()[0].IP()
     else:
-        controller = RemoteController('c0', ip=args.onos_ip, port=args.onos_port)
+        controller = RemoteController('c0', ip=args.onos_ip)
         onosIp = args.onos_ip
 
+    topo = ClosTopo(pipeconfId=args.pipeconf_id)
+
     net = Mininet(topo=topo, build=False, controller=[controller])
 
     net.build()
@@ -165,9 +244,10 @@
     # print "Starting traffic from h1 to h3..."
     # net.hosts[0].startIperfClient(net.hosts[-1], flowBw="200k", numFlows=100, duration=10)
 
-    print "Setting netcfg..."
-    call(("%s/onos-netcfg" % RUN_PACK_PATH, onosIp,
-          "%s/tools/test/topos/bmv2-demo-cfg.json" % ONOS_ROOT))
+    generateNetcfg(onosIp, net)
+
+    print "Uploading netcfg..."
+    call(("%s/onos-netcfg" % RUN_PACK_PATH, onosIp, TEMP_NETCFG_FILE))
 
     if not args.onos_ip:
         ONOSCLI(net)
@@ -175,6 +255,7 @@
         CLI(net)
 
     net.stop()
+    call(("rm", "-f", TEMP_NETCFG_FILE))
 
 
 if __name__ == '__main__':
@@ -182,8 +263,8 @@
         description='BMv2 mininet demo script (2-stage Clos topology)')
     parser.add_argument('--onos-ip', help='ONOS-BMv2 controller IP address',
                         type=str, action="store", required=False)
-    parser.add_argument('--onos-port', help='ONOS-BMv2 controller port',
-                        type=int, action="store", default=40123)
+    parser.add_argument('--pipeconf-id', help='Pipeconf ID for switches',
+                        type=str, action="store", required=False, default='')
     args = parser.parse_args()
     setLogLevel('info')
     main(args)