blob: 0545013c4f15d87473d367352148193d5987a84b [file] [log] [blame]
kelvin-onlabd9e23de2015-08-06 10:34:44 -07001#!/usr/bin/python
2
3"""
4Multiple ovsdb OVS!!
5
6We scale up by creating multiple ovsdb instances,
7each of which is shared by several OVS switches
8
9The shell may also be shared among switch instances,
10which causes switch.cmd() and switch.popen() to be
11delegated to the ovsdb instance.
12
13"""
14
15from mininet.net import Mininet
16from mininet.node import Node, OVSSwitch
17from mininet.node import OVSBridge
18from mininet.link import Link, OVSIntf
19from mininet.topo import LinearTopo, SingleSwitchTopo
20from mininet.topolib import TreeTopo
21from mininet.log import setLogLevel, info
22from mininet.cli import CLI
23from mininet.clean import Cleanup, sh
24
25from itertools import groupby
26from operator import attrgetter
27
28class 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
133Cleanup.addCleanupCallback( OVSDB.cleanUpOVS )
134
135
136class 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
243class 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
257switches = { 'ovsns': OVSSwitchNS, 'ovsm': OVSSwitchNS }
258
259links = { 'ovs': OVSLinkNS }
260
261def 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
273if __name__ == '__main__':
274 test()