Added configurable mininet script to create topology for multi-instance ONOS (ONOS-1308).

Change-Id: I9e8e1cf009786cdb6930e2d9af6971948d71bbbb
diff --git a/cluster-mgmt/mininet/start_topo.py b/cluster-mgmt/mininet/start_topo.py
new file mode 100755
index 0000000..ccabeda
--- /dev/null
+++ b/cluster-mgmt/mininet/start_topo.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+
+"""
+start_topo.py : Mininet topology builder for multiple controller.
+
+This script reads config file and creates a Mininet topology.
+
+Usage:
+
+    $ sudo -E start_topo.py [filename]
+    
+    filename: Config filename to specify topology.
+              If omitted, "topo.`hostname`.py" or "topo.py" in current
+              directory will be used.
+"""
+
+import platform
+import time
+import os.path
+import getpass
+from sys import argv
+from time import sleep
+
+from mininet.node import Controller, OVSSwitch, RemoteController
+from mininet.net import Mininet
+from mininet.cli import CLI
+from mininet.topo import Topo
+from mininet.log import setLogLevel, info, warn
+
+usage = """
+Usage: Create Mininet topology.
+  $ sudo -E %s [filename]
+      filename: config file (topo.`hostname`.py by default)
+""" % os.path.basename( argv[0] )
+
+# Mininet must be run by root user
+if ( getpass.getuser() != 'root' ):
+    print usage
+    exit( 0 )
+
+argc = len( argv )
+if ( argc > 2 ):
+    print usage
+    exit( 0 )
+
+if ( argc == 2 and ( argv[ 1 ] == "-h" or argv[ 1 ] == "--help" ) ):
+    print usage
+    exit( 0 )
+
+if ( argc == 2 ):
+    topofile = argv[1]
+else: 
+    hostname = platform.node()
+    default_topo = 'topo.%s.py' % hostname
+    fallback_topo = 'topo.py'
+    topofile = default_topo if os.path.exists(default_topo) else fallback_topo
+
+if not os.path.exists( topofile ):
+    print( 'Config file %s not found' % default_topo )
+    exit( 1 )
+
+execfile( topofile )
+conf = createTopo()
+print conf
+
+class ConfigTopo( Topo ):
+    "Topology created from config."
+  
+    def __init__( self, *args, **kwargs ):
+        "Create custom topo."
+  
+        Topo.__init__( self, *args, **kwargs )
+        
+        # Add hosts and switches
+        nmap = {}
+        for s, prop in conf[ 'switches' ].iteritems():
+            # make sure encoding is ASCII (if not, virtual eth creation fails)
+            s = s.strip().encode( 'ascii' )
+            dpid = prop[ 'dpid' ].strip().encode( 'ascii' )
+            nmap[ s ] = self.addSwitch( s, dpid=dpid )
+        
+        for h, prop in conf[ 'hosts' ].iteritems():
+            h = h.strip().encode( 'ascii' )
+            nmap[ h ] =self.addHost( h )
+        
+        # Add links
+        for l in conf[ 'links' ]:
+            node1 = nmap[ l[ 'node1' ] ].strip().encode( 'ascii' )
+            node2 = nmap[ l[ 'node2' ] ].strip().encode( 'ascii' )
+            self.addLink( node1, node2 )
+
+class ClusterConnectedSwitch( OVSSwitch ):
+    "OVSSwitch connected to controller cluster."
+  
+    def start( self, controllers ):
+        # make sure controllers contains only a ControllerCluster
+        assert len( controllers ) == 1
+        ccluster = controllers[ 0 ]
+        assert type( ccluster ) == ControllerCluster
+        
+        controller_list = ccluster.clist( self.name )
+        # TODO: manage order of controllers to control mastership
+        OVSSwitch.start( self, controllers=controller_list )
+
+class ControllerCluster( Controller ):
+    "Cluster of controllers. Relationship between controllers and switches is defined by config file"
+    
+    def __init__( self, name, **params ):
+        self.sw_clist_map = {}
+        self.cmap = {}
+        
+        Controller.__init__( self, name, **params )
+        
+        for cname, addr in conf[ 'controllers' ].iteritems():
+            cname = cname.strip().encode( 'ascii' )
+            addr = addr.strip().encode( 'ascii' )
+            ip, port = addr.split( ':' )
+            self.cmap[ cname ] = RemoteController( cname, ip=ip, port=int( port ) )
+        
+        for sw, params in conf[ 'switches' ].iteritems():
+            clist = []
+            for c in params[ 'controllers' ]:
+                assert not self.cmap[ c ] is None
+                clist.append( self.cmap[ c ] )
+            self.sw_clist_map[ sw ] = clist
+    
+    def start( self ):
+        # do nothing
+        return
+    
+    def stop( self ):
+        # do nothing
+        return
+    
+    def checkListening( self ):
+        for c in self.cmap.values():
+            c.checkListening()
+    
+    def clist( self, sw ):
+        return self.sw_clist_map[ sw ]
+
+if __name__ == '__main__':
+    setLogLevel( 'info' )
+    net = Mininet( topo=ConfigTopo(),
+                   controller=ControllerCluster,
+                   switch=ClusterConnectedSwitch )
+    net.start()
+    CLI( net )
+    net.stop()
\ No newline at end of file