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