blob: 521c59a407e963b7e55daf2cb2e9e9228222c355 [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' )
223 os.environ[ 'ONOS_ROOT' ] = onosDir
224 if not onosDir:
225 error( 'Please set ONOS_ROOT environment variable!\n' )
226 return False
227
228 info( '*** Writing Topology.json file\n' )
229 with open( 'Topology.json', 'w' ) as outfile:
230 json.dump( opticalJSON, outfile, indent=4, separators=(',', ': ') )
231
232 info( '*** Converting Topology.json to linc-oe format (TopoConfig.json) file\n' )
233 output = quietRun( '%s/tools/test/bin/onos-oecfg ./Topology.json > TopoConfig.json' % onosDir, shell=True )
234 if output:
235 error( '***ERROR: Error creating topology file: %s ' % output + '\n' )
236 return False
237
238 info( '*** Creating sys.config...\n' )
239 configGen = findDir( 'LINC-config-generator' )
240 if not configGen:
241 error( "***ERROR: Could not find LINC-config-generator in user's home directory\n" )
242 return False
243 output = quietRun( '%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
Marc De Leenheer68063942014-12-15 15:54:23 -0800244 % ( configGen, configGen, net.controllers[ 0 ].ip, net.controllers[ 0 ].port ), shell=True )
Brian O'Connoreb27c452014-12-07 02:43:58 -0800245 if output:
246 error( '***ERROR: Error creating sys.config file: %s\n' % output )
247 return False
248
Marc De Leenheer68063942014-12-15 15:54:23 -0800249 info ('*** Setting multiple controllers in sys.config...\n' )
250 searchStr = '{controllers,.*$'
251 ctrlStr = ''
252 for index in range(len(net.controllers)):
253 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
254 replaceStr = '{controllers,[%s]},' % ctrlStr[:-1] # Cut off last comma
255 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
256 output = quietRun( sedCmd, shell=True )
257
Brian O'Connoreb27c452014-12-07 02:43:58 -0800258 info( '*** Copying sys.config to linc-oe directory: ', output + '\n' )
259 lincDir = findDir( 'linc-oe' )
260 if not lincDir:
261 error( "***ERROR: Could not find linc-oe in user's home directory\n" )
262 return False
263 output = quietRun( 'cp -v sys.config %s/rel/linc/releases/1.0/' % lincDir, shell=True ).strip( '\n' )
264 info( output + '\n' )
265
266 info( '*** Starting linc OE...\n' )
267 output = quietRun( '%s/rel/linc/bin/linc start' % lincDir, shell=True )
268 if output:
269 error( '***ERROR: LINC-OE: %s' % output + '\n' )
270 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
271 return False
272
273 info( '*** Waiting for linc-oe to start...\n' )
274 waitStarted( net )
275
276 info( '*** Adding cross-connect (tap) interfaces to packet switches...\n' )
277 for link in net.links:
278 if isinstance( link, OpticalLink ):
279 if link.annotations[ 'optical.type' ] == 'cross-connect':
280 for intf in [ link.intf1, link.intf2 ]:
281 if not isinstance( intf, OpticalIntf ):
282 intfList = [ intf.link.intf1, intf.link.intf2 ]
283 intfList.remove( intf )
284 intf2 = intfList[ 0 ]
285 intf.node.attach( findTap( intf2.node, intf2.node.ports[ intf2 ] ) )
286
287 info( '*** Press any key to push Topology.json to onos...\n' )
288 raw_input() # FIXME... we should eventually remove this
289 info( '*** Pushing Topology.json to ONOS\n' )
Marc De Leenheer68063942014-12-15 15:54:23 -0800290 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 -0800291 # successful output contains the two characters '{}'
292 # if there is more output than this, there is an issue
293 if output.strip( '{}' ):
294 warn( '***WARNING: Could not push topology file to ONOS: %s' % output )
295
296def waitStarted( net, timeout=None ):
297 "wait until all tap interfaces are available"
298 tapCount = 0
299 time = 0
300 for link in net.links:
301 if isinstance( link, OpticalLink ):
302 if link.annotations[ 'optical.type' ] == 'cross-connect':
303 tapCount += 1
304
305 while True:
306 if str( tapCount ) == quietRun( 'ip addr | grep tap | wc -l', shell=True ).strip( '\n' ):
307 return True
308 if timeout:
309 if time >= timeout:
310 error( '***ERROR: Linc OE did not start within %s seconds' % timeout )
311 return False
312 time += .5
313 sleep( .5 )
314
315def stopOE():
316 "stop the optical emulator"
317 info( '*** Stopping linc OE...\n' )
318 lincDir = findDir( 'linc-oe' )
319 quietRun( '%s/rel/linc/bin/linc stop' % lincDir, shell=True )
320
321def findDir( directory ):
322 "finds and returns the path of any directory in the user's home directory"
323 user = findUser()
324 homeDir = '/home/' + user
325 Dir = quietRun( 'find %s -maxdepth 1 -name %s -type d' % ( homeDir, directory ) ).strip( '\n' )
326 DirList = Dir.split( '\n' )
327 if not Dir:
328 return None
329 elif len( DirList ) > 1 :
330 warn( '***WARNING: Found multiple instances of %s; using %s\n'
331 % ( directory, DirList[ 0 ] ) )
332 return DirList[ 0 ]
333 else:
334 return Dir
335
336def findUser():
337 "Try to return logged-in (usually non-root) user"
338 try:
339 # If we're running sudo
340 return os.environ[ 'SUDO_USER' ]
341 except:
342 try:
343 # Logged-in user (if we have a tty)
344 return quietRun( 'who am i' ).split()[ 0 ]
345 except:
346 # Give up and return effective user
347 return quietRun( 'whoami' )
348
349
350def findTap( node, port, path=None ):
351 '''utility function to parse through a sys.config
352 file to find tap interfaces for a switch'''
353 switch=False
354 portLine = ''
355 intfLines = []
356
357 if path is None:
358 lincDir = findDir( 'linc-oe' )
359 if not lincDir:
360 error( '***ERROR: Could not find linc-oe in users home directory\n' )
361 return None
362 path = '%s/rel/linc/releases/1.0/sys.config' % lincDir
363
364 with open( path ) as f:
365 for line in f:
366 if 'tap' in line:
367 intfLines.append( line )
368 if node.dpid in line.translate( None, ':' ):
369 switch=True
370 continue
371 if switch:
372 if 'switch' in line:
373 switch = False
374 if 'port_no,%s}' % port in line:
375 portLine = line
376 break
377
378 if portLine:
379 m = re.search( 'port,\d+', portLine )
380 port = m.group( 0 ).split( ',' )[ 1 ]
381 else:
382 error( '***ERROR: Could not find any ports in sys.config\n' )
383 return
384
385 for intfLine in intfLines:
386 if 'port,%s' % port in intfLine:
387 return re.findall( 'tap\d+', intfLine )[ 0 ]
388
389
390class MininetOE( Mininet ):
391 "Mininet with Linc-OE support (starts and stops linc-oe)"
392
393 def start( self ):
394 Mininet.start( self )
395 startOE( self )
396
397 def stop( self ):
398 Mininet.stop( self )
399 stopOE()
400
401 def addControllers( self, controllers ):
402 i = 0
403 for ctrl in controllers:
404 self.addController( RemoteController( 'c%d' % i, ip=ctrl ) )
405
406
407class OpticalTestTopo( Topo ):
408
409 def build( self ):
410 opticalAnn = { 'optical.waves': 80, 'optical.type': "WDM", 'durable': True }
411 switchAnn = { 'bandwidth': 100000, 'optical.type': 'cross-connect', 'durable': True }
412 h1 = self.addHost( 'h1' )
413 h2 = self.addHost( 'h2' )
414 s1 = self.addSwitch( 's1' )
415 s2 = self.addSwitch( 's2' )
416 O4 = self.addSwitch( 'O4', cls=OpticalSwitch )
417 O5 = self.addSwitch( 'O5', cls=OpticalSwitch )
418 O6 = self.addSwitch( 'O6', cls=OpticalSwitch )
419 self.addLink( O4, O5, cls=OpticalLink, annotations=opticalAnn )
420 self.addLink( O5, O6, cls=OpticalLink, annotations=opticalAnn )
421 self.addLink( s1, O4, cls=OpticalLink, annotations=switchAnn )
422 self.addLink( s2, O6, cls=OpticalLink, annotations=switchAnn )
423 self.addLink( h1, s1 )
424 self.addLink( h2, s2 )
425
426if __name__ == '__main__':
427 import sys
428 if len( sys.argv ) >= 2:
429 controllers = sys.argv[1:]
430 else:
431 print 'Usage: ./opticalUtils.py (<Controller IP>)+'
432 print 'Using localhost...\n'
433 controllers = [ '127.0.0.1' ]
434
435 setLogLevel( 'info' )
436 net = MininetOE( topo=OpticalTestTopo(), controller=None, autoSetMacs=True )
437 net.addControllers( controllers )
438 net.start()
439 CLI( net )
440 net.stop()