blob: 19b898b87097ded8748d2fcd2546477ff8d62efd [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 = {}
507
508 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800509 # Build device entries - keyed on uri (DPID) and config key 'basic'
510 # 'type' is necessary field, else ONOS assumes it's a SWITCH
511 # Annotations hold switch name and latitude/longitude
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700512 devDict = {}
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700513 devDict[ 'type' ] = switch[ 'type' ]
514 devDict.update(switch[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700515 devSubj = switch[ 'uri' ]
516 devices[ devSubj ] = { 'basic': devDict }
517
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800518 # Build port entries - keyed on "uri/port" and config key 'optical'
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700519 for port in switch[ 'ports' ]:
520 portSubj = devSubj + '/' + str(port[ 'port' ])
521 ports[ portSubj ] = { 'optical': port }
522
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800523 # Build link entries - keyed on "uri/port-uri/port" and config key 'basic'
524 # Annotations hold the 'durable' field, which is necessary as long as we don't discover optical links
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700525 for link in LINCSwitch.opticalJSON[ 'links' ]:
526 linkDict = {}
527 linkDict[ 'type' ] = link[ 'type' ]
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800528 linkDict.update(link[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700529 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
530 links[ linkSubj ] = { 'basic': linkDict }
531
532 topology[ 'links' ] = links
533 topology[ 'devices' ] = devices
534 topology[ 'ports' ] = ports
535
536 return topology
537
538 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800539 def getSwitchConfig(switches):
540 switchConfig = []
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800541
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800542 # Iterate through all switches and convert the ROADM switches to linc-oe format
543 for switch in switches:
544 if isinstance(switch, LINCSwitch):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700545 builtSwitch = {}
546
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800547 # Set basic switch params based on annotations
548 builtSwitch["allowed"] = True
549 builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
550 builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700551
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800552 # Convert dpid to linc-oe format
553 builtSwitch["name"] = switch.name
554 builtSwitch["nodeDpid"] = LINCSwitch.dpId('of:' + switch.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700555
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800556 # Set switch params and type
557 builtSwitch["params"] = {}
558 builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700559 builtSwitch["type"] = "Roadm"
560
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800561 switchConfig.append(builtSwitch)
562
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700563 return switchConfig
564
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700565 @staticmethod
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800566 def getLinkConfig(links):
567 linkConfig = []
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800568
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800569 # Iterate through all non-edge links and convert them to linc-oe format
570 for link in links:
571 if isinstance(link, LINCLink):
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700572 builtLink = {}
573
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800574 # Set basic link params for src and dst
575 builtLink["allowed"] = True
576 builtLink["nodeDpid1"] = LINCSwitch.dpId('of:' + link.intf1.node.dpid)
577 builtLink["nodeDpid2"] = LINCSwitch.dpId('of:' + link.intf2.node.dpid)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700578
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800579 # Set more params such as name/bandwidth/port if they exist
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700580 params = {}
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800581 params["nodeName1"] = link.intf1.node.name
582 params["nodeName2"] = link.intf2.node.name
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800583
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800584 params["port1"] = link.port1
585 params["port2"] = link.port2
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700586
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800587 if "bandwidth" in link.annotations:
588 params["bandwidth"] = link.annotations["bandwidth"]
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700589
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700590 builtLink["params"] = params
591
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800592 # Set link type to WDM or packet (LINC-config-generator relies on it)
593 if link.isTransportLayer():
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700594 builtLink["type"] = "wdmLink"
595 else:
596 builtLink["type"] = "pktOptLink"
597
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800598 linkConfig.append(builtLink)
599
600 return linkConfig
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700601
602
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000603 @staticmethod
604 def waitStarted(net, timeout=TIMEOUT):
605 "wait until all tap interfaces are available"
606 tapCount = 0
607 time = 0
608 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800609 if isinstance(link, LINCLink) and link.isCrossConnect():
610 tapCount += 1
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800611
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000612 while True:
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800613 # tapCount can be less than the actual number of taps if the optical network
614 # is a subgraph of a larger multidomain network.
615 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
616 if tapCount <= tapNum:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000617 return True
618 if timeout:
619 if time >= TIMEOUT:
620 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
621 return False
622 time += SLEEP_TIME
623 sleep(SLEEP_TIME)
624
625 @staticmethod
626 def shutdownOE():
627 "stop the optical emulator"
628 info('*** Stopping linc OE...\n')
629 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
630
631 @staticmethod
632 def setupInts(intfs):
633 '''
634 add taps and bring them up.
635 '''
636 for i in intfs:
637 quietRun('ip tuntap add dev %s mode tap' % i)
638 quietRun('ip link set dev %s up' % i)
639 info('*** Intf %s set\n' % i)
640
641 @staticmethod
642 def getTaps(path=None):
643 '''
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800644 return list of all the taps in sys.config
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000645 '''
646 if path is None:
647 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
648 fd = open(path, 'r', 0)
649 sys_data = fd.read()
650 taps = re.findall('tap\d+', sys_data)
651 fd.close()
652 return taps
653
654 @staticmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000655 def findTap(node, port, path=None):
656 '''utility function to parse through a sys.config
657 file to find tap interfaces for a switch'''
658 switch = False
659 portLine = ''
660 intfLines = []
661
662 if path is None:
663 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
664
665 with open(path) as f:
666 for line in f:
667 if 'tap' in line:
668 intfLines.append(line)
669 if node.dpid in line.translate(None, ':'):
670 switch = True
671 continue
672 if switch:
673 if 'switch' in line:
674 switch = False
675 if 'port_no,%s}' % port in line:
676 portLine = line
677 break
678
679 if portLine:
680 m = re.search('port,\d+', portLine)
681 port = m.group(0).split(',')[ 1 ]
682 else:
683 error('***ERROR: Could not find any ports in sys.config\n')
684 return
685
686 for intfLine in intfLines:
687 if 'port,%s' % port in intfLine:
688 return re.findall('tap\d+', intfLine)[ 0 ]
689
690 def json(self):
691 "return json configuration dictionary for switch"
692 return self.configDict
693
694 def terminate(self):
695 pass
696
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700697
698
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000699class LINCLink(Link):
700 """
701 LINC link class
702 """
703 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800704 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000705 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800706 "Creates a dummy link without a virtual ethernet pair."
707 self.allowed = allowed
708 self.annotations = annotations
709 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000710 self.port1 = port1
711 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800712 params1 = { 'speed': speed1 }
713 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000714 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800715 self.isXC = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000716 else:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800717 self.isXC = True
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000718 if isinstance(node1, LINCSwitch):
719 cls1 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800720 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000721 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800722 else:
723 cls1 = Intf
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800724 # 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 -0800725 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
726 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000727 if isinstance(node2, LINCSwitch):
728 cls2 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800729 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000730 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800731 else:
732 cls2 = Intf
733 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000734 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800735 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000736 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800737
738 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000739 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800740 pass
741
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000742 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800743 "build and return the json configuration dictionary for this link"
744 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000745 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
746 '/%s' % self.intf1.node.ports[ self.intf1 ])
747 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
748 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800749 configData[ 'type' ] = self.linkType
750 configData[ 'annotations' ] = self.annotations
751 return configData
752
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800753 def isCrossConnect(self):
754 if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
755 return True
756
757 return False
758
759 def isTransportLayer(self):
760 if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
761 return True
762
763 return False
764
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000765class LINCIntf(OpticalIntf):
766 """
767 LINC interface class
768 """
769 def __init__(self, name=None, node=None, speed=0,
770 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800771 self.node = node
772 self.speed = speed
773 self.port = port
774 self.link = link
775 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000776 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800777 self.params = params
778 self.ip = None
779
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000780 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800781 "build and return the JSON information for this interface( not used right now )"
782 configDict = {}
783 configDict[ 'port' ] = self.port
784 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700785 portType = 'COPPER'
786 if isinstance(self.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800787 portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700788 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800789 return configDict
790
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000791 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800792 "dont configure a dummy interface"
793 pass
794
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000795 def ifconfig(self, status):
796 "configure the status"
797 if status == "up":
798 return self.node.w_port_up(self.port)
799 elif status == "down":
800 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800801
802
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000803class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800804 "Mininet with Linc-OE support (starts and stops linc-oe)"
805
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000806 def start(self):
807 Mininet.start(self)
808 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800809
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000810 def stop(self):
811 Mininet.stop(self)
812 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800813
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000814 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800815 i = 0
816 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000817 self.addController(RemoteController('c%d' % i, ip=ctrl))
818 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800819
820if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000821 pass