blob: 8d20fade1887976d036aa6993590541794a08079 [file] [log] [blame]
Brian O'Connoreb27c452014-12-07 02:43:58 -08001#!/usr/bin/python
2
3'''
4Notes:
5
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -08006This 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
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -080027 - 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
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -080044it's tap interfaces from lincOE during startup. The start() method for mininet would
Brian O'Connoreb27c452014-12-07 02:43:58 -080045grab 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
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -080048file 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
Brian O'Connoreb27c452014-12-07 02:43:58 -080051Topology 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
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800122 return dpids_to_ids
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000123 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')
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800290 output = quietRun('%s/tools/test/bin/onos-netcfg %s\
291 Topology.json' % (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')
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800391
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
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800458 # expected devices availability map
459 devMap = dict.fromkeys(map( lambda x: x['uri'], devices ), False)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000460 while True:
461 response = json.load(urllib2.urlopen(url))
462 devs = response.get('devices')
463
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800464 # update availability map
465 for d in devs:
466 if devMap.has_key(d['id']):
467 devMap[d['id']] = d['available']
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000468
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800469 # Check if all devices we're interested became available
470 if all(devMap.viewvalues()):
471 break;
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000472
473 if (time >= TIMEOUT):
474 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
475 break
476
477 time += SLEEP_TIME
478 sleep(SLEEP_TIME)
479
480 info('*** Pushing Topology.json to ONOS\n')
481 for index in range(len(LINCSwitch.controllers)):
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800482 output = quietRun('%s/tools/test/bin/onos-netcfg %s Topology.json &'\
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700483 % (LINCSwitch.onosDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000484 # successful output contains the two characters '{}'
485 # if there is more output than this, there is an issue
486 if output.strip('{}'):
487 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
488
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700489 #converts node ids to linc-oe format, with colons every two chars
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700490 @staticmethod
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700491 def dpId(id):
492 nodeDpid = ""
493 id = id.split("/", 1)[0]
494 for i in range(3, len(id) - 1, 2):
495 nodeDpid += (id[i:(i + 2):]) + ":"
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800496 return nodeDpid[0:-1]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700497
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700498 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700499 def makeTopoJSON():
500 """
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800501 Builds ONOS network config system compatible dicts to be written as Topology.json file.
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700502 """
503 topology = {}
504 links = {}
505 devices = {}
506 ports = {}
HIGUCHI Yuta3fd2f942016-01-16 20:26:47 -0800507 BasicDevConfigKeys = ['name', 'type', 'latitude', 'longitude', 'allowed',
508 'rackAddress', 'owner', 'driver', 'manufacturer',
509 'hwVersion', 'swVersion', 'serial',
510 'managementAddress']
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700511
512 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800513 # Build device entries - keyed on uri (DPID) and config key 'basic'
514 # 'type' is necessary field, else ONOS assumes it's a SWITCH
515 # Annotations hold switch name and latitude/longitude
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700516 devDict = {}
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700517 devDict[ 'type' ] = switch[ 'type' ]
HIGUCHI Yuta3fd2f942016-01-16 20:26:47 -0800518 devDict.update({k: v for k, v in switch[ 'annotations' ].iteritems() if k in BasicDevConfigKeys})
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700519 devSubj = switch[ 'uri' ]
520 devices[ devSubj ] = { 'basic': devDict }
521
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800522 # Build port entries - keyed on "uri/port" and config key 'optical'
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700523 for port in switch[ 'ports' ]:
524 portSubj = devSubj + '/' + str(port[ 'port' ])
525 ports[ portSubj ] = { 'optical': port }
526
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800527 # Build link entries - keyed on "uri/port-uri/port" and config key 'basic'
528 # Annotations hold the 'durable' field, which is necessary as long as we don't discover optical links
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700529 for link in LINCSwitch.opticalJSON[ 'links' ]:
530 linkDict = {}
531 linkDict[ 'type' ] = link[ 'type' ]
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800532 linkDict.update(link[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700533 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
534 links[ linkSubj ] = { 'basic': linkDict }
535
536 topology[ 'links' ] = links
537 topology[ 'devices' ] = devices
538 topology[ 'ports' ] = ports
539
540 return topology
541
542 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800543 def getSwitchConfig(switches):
544 switchConfig = []
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800545
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800546 # Iterate through all switches and convert the ROADM switches to linc-oe format
547 for switch in switches:
548 if isinstance(switch, LINCSwitch):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700549 builtSwitch = {}
550
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800551 # Set basic switch params based on annotations
552 builtSwitch["allowed"] = True
553 builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
554 builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700555
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800556 # Convert dpid to linc-oe format
557 builtSwitch["name"] = switch.name
558 builtSwitch["nodeDpid"] = LINCSwitch.dpId('of:' + switch.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700559
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800560 # Set switch params and type
561 builtSwitch["params"] = {}
562 builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700563 builtSwitch["type"] = "Roadm"
564
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800565 switchConfig.append(builtSwitch)
566
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700567 return switchConfig
568
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700569 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800570 def getLinkConfig(links):
571 linkConfig = []
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800572
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800573 # Iterate through all non-edge links and convert them to linc-oe format
574 for link in links:
575 if isinstance(link, LINCLink):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700576 builtLink = {}
577
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800578 # Set basic link params for src and dst
579 builtLink["allowed"] = True
580 builtLink["nodeDpid1"] = LINCSwitch.dpId('of:' + link.intf1.node.dpid)
581 builtLink["nodeDpid2"] = LINCSwitch.dpId('of:' + link.intf2.node.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700582
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800583 # Set more params such as name/bandwidth/port if they exist
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700584 params = {}
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800585 params["nodeName1"] = link.intf1.node.name
586 params["nodeName2"] = link.intf2.node.name
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800587
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800588 params["port1"] = link.port1
589 params["port2"] = link.port2
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700590
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800591 if "bandwidth" in link.annotations:
592 params["bandwidth"] = link.annotations["bandwidth"]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700593
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700594 builtLink["params"] = params
595
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800596 # Set link type to WDM or packet (LINC-config-generator relies on it)
597 if link.isTransportLayer():
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700598 builtLink["type"] = "wdmLink"
599 else:
600 builtLink["type"] = "pktOptLink"
601
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800602 linkConfig.append(builtLink)
603
604 return linkConfig
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700605
606
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000607 @staticmethod
608 def waitStarted(net, timeout=TIMEOUT):
609 "wait until all tap interfaces are available"
610 tapCount = 0
611 time = 0
612 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800613 if isinstance(link, LINCLink) and link.isCrossConnect():
614 tapCount += 1
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800615
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000616 while True:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800617 # tapCount can be less than the actual number of taps if the optical network
618 # is a subgraph of a larger multidomain network.
619 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
620 if tapCount <= tapNum:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000621 return True
622 if timeout:
623 if time >= TIMEOUT:
624 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
625 return False
626 time += SLEEP_TIME
627 sleep(SLEEP_TIME)
628
629 @staticmethod
630 def shutdownOE():
631 "stop the optical emulator"
632 info('*** Stopping linc OE...\n')
633 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
634
635 @staticmethod
636 def setupInts(intfs):
637 '''
638 add taps and bring them up.
639 '''
640 for i in intfs:
641 quietRun('ip tuntap add dev %s mode tap' % i)
642 quietRun('ip link set dev %s up' % i)
643 info('*** Intf %s set\n' % i)
644
645 @staticmethod
646 def getTaps(path=None):
647 '''
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800648 return list of all the taps in sys.config
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000649 '''
650 if path is None:
651 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
652 fd = open(path, 'r', 0)
653 sys_data = fd.read()
654 taps = re.findall('tap\d+', sys_data)
655 fd.close()
656 return taps
657
658 @staticmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000659 def findTap(node, port, path=None):
660 '''utility function to parse through a sys.config
661 file to find tap interfaces for a switch'''
662 switch = False
663 portLine = ''
664 intfLines = []
665
666 if path is None:
667 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
668
669 with open(path) as f:
670 for line in f:
671 if 'tap' in line:
672 intfLines.append(line)
673 if node.dpid in line.translate(None, ':'):
674 switch = True
675 continue
676 if switch:
677 if 'switch' in line:
678 switch = False
679 if 'port_no,%s}' % port in line:
680 portLine = line
681 break
682
683 if portLine:
684 m = re.search('port,\d+', portLine)
685 port = m.group(0).split(',')[ 1 ]
686 else:
687 error('***ERROR: Could not find any ports in sys.config\n')
688 return
689
690 for intfLine in intfLines:
691 if 'port,%s' % port in intfLine:
692 return re.findall('tap\d+', intfLine)[ 0 ]
693
694 def json(self):
695 "return json configuration dictionary for switch"
696 return self.configDict
697
698 def terminate(self):
699 pass
700
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700701
702
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000703class LINCLink(Link):
704 """
705 LINC link class
706 """
707 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800708 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000709 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800710 "Creates a dummy link without a virtual ethernet pair."
711 self.allowed = allowed
712 self.annotations = annotations
713 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000714 self.port1 = port1
715 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800716 params1 = { 'speed': speed1 }
717 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000718 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800719 self.isXC = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000720 else:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800721 self.isXC = True
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000722 if isinstance(node1, LINCSwitch):
723 cls1 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800724 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000725 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800726 else:
727 cls1 = Intf
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800728 # bad hack to stop error message from appearing when we try to set up intf in a packet switch,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800729 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
730 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000731 if isinstance(node2, LINCSwitch):
732 cls2 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800733 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000734 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800735 else:
736 cls2 = Intf
737 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000738 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800739 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000740 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800741
742 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000743 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800744 pass
745
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000746 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800747 "build and return the json configuration dictionary for this link"
748 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000749 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
750 '/%s' % self.intf1.node.ports[ self.intf1 ])
751 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
752 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800753 configData[ 'type' ] = self.linkType
754 configData[ 'annotations' ] = self.annotations
755 return configData
756
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800757 def isCrossConnect(self):
758 if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
759 return True
760
761 return False
762
763 def isTransportLayer(self):
764 if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
765 return True
766
767 return False
768
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000769class LINCIntf(OpticalIntf):
770 """
771 LINC interface class
772 """
773 def __init__(self, name=None, node=None, speed=0,
774 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800775 self.node = node
776 self.speed = speed
777 self.port = port
778 self.link = link
779 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000780 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800781 self.params = params
782 self.ip = None
783
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000784 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800785 "build and return the JSON information for this interface( not used right now )"
786 configDict = {}
787 configDict[ 'port' ] = self.port
788 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700789 portType = 'COPPER'
790 if isinstance(self.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800791 portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700792 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800793 return configDict
794
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000795 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800796 "dont configure a dummy interface"
797 pass
798
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000799 def ifconfig(self, status):
800 "configure the status"
801 if status == "up":
802 return self.node.w_port_up(self.port)
803 elif status == "down":
804 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800805
806
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000807class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800808 "Mininet with Linc-OE support (starts and stops linc-oe)"
809
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000810 def start(self):
811 Mininet.start(self)
812 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800813
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000814 def stop(self):
815 Mininet.stop(self)
816 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800817
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000818 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800819 i = 0
820 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000821 self.addController(RemoteController('c%d' % i, ip=ctrl))
822 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800823
824if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000825 pass