blob: 0545013c4f15d87473d367352148193d5987a84b [file] [log] [blame]
#!/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()