blob: 21257d222a5420075b866dddc47edb36ea314a8d [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)
HIGUCHI Yuta11d16092015-12-04 23:35:43 -0800200 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):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800349 portType = 'OCH' if intf.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700350 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
Ayaka Koshibea879a042015-11-19 17:04:03 -0800359 def bootOE(net, domain=None):
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.
Ayaka Koshibea879a042015-11-19 17:04:03 -0800366
367 Inevitably, there are times when we have OVS switches that should not be
368 under the control of the controller in charge of the Linc switches. We
369 hint at these by passing domain information.
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800370 """
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700371 LINCSwitch.opticalJSON = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000372 linkConfig = []
373 devices = []
374 #setting up the controllers for LINCSwitch class
375 LINCSwitch.controllers = net.controllers
376
377 for switch in net.switches:
Ayaka Koshibea879a042015-11-19 17:04:03 -0800378 if domain and switch not in domain:
379 continue
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000380 if isinstance(switch, OpticalSwitch):
381 devices.append(switch.json())
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800382 elif isinstance(switch, OVSSwitch):
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000383 devices.append(LINCSwitch.switchJSON(switch))
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700384 LINCSwitch.opticalJSON[ 'devices' ] = devices
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000385
386 for link in net.links:
387 if isinstance(link, LINCLink) :
388 linkConfig.append(link.json())
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700389 LINCSwitch.opticalJSON[ 'links' ] = linkConfig
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000390
391 info('*** Writing Topology.json file\n')
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700392 topoJSON = LINCSwitch.makeTopoJSON()
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000393 with open('Topology.json', 'w') as outfile:
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700394 json.dump(topoJSON, outfile, indent=4, separators=(',', ': '))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000395
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700396 info('*** Converting Topology.json to linc-oe format (TopoConfig.json) file (no oecfg) \n')
397
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700398 topoConfigJson = {}
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700399
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800400 topoConfigJson["switchConfig"] = LINCSwitch.getSwitchConfig(net.switches)
401 topoConfigJson["linkConfig"] = LINCSwitch.getLinkConfig(net.links)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700402
403 #Writing to TopoConfig.json
404 with open( 'TopoConfig.json', 'w' ) as outfile:
405 json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000406
407 info('*** Creating sys.config...\n')
408 output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
409 % (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
410 if output:
411 error('***ERROR: Error creating sys.config file: %s\n' % output)
412 return False
413
414 info ('*** Setting multiple controllers in sys.config...\n')
415 searchStr = '\[{"Switch.*$'
416 ctrlStr = ''
417 for index in range(len(LINCSwitch.controllers)):
418 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
419 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
420 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
421 output = quietRun(sedCmd, shell=True)
422
423 info('*** Copying sys.config to linc-oe directory: ', output + '\n')
424 output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
425 info(output + '\n')
426
427 info('*** Adding taps and bringing them up...\n')
428 LINCSwitch.setupInts(LINCSwitch.getTaps())
429
430 info('*** removing pipes if any \n')
431 quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
432
433 info('*** Starting linc OE...\n')
434 output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
435 if output:
436 error('***ERROR: LINC-OE: %s' % output + '\n')
437 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
438 return False
439
440 info('*** Waiting for linc-oe to start...\n')
441 LINCSwitch.waitStarted(net)
442
443 info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
444 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800445 if isinstance(link, LINCLink) and link.isCrossConnect():
446 for intf in [ link.intf1, link.intf2 ]:
447 if not isinstance(intf, LINCIntf):
448 intfList = [ intf.link.intf1, intf.link.intf2 ]
449 intfList.remove(intf)
450 intf2 = intfList[ 0 ]
451 intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000452
453 info('*** Waiting for all devices to be available in ONOS...\n')
454 url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
455 time = 0
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700456 # Set up password authentication
457 pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700458 pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700459 handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
460 opener = urllib2.build_opener(handler)
461 opener.open(url)
462 urllib2.install_opener(opener)
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800463 # focus on just checking the state of devices we're interested in
464 devlist = map( lambda x: x['uri'], devices )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000465 while True:
466 response = json.load(urllib2.urlopen(url))
467 devs = response.get('devices')
468
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800469 # Wait for all devices to be registered. There is a chance that this is only a subgraph.
acsmarsbbab1a12015-12-11 14:32:46 -0800470 if (len(devices) == len(devs)):
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000471
acsmarsbbab1a12015-12-11 14:32:46 -0800472 # Wait for all devices to available
473 available = True
474 for d in devs:
475 if d['id'] in devlist:
476 available &= d['available']
477 if available:
478 break
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000479
480 if (time >= TIMEOUT):
481 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
482 break
483
484 time += SLEEP_TIME
485 sleep(SLEEP_TIME)
486
487 info('*** Pushing Topology.json to ONOS\n')
488 for index in range(len(LINCSwitch.controllers)):
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700489 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s Topology.json network/configuration/ &'\
490 % (LINCSwitch.onosDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000491 # successful output contains the two characters '{}'
492 # if there is more output than this, there is an issue
493 if output.strip('{}'):
494 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
495
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700496 #converts node ids to linc-oe format, with colons every two chars
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700497 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700498 def dpId(id):
499 nodeDpid = ""
500 id = id.split("/", 1)[0]
501 for i in range(3, len(id) - 1, 2):
502 nodeDpid += (id[i:(i + 2):]) + ":"
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800503 return nodeDpid[0:-1]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700504
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700505 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700506 def makeTopoJSON():
507 """
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800508 Builds ONOS network config system compatible dicts to be written as Topology.json file.
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700509 """
510 topology = {}
511 links = {}
512 devices = {}
513 ports = {}
514
515 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
516 # build device entries - keyed on uri (DPID) and config key 'basic'
517 devDict = {}
518 devDict[ 'driver' ] = switch[ 'hw' ]
519 devDict[ 'mfr' ] = switch[ 'mfr' ]
520 devDict[ 'mac' ] = switch[ 'mac' ]
521 devDict[ 'type' ] = switch[ 'type' ]
522 devDict.update(switch[ 'annotations' ])
523
524 devSubj = switch[ 'uri' ]
525 devices[ devSubj ] = { 'basic': devDict }
526
527 # build port entries - keyed on "uri/port" and config key 'optical'
528 for port in switch[ 'ports' ]:
529 portSubj = devSubj + '/' + str(port[ 'port' ])
530 ports[ portSubj ] = { 'optical': port }
531
532 # build link entries - keyed on "uri/port-uri/port" and config key 'basic'
533 for link in LINCSwitch.opticalJSON[ 'links' ]:
534 linkDict = {}
535 linkDict[ 'type' ] = link[ 'type' ]
Marc De Leenheer2c305302015-12-07 21:37:44 -0800536 # FIXME: Clean up unnecessary link/device attributes, then re-enable annotations
537 linkDict['durable'] = True
538 # linkDict.update(link[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700539
540 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
541 links[ linkSubj ] = { 'basic': linkDict }
542
543 topology[ 'links' ] = links
544 topology[ 'devices' ] = devices
545 topology[ 'ports' ] = ports
546
547 return topology
548
549 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800550 def getSwitchConfig(switches):
551 switchConfig = []
552
553 # Iterate through all switches and convert the ROADM switches to linc-oe format
554 for switch in switches:
555 if isinstance(switch, LINCSwitch):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700556 builtSwitch = {}
557
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800558 # Set basic switch params based on annotations
559 builtSwitch["allowed"] = True
560 builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
561 builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700562
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800563 # Convert dpid to linc-oe format
564 builtSwitch["name"] = switch.name
565 builtSwitch["nodeDpid"] = LINCSwitch.dpId('of:' + switch.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700566
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800567 # Set switch params and type
568 builtSwitch["params"] = {}
569 builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700570 builtSwitch["type"] = "Roadm"
571
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800572 switchConfig.append(builtSwitch)
573
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700574 return switchConfig
575
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700576 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800577 def getLinkConfig(links):
578 linkConfig = []
579
580 # Iterate through all non-edge links and convert them to linc-oe format
581 for link in links:
582 if isinstance(link, LINCLink):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700583 builtLink = {}
584
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800585 # Set basic link params for src and dst
586 builtLink["allowed"] = True
587 builtLink["nodeDpid1"] = LINCSwitch.dpId('of:' + link.intf1.node.dpid)
588 builtLink["nodeDpid2"] = LINCSwitch.dpId('of:' + link.intf2.node.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700589
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800590 # Set more params such as name/bandwidth/port if they exist
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700591 params = {}
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800592 params["nodeName1"] = link.intf1.node.name
593 params["nodeName2"] = link.intf2.node.name
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700594
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800595 params["port1"] = link.port1
596 params["port2"] = link.port2
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700597
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800598 if "bandwidth" in link.annotations:
599 params["bandwidth"] = link.annotations["bandwidth"]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700600
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700601 builtLink["params"] = params
602
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800603 # Set link type to WDM or packet (LINC-config-generator relies on it)
604 if link.isTransportLayer():
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700605 builtLink["type"] = "wdmLink"
606 else:
607 builtLink["type"] = "pktOptLink"
608
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800609 linkConfig.append(builtLink)
610
611 return linkConfig
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700612
613
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000614 @staticmethod
615 def waitStarted(net, timeout=TIMEOUT):
616 "wait until all tap interfaces are available"
617 tapCount = 0
618 time = 0
619 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800620 if isinstance(link, LINCLink) and link.isCrossConnect():
621 tapCount += 1
622
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000623 while True:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800624 # tapCount can be less than the actual number of taps if the optical network
625 # is a subgraph of a larger multidomain network.
626 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
627 if tapCount <= tapNum:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000628 return True
629 if timeout:
630 if time >= TIMEOUT:
631 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
632 return False
633 time += SLEEP_TIME
634 sleep(SLEEP_TIME)
635
636 @staticmethod
637 def shutdownOE():
638 "stop the optical emulator"
639 info('*** Stopping linc OE...\n')
640 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
641
642 @staticmethod
643 def setupInts(intfs):
644 '''
645 add taps and bring them up.
646 '''
647 for i in intfs:
648 quietRun('ip tuntap add dev %s mode tap' % i)
649 quietRun('ip link set dev %s up' % i)
650 info('*** Intf %s set\n' % i)
651
652 @staticmethod
653 def getTaps(path=None):
654 '''
655 return list of all the tops in sys.config
656 '''
657 if path is None:
658 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
659 fd = open(path, 'r', 0)
660 sys_data = fd.read()
661 taps = re.findall('tap\d+', sys_data)
662 fd.close()
663 return taps
664
665 @staticmethod
666 def findUser():
667 "Try to return logged-in (usually non-root) user"
668 try:
669 # If we're running sudo
670 return os.environ[ 'SUDO_USER' ]
671 except:
672 try:
673 # Logged-in user (if we have a tty)
674 return quietRun('who am i').split()[ 0 ]
675 except:
676 # Give up and return effective user
677 return quietRun('whoami')
678
679
680 @staticmethod
681 def findTap(node, port, path=None):
682 '''utility function to parse through a sys.config
683 file to find tap interfaces for a switch'''
684 switch = False
685 portLine = ''
686 intfLines = []
687
688 if path is None:
689 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
690
691 with open(path) as f:
692 for line in f:
693 if 'tap' in line:
694 intfLines.append(line)
695 if node.dpid in line.translate(None, ':'):
696 switch = True
697 continue
698 if switch:
699 if 'switch' in line:
700 switch = False
701 if 'port_no,%s}' % port in line:
702 portLine = line
703 break
704
705 if portLine:
706 m = re.search('port,\d+', portLine)
707 port = m.group(0).split(',')[ 1 ]
708 else:
709 error('***ERROR: Could not find any ports in sys.config\n')
710 return
711
712 for intfLine in intfLines:
713 if 'port,%s' % port in intfLine:
714 return re.findall('tap\d+', intfLine)[ 0 ]
715
716 def json(self):
717 "return json configuration dictionary for switch"
718 return self.configDict
719
720 def terminate(self):
721 pass
722
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700723
724
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000725class LINCLink(Link):
726 """
727 LINC link class
728 """
729 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800730 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000731 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800732 "Creates a dummy link without a virtual ethernet pair."
733 self.allowed = allowed
734 self.annotations = annotations
735 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000736 self.port1 = port1
737 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800738 params1 = { 'speed': speed1 }
739 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000740 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800741 self.isXC = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000742 else:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800743 self.isXC = True
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000744 if isinstance(node1, LINCSwitch):
745 cls1 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800746 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000747 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800748 else:
749 cls1 = Intf
750 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
751 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
752 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000753 if isinstance(node2, LINCSwitch):
754 cls2 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800755 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000756 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800757 else:
758 cls2 = Intf
759 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000760 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800761 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000762 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800763
764 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000765 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800766 pass
767
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000768 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800769 "build and return the json configuration dictionary for this link"
770 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000771 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
772 '/%s' % self.intf1.node.ports[ self.intf1 ])
773 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
774 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800775 configData[ 'type' ] = self.linkType
776 configData[ 'annotations' ] = self.annotations
777 return configData
778
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800779 def isCrossConnect(self):
780 if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
781 return True
782
783 return False
784
785 def isTransportLayer(self):
786 if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
787 return True
788
789 return False
790
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000791class LINCIntf(OpticalIntf):
792 """
793 LINC interface class
794 """
795 def __init__(self, name=None, node=None, speed=0,
796 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800797 self.node = node
798 self.speed = speed
799 self.port = port
800 self.link = link
801 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000802 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800803 self.params = params
804 self.ip = None
805
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000806 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800807 "build and return the JSON information for this interface( not used right now )"
808 configDict = {}
809 configDict[ 'port' ] = self.port
810 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700811 portType = 'COPPER'
812 if isinstance(self.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800813 portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700814 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800815 return configDict
816
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000817 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800818 "dont configure a dummy interface"
819 pass
820
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000821 def ifconfig(self, status):
822 "configure the status"
823 if status == "up":
824 return self.node.w_port_up(self.port)
825 elif status == "down":
826 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800827
828
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000829class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800830 "Mininet with Linc-OE support (starts and stops linc-oe)"
831
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000832 def start(self):
833 Mininet.start(self)
834 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800835
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000836 def stop(self):
837 Mininet.stop(self)
838 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800839
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000840 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800841 i = 0
842 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000843 self.addController(RemoteController('c%d' % i, ip=ctrl))
844 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800845
846if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000847 pass