Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ce07884f4 | |||
| be53ac31d6 | |||
| 50bfbd0566 | |||
| a4d8049661 | |||
| da4c53b3c9 | |||
| 1b96ff3539 | |||
| 93d04123c6 | |||
| 22655cf9fd | |||
| fe82d2c04a | |||
| 0534932bf3 |
Executable
+275
@@ -0,0 +1,275 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user