blob: e15fe72206e6530d4fafb21b11fe34524a9f515f [file] [log] [blame]
kelvin-onlabd9e23de2015-08-06 10:34:44 -07001#!/usr/bin/python
2
3"""
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -07004Copyright 2015 Open Networking Foundation ( ONF )
Jeremy Ronquillob27ce4c2017-07-17 12:41:28 -07005
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
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070013 ( at your option ) any later version.
Jeremy Ronquillob27ce4c2017-07-17 12:41:28 -070014
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"""
Jeremy Ronquillob27ce4c2017-07-17 12:41:28 -070023"""
kelvin-onlabd9e23de2015-08-06 10:34:44 -070024Multiple ovsdb OVS!!
25
26We scale up by creating multiple ovsdb instances,
27each of which is shared by several OVS switches
28
29The shell may also be shared among switch instances,
30which causes switch.cmd() and switch.popen() to be
31delegated to the ovsdb instance.
32
33"""
kelvin-onlabd9e23de2015-08-06 10:34:44 -070034from mininet.net import Mininet
35from mininet.node import Node, OVSSwitch
36from mininet.node import OVSBridge
37from mininet.link import Link, OVSIntf
38from mininet.topo import LinearTopo, SingleSwitchTopo
39from mininet.topolib import TreeTopo
40from mininet.log import setLogLevel, info
41from mininet.cli import CLI
42from mininet.clean import Cleanup, sh
43
44from itertools import groupby
45from operator import attrgetter
46
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070047
kelvin-onlabd9e23de2015-08-06 10:34:44 -070048class OVSDB( Node ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070049
kelvin-onlabd9e23de2015-08-06 10:34:44 -070050 "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
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070073 cls.nat = cnet.addNAT( 'ovsdbnat0' )
kelvin-onlabd9e23de2015-08-06 10:34:44 -070074 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 ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700158
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700159 "OVS Switch in shared OVSNS namespace"
160
161 isSetup = False
162
163 @classmethod
164 def batchStartup( cls, switches ):
165 result = []
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700166 for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb' ) ):
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700167 switchGroup = list( switchGroup )
168 info( '(%s)' % ovsdb )
169 result += OVSSwitch.batchStartup( switchGroup, run=ovsdb.cmd )
170 return result
171
172 @classmethod
173 def batchShutdown( cls, switches ):
174 result = []
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700175 for ovsdb, switchGroup in groupby( switches, attrgetter( 'ovsdb' ) ):
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700176 switchGroup = list( switchGroup )
177 info( '(%s)' % ovsdb )
178 for switch in switches:
179 if switch.pid == ovsdb.pid:
180 switch.pid = None
181 switch.shell = None
182 result += OVSSwitch.batchShutdown( switchGroup, run=ovsdb.cmd )
183 for switch in switchGroup:
184 switch.ovsdbFree()
185 return result
186
187 # OVSDB allocation
188 groupSize = 64
189 switchCount = 0
190 lastOvsdb = None
191
192 @classmethod
193 def ovsdbAlloc( cls, switch ):
194 "Allocate (possibly new) OVSDB instance for switch"
195 if cls.switchCount % switch.groupSize == 0:
196 cls.lastOvsdb = OVSDB()
197 cls.switchCount += 1
198 cls.lastOvsdb.addSwitch( switch )
199 return cls.lastOvsdb
200
201 def ovsdbFree( self ):
202 "Deallocate OVSDB instance for switch"
203 self.ovsdb.delSwitch( self )
204
205 def startShell( self, *args, **kwargs ):
206 "Start shell in shared OVSDB namespace"
207 ovsdb = self.ovsdbAlloc( self )
208 kwargs.update( mnopts='-da %d ' % ovsdb.pid )
209 self.ns = [ 'net' ]
210 self.ovsdb = ovsdb
211 self._waiting = False
212 if self.privateShell:
213 super( OVSSwitchNS, self ).startShell( *args, **kwargs )
214 else:
215 # Delegate methods and initialize local vars
216 attrs = ( 'cmd', 'cmdPrint', 'sendCmd', 'waitOutput',
217 'monitor', 'write', 'read',
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700218 'pid', 'shell', 'stdout', )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700219 for attr in attrs:
220 setattr( self, attr, getattr( ovsdb, attr ) )
221 self.defaultIntf().updateIP()
222
223 @property
224 def waiting( self ):
225 "Optionally delegated to ovsdb"
226 return self._waiting if self.privateShell else self.ovsdb.waiting
227
228 @waiting.setter
229 def waiting( self, value ):
230 "Optionally delegated to ovsdb (read only!)"
231 if self.privateShell:
232 _waiting = value
233
234 def start( self, controllers ):
235 "Update controller IP addresses if necessary"
236 for controller in controllers:
237 if controller.IP() == '127.0.0.1' and not controller.intfs:
238 controller.intfs[ 0 ] = self.ovsdb.nat.intfs[ 0 ]
239 super( OVSSwitchNS, self ).start( controllers )
240
241 def stop( self, *args, **kwargs ):
242 "Stop and free OVSDB namespace if necessary"
243 self.ovsdbFree()
244
245 def terminate( self, *args, **kwargs ):
246 if self.privateShell:
247 super( OVSSwitchNS, self ).terminate( *args, **kwargs )
248 else:
249 self.pid = None
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700250 self.shell = None
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700251
252 def defaultIntf( self ):
253 return self.ovsdb.defaultIntf()
254
255 def __init__( self, *args, **kwargs ):
256 """n: number of OVS instances per OVSDB
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700257 shell: run private shell/bash process? ( False )
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700258 If shell is shared/not private, cmd() and popen() are
259 delegated to the OVSDB instance, which is different than
260 regular OVSSwitch semantics!!"""
261 self.groupSize = kwargs.pop( 'n', self.groupSize )
262 self.privateShell = kwargs.pop( 'shell', False )
263 super( OVSSwitchNS, self ).__init__( *args, **kwargs )
264
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700265
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700266class OVSLinkNS( Link ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700267
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700268 "OVSLink that supports OVSSwitchNS"
269
270 def __init__( self, node1, node2, **kwargs ):
271 "See Link.__init__() for options"
272 self.isPatchLink = False
273 if ( isinstance( node1, OVSSwitch ) and
274 isinstance( node2, OVSSwitch ) and
275 getattr( node1, 'ovsdb', None ) ==
276 getattr( node2, 'ovsdb', None ) ):
277 self.isPatchLink = True
278 kwargs.update( cls1=OVSIntf, cls2=OVSIntf )
279 Link.__init__( self, node1, node2, **kwargs )
280
281switches = { 'ovsns': OVSSwitchNS, 'ovsm': OVSSwitchNS }
282
283links = { 'ovs': OVSLinkNS }
284
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700285
kelvin-onlabd9e23de2015-08-06 10:34:44 -0700286def test():
287 "Test OVSNS switch"
288 setLogLevel( 'info' )
289 topo = TreeTopo( depth=4, fanout=2 )
290 net = Mininet( topo=topo, switch=OVSSwitchNS )
291 # Add connectivity to controller which is on LAN or in root NS
292 # net.addNAT().configDefault()
293 net.start()
294 CLI( net )
295 net.stop()
296
297
298if __name__ == '__main__':
299 test()