blob: a1ae834b6e58c67590e206a40c435cebbcf79700 [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,
Marc De Leenheer6ff97642015-07-08 19:21:16 +00007such as startOE, stopOE, LINCLink, and OpticalSwitch
Brian O'Connoreb27c452014-12-07 02:43:58 -08008
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 ------------
Marc De Leenheer6ff97642015-07-08 19:21:16 +000025 - import LINCLink and OpticalSwitch from this module
Brian O'Connoreb27c452014-12-07 02:43:58 -080026 - import startOE and stopOE from this module
27 - create topology as you would a normal topology. when
Marc De Leenheer6ff97642015-07-08 19:21:16 +000028 to an optical switch with topo.addLink, always specify cls=LINCLink
Brian O'Connoreb27c452014-12-07 02:43:58 -080029 - 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'''
Marc De Leenheer6ff97642015-07-08 19:21:16 +000054import sys
Brian O'Connoreb27c452014-12-07 02:43:58 -080055import re
56import json
57import os
58from time import sleep
Marc De Leenheer6ff97642015-07-08 19:21:16 +000059import urllib2
Brian O'Connoreb27c452014-12-07 02:43:58 -080060
61from mininet.node import Switch, RemoteController
62from mininet.topo import Topo
63from mininet.util import quietRun
64from mininet.net import Mininet
65from mininet.log import setLogLevel, info, error, warn
66from mininet.link import Link, Intf
67from mininet.cli import CLI
68
Marc De Leenheer6ff97642015-07-08 19:21:16 +000069# Sleep time and timeout values in seconds
70SLEEP_TIME = 2
71TIMEOUT = 60
Marc De Leenheer16f857b2015-05-05 20:50:24 -070072
Marc De Leenheer6ff97642015-07-08 19:21:16 +000073class OpticalSwitch(Switch):
74 """
75 For now, same as Switch class.
76 """
77 pass
78
79class OpticalIntf(Intf):
80 """
81 For now,same as Intf class.
82 """
83 pass
84
85class OpticalLink(Link):
86 """
87 For now, same as Link.
88 """
89 pass
90
91class LINCSwitch(OpticalSwitch):
92 """
93 LINCSwitch class
94 """
95 # FIXME:Sometimes LINC doesn't remove pipes and on restart increase the pipe
96 # number from erlang.pipe.1.* to erlang.pipe.2.*, so should read and write
97 # from latest pipe files. For now we are removing all the pipes before
98 # starting LINC.
99 ### User Name ###
100 user = os.getlogin()
101 ### pipes ###
102 readPipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.r".format(user)
103 writePipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.w".format(user)
104 ### sys.config path ###
105 sysConfig = "/home/{}/linc-oe/rel/linc/releases/1.0/sys.config".format(user)
106 ### method, mapping dpid to LINC switchId ###
107 @staticmethod
108 def dpids_to_ids(sysConfig):
109 '''
110 return the dict containing switch dpids as key and LINC switch id as values
111 '''
112 dpids_to_ids = {}
113 fd = None
114 try:
115 with open(sysConfig, 'r', 0) as fd:
116 switch_id = 1
117 for line in fd:
118 dpid = re.search(r'([0-9A-Fa-f]{2}[:-]){7}([0-9A-Fa-f]{2})+', line, re.I)
119 if dpid:
120 dpids_to_ids[dpid.group().replace(':', '')] = switch_id
121 switch_id += 1
122 return dpids_to_ids
123 except:
124 print "Error working with {}\nError: {}\n".format(sysConfig, sys.exc_info())
125 fd.close()
126 return None
127 ### dict of containing dpids as key and corresponding LINC switchId as values ###
128 dpidsToLINCSwitchId = dpids_to_ids.__func__(sysConfig)
129 @staticmethod
130 def findDir(directory, userName):
131 "finds and returns the path of any directory in the user's home directory"
132 homeDir = '/home/' + userName
133 Dir = quietRun('find %s -maxdepth 1 -name %s -type d' % (homeDir, directory)).strip('\n')
134 DirList = Dir.split('\n')
135 if not Dir:
136 return None
137 elif len(DirList) > 1 :
138 warn('***WARNING: Found multiple instances of %s; using %s\n'
139 % (directory, DirList[ 0 ]))
140 return DirList[ 0 ]
141 else:
142 return Dir
143 ### ONOS Directory ###
144 try:
145 onosDir = os.environ[ 'ONOS_ROOT' ]
146 except:
147 onosDir = findDir('onos', user)
148 if not onosDir:
149 error('Please set ONOS_ROOT environment variable!\n')
150 else:
151 os.environ[ 'ONOS_ROOT' ] = onosDir
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700152 ### REST USER/PASS ###
153 try:
154 restUser = os.environ[ 'ONOS_WEB_USER' ]
155 restPass = os.environ[ 'ONOS_WEB_PASS' ]
156 except:
157 error('***WARNING: $ONOS_WEB_USER and $ONOS_WEB_PASS aren\'t set!\n')
158 error('***WARNING: Setting (probably) sane WEB user/pass values\n')
159 restUser = 'onos'
160 restPass = 'rocks'
161 os.environ[ 'ONOS_WEB_USER' ] = restUser
162 os.environ[ 'ONOS_WEB_PASS' ] = restPass
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000163 ### LINC-directory
164 lincDir = findDir.__func__('linc-oe', user)
165 if not lincDir:
166 error("***ERROR: Could not find linc-oe in user's home directory\n")
167 ### LINC config generator directory###
168 configGen = findDir.__func__('LINC-config-generator', user)
169 if not configGen:
170 error("***ERROR: Could not find LINC-config-generator in user's home directory\n")
171 # list of all the controllers
172 controllers = None
173 def __init__(self, name, dpid=None, allowed=True,
174 switchType='ROADM', topo=None, annotations={}, controller=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800175 params[ 'inNamespace' ] = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000176 Switch.__init__(self, name, dpid=dpid, **params)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800177 self.name = name
178 self.annotations = annotations
179 self.allowed = allowed
180 self.switchType = switchType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000181 self.configDict = {} # dictionary that holds all of the JSON configuration data
182 self.crossConnects = []
183 self.deletedCrossConnects = []
184 self.controller = controller
185 self.lincId = self._get_linc_id() # use to communicate with LINC
186 self.lincStarted = False
Brian O'Connoreb27c452014-12-07 02:43:58 -0800187
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000188 def start(self, *opts, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800189 '''Instead of starting a virtual switch, we build the JSON
190 dictionary for the emulated optical switch'''
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000191 # TODO:Once LINC has the ability to spawn network element dynamically
192 # we need to use this method to spawn new logical LINC switch rather then
193 # bulding JSON.
194 # if LINC is started then we can start and stop logical switches else create JSON
195 if self.lincStarted:
196 return self.start_oe()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800197 self.configDict[ 'uri' ] = 'of:' + self.dpid
198 self.configDict[ 'annotations' ] = self.annotations
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000199 self.configDict[ 'annotations' ].setdefault('name', self.name)
200 self.configDict[ 'hw' ] = 'LINC-OE'
Brian O'Connoreb27c452014-12-07 02:43:58 -0800201 self.configDict[ 'mfr' ] = 'Linc'
202 self.configDict[ 'mac' ] = 'ffffffffffff' + self.dpid[-2] + self.dpid[-1]
203 self.configDict[ 'type' ] = self.switchType
204 self.configDict[ 'ports' ] = []
205 for port, intf in self.intfs.items():
206 if intf.name == 'lo':
207 continue
208 else:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000209 self.configDict[ 'ports' ].append(intf.json())
210 self.lincStarted = True
fahad44e62c72015-03-04 14:55:35 -0800211
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000212 def stop(self, deleteIntfs=False):
213 '''
214 stop the existing switch
215 '''
216 # TODO:Add support for deleteIntf
217 self.stop_oe()
fahad44e62c72015-03-04 14:55:35 -0800218
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000219 def dpctl( self, *args ):
220 "Run dpctl command: ignore for now"
fahad44e62c72015-03-04 14:55:35 -0800221 pass
222
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000223 def write_to_cli(self, command):
224 '''
225 send command to LINC
226 '''
227 fd = None
228 try:
229 fd = open(self.writePipe, 'w', 0)
230 fd.write(command)
231 fd.close()
232 except:
233 print "Error working with {}\nError: {}\n".format(self.writePipe, sys.exc_info())
234 if fd:
235 fd.close()
Nikhil Cheerla5f8f8f02015-07-07 16:01:17 -0700236
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000237 def read_from_cli(self):
238 '''
239 read the output from the LINC CLI
240 '''
241 response = None
242 fd = None
243 try:
244 fd = open(self.readPipe, 'r', 0)
245 fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # for non-blocking read
246 # FIXME:Due to non-blocking read most for the time we read nothing
247 response = fd.read()
248 fd.close()
249 except :
250 # print "Error working with {}\nError: {}\n".format(self.readPipe, sys.exc_info())
251 if fd:
252 fd.close()
253 return response
254
255 def _get_linc_id(self):
256 '''
257 return the corresponding LINC switchId.
258 '''
259 return LINCSwitch.dpidsToLINCSwitchId.get(self.dpid)
260 #--------------------------------------------------------------------------
261 # LINC CLI commands
262 #--------------------------------------------------------------------------
263 def start_oe(self):
264 '''
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700265 existing LINC switch
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000266 '''
267 #starting Switch
268 cmd = "linc:start_switch({}).\r\n".format(self.lincId)
269 self.write_to_cli(cmd)
270 #hanlding taps interfaces related to the switch
271 crossConnectJSON = {}
272 linkConfig = []
273 for i in range(0,len(self.deletedCrossConnects)):
274 crossConnect = self.deletedCrossConnects.pop()
275 tap = None
276 if isinstance(crossConnect.intf1.node, LINCSwitch):
277 intf = crossConnect.intf2
278 tapPort = crossConnect.intf1.port
279 else:
280 intf = crossConnect.intf1
281 tapPort = crossConnect.intf2.port
282 tap = LINCSwitch.findTap(self, tapPort)
283 if tap:
284 LINCSwitch.setupInts([tap])
285 intf.node.attach(tap)
286 self.crossConnects.append(crossConnect)
287 linkConfig.append(crossConnect.json())
288 #Sending crossConnect info to the ONOS.
289 crossConnectJSON['links'] = linkConfig
290 with open("crossConnect.json", 'w') as fd:
291 json.dump(crossConnectJSON, fd, indent=4, separators=(',', ': '))
292 info('*** Pushing crossConnect.json to ONOS\n')
293 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s\
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700294 Topology.json network/configuration/' % (self.onosDir, self.controllers[ 0 ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000295
296 def stop_oe(self):
297 '''
298 stop the existing LINC switch
299 '''
300 cmd = "linc:stop_switch({}).\r\n".format(self.lincId)
301 self.write_to_cli(cmd)
302 #handling taps if any
303 for i in range(0, len(self.crossConnects)):
304 crossConnect = self.crossConnects.pop()
305 if isinstance(crossConnect.intf1.node, LINCSwitch):
306 intf = crossConnect.intf2
307 tapPort = crossConnect.intf1.port
308 else:
309 intf = crossConnect.intf1
310 tapPort = crossConnect.intf2.port
311 intf.node.detach(LINCSwitch.findTap(self, tapPort))
312 self.deletedCrossConnects.append(crossConnect)
313
314 def w_port_up(self, port):
315 '''
316 port_up
317 '''
318 cmd = "linc:port_up({},{}).\r\n".format(self.lincId, port)
319 self.write_to_cli(cmd)
320
321 def w_port_down(self, port):
322 '''
323 port_down
324 '''
325 cmd = "linc:port_down({},{}).\r\n".format(self.lincId, port)
326 self.write_to_cli(cmd)
327
328 # helper functions
329 @staticmethod
330 def switchJSON(switch):
331 "Returns the json configuration for a packet switch"
332 configDict = {}
333 configDict[ 'uri' ] = 'of:' + switch.dpid
334 configDict[ 'mac' ] = quietRun('cat /sys/class/net/%s/address' % switch.name).strip('\n').translate(None, ':')
335 configDict[ 'hw' ] = 'PK' # FIXME what about OVS?
336 configDict[ 'mfr' ] = 'Linc' # FIXME what about OVS?
337 configDict[ 'type' ] = 'SWITCH' # FIXME what about OVS?
338 annotations = switch.params.get('annotations', {})
339 annotations.setdefault('name', switch.name)
340 configDict[ 'annotations' ] = annotations
341 ports = []
342 for port, intf in switch.intfs.items():
343 if intf.name == 'lo':
344 continue
345 portDict = {}
346 portDict[ 'port' ] = port
347 portDict[ 'type' ] = 'FIBER' if isinstance(intf.link, LINCLink) else 'COPPER'
348 intfList = [ intf.link.intf1, intf.link.intf2 ]
349 intfList.remove(intf)
350 portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance(intf.link, LINCLink) else 0
351 ports.append(portDict)
352 configDict[ 'ports' ] = ports
353 return configDict
354
355 @staticmethod
356 def bootOE(net):
357 "Start the LINC optical emulator within a mininet instance"
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700358 LINCSwitch.opticalJSON = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000359 linkConfig = []
360 devices = []
361 #setting up the controllers for LINCSwitch class
362 LINCSwitch.controllers = net.controllers
363
364 for switch in net.switches:
365 if isinstance(switch, OpticalSwitch):
366 devices.append(switch.json())
367 else:
368 devices.append(LINCSwitch.switchJSON(switch))
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700369 LINCSwitch.opticalJSON[ 'devices' ] = devices
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000370
371 for link in net.links:
372 if isinstance(link, LINCLink) :
373 linkConfig.append(link.json())
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700374 LINCSwitch.opticalJSON[ 'links' ] = linkConfig
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000375
376 info('*** Writing Topology.json file\n')
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700377 topoJSON = LINCSwitch.makeTopoJSON()
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000378 with open('Topology.json', 'w') as outfile:
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700379 json.dump(topoJSON, outfile, indent=4, separators=(',', ': '))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000380
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700381 info('*** Converting Topology.json to linc-oe format (TopoConfig.json) file (no oecfg) \n')
382
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700383 topoConfigJson = {}
384 dpIdToName = {}
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700385
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700386 topoConfigJson["switchConfig"] = LINCSwitch.getSwitchConfig(dpIdToName)
387 topoConfigJson["linkConfig"] = LINCSwitch.getLinkConfig(dpIdToName)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700388
389 #Writing to TopoConfig.json
390 with open( 'TopoConfig.json', 'w' ) as outfile:
391 json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000392
393 info('*** Creating sys.config...\n')
394 output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
395 % (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
396 if output:
397 error('***ERROR: Error creating sys.config file: %s\n' % output)
398 return False
399
400 info ('*** Setting multiple controllers in sys.config...\n')
401 searchStr = '\[{"Switch.*$'
402 ctrlStr = ''
403 for index in range(len(LINCSwitch.controllers)):
404 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
405 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
406 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
407 output = quietRun(sedCmd, shell=True)
408
409 info('*** Copying sys.config to linc-oe directory: ', output + '\n')
410 output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
411 info(output + '\n')
412
413 info('*** Adding taps and bringing them up...\n')
414 LINCSwitch.setupInts(LINCSwitch.getTaps())
415
416 info('*** removing pipes if any \n')
417 quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
418
419 info('*** Starting linc OE...\n')
420 output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
421 if output:
422 error('***ERROR: LINC-OE: %s' % output + '\n')
423 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
424 return False
425
426 info('*** Waiting for linc-oe to start...\n')
427 LINCSwitch.waitStarted(net)
428
429 info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
430 for link in net.links:
431 if isinstance(link, LINCLink):
432 if link.annotations[ 'optical.type' ] == 'cross-connect':
433 for intf in [ link.intf1, link.intf2 ]:
434 if not isinstance(intf, LINCIntf):
435 intfList = [ intf.link.intf1, intf.link.intf2 ]
436 intfList.remove(intf)
437 intf2 = intfList[ 0 ]
438 intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
439
440 info('*** Waiting for all devices to be available in ONOS...\n')
441 url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
442 time = 0
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700443 # Set up password authentication
444 pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700445 pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700446 handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
447 opener = urllib2.build_opener(handler)
448 opener.open(url)
449 urllib2.install_opener(opener)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000450 while True:
451 response = json.load(urllib2.urlopen(url))
452 devs = response.get('devices')
453
454 # Wait for all devices to be registered
455 if (len(devices) != len(devs)):
456 continue
457
458 # Wait for all devices to available
459 available = True
460 for d in devs:
461 available &= d['available']
462 if available:
463 break
464
465 if (time >= TIMEOUT):
466 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
467 break
468
469 time += SLEEP_TIME
470 sleep(SLEEP_TIME)
471
472 info('*** Pushing Topology.json to ONOS\n')
473 for index in range(len(LINCSwitch.controllers)):
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700474 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s Topology.json network/configuration/ &'\
475 % (LINCSwitch.onosDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000476 # successful output contains the two characters '{}'
477 # if there is more output than this, there is an issue
478 if output.strip('{}'):
479 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
480
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700481 #converts node ids to linc-oe format, with colons every two chars
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700482 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700483 def dpId(id):
484 nodeDpid = ""
485 id = id.split("/", 1)[0]
486 for i in range(3, len(id) - 1, 2):
487 nodeDpid += (id[i:(i + 2):]) + ":"
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700488 return nodeDpid[0:-1];
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700489
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700490 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700491 def makeTopoJSON():
492 """
493 Builds ONOS network conifg system compatible dicts to be written as Topology.json file.
494 """
495 topology = {}
496 links = {}
497 devices = {}
498 ports = {}
499
500 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
501 # build device entries - keyed on uri (DPID) and config key 'basic'
502 devDict = {}
503 devDict[ 'driver' ] = switch[ 'hw' ]
504 devDict[ 'mfr' ] = switch[ 'mfr' ]
505 devDict[ 'mac' ] = switch[ 'mac' ]
506 devDict[ 'type' ] = switch[ 'type' ]
507 devDict.update(switch[ 'annotations' ])
508
509 devSubj = switch[ 'uri' ]
510 devices[ devSubj ] = { 'basic': devDict }
511
512 # build port entries - keyed on "uri/port" and config key 'optical'
513 for port in switch[ 'ports' ]:
514 portSubj = devSubj + '/' + str(port[ 'port' ])
515 ports[ portSubj ] = { 'optical': port }
516
517 # build link entries - keyed on "uri/port-uri/port" and config key 'basic'
518 for link in LINCSwitch.opticalJSON[ 'links' ]:
519 linkDict = {}
520 linkDict[ 'type' ] = link[ 'type' ]
521 linkDict.update(link[ 'annotations' ])
522
523 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
524 links[ linkSubj ] = { 'basic': linkDict }
525
526 topology[ 'links' ] = links
527 topology[ 'devices' ] = devices
528 topology[ 'ports' ] = ports
529
530 return topology
531
532 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700533 def getSwitchConfig (dpIdToName):
534 switchConfig = [];
535 #Iterate through all switches and convert the ROADM switches to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700536 for switch in LINCSwitch.opticalJSON["devices"]:
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700537 if switch.get("type", "none") == "ROADM":
538 builtSwitch = {}
539
540 #set basic switch params based on annotations
541 builtSwitch["allowed"] = True;
542 builtSwitch["latitude"] = switch["annotations"].get("latitude", 0.0);
543 builtSwitch["longitude"] = switch["annotations"].get("longitude", 0.0);
544
545 #assumed that all switches have this entry
546 nodeId = switch["uri"]
547
548 #convert the nodeId to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700549 nodeDpid = LINCSwitch.dpId(nodeId);
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700550
551 builtSwitch["name"] = switch.get("name", "none");
552
553 #keep track of the name corresponding to each switch dpid
554 dpIdToName[nodeDpid] = builtSwitch["name"];
555
556 builtSwitch["nodeDpid"] = nodeDpid
557
558 #set switch params and type
559 builtSwitch["params"] = {};
560 builtSwitch["params"]["numregens"] = switch["annotations"].get("optical.regens", 0);
561 builtSwitch["type"] = "Roadm"
562
563 #append to list of switches
564 switchConfig.append(builtSwitch);
565 return switchConfig
566
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700567 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700568 def getLinkConfig (dpIdToName):
569 newLinkConfig = [];
570 #Iterate through all optical links and convert them to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700571 for link in LINCSwitch.opticalJSON["links"]:
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700572 if link.get("type", "none") == "OPTICAL":
573 builtLink = {}
574
575 #set basic link params for src and dst
576 builtLink["allowed"] = True;
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700577 builtLink["nodeDpid1"] = LINCSwitch.dpId(link["src"])
578 builtLink["nodeDpid2"] = LINCSwitch.dpId(link["dst"])
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700579
580 #set more params such as name/bandwidth/port/waves if they exist
581 params = {}
582 params["nodeName1"] = dpIdToName.get(builtLink["nodeDpid1"], "none")
583 params["nodeName2"] = dpIdToName.get(builtLink["nodeDpid2"], "none")
584
585 params["port1"] = int(link["src"].split("/")[1])
586 params["port2"] = int(link["dst"].split("/")[1])
587
588 if "bandwidth" in link["annotations"]:
589 params["bandwidth"] = link["annotations"]["bandwidth"]
590
591 if "optical.waves" in link["annotations"]:
592 params["numWaves"] = link["annotations"]["optical.waves"]
593
594 builtLink["params"] = params
595
596 #set type of link (WDM or pktOpt)
597 if link["annotations"].get("optical.type", "cross-connect") == "WDM":
598 builtLink["type"] = "wdmLink"
599 else:
600 builtLink["type"] = "pktOptLink"
601
602 newLinkConfig.append(builtLink);
603 return newLinkConfig
604
605
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000606 @staticmethod
607 def waitStarted(net, timeout=TIMEOUT):
608 "wait until all tap interfaces are available"
609 tapCount = 0
610 time = 0
611 for link in net.links:
612 if isinstance(link, LINCLink):
613 if link.annotations[ 'optical.type' ] == 'cross-connect':
614 tapCount += 1
615
616 while True:
617 if str(tapCount) == quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'):
618 return True
619 if timeout:
620 if time >= TIMEOUT:
621 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
622 return False
623 time += SLEEP_TIME
624 sleep(SLEEP_TIME)
625
626 @staticmethod
627 def shutdownOE():
628 "stop the optical emulator"
629 info('*** Stopping linc OE...\n')
630 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
631
632 @staticmethod
633 def setupInts(intfs):
634 '''
635 add taps and bring them up.
636 '''
637 for i in intfs:
638 quietRun('ip tuntap add dev %s mode tap' % i)
639 quietRun('ip link set dev %s up' % i)
640 info('*** Intf %s set\n' % i)
641
642 @staticmethod
643 def getTaps(path=None):
644 '''
645 return list of all the tops in sys.config
646 '''
647 if path is None:
648 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
649 fd = open(path, 'r', 0)
650 sys_data = fd.read()
651 taps = re.findall('tap\d+', sys_data)
652 fd.close()
653 return taps
654
655 @staticmethod
656 def findUser():
657 "Try to return logged-in (usually non-root) user"
658 try:
659 # If we're running sudo
660 return os.environ[ 'SUDO_USER' ]
661 except:
662 try:
663 # Logged-in user (if we have a tty)
664 return quietRun('who am i').split()[ 0 ]
665 except:
666 # Give up and return effective user
667 return quietRun('whoami')
668
669
670 @staticmethod
671 def findTap(node, port, path=None):
672 '''utility function to parse through a sys.config
673 file to find tap interfaces for a switch'''
674 switch = False
675 portLine = ''
676 intfLines = []
677
678 if path is None:
679 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
680
681 with open(path) as f:
682 for line in f:
683 if 'tap' in line:
684 intfLines.append(line)
685 if node.dpid in line.translate(None, ':'):
686 switch = True
687 continue
688 if switch:
689 if 'switch' in line:
690 switch = False
691 if 'port_no,%s}' % port in line:
692 portLine = line
693 break
694
695 if portLine:
696 m = re.search('port,\d+', portLine)
697 port = m.group(0).split(',')[ 1 ]
698 else:
699 error('***ERROR: Could not find any ports in sys.config\n')
700 return
701
702 for intfLine in intfLines:
703 if 'port,%s' % port in intfLine:
704 return re.findall('tap\d+', intfLine)[ 0 ]
705
706 def json(self):
707 "return json configuration dictionary for switch"
708 return self.configDict
709
710 def terminate(self):
711 pass
712
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700713
714
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000715class LINCLink(Link):
716 """
717 LINC link class
718 """
719 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800720 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000721 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800722 "Creates a dummy link without a virtual ethernet pair."
723 self.allowed = allowed
724 self.annotations = annotations
725 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000726 self.port1 = port1
727 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800728 params1 = { 'speed': speed1 }
729 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000730 # self.isCrossConnect = True if self.annotations.get('optical.type') == 'cross-connect' else False
731 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
732 self.isCrossConnect = False
733 else:
734 self.isCrossConnect = True
735 if isinstance(node1, LINCSwitch):
736 cls1 = LINCIntf
737 if self.isCrossConnect:
738 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800739 else:
740 cls1 = Intf
741 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
742 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
743 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000744 if isinstance(node2, LINCSwitch):
745 cls2 = LINCIntf
746 if self.isCrossConnect:
747 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800748 else:
749 cls2 = Intf
750 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000751 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800752 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000753 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800754
755 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000756 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800757 pass
758
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000759 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800760 "build and return the json configuration dictionary for this link"
761 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000762 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
763 '/%s' % self.intf1.node.ports[ self.intf1 ])
764 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
765 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800766 configData[ 'type' ] = self.linkType
767 configData[ 'annotations' ] = self.annotations
768 return configData
769
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000770class LINCIntf(OpticalIntf):
771 """
772 LINC interface class
773 """
774 def __init__(self, name=None, node=None, speed=0,
775 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800776 self.node = node
777 self.speed = speed
778 self.port = port
779 self.link = link
780 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000781 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800782 self.params = params
783 self.ip = None
784
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000785 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800786 "build and return the JSON information for this interface( not used right now )"
787 configDict = {}
788 configDict[ 'port' ] = self.port
789 configDict[ 'speed' ] = self.speed
790 configDict[ 'type' ] = 'FIBER'
791 return configDict
792
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000793 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800794 "dont configure a dummy interface"
795 pass
796
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000797 def ifconfig(self, status):
798 "configure the status"
799 if status == "up":
800 return self.node.w_port_up(self.port)
801 elif status == "down":
802 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800803
804
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000805class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800806 "Mininet with Linc-OE support (starts and stops linc-oe)"
807
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000808 def start(self):
809 Mininet.start(self)
810 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800811
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000812 def stop(self):
813 Mininet.stop(self)
814 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800815
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000816 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800817 i = 0
818 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000819 self.addController(RemoteController('c%d' % i, ip=ctrl))
820 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800821
822if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000823 pass