kelvin-onlab | d9e23de | 2015-08-06 10:34:44 -0700 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | """ |
| 4 | Multiple ovsdb OVS!! |
| 5 | |
| 6 | We scale up by creating multiple ovsdb instances, |
| 7 | each of which is shared by several OVS switches |
| 8 | |
| 9 | The shell may also be shared among switch instances, |
| 10 | which causes switch.cmd() and switch.popen() to be |
| 11 | delegated to the ovsdb instance. |
| 12 | |
| 13 | """ |
| 14 | |
| 15 | from mininet.net import Mininet |
| 16 | from mininet.node import Node, OVSSwitch |
| 17 | from mininet.node import OVSBridge |
| 18 | from mininet.link import Link, OVSIntf |
| 19 | from mininet.topo import LinearTopo, SingleSwitchTopo |
| 20 | from mininet.topolib import TreeTopo |
| 21 | from mininet.log import setLogLevel, info |
| 22 | from mininet.cli import CLI |
| 23 | from mininet.clean import Cleanup, sh |
| 24 | |
| 25 | from itertools import groupby |
| 26 | from operator import attrgetter |
| 27 | |
| 28 | class OVSDB( Node ): |
| 29 | "Namespace for an OVSDB instance" |
| 30 | |
| 31 | privateDirs = [ '/etc/openvswitch', |
| 32 | '/var/run/openvswitch', |
| 33 | '/var/log/openvswitch' ] |
| 34 | |
| 35 | # Control network |
| 36 | ipBase = '172.123.123.0/24' |
| 37 | cnet = None |
| 38 | nat = None |
| 39 | |
| 40 | @classmethod |
| 41 | def startControlNet( cls ): |
| 42 | "Start control net if necessary and return it" |
| 43 | cnet = cls.cnet |
| 44 | if not cnet: |
| 45 | info( '### Starting control network\n' ) |
| 46 | cnet = Mininet( ipBase=cls.ipBase ) |
| 47 | cswitch = cnet.addSwitch( 'ovsbr0', cls=OVSBridge ) |
| 48 | # Add NAT - note this can conflict with data network NAT |
| 49 | info( '### Adding NAT for control and data networks' |
| 50 | ' (use --nat flush=0 for data network)\n' ) |
| 51 | cls.cnet = cnet |
| 52 | cls.nat = cnet.addNAT( 'ovsdbnat0') |
| 53 | cnet.start() |
| 54 | info( '### Control network started\n' ) |
| 55 | return cnet |
| 56 | |
| 57 | def stopControlNet( self ): |
| 58 | info( '\n### Stopping control network\n' ) |
| 59 | cls = self.__class__ |
| 60 | cls.cnet.stop() |
| 61 | info( '### Control network stopped\n' ) |
| 62 | |
| 63 | def addSwitch( self, switch ): |
| 64 | "Add a switch to our namespace" |
| 65 | # Attach first switch to cswitch! |
| 66 | self.switches.append( switch ) |
| 67 | |
| 68 | def delSwitch( self, switch ): |
| 69 | "Delete a switch from our namespace, and terminate if none left" |
| 70 | self.switches.remove( switch ) |
| 71 | if not self.switches: |
| 72 | self.stopOVS() |
| 73 | |
| 74 | ovsdbCount = 0 |
| 75 | |
| 76 | def startOVS( self ): |
| 77 | "Start new OVS instance" |
| 78 | self.cmd( 'ovsdb-tool create /etc/openvswitch/conf.db' ) |
| 79 | self.cmd( 'ovsdb-server /etc/openvswitch/conf.db' |
| 80 | ' -vfile:emer -vfile:err -vfile:info' |
| 81 | ' --remote=punix:/var/run/openvswitch/db.sock ' |
| 82 | ' --log-file=/var/log/openvswitch/ovsdb-server.log' |
| 83 | ' --pidfile=/var/run/openvswitch/ovsdb-server-mn.pid' |
| 84 | ' --no-chdir' |
| 85 | ' --detach' ) |
| 86 | |
| 87 | self.cmd( 'ovs-vswitchd unix:/var/run/openvswitch/db.sock' |
| 88 | ' -vfile:emer -vfile:err -vfile:info' |
| 89 | ' --mlockall --log-file=/var/log/openvswitch/ovs-vswitchd.log' |
| 90 | ' --pidfile=/var/run/openvswitch/ovs-vswitchd-mn.pid' |
| 91 | ' --no-chdir' |
| 92 | ' --detach' ) |
| 93 | |
| 94 | def stopOVS( self ): |
| 95 | self.cmd( 'kill', |
| 96 | '`cat /var/run/openvswitch/ovs-vswitchd-mn.pid`', |
| 97 | '`cat /var/run/openvswitch/ovsdb-server-mn.pid`' ) |
| 98 | self.cmd( 'wait' ) |
| 99 | self.__class__.ovsdbCount -= 1 |
| 100 | if self.__class__.ovsdbCount <= 0: |
| 101 | self.stopControlNet() |
| 102 | |
| 103 | @classmethod |
| 104 | def cleanUpOVS( cls ): |
| 105 | "Clean up leftover ovsdb-server/ovs-vswitchd processes" |
| 106 | info( '*** Shutting down extra ovsdb-server/ovs-vswitchd processes\n' ) |
| 107 | sh( 'pkill -f mn.pid' ) |
| 108 | |
| 109 | def self( self, *args, **kwargs ): |
| 110 | "A fake constructor that sets params and returns self" |
| 111 | self.params = kwargs |
| 112 | return self |
| 113 | |
| 114 | def __init__( self, **kwargs ): |
| 115 | cls = self.__class__ |
| 116 | cls.ovsdbCount += 1 |
| 117 | cnet = self.startControlNet() |
| 118 | # Create a new ovsdb namespace |
| 119 | self.switches = [] |
| 120 | name = 'ovsdb%d' % cls.ovsdbCount |
| 121 | kwargs.update( inNamespace=True ) |
| 122 | kwargs.setdefault( 'privateDirs', self.privateDirs ) |
| 123 | super( OVSDB, self ).__init__( name, **kwargs ) |
| 124 | ovsdb = cnet.addHost( name, cls=self.self, **kwargs ) |
| 125 | link = cnet.addLink( ovsdb, cnet.switches[ 0 ] ) |
| 126 | cnet.switches[ 0 ].attach( link.intf2 ) |
| 127 | ovsdb.configDefault() |
| 128 | ovsdb.setDefaultRoute( 'via %s' % self.nat.intfs[ 0 ].IP() ) |
| 129 | ovsdb.startOVS() |
| 130 | |
| 131 | |
| 132 | # Install cleanup callback |
| 133 | Cleanup.addCleanupCallback( OVSDB.cleanUpOVS ) |
| 134 | |
| 135 | |
| 136 | class OVSSwitchNS( OVSSwitch ): |
| 137 | "OVS Switch in shared OVSNS namespace" |
| 138 | |
| 139 | isSetup = False |
| 140 | |
| 141 | @classmethod |
| 142 | def batchStartup( cls, switches ): |
| 143 | result = [] |
| 144 | for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb') ): |
| 145 | switchGroup = list( switchGroup ) |
| 146 | info( '(%s)' % ovsdb ) |
| 147 | result += OVSSwitch.batchStartup( switchGroup, run=ovsdb.cmd ) |
| 148 | return result |
| 149 | |
| 150 | @classmethod |
| 151 | def batchShutdown( cls, switches ): |
| 152 | result = [] |
| 153 | for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb') ): |
| 154 | switchGroup = list( switchGroup ) |
| 155 | info( '(%s)' % ovsdb ) |
| 156 | for switch in switches: |
| 157 | if switch.pid == ovsdb.pid: |
| 158 | switch.pid = None |
| 159 | switch.shell = None |
| 160 | result += OVSSwitch.batchShutdown( switchGroup, run=ovsdb.cmd ) |
| 161 | for switch in switchGroup: |
| 162 | switch.ovsdbFree() |
| 163 | return result |
| 164 | |
| 165 | # OVSDB allocation |
| 166 | groupSize = 64 |
| 167 | switchCount = 0 |
| 168 | lastOvsdb = None |
| 169 | |
| 170 | @classmethod |
| 171 | def ovsdbAlloc( cls, switch ): |
| 172 | "Allocate (possibly new) OVSDB instance for switch" |
| 173 | if cls.switchCount % switch.groupSize == 0: |
| 174 | cls.lastOvsdb = OVSDB() |
| 175 | cls.switchCount += 1 |
| 176 | cls.lastOvsdb.addSwitch( switch ) |
| 177 | return cls.lastOvsdb |
| 178 | |
| 179 | def ovsdbFree( self ): |
| 180 | "Deallocate OVSDB instance for switch" |
| 181 | self.ovsdb.delSwitch( self ) |
| 182 | |
| 183 | def startShell( self, *args, **kwargs ): |
| 184 | "Start shell in shared OVSDB namespace" |
| 185 | ovsdb = self.ovsdbAlloc( self ) |
| 186 | kwargs.update( mnopts='-da %d ' % ovsdb.pid ) |
| 187 | self.ns = [ 'net' ] |
| 188 | self.ovsdb = ovsdb |
| 189 | self._waiting = False |
| 190 | if self.privateShell: |
| 191 | super( OVSSwitchNS, self ).startShell( *args, **kwargs ) |
| 192 | else: |
| 193 | # Delegate methods and initialize local vars |
| 194 | attrs = ( 'cmd', 'cmdPrint', 'sendCmd', 'waitOutput', |
| 195 | 'monitor', 'write', 'read', |
| 196 | 'pid', 'shell', 'stdout',) |
| 197 | for attr in attrs: |
| 198 | setattr( self, attr, getattr( ovsdb, attr ) ) |
| 199 | self.defaultIntf().updateIP() |
| 200 | |
| 201 | @property |
| 202 | def waiting( self ): |
| 203 | "Optionally delegated to ovsdb" |
| 204 | return self._waiting if self.privateShell else self.ovsdb.waiting |
| 205 | |
| 206 | @waiting.setter |
| 207 | def waiting( self, value ): |
| 208 | "Optionally delegated to ovsdb (read only!)" |
| 209 | if self.privateShell: |
| 210 | _waiting = value |
| 211 | |
| 212 | def start( self, controllers ): |
| 213 | "Update controller IP addresses if necessary" |
| 214 | for controller in controllers: |
| 215 | if controller.IP() == '127.0.0.1' and not controller.intfs: |
| 216 | controller.intfs[ 0 ] = self.ovsdb.nat.intfs[ 0 ] |
| 217 | super( OVSSwitchNS, self ).start( controllers ) |
| 218 | |
| 219 | def stop( self, *args, **kwargs ): |
| 220 | "Stop and free OVSDB namespace if necessary" |
| 221 | self.ovsdbFree() |
| 222 | |
| 223 | def terminate( self, *args, **kwargs ): |
| 224 | if self.privateShell: |
| 225 | super( OVSSwitchNS, self ).terminate( *args, **kwargs ) |
| 226 | else: |
| 227 | self.pid = None |
| 228 | self.shell= None |
| 229 | |
| 230 | def defaultIntf( self ): |
| 231 | return self.ovsdb.defaultIntf() |
| 232 | |
| 233 | def __init__( self, *args, **kwargs ): |
| 234 | """n: number of OVS instances per OVSDB |
| 235 | shell: run private shell/bash process? (False) |
| 236 | If shell is shared/not private, cmd() and popen() are |
| 237 | delegated to the OVSDB instance, which is different than |
| 238 | regular OVSSwitch semantics!!""" |
| 239 | self.groupSize = kwargs.pop( 'n', self.groupSize ) |
| 240 | self.privateShell = kwargs.pop( 'shell', False ) |
| 241 | super( OVSSwitchNS, self ).__init__( *args, **kwargs ) |
| 242 | |
| 243 | class OVSLinkNS( Link ): |
| 244 | "OVSLink that supports OVSSwitchNS" |
| 245 | |
| 246 | def __init__( self, node1, node2, **kwargs ): |
| 247 | "See Link.__init__() for options" |
| 248 | self.isPatchLink = False |
| 249 | if ( isinstance( node1, OVSSwitch ) and |
| 250 | isinstance( node2, OVSSwitch ) and |
| 251 | getattr( node1, 'ovsdb', None ) == |
| 252 | getattr( node2, 'ovsdb', None ) ): |
| 253 | self.isPatchLink = True |
| 254 | kwargs.update( cls1=OVSIntf, cls2=OVSIntf ) |
| 255 | Link.__init__( self, node1, node2, **kwargs ) |
| 256 | |
| 257 | switches = { 'ovsns': OVSSwitchNS, 'ovsm': OVSSwitchNS } |
| 258 | |
| 259 | links = { 'ovs': OVSLinkNS } |
| 260 | |
| 261 | def test(): |
| 262 | "Test OVSNS switch" |
| 263 | setLogLevel( 'info' ) |
| 264 | topo = TreeTopo( depth=4, fanout=2 ) |
| 265 | net = Mininet( topo=topo, switch=OVSSwitchNS ) |
| 266 | # Add connectivity to controller which is on LAN or in root NS |
| 267 | # net.addNAT().configDefault() |
| 268 | net.start() |
| 269 | CLI( net ) |
| 270 | net.stop() |
| 271 | |
| 272 | |
| 273 | if __name__ == '__main__': |
| 274 | test() |