276 lines
9.1 KiB
Python
Executable File
276 lines
9.1 KiB
Python
Executable File
#!/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()
|