blob: b5147fe082489eeb6eccc46b3e03cd043056d0db [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
fahadae06b3b2015-02-17 14:16:41 -0800267 info( '*** Adding taps and bring them up...\n' )
268 setupInts( getTaps() )
269
Brian O'Connoreb27c452014-12-07 02:43:58 -0800270 info( '*** Starting linc OE...\n' )
271 output = quietRun( '%s/rel/linc/bin/linc start' % lincDir, shell=True )
272 if output:
273 error( '***ERROR: LINC-OE: %s' % output + '\n' )
274 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
275 return False
276
277 info( '*** Waiting for linc-oe to start...\n' )
278 waitStarted( net )
279
280 info( '*** Adding cross-connect (tap) interfaces to packet switches...\n' )
281 for link in net.links:
282 if isinstance( link, OpticalLink ):
283 if link.annotations[ 'optical.type' ] == 'cross-connect':
284 for intf in [ link.intf1, link.intf2 ]:
285 if not isinstance( intf, OpticalIntf ):
286 intfList = [ intf.link.intf1, intf.link.intf2 ]
287 intfList.remove( intf )
288 intf2 = intfList[ 0 ]
289 intf.node.attach( findTap( intf2.node, intf2.node.ports[ intf2 ] ) )
290
Brian O'Connor025fb442014-12-08 20:02:15 -0800291 info( '*** Press ENTER to push Topology.json to onos...\n' )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800292 raw_input() # FIXME... we should eventually remove this
293 info( '*** Pushing Topology.json to ONOS\n' )
Marc De Leenheer68063942014-12-15 15:54:23 -0800294 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 -0800295 # successful output contains the two characters '{}'
296 # if there is more output than this, there is an issue
297 if output.strip( '{}' ):
298 warn( '***WARNING: Could not push topology file to ONOS: %s' % output )
299
300def waitStarted( net, timeout=None ):
301 "wait until all tap interfaces are available"
302 tapCount = 0
303 time = 0
304 for link in net.links:
305 if isinstance( link, OpticalLink ):
306 if link.annotations[ 'optical.type' ] == 'cross-connect':
307 tapCount += 1
308
309 while True:
310 if str( tapCount ) == quietRun( 'ip addr | grep tap | wc -l', shell=True ).strip( '\n' ):
311 return True
312 if timeout:
313 if time >= timeout:
314 error( '***ERROR: Linc OE did not start within %s seconds' % timeout )
315 return False
316 time += .5
317 sleep( .5 )
318
319def stopOE():
320 "stop the optical emulator"
321 info( '*** Stopping linc OE...\n' )
322 lincDir = findDir( 'linc-oe' )
323 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
324
325def findDir( directory ):
326 "finds and returns the path of any directory in the user's home directory"
327 user = findUser()
328 homeDir = '/home/' + user
329 Dir = quietRun( 'find %s -maxdepth 1 -name %s -type d' % ( homeDir, directory ) ).strip( '\n' )
330 DirList = Dir.split( '\n' )
331 if not Dir:
332 return None
333 elif len( DirList ) > 1 :
334 warn( '***WARNING: Found multiple instances of %s; using %s\n'
335 % ( directory, DirList[ 0 ] ) )
336 return DirList[ 0 ]
337 else:
338 return Dir
339
340def findUser():
341 "Try to return logged-in (usually non-root) user"
342 try:
343 # If we're running sudo
344 return os.environ[ 'SUDO_USER' ]
345 except:
346 try:
347 # Logged-in user (if we have a tty)
348 return quietRun( 'who am i' ).split()[ 0 ]
349 except:
350 # Give up and return effective user
351 return quietRun( 'whoami' )
352
353
354def findTap( node, port, path=None ):
355 '''utility function to parse through a sys.config
356 file to find tap interfaces for a switch'''
357 switch=False
358 portLine = ''
359 intfLines = []
360
361 if path is None:
362 lincDir = findDir( 'linc-oe' )
363 if not lincDir:
364 error( '***ERROR: Could not find linc-oe in users home directory\n' )
365 return None
366 path = '%s/rel/linc/releases/1.0/sys.config' % lincDir
367
368 with open( path ) as f:
369 for line in f:
370 if 'tap' in line:
371 intfLines.append( line )
372 if node.dpid in line.translate( None, ':' ):
373 switch=True
374 continue
375 if switch:
376 if 'switch' in line:
377 switch = False
378 if 'port_no,%s}' % port in line:
379 portLine = line
380 break
381
382 if portLine:
383 m = re.search( 'port,\d+', portLine )
384 port = m.group( 0 ).split( ',' )[ 1 ]
385 else:
386 error( '***ERROR: Could not find any ports in sys.config\n' )
387 return
388
389 for intfLine in intfLines:
390 if 'port,%s' % port in intfLine:
391 return re.findall( 'tap\d+', intfLine )[ 0 ]
392
fahadae06b3b2015-02-17 14:16:41 -0800393def getTaps(path = None):
394 '''
395 return list of all the tops in sys.config
396 '''
397 if path is None:
398 lincDir = findDir( 'linc-oe' )
399 if not lincDir:
400 error( '***ERROR: Could not find linc-oe in users home directory\n' )
401 return None
402 path = '%s/rel/linc/releases/1.0/sys.config' % lincDir
403 fd = open( path, 'r', 0 )
404 sys_data = fd.read()
405 taps = re.findall( 'tap\d+', sys_data )
406 fd.close()
407 return taps
408
409def setupInts(intfs):
410 '''
411 add taps and bring them up.
412 '''
413 for i in intfs:
414 quietRun( 'ip tuntap add dev %s mode tap' %i )
415 quietRun( 'ip link set dev %s up' % i )
416 info( '*** Intf %s set\n' % i )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800417
418class MininetOE( Mininet ):
419 "Mininet with Linc-OE support (starts and stops linc-oe)"
420
421 def start( self ):
422 Mininet.start( self )
423 startOE( self )
424
425 def stop( self ):
426 Mininet.stop( self )
427 stopOE()
428
429 def addControllers( self, controllers ):
430 i = 0
431 for ctrl in controllers:
432 self.addController( RemoteController( 'c%d' % i, ip=ctrl ) )
433
434
435class OpticalTestTopo( Topo ):
436
437 def build( self ):
438 opticalAnn = { 'optical.waves': 80, 'optical.type': "WDM", 'durable': True }
439 switchAnn = { 'bandwidth': 100000, 'optical.type': 'cross-connect', 'durable': True }
440 h1 = self.addHost( 'h1' )
441 h2 = self.addHost( 'h2' )
442 s1 = self.addSwitch( 's1' )
443 s2 = self.addSwitch( 's2' )
444 O4 = self.addSwitch( 'O4', cls=OpticalSwitch )
445 O5 = self.addSwitch( 'O5', cls=OpticalSwitch )
446 O6 = self.addSwitch( 'O6', cls=OpticalSwitch )
447 self.addLink( O4, O5, cls=OpticalLink, annotations=opticalAnn )
448 self.addLink( O5, O6, cls=OpticalLink, annotations=opticalAnn )
449 self.addLink( s1, O4, cls=OpticalLink, annotations=switchAnn )
450 self.addLink( s2, O6, cls=OpticalLink, annotations=switchAnn )
451 self.addLink( h1, s1 )
452 self.addLink( h2, s2 )
453
454if __name__ == '__main__':
455 import sys
456 if len( sys.argv ) >= 2:
457 controllers = sys.argv[1:]
458 else:
459 print 'Usage: ./opticalUtils.py (<Controller IP>)+'
460 print 'Using localhost...\n'
461 controllers = [ '127.0.0.1' ]
462
463 setLogLevel( 'info' )
464 net = MininetOE( topo=OpticalTestTopo(), controller=None, autoSetMacs=True )
465 net.addControllers( controllers )
466 net.start()
467 CLI( net )
468 net.stop()