blob: dddf7554e87bcaedf5acbf05b95dca3c85a4bd7f [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
Ayaka Koshibe143b1c72015-11-18 17:19:04 -080061from mininet.node import Switch, OVSSwitch, RemoteController
Brian O'Connoreb27c452014-12-07 02:43:58 -080062from 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
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700347 portType = 'COPPER'
348 if isinstance(intf.link, LINCLink):
349 portType = 'OCH' if intf.link.isCrossConnect else 'OMS'
350 portDict[ 'type' ] = portType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000351 intfList = [ intf.link.intf1, intf.link.intf2 ]
352 intfList.remove(intf)
353 portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance(intf.link, LINCLink) else 0
354 ports.append(portDict)
355 configDict[ 'ports' ] = ports
356 return configDict
357
358 @staticmethod
359 def bootOE(net):
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800360 """
361 Start the LINC optical emulator within a mininet instance
362
363 This involves 1. converting the information stored in Linc* to configs
364 for both LINC and the network config system, 2. starting Linc, 3. connecting
365 cross-connects, and finally pushing the network configs to ONOS.
366 """
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700367 LINCSwitch.opticalJSON = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000368 linkConfig = []
369 devices = []
370 #setting up the controllers for LINCSwitch class
371 LINCSwitch.controllers = net.controllers
372
373 for switch in net.switches:
374 if isinstance(switch, OpticalSwitch):
375 devices.append(switch.json())
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800376 elif isinstance(switch, OVSSwitch):
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000377 devices.append(LINCSwitch.switchJSON(switch))
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700378 LINCSwitch.opticalJSON[ 'devices' ] = devices
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000379
380 for link in net.links:
381 if isinstance(link, LINCLink) :
382 linkConfig.append(link.json())
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700383 LINCSwitch.opticalJSON[ 'links' ] = linkConfig
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000384
385 info('*** Writing Topology.json file\n')
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700386 topoJSON = LINCSwitch.makeTopoJSON()
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000387 with open('Topology.json', 'w') as outfile:
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700388 json.dump(topoJSON, outfile, indent=4, separators=(',', ': '))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000389
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700390 info('*** Converting Topology.json to linc-oe format (TopoConfig.json) file (no oecfg) \n')
391
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700392 topoConfigJson = {}
393 dpIdToName = {}
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700394
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700395 topoConfigJson["switchConfig"] = LINCSwitch.getSwitchConfig(dpIdToName)
396 topoConfigJson["linkConfig"] = LINCSwitch.getLinkConfig(dpIdToName)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700397
398 #Writing to TopoConfig.json
399 with open( 'TopoConfig.json', 'w' ) as outfile:
400 json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000401
402 info('*** Creating sys.config...\n')
403 output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
404 % (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
405 if output:
406 error('***ERROR: Error creating sys.config file: %s\n' % output)
407 return False
408
409 info ('*** Setting multiple controllers in sys.config...\n')
410 searchStr = '\[{"Switch.*$'
411 ctrlStr = ''
412 for index in range(len(LINCSwitch.controllers)):
413 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
414 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
415 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
416 output = quietRun(sedCmd, shell=True)
417
418 info('*** Copying sys.config to linc-oe directory: ', output + '\n')
419 output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
420 info(output + '\n')
421
422 info('*** Adding taps and bringing them up...\n')
423 LINCSwitch.setupInts(LINCSwitch.getTaps())
424
425 info('*** removing pipes if any \n')
426 quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
427
428 info('*** Starting linc OE...\n')
429 output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
430 if output:
431 error('***ERROR: LINC-OE: %s' % output + '\n')
432 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
433 return False
434
435 info('*** Waiting for linc-oe to start...\n')
436 LINCSwitch.waitStarted(net)
437
438 info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
439 for link in net.links:
440 if isinstance(link, LINCLink):
441 if link.annotations[ 'optical.type' ] == 'cross-connect':
442 for intf in [ link.intf1, link.intf2 ]:
443 if not isinstance(intf, LINCIntf):
444 intfList = [ intf.link.intf1, intf.link.intf2 ]
445 intfList.remove(intf)
446 intf2 = intfList[ 0 ]
447 intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
448
449 info('*** Waiting for all devices to be available in ONOS...\n')
450 url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
451 time = 0
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700452 # Set up password authentication
453 pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700454 pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700455 handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
456 opener = urllib2.build_opener(handler)
457 opener.open(url)
458 urllib2.install_opener(opener)
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800459 # focus on just checking the state of devices we're interested in
460 devlist = map( lambda x: x['uri'], devices )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000461 while True:
462 response = json.load(urllib2.urlopen(url))
463 devs = response.get('devices')
464
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800465 # Wait for all devices to be registered. There is a chance that this is only a subgraph.
466 if (len(devices) > len(devs)):
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000467 continue
468
469 # Wait for all devices to available
470 available = True
471 for d in devs:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800472 if d['id'] in devlist:
473 available &= d['available']
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000474 if available:
475 break
476
477 if (time >= TIMEOUT):
478 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
479 break
480
481 time += SLEEP_TIME
482 sleep(SLEEP_TIME)
483
484 info('*** Pushing Topology.json to ONOS\n')
485 for index in range(len(LINCSwitch.controllers)):
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700486 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s Topology.json network/configuration/ &'\
487 % (LINCSwitch.onosDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000488 # successful output contains the two characters '{}'
489 # if there is more output than this, there is an issue
490 if output.strip('{}'):
491 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
492
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700493 #converts node ids to linc-oe format, with colons every two chars
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700494 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700495 def dpId(id):
496 nodeDpid = ""
497 id = id.split("/", 1)[0]
498 for i in range(3, len(id) - 1, 2):
499 nodeDpid += (id[i:(i + 2):]) + ":"
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700500 return nodeDpid[0:-1];
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700501
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700502 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700503 def makeTopoJSON():
504 """
505 Builds ONOS network conifg system compatible dicts to be written as Topology.json file.
506 """
507 topology = {}
508 links = {}
509 devices = {}
510 ports = {}
511
512 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
513 # build device entries - keyed on uri (DPID) and config key 'basic'
514 devDict = {}
515 devDict[ 'driver' ] = switch[ 'hw' ]
516 devDict[ 'mfr' ] = switch[ 'mfr' ]
517 devDict[ 'mac' ] = switch[ 'mac' ]
518 devDict[ 'type' ] = switch[ 'type' ]
519 devDict.update(switch[ 'annotations' ])
520
521 devSubj = switch[ 'uri' ]
522 devices[ devSubj ] = { 'basic': devDict }
523
524 # build port entries - keyed on "uri/port" and config key 'optical'
525 for port in switch[ 'ports' ]:
526 portSubj = devSubj + '/' + str(port[ 'port' ])
527 ports[ portSubj ] = { 'optical': port }
528
529 # build link entries - keyed on "uri/port-uri/port" and config key 'basic'
530 for link in LINCSwitch.opticalJSON[ 'links' ]:
531 linkDict = {}
532 linkDict[ 'type' ] = link[ 'type' ]
533 linkDict.update(link[ 'annotations' ])
534
535 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
536 links[ linkSubj ] = { 'basic': linkDict }
537
538 topology[ 'links' ] = links
539 topology[ 'devices' ] = devices
540 topology[ 'ports' ] = ports
541
542 return topology
543
544 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700545 def getSwitchConfig (dpIdToName):
546 switchConfig = [];
547 #Iterate through all switches and convert the ROADM switches to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700548 for switch in LINCSwitch.opticalJSON["devices"]:
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700549 if switch.get("type", "none") == "ROADM":
550 builtSwitch = {}
551
552 #set basic switch params based on annotations
553 builtSwitch["allowed"] = True;
554 builtSwitch["latitude"] = switch["annotations"].get("latitude", 0.0);
555 builtSwitch["longitude"] = switch["annotations"].get("longitude", 0.0);
556
557 #assumed that all switches have this entry
558 nodeId = switch["uri"]
559
560 #convert the nodeId to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700561 nodeDpid = LINCSwitch.dpId(nodeId);
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700562
563 builtSwitch["name"] = switch.get("name", "none");
564
565 #keep track of the name corresponding to each switch dpid
566 dpIdToName[nodeDpid] = builtSwitch["name"];
567
568 builtSwitch["nodeDpid"] = nodeDpid
569
570 #set switch params and type
571 builtSwitch["params"] = {};
572 builtSwitch["params"]["numregens"] = switch["annotations"].get("optical.regens", 0);
573 builtSwitch["type"] = "Roadm"
574
575 #append to list of switches
576 switchConfig.append(builtSwitch);
577 return switchConfig
578
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700579 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700580 def getLinkConfig (dpIdToName):
581 newLinkConfig = [];
582 #Iterate through all optical links and convert them to linc-oe format
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700583 for link in LINCSwitch.opticalJSON["links"]:
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700584 if link.get("type", "none") == "OPTICAL":
585 builtLink = {}
586
587 #set basic link params for src and dst
588 builtLink["allowed"] = True;
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700589 builtLink["nodeDpid1"] = LINCSwitch.dpId(link["src"])
590 builtLink["nodeDpid2"] = LINCSwitch.dpId(link["dst"])
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700591
592 #set more params such as name/bandwidth/port/waves if they exist
593 params = {}
594 params["nodeName1"] = dpIdToName.get(builtLink["nodeDpid1"], "none")
595 params["nodeName2"] = dpIdToName.get(builtLink["nodeDpid2"], "none")
596
597 params["port1"] = int(link["src"].split("/")[1])
598 params["port2"] = int(link["dst"].split("/")[1])
599
600 if "bandwidth" in link["annotations"]:
601 params["bandwidth"] = link["annotations"]["bandwidth"]
602
603 if "optical.waves" in link["annotations"]:
604 params["numWaves"] = link["annotations"]["optical.waves"]
605
606 builtLink["params"] = params
607
608 #set type of link (WDM or pktOpt)
609 if link["annotations"].get("optical.type", "cross-connect") == "WDM":
610 builtLink["type"] = "wdmLink"
611 else:
612 builtLink["type"] = "pktOptLink"
613
614 newLinkConfig.append(builtLink);
615 return newLinkConfig
616
617
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000618 @staticmethod
619 def waitStarted(net, timeout=TIMEOUT):
620 "wait until all tap interfaces are available"
621 tapCount = 0
622 time = 0
623 for link in net.links:
624 if isinstance(link, LINCLink):
625 if link.annotations[ 'optical.type' ] == 'cross-connect':
626 tapCount += 1
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000627 while True:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800628 # tapCount can be less than the actual number of taps if the optical network
629 # is a subgraph of a larger multidomain network.
630 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
631 if tapCount <= tapNum:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000632 return True
633 if timeout:
634 if time >= TIMEOUT:
635 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
636 return False
637 time += SLEEP_TIME
638 sleep(SLEEP_TIME)
639
640 @staticmethod
641 def shutdownOE():
642 "stop the optical emulator"
643 info('*** Stopping linc OE...\n')
644 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
645
646 @staticmethod
647 def setupInts(intfs):
648 '''
649 add taps and bring them up.
650 '''
651 for i in intfs:
652 quietRun('ip tuntap add dev %s mode tap' % i)
653 quietRun('ip link set dev %s up' % i)
654 info('*** Intf %s set\n' % i)
655
656 @staticmethod
657 def getTaps(path=None):
658 '''
659 return list of all the tops in sys.config
660 '''
661 if path is None:
662 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
663 fd = open(path, 'r', 0)
664 sys_data = fd.read()
665 taps = re.findall('tap\d+', sys_data)
666 fd.close()
667 return taps
668
669 @staticmethod
670 def findUser():
671 "Try to return logged-in (usually non-root) user"
672 try:
673 # If we're running sudo
674 return os.environ[ 'SUDO_USER' ]
675 except:
676 try:
677 # Logged-in user (if we have a tty)
678 return quietRun('who am i').split()[ 0 ]
679 except:
680 # Give up and return effective user
681 return quietRun('whoami')
682
683
684 @staticmethod
685 def findTap(node, port, path=None):
686 '''utility function to parse through a sys.config
687 file to find tap interfaces for a switch'''
688 switch = False
689 portLine = ''
690 intfLines = []
691
692 if path is None:
693 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
694
695 with open(path) as f:
696 for line in f:
697 if 'tap' in line:
698 intfLines.append(line)
699 if node.dpid in line.translate(None, ':'):
700 switch = True
701 continue
702 if switch:
703 if 'switch' in line:
704 switch = False
705 if 'port_no,%s}' % port in line:
706 portLine = line
707 break
708
709 if portLine:
710 m = re.search('port,\d+', portLine)
711 port = m.group(0).split(',')[ 1 ]
712 else:
713 error('***ERROR: Could not find any ports in sys.config\n')
714 return
715
716 for intfLine in intfLines:
717 if 'port,%s' % port in intfLine:
718 return re.findall('tap\d+', intfLine)[ 0 ]
719
720 def json(self):
721 "return json configuration dictionary for switch"
722 return self.configDict
723
724 def terminate(self):
725 pass
726
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700727
728
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000729class LINCLink(Link):
730 """
731 LINC link class
732 """
733 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800734 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000735 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800736 "Creates a dummy link without a virtual ethernet pair."
737 self.allowed = allowed
738 self.annotations = annotations
739 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000740 self.port1 = port1
741 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800742 params1 = { 'speed': speed1 }
743 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000744 # self.isCrossConnect = True if self.annotations.get('optical.type') == 'cross-connect' else False
745 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
746 self.isCrossConnect = False
747 else:
748 self.isCrossConnect = True
749 if isinstance(node1, LINCSwitch):
750 cls1 = LINCIntf
751 if self.isCrossConnect:
752 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800753 else:
754 cls1 = Intf
755 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
756 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
757 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000758 if isinstance(node2, LINCSwitch):
759 cls2 = LINCIntf
760 if self.isCrossConnect:
761 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800762 else:
763 cls2 = Intf
764 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000765 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800766 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000767 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800768
769 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000770 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800771 pass
772
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000773 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800774 "build and return the json configuration dictionary for this link"
775 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000776 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
777 '/%s' % self.intf1.node.ports[ self.intf1 ])
778 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
779 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800780 configData[ 'type' ] = self.linkType
781 configData[ 'annotations' ] = self.annotations
782 return configData
783
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000784class LINCIntf(OpticalIntf):
785 """
786 LINC interface class
787 """
788 def __init__(self, name=None, node=None, speed=0,
789 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800790 self.node = node
791 self.speed = speed
792 self.port = port
793 self.link = link
794 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000795 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800796 self.params = params
797 self.ip = None
798
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000799 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800800 "build and return the JSON information for this interface( not used right now )"
801 configDict = {}
802 configDict[ 'port' ] = self.port
803 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700804 portType = 'COPPER'
805 if isinstance(self.link, LINCLink):
806 portType = 'OCH' if self.link.isCrossConnect else 'OMS'
807 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800808 return configDict
809
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000810 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800811 "dont configure a dummy interface"
812 pass
813
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000814 def ifconfig(self, status):
815 "configure the status"
816 if status == "up":
817 return self.node.w_port_up(self.port)
818 elif status == "down":
819 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800820
821
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000822class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800823 "Mininet with Linc-OE support (starts and stops linc-oe)"
824
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000825 def start(self):
826 Mininet.start(self)
827 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800828
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000829 def stop(self):
830 Mininet.stop(self)
831 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800832
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000833 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800834 i = 0
835 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000836 self.addController(RemoteController('c%d' % i, ip=ctrl))
837 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800838
839if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000840 pass