blob: 9a908ef4f9c5c290bee883c328e2e3504973d9b2 [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)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800200 self.configDict[ 'type' ] = self.switchType
201 self.configDict[ 'ports' ] = []
202 for port, intf in self.intfs.items():
203 if intf.name == 'lo':
204 continue
205 else:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000206 self.configDict[ 'ports' ].append(intf.json())
207 self.lincStarted = True
fahad44e62c72015-03-04 14:55:35 -0800208
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000209 def stop(self, deleteIntfs=False):
210 '''
211 stop the existing switch
212 '''
213 # TODO:Add support for deleteIntf
214 self.stop_oe()
fahad44e62c72015-03-04 14:55:35 -0800215
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000216 def dpctl( self, *args ):
217 "Run dpctl command: ignore for now"
fahad44e62c72015-03-04 14:55:35 -0800218 pass
219
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000220 def write_to_cli(self, command):
221 '''
222 send command to LINC
223 '''
224 fd = None
225 try:
226 fd = open(self.writePipe, 'w', 0)
227 fd.write(command)
228 fd.close()
229 except:
230 print "Error working with {}\nError: {}\n".format(self.writePipe, sys.exc_info())
231 if fd:
232 fd.close()
Nikhil Cheerla5f8f8f02015-07-07 16:01:17 -0700233
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000234 def read_from_cli(self):
235 '''
236 read the output from the LINC CLI
237 '''
238 response = None
239 fd = None
240 try:
241 fd = open(self.readPipe, 'r', 0)
242 fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # for non-blocking read
243 # FIXME:Due to non-blocking read most for the time we read nothing
244 response = fd.read()
245 fd.close()
246 except :
247 # print "Error working with {}\nError: {}\n".format(self.readPipe, sys.exc_info())
248 if fd:
249 fd.close()
250 return response
251
252 def _get_linc_id(self):
253 '''
254 return the corresponding LINC switchId.
255 '''
256 return LINCSwitch.dpidsToLINCSwitchId.get(self.dpid)
257 #--------------------------------------------------------------------------
258 # LINC CLI commands
259 #--------------------------------------------------------------------------
260 def start_oe(self):
261 '''
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700262 existing LINC switch
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000263 '''
264 #starting Switch
265 cmd = "linc:start_switch({}).\r\n".format(self.lincId)
266 self.write_to_cli(cmd)
267 #hanlding taps interfaces related to the switch
268 crossConnectJSON = {}
269 linkConfig = []
270 for i in range(0,len(self.deletedCrossConnects)):
271 crossConnect = self.deletedCrossConnects.pop()
272 tap = None
273 if isinstance(crossConnect.intf1.node, LINCSwitch):
274 intf = crossConnect.intf2
275 tapPort = crossConnect.intf1.port
276 else:
277 intf = crossConnect.intf1
278 tapPort = crossConnect.intf2.port
279 tap = LINCSwitch.findTap(self, tapPort)
280 if tap:
281 LINCSwitch.setupInts([tap])
282 intf.node.attach(tap)
283 self.crossConnects.append(crossConnect)
284 linkConfig.append(crossConnect.json())
285 #Sending crossConnect info to the ONOS.
286 crossConnectJSON['links'] = linkConfig
287 with open("crossConnect.json", 'w') as fd:
288 json.dump(crossConnectJSON, fd, indent=4, separators=(',', ': '))
289 info('*** Pushing crossConnect.json to ONOS\n')
290 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s\
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700291 Topology.json network/configuration/' % (self.onosDir, self.controllers[ 0 ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000292
293 def stop_oe(self):
294 '''
295 stop the existing LINC switch
296 '''
297 cmd = "linc:stop_switch({}).\r\n".format(self.lincId)
298 self.write_to_cli(cmd)
299 #handling taps if any
300 for i in range(0, len(self.crossConnects)):
301 crossConnect = self.crossConnects.pop()
302 if isinstance(crossConnect.intf1.node, LINCSwitch):
303 intf = crossConnect.intf2
304 tapPort = crossConnect.intf1.port
305 else:
306 intf = crossConnect.intf1
307 tapPort = crossConnect.intf2.port
308 intf.node.detach(LINCSwitch.findTap(self, tapPort))
309 self.deletedCrossConnects.append(crossConnect)
310
311 def w_port_up(self, port):
312 '''
313 port_up
314 '''
315 cmd = "linc:port_up({},{}).\r\n".format(self.lincId, port)
316 self.write_to_cli(cmd)
317
318 def w_port_down(self, port):
319 '''
320 port_down
321 '''
322 cmd = "linc:port_down({},{}).\r\n".format(self.lincId, port)
323 self.write_to_cli(cmd)
324
325 # helper functions
326 @staticmethod
327 def switchJSON(switch):
328 "Returns the json configuration for a packet switch"
329 configDict = {}
330 configDict[ 'uri' ] = 'of:' + switch.dpid
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800331 configDict[ 'type' ] = 'SWITCH'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000332 annotations = switch.params.get('annotations', {})
333 annotations.setdefault('name', switch.name)
334 configDict[ 'annotations' ] = annotations
335 ports = []
336 for port, intf in switch.intfs.items():
337 if intf.name == 'lo':
338 continue
339 portDict = {}
340 portDict[ 'port' ] = port
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700341 portType = 'COPPER'
342 if isinstance(intf.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800343 portType = 'OCH' if intf.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700344 portDict[ 'type' ] = portType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000345 intfList = [ intf.link.intf1, intf.link.intf2 ]
346 intfList.remove(intf)
347 portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance(intf.link, LINCLink) else 0
348 ports.append(portDict)
349 configDict[ 'ports' ] = ports
350 return configDict
351
352 @staticmethod
Ayaka Koshibea879a042015-11-19 17:04:03 -0800353 def bootOE(net, domain=None):
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800354 """
355 Start the LINC optical emulator within a mininet instance
356
357 This involves 1. converting the information stored in Linc* to configs
358 for both LINC and the network config system, 2. starting Linc, 3. connecting
359 cross-connects, and finally pushing the network configs to ONOS.
Ayaka Koshibea879a042015-11-19 17:04:03 -0800360
361 Inevitably, there are times when we have OVS switches that should not be
362 under the control of the controller in charge of the Linc switches. We
363 hint at these by passing domain information.
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800364 """
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700365 LINCSwitch.opticalJSON = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000366 linkConfig = []
367 devices = []
368 #setting up the controllers for LINCSwitch class
369 LINCSwitch.controllers = net.controllers
370
371 for switch in net.switches:
Ayaka Koshibea879a042015-11-19 17:04:03 -0800372 if domain and switch not in domain:
373 continue
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000374 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 = {}
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700393
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800394 topoConfigJson["switchConfig"] = LINCSwitch.getSwitchConfig(net.switches)
395 topoConfigJson["linkConfig"] = LINCSwitch.getLinkConfig(net.links)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700396
397 #Writing to TopoConfig.json
398 with open( 'TopoConfig.json', 'w' ) as outfile:
399 json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000400
401 info('*** Creating sys.config...\n')
402 output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
403 % (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
404 if output:
405 error('***ERROR: Error creating sys.config file: %s\n' % output)
406 return False
407
408 info ('*** Setting multiple controllers in sys.config...\n')
409 searchStr = '\[{"Switch.*$'
410 ctrlStr = ''
411 for index in range(len(LINCSwitch.controllers)):
412 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
413 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
414 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
415 output = quietRun(sedCmd, shell=True)
416
417 info('*** Copying sys.config to linc-oe directory: ', output + '\n')
418 output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
419 info(output + '\n')
420
421 info('*** Adding taps and bringing them up...\n')
422 LINCSwitch.setupInts(LINCSwitch.getTaps())
423
424 info('*** removing pipes if any \n')
425 quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
426
427 info('*** Starting linc OE...\n')
428 output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
429 if output:
430 error('***ERROR: LINC-OE: %s' % output + '\n')
431 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
432 return False
433
434 info('*** Waiting for linc-oe to start...\n')
435 LINCSwitch.waitStarted(net)
436
437 info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
438 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800439 if isinstance(link, LINCLink) and link.isCrossConnect():
440 for intf in [ link.intf1, link.intf2 ]:
441 if not isinstance(intf, LINCIntf):
442 intfList = [ intf.link.intf1, intf.link.intf2 ]
443 intfList.remove(intf)
444 intf2 = intfList[ 0 ]
445 intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000446
447 info('*** Waiting for all devices to be available in ONOS...\n')
448 url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
449 time = 0
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700450 # Set up password authentication
451 pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700452 pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700453 handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
454 opener = urllib2.build_opener(handler)
455 opener.open(url)
456 urllib2.install_opener(opener)
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800457 # focus on just checking the state of devices we're interested in
458 devlist = map( lambda x: x['uri'], devices )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000459 while True:
460 response = json.load(urllib2.urlopen(url))
461 devs = response.get('devices')
462
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800463 # Wait for all devices to be registered. There is a chance that this is only a subgraph.
acsmarsbbab1a12015-12-11 14:32:46 -0800464 if (len(devices) == len(devs)):
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000465
acsmarsbbab1a12015-12-11 14:32:46 -0800466 # Wait for all devices to available
467 available = True
468 for d in devs:
469 if d['id'] in devlist:
470 available &= d['available']
471 if available:
472 break
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000473
474 if (time >= TIMEOUT):
475 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
476 break
477
478 time += SLEEP_TIME
479 sleep(SLEEP_TIME)
480
481 info('*** Pushing Topology.json to ONOS\n')
482 for index in range(len(LINCSwitch.controllers)):
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700483 output = quietRun('%s/tools/test/bin/onos-topo-cfg %s Topology.json network/configuration/ &'\
484 % (LINCSwitch.onosDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000485 # successful output contains the two characters '{}'
486 # if there is more output than this, there is an issue
487 if output.strip('{}'):
488 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
489
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700490 #converts node ids to linc-oe format, with colons every two chars
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700491 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700492 def dpId(id):
493 nodeDpid = ""
494 id = id.split("/", 1)[0]
495 for i in range(3, len(id) - 1, 2):
496 nodeDpid += (id[i:(i + 2):]) + ":"
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800497 return nodeDpid[0:-1]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700498
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700499 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700500 def makeTopoJSON():
501 """
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800502 Builds ONOS network config system compatible dicts to be written as Topology.json file.
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700503 """
504 topology = {}
505 links = {}
506 devices = {}
507 ports = {}
508
509 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800510 # Build device entries - keyed on uri (DPID) and config key 'basic'
511 # 'type' is necessary field, else ONOS assumes it's a SWITCH
512 # Annotations hold switch name and latitude/longitude
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700513 devDict = {}
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700514 devDict[ 'type' ] = switch[ 'type' ]
515 devDict.update(switch[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700516 devSubj = switch[ 'uri' ]
517 devices[ devSubj ] = { 'basic': devDict }
518
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800519 # Build port entries - keyed on "uri/port" and config key 'optical'
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700520 for port in switch[ 'ports' ]:
521 portSubj = devSubj + '/' + str(port[ 'port' ])
522 ports[ portSubj ] = { 'optical': port }
523
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800524 # Build link entries - keyed on "uri/port-uri/port" and config key 'basic'
525 # Annotations hold the 'durable' field, which is necessary as long as we don't discover optical links
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700526 for link in LINCSwitch.opticalJSON[ 'links' ]:
527 linkDict = {}
528 linkDict[ 'type' ] = link[ 'type' ]
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800529 linkDict.update(link[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700530 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
531 links[ linkSubj ] = { 'basic': linkDict }
532
533 topology[ 'links' ] = links
534 topology[ 'devices' ] = devices
535 topology[ 'ports' ] = ports
536
537 return topology
538
539 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800540 def getSwitchConfig(switches):
541 switchConfig = []
542
543 # Iterate through all switches and convert the ROADM switches to linc-oe format
544 for switch in switches:
545 if isinstance(switch, LINCSwitch):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700546 builtSwitch = {}
547
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800548 # Set basic switch params based on annotations
549 builtSwitch["allowed"] = True
550 builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
551 builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700552
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800553 # Convert dpid to linc-oe format
554 builtSwitch["name"] = switch.name
555 builtSwitch["nodeDpid"] = LINCSwitch.dpId('of:' + switch.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700556
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800557 # Set switch params and type
558 builtSwitch["params"] = {}
559 builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700560 builtSwitch["type"] = "Roadm"
561
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800562 switchConfig.append(builtSwitch)
563
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700564 return switchConfig
565
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700566 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800567 def getLinkConfig(links):
568 linkConfig = []
569
570 # Iterate through all non-edge links and convert them to linc-oe format
571 for link in links:
572 if isinstance(link, LINCLink):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700573 builtLink = {}
574
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800575 # Set basic link params for src and dst
576 builtLink["allowed"] = True
577 builtLink["nodeDpid1"] = LINCSwitch.dpId('of:' + link.intf1.node.dpid)
578 builtLink["nodeDpid2"] = LINCSwitch.dpId('of:' + link.intf2.node.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700579
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800580 # Set more params such as name/bandwidth/port if they exist
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700581 params = {}
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800582 params["nodeName1"] = link.intf1.node.name
583 params["nodeName2"] = link.intf2.node.name
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700584
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800585 params["port1"] = link.port1
586 params["port2"] = link.port2
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700587
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800588 if "bandwidth" in link.annotations:
589 params["bandwidth"] = link.annotations["bandwidth"]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700590
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700591 builtLink["params"] = params
592
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800593 # Set link type to WDM or packet (LINC-config-generator relies on it)
594 if link.isTransportLayer():
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700595 builtLink["type"] = "wdmLink"
596 else:
597 builtLink["type"] = "pktOptLink"
598
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800599 linkConfig.append(builtLink)
600
601 return linkConfig
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700602
603
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000604 @staticmethod
605 def waitStarted(net, timeout=TIMEOUT):
606 "wait until all tap interfaces are available"
607 tapCount = 0
608 time = 0
609 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800610 if isinstance(link, LINCLink) and link.isCrossConnect():
611 tapCount += 1
612
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000613 while True:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800614 # tapCount can be less than the actual number of taps if the optical network
615 # is a subgraph of a larger multidomain network.
616 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
617 if tapCount <= tapNum:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000618 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 '''
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800645 return list of all the taps in sys.config
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000646 '''
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
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000656 def findTap(node, port, path=None):
657 '''utility function to parse through a sys.config
658 file to find tap interfaces for a switch'''
659 switch = False
660 portLine = ''
661 intfLines = []
662
663 if path is None:
664 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
665
666 with open(path) as f:
667 for line in f:
668 if 'tap' in line:
669 intfLines.append(line)
670 if node.dpid in line.translate(None, ':'):
671 switch = True
672 continue
673 if switch:
674 if 'switch' in line:
675 switch = False
676 if 'port_no,%s}' % port in line:
677 portLine = line
678 break
679
680 if portLine:
681 m = re.search('port,\d+', portLine)
682 port = m.group(0).split(',')[ 1 ]
683 else:
684 error('***ERROR: Could not find any ports in sys.config\n')
685 return
686
687 for intfLine in intfLines:
688 if 'port,%s' % port in intfLine:
689 return re.findall('tap\d+', intfLine)[ 0 ]
690
691 def json(self):
692 "return json configuration dictionary for switch"
693 return self.configDict
694
695 def terminate(self):
696 pass
697
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700698
699
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000700class LINCLink(Link):
701 """
702 LINC link class
703 """
704 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800705 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000706 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800707 "Creates a dummy link without a virtual ethernet pair."
708 self.allowed = allowed
709 self.annotations = annotations
710 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000711 self.port1 = port1
712 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800713 params1 = { 'speed': speed1 }
714 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000715 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800716 self.isXC = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000717 else:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800718 self.isXC = True
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000719 if isinstance(node1, LINCSwitch):
720 cls1 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800721 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000722 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800723 else:
724 cls1 = Intf
725 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
726 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
727 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000728 if isinstance(node2, LINCSwitch):
729 cls2 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800730 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000731 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800732 else:
733 cls2 = Intf
734 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000735 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800736 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000737 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800738
739 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000740 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800741 pass
742
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000743 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800744 "build and return the json configuration dictionary for this link"
745 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000746 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
747 '/%s' % self.intf1.node.ports[ self.intf1 ])
748 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
749 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800750 configData[ 'type' ] = self.linkType
751 configData[ 'annotations' ] = self.annotations
752 return configData
753
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800754 def isCrossConnect(self):
755 if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
756 return True
757
758 return False
759
760 def isTransportLayer(self):
761 if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
762 return True
763
764 return False
765
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000766class LINCIntf(OpticalIntf):
767 """
768 LINC interface class
769 """
770 def __init__(self, name=None, node=None, speed=0,
771 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800772 self.node = node
773 self.speed = speed
774 self.port = port
775 self.link = link
776 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000777 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800778 self.params = params
779 self.ip = None
780
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000781 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800782 "build and return the JSON information for this interface( not used right now )"
783 configDict = {}
784 configDict[ 'port' ] = self.port
785 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700786 portType = 'COPPER'
787 if isinstance(self.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800788 portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700789 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800790 return configDict
791
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000792 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800793 "dont configure a dummy interface"
794 pass
795
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000796 def ifconfig(self, status):
797 "configure the status"
798 if status == "up":
799 return self.node.w_port_up(self.port)
800 elif status == "down":
801 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800802
803
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000804class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800805 "Mininet with Linc-OE support (starts and stops linc-oe)"
806
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000807 def start(self):
808 Mininet.start(self)
809 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800810
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000811 def stop(self):
812 Mininet.stop(self)
813 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800814
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000815 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800816 i = 0
817 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000818 self.addController(RemoteController('c%d' % i, ip=ctrl))
819 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800820
821if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000822 pass