blob: 9849069ebaa8a059a304bbd1d3e26dec01ce5623 [file] [log] [blame]
kelvin-onlabd9e23de2015-08-06 10:34:44 -07001#!/usr/bin/python
2
3"""
Jeremy Ronquillob27ce4c2017-07-17 12:41:28 -07004Copyright 2015 Open Networking Foundation (ONF)
5
6Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
7the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
8or 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-onlabd9e23de2015-08-06 10:34:44 -070025Multiple ovsdb OVS!!
26
27We scale up by creating multiple ovsdb instances,
28each of which is shared by several OVS switches
29
30The shell may also be shared among switch instances,
31which causes switch.cmd() and switch.popen() to be
32delegated to the ovsdb instance.
33
34"""
35
36from mininet.net import Mininet
37from mininet.node import Node, OVSSwitch
38from mininet.node import OVSBridge
39from mininet.link import Link, OVSIntf
40from mininet.topo import LinearTopo, SingleSwitchTopo
41from mininet.topolib import TreeTopo
42from mininet.log import setLogLevel, info
43from mininet.cli import CLI
44from mininet.clean import Cleanup, sh
45
46from itertools import groupby
47from operator import attrgetter
48
49class 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
154Cleanup.addCleanupCallback( OVSDB.cleanUpOVS )
155
156
157class 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
264class 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
278switches = { 'ovsns': OVSSwitchNS, 'ovsm': OVSSwitchNS }
279
280links = { 'ovs': OVSLinkNS }
281
282def 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
294if __name__ == '__main__':
295 test()