blob: b645c14d81d7782e2b385d61a8f3c8abf5ad985a [file] [log] [blame]
Brian O'Connoreb27c452014-12-07 02:43:58 -08001#!/usr/bin/python
2
3'''
4Notes:
5
6This file contains classes and methods useful for integrating LincOE with Mininet,
7such as startOE, stopOE, OpticalLink, and OpticalSwitch
8
9- $ONOS_ROOT ust be set
10- Need to run with sudo -E to preserve ONOS_ROOT env var
11- We assume LINC-Config-Generator is named LINC-Config-Generator
12- We also assume linc-oe is named linc-oe
13- LINC-config-generator and linc-oe must be subdirectories of the user's
14 home directory
15
16 TODO
17 -----------
18 - clean up files after runtime
19 - maybe save the old files in a separate directory?
20 - modify script to allow startOE to run before net.start()
21 - add ONOS as a controller in script
22
23 Usage:
24 ------------
25 - import OpticalLink and OpticalSwitch from this module
26 - import startOE and stopOE from this module
27 - create topology as you would a normal topology. when
28 to an optical switch with topo.addLink, always specify cls=OpticalLink
29 - when creating an optical switch, use cls=OpticalSwitch in topo.addSwitch
30 - for annotations on links and switches, a dictionary must be passed in as
31 the annotations argument
32 - startOE must be run AFTER net.start() with net as an argument.
33 - stopOE can be run at any time
34
35I created a separate function to start lincOE to avoid subclassing Mininet.
36In case anyone wants to write something that DOES subclass Mininet, I
37thought I would outline how:
38
39If we want an object that starts lincOE within the mininet class itself,
40we need to add another object to Mininet that contains all of the json object
41information for each switch. We would still subclass switch and link, but these
42classes would basically be dummy classes that store their own json information
43in the Mininet class object. We may also change the default switch class to add
44it's tap interfaces from lincOE during startup. The start() method for mininet would
45grab all of the information from these switches and links, write configuration files
46for lincOE using the json module, start lincOE, then run the start methodfor each
47switch. The new start() method for each switch would parse through the sys.config
48file that was created and find the tap interface it needs to connect to, similar
49to the findTap function that I currently use. After all of the controllers and
50switches have been started, the new Mininet start() method should also push the
51Topology configuration file to ONOS.
52
53'''
54
55import re
56import json
57import os
58from time import sleep
59
60from mininet.node import Switch, RemoteController
61from mininet.topo import Topo
62from mininet.util import quietRun
63from mininet.net import Mininet
64from mininet.log import setLogLevel, info, error, warn
65from mininet.link import Link, Intf
66from mininet.cli import CLI
67
68class OpticalSwitch( Switch ):
69
70 def __init__( self, name, dpid=None, allowed=True,
71 switchType='ROADM', annotations={}, **params ):
72 params[ 'inNamespace' ] = False
73 Switch.__init__( self, name, dpid=dpid, **params )
74 self.name = name
75 self.annotations = annotations
76 self.allowed = allowed
77 self.switchType = switchType
78 self.configDict = {} # dictionary that holds all of the JSON configuration data
79
80 def start( self, *opts, **params ):
81 '''Instead of starting a virtual switch, we build the JSON
82 dictionary for the emulated optical switch'''
83 self.configDict[ 'uri' ] = 'of:' + self.dpid
84 self.configDict[ 'annotations' ] = self.annotations
85 self.configDict[ 'annotations' ].setdefault( 'name', self.name )
86 self.configDict[ 'hw' ] = 'OE'
87 self.configDict[ 'mfr' ] = 'Linc'
88 self.configDict[ 'mac' ] = 'ffffffffffff' + self.dpid[-2] + self.dpid[-1]
89 self.configDict[ 'type' ] = self.switchType
90 self.configDict[ 'ports' ] = []
91 for port, intf in self.intfs.items():
92 if intf.name == 'lo':
93 continue
94 else:
95 self.configDict[ 'ports' ].append( intf.json() )
96
97
98 def json( self ):
99 "return json configuration dictionary for switch"
100 return self.configDict
101
102 def terminate( self ):
103 pass
104
105class OpticalLink( Link ):
106
107 def __init__( self, node1, node2, port1=None, port2=None, allowed=True,
108 intfName1=None, intfName2=None, linkType='OPTICAL',
109 annotations={}, speed1=0, speed2=0, **params ):
110 "Creates a dummy link without a virtual ethernet pair."
111 self.allowed = allowed
112 self.annotations = annotations
113 self.linkType = linkType
114 params1 = { 'speed': speed1 }
115 params2 = { 'speed': speed2 }
116
117 if isinstance( node1, OpticalSwitch ):
118 cls1 = OpticalIntf
119 else:
120 cls1 = Intf
121 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
122 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
123 intfName1 = 'lo'
124 if isinstance( node2, OpticalSwitch ):
125 cls2 = OpticalIntf
126 else:
127 cls2 = Intf
128 intfName2 = 'lo'
129 Link.__init__( self, node1, node2, port1=port1, port2=port2,
130 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
131 cls2=cls2, params1=params1, params2=params2 )
132
133
134 @classmethod
135 def makeIntfPair( _cls, intfName1, intfName2, *args, **kwargs ):
136 pass
137
138 def json( self ):
139 "build and return the json configuration dictionary for this link"
140 configData = {}
141 configData[ 'src' ] = ( 'of:' + self.intf1.node.dpid +
142 '/%s' % self.intf1.node.ports[ self.intf1 ] )
143 configData[ 'dst' ] = ( 'of:' + self.intf2.node.dpid +
144 '/%s' % self.intf2.node.ports[ self.intf2 ] )
145 configData[ 'type' ] = self.linkType
146 configData[ 'annotations' ] = self.annotations
147 return configData
148
149class OpticalIntf( Intf ):
150
151 def __init__( self, name=None, node=None, speed=0,
152 port=None, link=None, **params ):
153 self.node = node
154 self.speed = speed
155 self.port = port
156 self.link = link
157 self.name = name
158 node.addIntf( self, port=port )
159 self.params = params
160 self.ip = None
161
162 def json( self ):
163 "build and return the JSON information for this interface( not used right now )"
164 configDict = {}
165 configDict[ 'port' ] = self.port
166 configDict[ 'speed' ] = self.speed
167 configDict[ 'type' ] = 'FIBER'
168 return configDict
169
170 def config( self, *args, **kwargs ):
171 "dont configure a dummy interface"
172 pass
173
174def switchJSON( switch ):
175 "Returns the json configuration for a packet switch"
176 configDict = {}
177 configDict[ 'uri' ] = 'of:' + switch.dpid
178 configDict[ 'mac' ] = quietRun( 'cat /sys/class/net/%s/address' % switch.name ).strip( '\n' ).translate( None, ':' )
179 configDict[ 'hw' ] = 'PK' # FIXME what about OVS?
180 configDict[ 'mfr' ] = 'Linc' # FIXME what about OVS?
181 configDict[ 'type' ] = 'SWITCH' # FIXME what about OVS?
182 annotations = switch.params.get( 'annotations', {} )
183 annotations.setdefault( 'name', switch.name )
184 configDict[ 'annotations' ] = annotations
185 ports = []
186 for port, intf in switch.intfs.items():
187 if intf.name == 'lo':
188 continue
189 portDict = {}
190 portDict[ 'port' ] = port
191 portDict[ 'type' ] = 'FIBER' if isinstance( intf.link, OpticalLink ) else 'COPPER'
192 intfList = [ intf.link.intf1, intf.link.intf2 ]
193 intfList.remove( intf )
194 portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance( intf.link, OpticalLink ) else 0
195 ports.append( portDict )
196 configDict[ 'ports' ] = ports
197 return configDict
198
199
Marc De Leenheer68063942014-12-15 15:54:23 -0800200def startOE( net ):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800201 "Start the LINC optical emulator within a mininet instance"
202 opticalJSON = {}
203 linkConfig = []
204 devices = []
205
Brian O'Connoreb27c452014-12-07 02:43:58 -0800206 for switch in net.switches:
207 if isinstance( switch, OpticalSwitch ):
208 devices.append( switch.json() )
209 else:
210 devices.append( switchJSON( switch ) )
211 opticalJSON[ 'devices' ] = devices
212
213 for link in net.links:
214 if isinstance( link, OpticalLink ) :
215 linkConfig.append( link.json() )
216
217 opticalJSON[ 'links' ] = linkConfig
218
219 try:
220 onosDir = os.environ[ 'ONOS_ROOT' ]
221 except:
222 onosDir = findDir( 'onos' )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800223 if not onosDir:
224 error( 'Please set ONOS_ROOT environment variable!\n' )
225 return False
Brian O'Connor025fb442014-12-08 20:02:15 -0800226 else:
227 os.environ[ 'ONOS_ROOT' ] = onosDir
Brian O'Connoreb27c452014-12-07 02:43:58 -0800228
229 info( '*** Writing Topology.json file\n' )
230 with open( 'Topology.json', 'w' ) as outfile:
231 json.dump( opticalJSON, outfile, indent=4, separators=(',', ': ') )
232
233 info( '*** Converting Topology.json to linc-oe format (TopoConfig.json) file\n' )
234 output = quietRun( '%s/tools/test/bin/onos-oecfg ./Topology.json > TopoConfig.json' % onosDir, shell=True )
235 if output:
236 error( '***ERROR: Error creating topology file: %s ' % output + '\n' )
237 return False
238
239 info( '*** Creating sys.config...\n' )
240 configGen = findDir( 'LINC-config-generator' )
241 if not configGen:
242 error( "***ERROR: Could not find LINC-config-generator in user's home directory\n" )
243 return False
244 output = quietRun( '%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
Marc De Leenheer68063942014-12-15 15:54:23 -0800245 % ( configGen, configGen, net.controllers[ 0 ].ip, net.controllers[ 0 ].port ), shell=True )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800246 if output:
247 error( '***ERROR: Error creating sys.config file: %s\n' % output )
248 return False
249
Marc De Leenheer68063942014-12-15 15:54:23 -0800250 info ('*** Setting multiple controllers in sys.config...\n' )
Marc De Leenheercb9c0ba2015-01-22 14:50:04 -0800251 searchStr = '\[{"Switch.*$'
Marc De Leenheer68063942014-12-15 15:54:23 -0800252 ctrlStr = ''
253 for index in range(len(net.controllers)):
254 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
Marc De Leenheercb9c0ba2015-01-22 14:50:04 -0800255 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
Marc De Leenheer68063942014-12-15 15:54:23 -0800256 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
257 output = quietRun( sedCmd, shell=True )
258
Brian O'Connoreb27c452014-12-07 02:43:58 -0800259 info( '*** Copying sys.config to linc-oe directory: ', output + '\n' )
260 lincDir = findDir( 'linc-oe' )
261 if not lincDir:
262 error( "***ERROR: Could not find linc-oe in user's home directory\n" )
263 return False
264 output = quietRun( 'cp -v sys.config %s/rel/linc/releases/1.0/' % lincDir, shell=True ).strip( '\n' )
265 info( output + '\n' )
266
267 info( '*** Starting linc OE...\n' )
268 output = quietRun( '%s/rel/linc/bin/linc start' % lincDir, shell=True )
269 if output:
270 error( '***ERROR: LINC-OE: %s' % output + '\n' )
271 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
272 return False
273
274 info( '*** Waiting for linc-oe to start...\n' )
275 waitStarted( net )
276
277 info( '*** Adding cross-connect (tap) interfaces to packet switches...\n' )
278 for link in net.links:
279 if isinstance( link, OpticalLink ):
280 if link.annotations[ 'optical.type' ] == 'cross-connect':
281 for intf in [ link.intf1, link.intf2 ]:
282 if not isinstance( intf, OpticalIntf ):
283 intfList = [ intf.link.intf1, intf.link.intf2 ]
284 intfList.remove( intf )
285 intf2 = intfList[ 0 ]
286 intf.node.attach( findTap( intf2.node, intf2.node.ports[ intf2 ] ) )
287
Brian O'Connor025fb442014-12-08 20:02:15 -0800288 info( '*** Press ENTER to push Topology.json to onos...\n' )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800289 raw_input() # FIXME... we should eventually remove this
290 info( '*** Pushing Topology.json to ONOS\n' )
Marc De Leenheer68063942014-12-15 15:54:23 -0800291 output = quietRun( '%s/tools/test/bin/onos-topo-cfg %s Topology.json' % ( onosDir, net.controllers[ 0 ].ip ), shell=True )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800292 # successful output contains the two characters '{}'
293 # if there is more output than this, there is an issue
294 if output.strip( '{}' ):
295 warn( '***WARNING: Could not push topology file to ONOS: %s' % output )
296
297def waitStarted( net, timeout=None ):
298 "wait until all tap interfaces are available"
299 tapCount = 0
300 time = 0
301 for link in net.links:
302 if isinstance( link, OpticalLink ):
303 if link.annotations[ 'optical.type' ] == 'cross-connect':
304 tapCount += 1
305
306 while True:
307 if str( tapCount ) == quietRun( 'ip addr | grep tap | wc -l', shell=True ).strip( '\n' ):
308 return True
309 if timeout:
310 if time >= timeout:
311 error( '***ERROR: Linc OE did not start within %s seconds' % timeout )
312 return False
313 time += .5
314 sleep( .5 )
315
316def stopOE():
317 "stop the optical emulator"
318 info( '*** Stopping linc OE...\n' )
319 lincDir = findDir( 'linc-oe' )
320 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
321
322def findDir( directory ):
323 "finds and returns the path of any directory in the user's home directory"
324 user = findUser()
325 homeDir = '/home/' + user
326 Dir = quietRun( 'find %s -maxdepth 1 -name %s -type d' % ( homeDir, directory ) ).strip( '\n' )
327 DirList = Dir.split( '\n' )
328 if not Dir:
329 return None
330 elif len( DirList ) > 1 :
331 warn( '***WARNING: Found multiple instances of %s; using %s\n'
332 % ( directory, DirList[ 0 ] ) )
333 return DirList[ 0 ]
334 else:
335 return Dir
336
337def findUser():
338 "Try to return logged-in (usually non-root) user"
339 try:
340 # If we're running sudo
341 return os.environ[ 'SUDO_USER' ]
342 except:
343 try:
344 # Logged-in user (if we have a tty)
345 return quietRun( 'who am i' ).split()[ 0 ]
346 except:
347 # Give up and return effective user
348 return quietRun( 'whoami' )
349
350
351def findTap( node, port, path=None ):
352 '''utility function to parse through a sys.config
353 file to find tap interfaces for a switch'''
354 switch=False
355 portLine = ''
356 intfLines = []
357
358 if path is None:
359 lincDir = findDir( 'linc-oe' )
360 if not lincDir:
361 error( '***ERROR: Could not find linc-oe in users home directory\n' )
362 return None
363 path = '%s/rel/linc/releases/1.0/sys.config' % lincDir
364
365 with open( path ) as f:
366 for line in f:
367 if 'tap' in line:
368 intfLines.append( line )
369 if node.dpid in line.translate( None, ':' ):
370 switch=True
371 continue
372 if switch:
373 if 'switch' in line:
374 switch = False
375 if 'port_no,%s}' % port in line:
376 portLine = line
377 break
378
379 if portLine:
380 m = re.search( 'port,\d+', portLine )
381 port = m.group( 0 ).split( ',' )[ 1 ]
382 else:
383 error( '***ERROR: Could not find any ports in sys.config\n' )
384 return
385
386 for intfLine in intfLines:
387 if 'port,%s' % port in intfLine:
388 return re.findall( 'tap\d+', intfLine )[ 0 ]
389
390
391class MininetOE( Mininet ):
392 "Mininet with Linc-OE support (starts and stops linc-oe)"
393
394 def start( self ):
395 Mininet.start( self )
396 startOE( self )
397
398 def stop( self ):
399 Mininet.stop( self )
400 stopOE()
401
402 def addControllers( self, controllers ):
403 i = 0
404 for ctrl in controllers:
405 self.addController( RemoteController( 'c%d' % i, ip=ctrl ) )
406
407
408class OpticalTestTopo( Topo ):
409
410 def build( self ):
411 opticalAnn = { 'optical.waves': 80, 'optical.type': "WDM", 'durable': True }
412 switchAnn = { 'bandwidth': 100000, 'optical.type': 'cross-connect', 'durable': True }
413 h1 = self.addHost( 'h1' )
414 h2 = self.addHost( 'h2' )
415 s1 = self.addSwitch( 's1' )
416 s2 = self.addSwitch( 's2' )
417 O4 = self.addSwitch( 'O4', cls=OpticalSwitch )
418 O5 = self.addSwitch( 'O5', cls=OpticalSwitch )
419 O6 = self.addSwitch( 'O6', cls=OpticalSwitch )
420 self.addLink( O4, O5, cls=OpticalLink, annotations=opticalAnn )
421 self.addLink( O5, O6, cls=OpticalLink, annotations=opticalAnn )
422 self.addLink( s1, O4, cls=OpticalLink, annotations=switchAnn )
423 self.addLink( s2, O6, cls=OpticalLink, annotations=switchAnn )
424 self.addLink( h1, s1 )
425 self.addLink( h2, s2 )
426
427if __name__ == '__main__':
428 import sys
429 if len( sys.argv ) >= 2:
430 controllers = sys.argv[1:]
431 else:
432 print 'Usage: ./opticalUtils.py (<Controller IP>)+'
433 print 'Using localhost...\n'
434 controllers = [ '127.0.0.1' ]
435
436 setLogLevel( 'info' )
437 net = MininetOE( topo=OpticalTestTopo(), controller=None, autoSetMacs=True )
438 net.addControllers( controllers )
439 net.start()
440 CLI( net )
441 net.stop()