#!/usr/bin/python """ Multiple ovsdb OVS!! We scale up by creating multiple ovsdb instances, each of which is shared by several OVS switches The shell may also be shared among switch instances, which causes switch.cmd() and switch.popen() to be delegated to the ovsdb instance. """ from mininet.net import Mininet from mininet.node import Node, OVSSwitch from mininet.node import OVSBridge from mininet.link import Link, OVSIntf from mininet.topo import LinearTopo, SingleSwitchTopo from mininet.topolib import TreeTopo from mininet.log import setLogLevel, info from mininet.cli import CLI from mininet.clean import Cleanup, sh from itertools import groupby from operator import attrgetter class OVSDB( Node ): "Namespace for an OVSDB instance" privateDirs = [ '/etc/openvswitch', '/var/run/openvswitch', '/var/log/openvswitch' ] # Control network ipBase = '172.123.123.0/24' cnet = None nat = None @classmethod def startControlNet( cls ): "Start control net if necessary and return it" cnet = cls.cnet if not cnet: info( '### Starting control network\n' ) cnet = Mininet( ipBase=cls.ipBase ) cswitch = cnet.addSwitch( 'ovsbr0', cls=OVSBridge ) # Add NAT - note this can conflict with data network NAT info( '### Adding NAT for control and data networks' ' (use --nat flush=0 for data network)\n' ) cls.cnet = cnet cls.nat = cnet.addNAT( 'ovsdbnat0') cnet.start() info( '### Control network started\n' ) return cnet def stopControlNet( self ): info( '\n### Stopping control network\n' ) cls = self.__class__ cls.cnet.stop() info( '### Control network stopped\n' ) def addSwitch( self, switch ): "Add a switch to our namespace" # Attach first switch to cswitch! self.switches.append( switch ) def delSwitch( self, switch ): "Delete a switch from our namespace, and terminate if none left" self.switches.remove( switch ) if not self.switches: self.stopOVS() ovsdbCount = 0 def startOVS( self ): "Start new OVS instance" self.cmd( 'ovsdb-tool create /etc/openvswitch/conf.db' ) self.cmd( 'ovsdb-server /etc/openvswitch/conf.db' ' -vfile:emer -vfile:err -vfile:info' ' --remote=punix:/var/run/openvswitch/db.sock ' ' --log-file=/var/log/openvswitch/ovsdb-server.log' ' --pidfile=/var/run/openvswitch/ovsdb-server-mn.pid' ' --no-chdir' ' --detach' ) self.cmd( 'ovs-vswitchd unix:/var/run/openvswitch/db.sock' ' -vfile:emer -vfile:err -vfile:info' ' --mlockall --log-file=/var/log/openvswitch/ovs-vswitchd.log' ' --pidfile=/var/run/openvswitch/ovs-vswitchd-mn.pid' ' --no-chdir' ' --detach' ) def stopOVS( self ): self.cmd( 'kill', '`cat /var/run/openvswitch/ovs-vswitchd-mn.pid`', '`cat /var/run/openvswitch/ovsdb-server-mn.pid`' ) self.cmd( 'wait' ) self.__class__.ovsdbCount -= 1 if self.__class__.ovsdbCount <= 0: self.stopControlNet() @classmethod def cleanUpOVS( cls ): "Clean up leftover ovsdb-server/ovs-vswitchd processes" info( '*** Shutting down extra ovsdb-server/ovs-vswitchd processes\n' ) sh( 'pkill -f mn.pid' ) def self( self, *args, **kwargs ): "A fake constructor that sets params and returns self" self.params = kwargs return self def __init__( self, **kwargs ): cls = self.__class__ cls.ovsdbCount += 1 cnet = self.startControlNet() # Create a new ovsdb namespace self.switches = [] name = 'ovsdb%d' % cls.ovsdbCount kwargs.update( inNamespace=True ) kwargs.setdefault( 'privateDirs', self.privateDirs ) super( OVSDB, self ).__init__( name, **kwargs ) ovsdb = cnet.addHost( name, cls=self.self, **kwargs ) link = cnet.addLink( ovsdb, cnet.switches[ 0 ] ) cnet.switches[ 0 ].attach( link.intf2 ) ovsdb.configDefault() ovsdb.setDefaultRoute( 'via %s' % self.nat.intfs[ 0 ].IP() ) ovsdb.startOVS() # Install cleanup callback Cleanup.addCleanupCallback( OVSDB.cleanUpOVS ) class OVSSwitchNS( OVSSwitch ): "OVS Switch in shared OVSNS namespace" isSetup = False @classmethod def batchStartup( cls, switches ): result = [] for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb') ): switchGroup = list( switchGroup ) info( '(%s)' % ovsdb ) result += OVSSwitch.batchStartup( switchGroup, run=ovsdb.cmd ) return result @classmethod def batchShutdown( cls, switches ): result = [] for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb') ): switchGroup = list( switchGroup ) info( '(%s)' % ovsdb ) for switch in switches: if switch.pid == ovsdb.pid: switch.pid = None switch.shell = None result += OVSSwitch.batchShutdown( switchGroup, run=ovsdb.cmd ) for switch in switchGroup: switch.ovsdbFree() return result # OVSDB allocation groupSize = 64 switchCount = 0 lastOvsdb = None @classmethod def ovsdbAlloc( cls, switch ): "Allocate (possibly new) OVSDB instance for switch" if cls.switchCount % switch.groupSize == 0: cls.lastOvsdb = OVSDB() cls.switchCount += 1 cls.lastOvsdb.addSwitch( switch ) return cls.lastOvsdb def ovsdbFree( self ): "Deallocate OVSDB instance for switch" self.ovsdb.delSwitch( self ) def startShell( self, *args, **kwargs ): "Start shell in shared OVSDB namespace" ovsdb = self.ovsdbAlloc( self ) kwargs.update( mnopts='-da %d ' % ovsdb.pid ) self.ns = [ 'net' ] self.ovsdb = ovsdb self._waiting = False if self.privateShell: super( OVSSwitchNS, self ).startShell( *args, **kwargs ) else: # Delegate methods and initialize local vars attrs = ( 'cmd', 'cmdPrint', 'sendCmd', 'waitOutput', 'monitor', 'write', 'read', 'pid', 'shell', 'stdout',) for attr in attrs: setattr( self, attr, getattr( ovsdb, attr ) ) self.defaultIntf().updateIP() @property def waiting( self ): "Optionally delegated to ovsdb" return self._waiting if self.privateShell else self.ovsdb.waiting @waiting.setter def waiting( self, value ): "Optionally delegated to ovsdb (read only!)" if self.privateShell: _waiting = value def start( self, controllers ): "Update controller IP addresses if necessary" for controller in controllers: if controller.IP() == '127.0.0.1' and not controller.intfs: controller.intfs[ 0 ] = self.ovsdb.nat.intfs[ 0 ] super( OVSSwitchNS, self ).start( controllers ) def stop( self, *args, **kwargs ): "Stop and free OVSDB namespace if necessary" self.ovsdbFree() def terminate( self, *args, **kwargs ): if self.privateShell: super( OVSSwitchNS, self ).terminate( *args, **kwargs ) else: self.pid = None self.shell= None def defaultIntf( self ): return self.ovsdb.defaultIntf() def __init__( self, *args, **kwargs ): """n: number of OVS instances per OVSDB shell: run private shell/bash process? (False) If shell is shared/not private, cmd() and popen() are delegated to the OVSDB instance, which is different than regular OVSSwitch semantics!!""" self.groupSize = kwargs.pop( 'n', self.groupSize ) self.privateShell = kwargs.pop( 'shell', False ) super( OVSSwitchNS, self ).__init__( *args, **kwargs ) class OVSLinkNS( Link ): "OVSLink that supports OVSSwitchNS" def __init__( self, node1, node2, **kwargs ): "See Link.__init__() for options" self.isPatchLink = False if ( isinstance( node1, OVSSwitch ) and isinstance( node2, OVSSwitch ) and getattr( node1, 'ovsdb', None ) == getattr( node2, 'ovsdb', None ) ): self.isPatchLink = True kwargs.update( cls1=OVSIntf, cls2=OVSIntf ) Link.__init__( self, node1, node2, **kwargs ) switches = { 'ovsns': OVSSwitchNS, 'ovsm': OVSSwitchNS } links = { 'ovs': OVSLinkNS } def test(): "Test OVSNS switch" setLogLevel( 'info' ) topo = TreeTopo( depth=4, fanout=2 ) net = Mininet( topo=topo, switch=OVSSwitchNS ) # Add connectivity to controller which is on LAN or in root NS # net.addNAT().configDefault() net.start() CLI( net ) net.stop() if __name__ == '__main__': test()