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