ONOS-6457 Improved bmv2.py mininet script

- Starts BMv2 with empty.p4 as when running with --no-p4 the switch
  crashes
- Automatically send a netcfg JSON to ONOS for each device
- Makefile to build all P4 programs (needed for empty.p4)

Change-Id: Ib872641751c543aac6c752610b1ce88a1a00d0d2
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 9e7a549..65c7003 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -38,6 +38,10 @@
 # Setup default location of test scenarios
 export ONOS_SCENARIOS=$ONOS_ROOT/tools/test/scenarios
 
+# Setup path to Mininet custom scripts
+export ONOS_MN_PY=$ONOS_ROOT/tools/dev/mininet/onos.py
+export BMV2_MN_PY=$ONOS_ROOT/tools/dev/mininet/bmv2.py
+
 # Convenience utility to warp to various ONOS source projects
 # e.g. 'o api', 'o dev', 'o'
 function o {
diff --git a/tools/dev/mininet/bmv2.py b/tools/dev/mininet/bmv2.py
index d6f96b1..6998008 100644
--- a/tools/dev/mininet/bmv2.py
+++ b/tools/dev/mininet/bmv2.py
@@ -1,21 +1,31 @@
+import os
 import socket
+import re
+import json
 
-from mininet.log import error, info
+from mininet.log import info, warn, error
 from mininet.node import Switch
 
+if 'ONOS_ROOT' not in os.environ:
+    error("ERROR: environment var $ONOS_ROOT not set")
+    exit()
+
 BMV2_TARGET = 'simple_switch_grpc'
+ONOS_ROOT = os.environ["ONOS_ROOT"]
+INIT_BMV2_JSON = '%s/tools/test/p4src/p4c-out/empty.json' % ONOS_ROOT
+
 
 class ONOSBmv2Switch(Switch):
-    """BMv2 software switch """
+    """BMv2 software switch with gRPC server"""
 
     deviceId = 0
     instanceCount = 0
 
     def __init__(self, name, debugger=False, loglevel="warn", elogger=False, persistent=False,
-                 logflush=False, **kwargs):
+                 logflush=False, thriftPort=None, grpcPort=None, netcfg=True, **kwargs):
         Switch.__init__(self, name, **kwargs)
-        self.thriftPort = ONOSBmv2Switch.pickUnusedPort()
-        self.grpcPort = ONOSBmv2Switch.pickUnusedPort()
+        self.thriftPort = ONOSBmv2Switch.pickUnusedPort() if not thriftPort else thriftPort
+        self.grpcPort = ONOSBmv2Switch.pickUnusedPort() if not grpcPort else grpcPort
         if self.dpid:
             self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16)
         else:
@@ -27,6 +37,8 @@
         self.elogger = elogger
         self.persistent = persistent
         self.logflush = logflush
+        self.netcfg = netcfg
+        self.netcfgfile = '/tmp/bmv2-%d-netcfg.json' % self.deviceId
         if persistent:
             self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId
             self.cmd("touch %s" % self.exectoken)
@@ -42,6 +54,38 @@
         s.close()
         return port
 
+    def getSourceIp(self, dstIP):
+        """
+        Queries the Linux routing table to get the source IP that can talk with dstIP, and vice
+        versa.
+        """
+        ipRouteOut = self.cmd('ip route get %s' % dstIP)
+        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:%s#%s" % (srcIP, self.grpcPort, self.deviceId)
+        cfgData = {"devices": {
+            onosDeviceId: {
+                "basic": {
+                    "name": "bmv2:%s" % self.deviceId
+                }
+            }
+        }}
+        with open(self.netcfgfile, 'w') as fp:
+            json.dump(cfgData, fp, indent=4)
+        out = self.cmd("%s/tools/test/bin/onos-netcfg %s %s"
+                       % (ONOS_ROOT, controllerIP, self.netcfgfile))
+        if out:
+            print out
+
     def start(self, controllers):
         args = [BMV2_TARGET, '--device-id %s' % str(self.deviceId)]
         for port, intf in self.intfs.items():
@@ -57,32 +101,47 @@
         args.append('--log-file %s -L%s' % (self.logfile, self.loglevel))
         if self.logflush:
             args.append('--log-flush')
-        args.append('--no-p4')
+
+        args.append(INIT_BMV2_JSON)
+
+        # gRPC target-specific options.
         args.append('--')
         args.append('--enable-swap')
-        if self.grpcPort:
-            args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
+        args.append('--grpc-server-addr 0.0.0.0:%d' % self.grpcPort)
 
         bmv2cmd = " ".join(args)
         info("\nStarting BMv2 target: %s\n" % bmv2cmd)
         if self.persistent:
-            # Re-exec the switch if it crashes.
+            # Bash loop to re-exec the switch if it crashes.
             cmdStr = "(while [ -e {} ]; " \
                      "do {} ; " \
                      "sleep 1; " \
                      "done;) &".format(self.exectoken, bmv2cmd)
         else:
             cmdStr = "{} &".format(bmv2cmd)
-        self.cmd(cmdStr)
 
-    def stop(self):
-        "Terminate switch."
+        # Starts the switch.
+        out = self.cmd(cmdStr)
+        if out:
+            print out
+
+        if self.netcfg:
+            try:  # onos.py
+                clist = controllers[0].nodes()
+            except AttributeError:
+                clist = controllers
+            assert len(clist) > 0
+            cip = clist[0].IP()
+            self.doOnosNetcfg(cip)
+
+    def stop(self, deleteIntfs=True):
+        """Terminate switch."""
         self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId)
         # wildcard end as BMv2 might create log files with .txt extension
         self.cmd("rm -f /tmp/bmv2-%d.log*" % self.deviceId)
         self.cmd('kill %' + BMV2_TARGET)
-        self.deleteIntfs()
+        Switch.stop(self, deleteIntfs)
 
 
-### Exports for bin/mn
+# Exports for bin/mn
 switches = {'onosbmv2': ONOSBmv2Switch}
diff --git a/tools/test/p4src/Makefile b/tools/test/p4src/Makefile
new file mode 100644
index 0000000..87888e0
--- /dev/null
+++ b/tools/test/p4src/Makefile
@@ -0,0 +1,23 @@
+all: default.json empty.json ecmp.json wcmp.json
+
+default.json: default.p4
+	p4c-bm2-ss --p4v 14 -o p4c-out/default.json \
+	--p4runtime-file p4c-out/default.p4info --p4runtime-format text \
+	default.p4
+
+empty.json: empty.p4
+	p4c-bm2-ss --p4v 14 -o p4c-out/empty.json \
+	--p4runtime-file p4c-out/empty.p4info --p4runtime-format text \
+	empty.p4
+
+ecmp.json: ecmp.p4
+	p4c-bmv2 --json p4c-out/ecmp.json \
+	ecmp.p4
+
+wcmp.json: wcmp.p4
+	p4c-bmv2 --json p4c-out/wcmp.json \
+	wcmp.p4
+
+clean:
+	rm -rf p4c-out/*.json
+	rm -rf p4c-out/*.p4info
\ No newline at end of file
diff --git a/tools/test/p4src/p4c-out/.gitignore b/tools/test/p4src/p4c-out/.gitignore
new file mode 100644
index 0000000..f02ec97
--- /dev/null
+++ b/tools/test/p4src/p4c-out/.gitignore
@@ -0,0 +1,2 @@
+*.json
+*.p4info
\ No newline at end of file