blob: e8db648eec633f9f5ec8f8a8599e796580814751 [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
Ching-Ting Sun60445dd2017-01-05 23:56:40 -080073### method, mapping dpid to LINC switchId ###
74def dpids_to_ids(sysConfig):
75 '''
76 return the dict containing switch dpids as key and LINC switch id as values
77 '''
78 dpids_to_ids = {}
79 fd = None
80 try:
81 with open(sysConfig, 'r', 0) as fd:
82 switch_id = 1
83 for line in fd:
84 dpid = re.search(r'([0-9A-Fa-f]{2}[:-]){7}([0-9A-Fa-f]{2})+', line, re.I)
85 if dpid:
86 dpids_to_ids[dpid.group().replace(':', '')] = switch_id
87 switch_id += 1
88 return dpids_to_ids
89 except:
90 print "Error working with {}\nError: {}\n".format(sysConfig, sys.exc_info())
91 fd.close()
92 return None
93
94def findDir(directory, userName):
95 "finds and returns the path of any directory in the user's home directory"
96 homeDir = '/home/' + userName
97 Dir = quietRun('find %s -maxdepth 1 -name %s -type d' % (homeDir, directory)).strip('\n')
98 DirList = Dir.split('\n')
99 if not Dir:
100 return None
101 elif len(DirList) > 1 :
102 warn('***WARNING: Found multiple instances of %s; using %s\n'
103 % (directory, DirList[ 0 ]))
104 return DirList[ 0 ]
105 else:
106 return Dir
107
108def switchJSON(switch):
109 "Returns the json configuration for a packet switch"
110 configDict = {}
111 configDict[ 'uri' ] = 'of:' + switch.dpid
112 configDict[ 'type' ] = 'SWITCH'
113 annotations = switch.params.get('annotations', {})
114 annotations.setdefault('name', switch.name)
115 configDict[ 'annotations' ] = annotations
116 ports = []
117 for port, intf in switch.intfs.items():
118 if intf.name == 'lo':
119 continue
120 portDict = {}
121 portDict[ 'port' ] = port
122 portType = 'COPPER'
123 if isinstance(intf.link, LINCLink):
124 portType = 'OCH' if intf.link.isCrossConnect() else 'OMS'
125 portDict[ 'type' ] = portType
126 intfList = [ intf.link.intf1, intf.link.intf2 ]
127 intfList.remove(intf)
128 portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance(intf.link, LINCLink) else 0
129 ports.append(portDict)
130 configDict[ 'ports' ] = ports
131 return configDict
132
133def dpId(id):
134 nodeDpid = ""
135 id = id.split("/", 1)[0]
136 for i in range(3, len(id) - 1, 2):
137 nodeDpid += (id[i:(i + 2):]) + ":"
138 return nodeDpid[0:-1]
139
140def getSwitchConfig(switches):
141 switchConfig = []
142
143 # Iterate through all switches and convert the ROADM switches to linc-oe format
144 for switch in switches:
145 if isinstance(switch, LINCSwitch):
146 builtSwitch = {}
147
148 # Set basic switch params based on annotations
149 builtSwitch["allowed"] = True
150 builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
151 builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
152
153 # Convert dpid to linc-oe format
154 builtSwitch["name"] = switch.name
155 builtSwitch["nodeDpid"] = dpId('of:' + switch.dpid)
156
157 # Set switch params and type
158 builtSwitch["params"] = {}
159 builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
160 builtSwitch["type"] = "Roadm"
161
162 switchConfig.append(builtSwitch)
163
164 return switchConfig
165
166def getLinkConfig(links):
167 linkConfig = []
168
169 # Iterate through all non-edge links and convert them to linc-oe format
170 for link in links:
171 if isinstance(link, LINCLink):
172 builtLink = {}
173
174 # Set basic link params for src and dst
175 builtLink["allowed"] = True
176 builtLink["nodeDpid1"] = dpId('of:' + link.intf1.node.dpid)
177 builtLink["nodeDpid2"] = dpId('of:' + link.intf2.node.dpid)
178
179 # Set more params such as name/bandwidth/port if they exist
180 params = {}
181 params["nodeName1"] = link.intf1.node.name
182 params["nodeName2"] = link.intf2.node.name
183
184 params["port1"] = link.port1
185 params["port2"] = link.port2
186
187 if "bandwidth" in link.annotations:
188 params["bandwidth"] = link.annotations["bandwidth"]
189
190 builtLink["params"] = params
191
192 # Set link type to WDM or packet (LINC-config-generator relies on it)
193 if link.isTransportLayer():
194 builtLink["type"] = "wdmLink"
195 else:
196 builtLink["type"] = "pktOptLink"
197
198 linkConfig.append(builtLink)
199
200 return linkConfig
201
202def waitStarted(net, timeout=TIMEOUT):
203 "wait until all tap interfaces are available"
204 tapCount = 0
205 time = 0
206 for link in net.links:
207 if isinstance(link, LINCLink) and link.isCrossConnect():
208 tapCount += 1
209
210 while True:
211 # tapCount can be less than the actual number of taps if the optical network
212 # is a subgraph of a larger multidomain network.
213 tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
214 if tapCount <= tapNum:
215 return True
216 if timeout:
217 if time >= TIMEOUT:
218 error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
219 return False
220 time += SLEEP_TIME
221 sleep(SLEEP_TIME)
222
223def setupInts(intfs):
224 '''
225 add taps and bring them up.
226 '''
227 for i in intfs:
228 quietRun('ip tuntap add dev %s mode tap' % i)
229 quietRun('ip link set dev %s up' % i)
230 info('*** Intf %s set\n' % i)
231
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000232class OpticalSwitch(Switch):
233 """
234 For now, same as Switch class.
235 """
236 pass
237
238class OpticalIntf(Intf):
239 """
240 For now,same as Intf class.
241 """
242 pass
243
244class OpticalLink(Link):
245 """
246 For now, same as Link.
247 """
248 pass
249
250class LINCSwitch(OpticalSwitch):
251 """
252 LINCSwitch class
253 """
254 # FIXME:Sometimes LINC doesn't remove pipes and on restart increase the pipe
255 # number from erlang.pipe.1.* to erlang.pipe.2.*, so should read and write
256 # from latest pipe files. For now we are removing all the pipes before
257 # starting LINC.
258 ### User Name ###
259 user = os.getlogin()
260 ### pipes ###
261 readPipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.r".format(user)
262 writePipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.w".format(user)
263 ### sys.config path ###
264 sysConfig = "/home/{}/linc-oe/rel/linc/releases/1.0/sys.config".format(user)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000265 ### dict of containing dpids as key and corresponding LINC switchId as values ###
Yuta HIGUCHI991caf92017-06-02 11:12:03 -0700266 dpidsToLINCSwitchId = dpids_to_ids(sysConfig)
Devin Lim0d944e22017-06-23 15:17:53 -0700267
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000268 ### ONOS Directory ###
269 try:
270 onosDir = os.environ[ 'ONOS_ROOT' ]
271 except:
272 onosDir = findDir('onos', user)
273 if not onosDir:
274 error('Please set ONOS_ROOT environment variable!\n')
275 else:
276 os.environ[ 'ONOS_ROOT' ] = onosDir
Devin Lim0d944e22017-06-23 15:17:53 -0700277 ### ONOS-netcfg directory ###
278 try:
279 runPackDir = os.environ[ 'RUN_PACK_PATH' ]
280 except:
281 runPackDir = onosDir+"/tools/package/runtime/bin"
282 os.environ[ 'RUN_PACK_PATH' ] = runPackDir
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700283 ### REST USER/PASS ###
284 try:
285 restUser = os.environ[ 'ONOS_WEB_USER' ]
286 restPass = os.environ[ 'ONOS_WEB_PASS' ]
287 except:
288 error('***WARNING: $ONOS_WEB_USER and $ONOS_WEB_PASS aren\'t set!\n')
289 error('***WARNING: Setting (probably) sane WEB user/pass values\n')
290 restUser = 'onos'
291 restPass = 'rocks'
292 os.environ[ 'ONOS_WEB_USER' ] = restUser
293 os.environ[ 'ONOS_WEB_PASS' ] = restPass
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000294 ### LINC-directory
Yuta HIGUCHI991caf92017-06-02 11:12:03 -0700295 lincDir = findDir('linc-oe', user)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000296 if not lincDir:
297 error("***ERROR: Could not find linc-oe in user's home directory\n")
298 ### LINC config generator directory###
Yuta HIGUCHI991caf92017-06-02 11:12:03 -0700299 configGen = findDir('LINC-config-generator', user)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000300 if not configGen:
301 error("***ERROR: Could not find LINC-config-generator in user's home directory\n")
302 # list of all the controllers
303 controllers = None
304 def __init__(self, name, dpid=None, allowed=True,
305 switchType='ROADM', topo=None, annotations={}, controller=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800306 params[ 'inNamespace' ] = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000307 Switch.__init__(self, name, dpid=dpid, **params)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800308 self.name = name
309 self.annotations = annotations
310 self.allowed = allowed
311 self.switchType = switchType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000312 self.configDict = {} # dictionary that holds all of the JSON configuration data
313 self.crossConnects = []
314 self.deletedCrossConnects = []
315 self.controller = controller
316 self.lincId = self._get_linc_id() # use to communicate with LINC
317 self.lincStarted = False
Brian O'Connoreb27c452014-12-07 02:43:58 -0800318
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000319 def start(self, *opts, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800320 '''Instead of starting a virtual switch, we build the JSON
321 dictionary for the emulated optical switch'''
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000322 # TODO:Once LINC has the ability to spawn network element dynamically
323 # we need to use this method to spawn new logical LINC switch rather then
324 # bulding JSON.
325 # if LINC is started then we can start and stop logical switches else create JSON
326 if self.lincStarted:
327 return self.start_oe()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800328 self.configDict[ 'uri' ] = 'of:' + self.dpid
329 self.configDict[ 'annotations' ] = self.annotations
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000330 self.configDict[ 'annotations' ].setdefault('name', self.name)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800331 self.configDict[ 'type' ] = self.switchType
332 self.configDict[ 'ports' ] = []
333 for port, intf in self.intfs.items():
334 if intf.name == 'lo':
335 continue
336 else:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000337 self.configDict[ 'ports' ].append(intf.json())
338 self.lincStarted = True
fahad44e62c72015-03-04 14:55:35 -0800339
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000340 def stop(self, deleteIntfs=False):
341 '''
342 stop the existing switch
343 '''
344 # TODO:Add support for deleteIntf
345 self.stop_oe()
fahad44e62c72015-03-04 14:55:35 -0800346
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000347 def dpctl( self, *args ):
348 "Run dpctl command: ignore for now"
fahad44e62c72015-03-04 14:55:35 -0800349 pass
350
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000351 def write_to_cli(self, command):
352 '''
353 send command to LINC
354 '''
355 fd = None
356 try:
357 fd = open(self.writePipe, 'w', 0)
358 fd.write(command)
359 fd.close()
360 except:
361 print "Error working with {}\nError: {}\n".format(self.writePipe, sys.exc_info())
362 if fd:
363 fd.close()
Nikhil Cheerla5f8f8f02015-07-07 16:01:17 -0700364
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000365 def read_from_cli(self):
366 '''
367 read the output from the LINC CLI
368 '''
369 response = None
370 fd = None
371 try:
372 fd = open(self.readPipe, 'r', 0)
373 fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # for non-blocking read
374 # FIXME:Due to non-blocking read most for the time we read nothing
375 response = fd.read()
376 fd.close()
377 except :
378 # print "Error working with {}\nError: {}\n".format(self.readPipe, sys.exc_info())
379 if fd:
380 fd.close()
381 return response
382
383 def _get_linc_id(self):
384 '''
385 return the corresponding LINC switchId.
386 '''
387 return LINCSwitch.dpidsToLINCSwitchId.get(self.dpid)
388 #--------------------------------------------------------------------------
389 # LINC CLI commands
390 #--------------------------------------------------------------------------
391 def start_oe(self):
392 '''
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700393 existing LINC switch
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000394 '''
395 #starting Switch
396 cmd = "linc:start_switch({}).\r\n".format(self.lincId)
397 self.write_to_cli(cmd)
398 #hanlding taps interfaces related to the switch
399 crossConnectJSON = {}
400 linkConfig = []
401 for i in range(0,len(self.deletedCrossConnects)):
402 crossConnect = self.deletedCrossConnects.pop()
403 tap = None
404 if isinstance(crossConnect.intf1.node, LINCSwitch):
405 intf = crossConnect.intf2
406 tapPort = crossConnect.intf1.port
407 else:
408 intf = crossConnect.intf1
409 tapPort = crossConnect.intf2.port
410 tap = LINCSwitch.findTap(self, tapPort)
411 if tap:
Ching-Ting Sun60445dd2017-01-05 23:56:40 -0800412 setupInts([tap])
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000413 intf.node.attach(tap)
414 self.crossConnects.append(crossConnect)
415 linkConfig.append(crossConnect.json())
416 #Sending crossConnect info to the ONOS.
417 crossConnectJSON['links'] = linkConfig
418 with open("crossConnect.json", 'w') as fd:
419 json.dump(crossConnectJSON, fd, indent=4, separators=(',', ': '))
420 info('*** Pushing crossConnect.json to ONOS\n')
Devin Lim0d944e22017-06-23 15:17:53 -0700421 output = quietRun('%s/onos-netcfg %s\
422 Topology.json' % (self.runPackDir, self.controllers[ 0 ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000423
424 def stop_oe(self):
425 '''
426 stop the existing LINC switch
427 '''
428 cmd = "linc:stop_switch({}).\r\n".format(self.lincId)
429 self.write_to_cli(cmd)
430 #handling taps if any
431 for i in range(0, len(self.crossConnects)):
432 crossConnect = self.crossConnects.pop()
433 if isinstance(crossConnect.intf1.node, LINCSwitch):
434 intf = crossConnect.intf2
435 tapPort = crossConnect.intf1.port
436 else:
437 intf = crossConnect.intf1
438 tapPort = crossConnect.intf2.port
439 intf.node.detach(LINCSwitch.findTap(self, tapPort))
440 self.deletedCrossConnects.append(crossConnect)
441
442 def w_port_up(self, port):
443 '''
444 port_up
445 '''
446 cmd = "linc:port_up({},{}).\r\n".format(self.lincId, port)
447 self.write_to_cli(cmd)
448
449 def w_port_down(self, port):
450 '''
451 port_down
452 '''
453 cmd = "linc:port_down({},{}).\r\n".format(self.lincId, port)
454 self.write_to_cli(cmd)
455
456 # helper functions
457 @staticmethod
Ayaka Koshibea879a042015-11-19 17:04:03 -0800458 def bootOE(net, domain=None):
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800459 """
460 Start the LINC optical emulator within a mininet instance
461
462 This involves 1. converting the information stored in Linc* to configs
463 for both LINC and the network config system, 2. starting Linc, 3. connecting
464 cross-connects, and finally pushing the network configs to ONOS.
Ayaka Koshibea879a042015-11-19 17:04:03 -0800465
466 Inevitably, there are times when we have OVS switches that should not be
467 under the control of the controller in charge of the Linc switches. We
468 hint at these by passing domain information.
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800469 """
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700470 LINCSwitch.opticalJSON = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000471 linkConfig = []
472 devices = []
473 #setting up the controllers for LINCSwitch class
474 LINCSwitch.controllers = net.controllers
475
476 for switch in net.switches:
Ayaka Koshibea879a042015-11-19 17:04:03 -0800477 if domain and switch not in domain:
478 continue
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000479 if isinstance(switch, OpticalSwitch):
480 devices.append(switch.json())
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800481 elif isinstance(switch, OVSSwitch):
Ching-Ting Sun60445dd2017-01-05 23:56:40 -0800482 devices.append(switchJSON(switch))
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700483 LINCSwitch.opticalJSON[ 'devices' ] = devices
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000484
485 for link in net.links:
486 if isinstance(link, LINCLink) :
487 linkConfig.append(link.json())
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700488 LINCSwitch.opticalJSON[ 'links' ] = linkConfig
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000489
490 info('*** Writing Topology.json file\n')
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700491 topoJSON = LINCSwitch.makeTopoJSON()
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000492 with open('Topology.json', 'w') as outfile:
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700493 json.dump(topoJSON, outfile, indent=4, separators=(',', ': '))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000494
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700495 info('*** Converting Topology.json to linc-oe format (TopoConfig.json) file (no oecfg) \n')
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800496
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700497 topoConfigJson = {}
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700498
Yuta HIGUCHI991caf92017-06-02 11:12:03 -0700499 topoConfigJson["switchConfig"] = getSwitchConfig(net.switches)
Ching-Ting Sun60445dd2017-01-05 23:56:40 -0800500 topoConfigJson["linkConfig"] = getLinkConfig(net.links)
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700501
502 #Writing to TopoConfig.json
503 with open( 'TopoConfig.json', 'w' ) as outfile:
504 json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000505
506 info('*** Creating sys.config...\n')
507 output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
508 % (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
509 if output:
510 error('***ERROR: Error creating sys.config file: %s\n' % output)
511 return False
512
513 info ('*** Setting multiple controllers in sys.config...\n')
514 searchStr = '\[{"Switch.*$'
515 ctrlStr = ''
516 for index in range(len(LINCSwitch.controllers)):
517 ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
518 replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
519 sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
520 output = quietRun(sedCmd, shell=True)
521
522 info('*** Copying sys.config to linc-oe directory: ', output + '\n')
523 output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
524 info(output + '\n')
525
526 info('*** Adding taps and bringing them up...\n')
Ching-Ting Sun60445dd2017-01-05 23:56:40 -0800527 setupInts(LINCSwitch.getTaps())
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000528
529 info('*** removing pipes if any \n')
530 quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
531
532 info('*** Starting linc OE...\n')
533 output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
534 if output:
535 error('***ERROR: LINC-OE: %s' % output + '\n')
536 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
537 return False
538
539 info('*** Waiting for linc-oe to start...\n')
Ching-Ting Sun60445dd2017-01-05 23:56:40 -0800540 waitStarted(net)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000541
542 info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
543 for link in net.links:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800544 if isinstance(link, LINCLink) and link.isCrossConnect():
545 for intf in [ link.intf1, link.intf2 ]:
546 if not isinstance(intf, LINCIntf):
547 intfList = [ intf.link.intf1, intf.link.intf2 ]
548 intfList.remove(intf)
549 intf2 = intfList[ 0 ]
550 intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000551
552 info('*** Waiting for all devices to be available in ONOS...\n')
553 url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
554 time = 0
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700555 # Set up password authentication
556 pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
Ayaka Koshibeb0d70582015-09-11 11:29:36 -0700557 pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
Ayaka Koshibec9eed382015-09-03 14:38:55 -0700558 handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
559 opener = urllib2.build_opener(handler)
560 opener.open(url)
561 urllib2.install_opener(opener)
Ayaka Koshibe143b1c72015-11-18 17:19:04 -0800562 # focus on just checking the state of devices we're interested in
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800563 # expected devices availability map
564 devMap = dict.fromkeys(map( lambda x: x['uri'], devices ), False)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000565 while True:
566 response = json.load(urllib2.urlopen(url))
567 devs = response.get('devices')
568
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800569 # update availability map
570 for d in devs:
571 if devMap.has_key(d['id']):
572 devMap[d['id']] = d['available']
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000573
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800574 # Check if all devices we're interested became available
575 if all(devMap.viewvalues()):
576 break;
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000577
578 if (time >= TIMEOUT):
579 error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
580 break
581
582 time += SLEEP_TIME
583 sleep(SLEEP_TIME)
584
585 info('*** Pushing Topology.json to ONOS\n')
586 for index in range(len(LINCSwitch.controllers)):
Devin Lim0d944e22017-06-23 15:17:53 -0700587 output = quietRun('%s/onos-netcfg %s Topology.json &'\
588 % (LINCSwitch.runPackDir, LINCSwitch.controllers[ index ].ip), shell=True)
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000589 # successful output contains the two characters '{}'
590 # if there is more output than this, there is an issue
591 if output.strip('{}'):
592 warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
593
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700594
Nikhil Cheerlaf9391e12015-07-21 10:49:12 -0700595 @staticmethod
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700596 def makeTopoJSON():
597 """
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800598 Builds ONOS network config system compatible dicts to be written as Topology.json file.
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700599 """
600 topology = {}
601 links = {}
602 devices = {}
603 ports = {}
HIGUCHI Yuta3fd2f942016-01-16 20:26:47 -0800604 BasicDevConfigKeys = ['name', 'type', 'latitude', 'longitude', 'allowed',
605 'rackAddress', 'owner', 'driver', 'manufacturer',
606 'hwVersion', 'swVersion', 'serial',
607 'managementAddress']
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700608
609 for switch in LINCSwitch.opticalJSON[ 'devices' ]:
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800610 # Build device entries - keyed on uri (DPID) and config key 'basic'
611 # 'type' is necessary field, else ONOS assumes it's a SWITCH
612 # Annotations hold switch name and latitude/longitude
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700613 devDict = {}
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700614 devDict[ 'type' ] = switch[ 'type' ]
HIGUCHI Yuta3fd2f942016-01-16 20:26:47 -0800615 devDict.update({k: v for k, v in switch[ 'annotations' ].iteritems() if k in BasicDevConfigKeys})
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700616 devSubj = switch[ 'uri' ]
617 devices[ devSubj ] = { 'basic': devDict }
618
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800619 # Build port entries - keyed on "uri/port" and config key 'optical'
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700620 for port in switch[ 'ports' ]:
621 portSubj = devSubj + '/' + str(port[ 'port' ])
622 ports[ portSubj ] = { 'optical': port }
623
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800624 # Build link entries - keyed on "uri/port-uri/port" and config key 'basic'
625 # Annotations hold the 'durable' field, which is necessary as long as we don't discover optical links
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700626 for link in LINCSwitch.opticalJSON[ 'links' ]:
627 linkDict = {}
628 linkDict[ 'type' ] = link[ 'type' ]
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800629 linkDict.update(link[ 'annotations' ])
Ayaka Koshibed88b81d2015-09-17 17:52:27 -0700630 linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
631 links[ linkSubj ] = { 'basic': linkDict }
632
633 topology[ 'links' ] = links
634 topology[ 'devices' ] = devices
635 topology[ 'ports' ] = ports
636
637 return topology
638
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000639
640 @staticmethod
641 def shutdownOE():
642 "stop the optical emulator"
643 info('*** Stopping linc OE...\n')
644 quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
645
646 @staticmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000647 def getTaps(path=None):
648 '''
Marc De Leenheer8b5aae82015-12-22 11:27:01 -0800649 return list of all the taps in sys.config
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000650 '''
651 if path is None:
652 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
653 fd = open(path, 'r', 0)
654 sys_data = fd.read()
655 taps = re.findall('tap\d+', sys_data)
656 fd.close()
657 return taps
658
659 @staticmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000660 def findTap(node, port, path=None):
661 '''utility function to parse through a sys.config
662 file to find tap interfaces for a switch'''
663 switch = False
664 portLine = ''
665 intfLines = []
666
667 if path is None:
668 path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
669
670 with open(path) as f:
671 for line in f:
672 if 'tap' in line:
673 intfLines.append(line)
674 if node.dpid in line.translate(None, ':'):
675 switch = True
676 continue
677 if switch:
678 if 'switch' in line:
679 switch = False
680 if 'port_no,%s}' % port in line:
681 portLine = line
682 break
683
684 if portLine:
685 m = re.search('port,\d+', portLine)
686 port = m.group(0).split(',')[ 1 ]
687 else:
688 error('***ERROR: Could not find any ports in sys.config\n')
689 return
690
691 for intfLine in intfLines:
692 if 'port,%s' % port in intfLine:
693 return re.findall('tap\d+', intfLine)[ 0 ]
694
695 def json(self):
696 "return json configuration dictionary for switch"
697 return self.configDict
698
699 def terminate(self):
700 pass
701
Nikhil Cheerla7d7f3be2015-07-09 13:13:40 -0700702
703
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000704class LINCLink(Link):
705 """
706 LINC link class
707 """
708 def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800709 intfName1=None, intfName2=None, linkType='OPTICAL',
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000710 annotations={}, speed1=0, speed2=0, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800711 "Creates a dummy link without a virtual ethernet pair."
712 self.allowed = allowed
713 self.annotations = annotations
714 self.linkType = linkType
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000715 self.port1 = port1
716 self.port2 = port2
Brian O'Connoreb27c452014-12-07 02:43:58 -0800717 params1 = { 'speed': speed1 }
718 params2 = { 'speed': speed2 }
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000719 if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800720 self.isXC = False
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000721 else:
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800722 self.isXC = True
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000723 if isinstance(node1, LINCSwitch):
724 cls1 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800725 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000726 node1.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800727 else:
728 cls1 = Intf
HIGUCHI Yutad95f3cd2015-12-15 15:05:22 -0800729 # 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 -0800730 # and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
731 intfName1 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000732 if isinstance(node2, LINCSwitch):
733 cls2 = LINCIntf
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800734 if self.isXC:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000735 node2.crossConnects.append(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800736 else:
737 cls2 = Intf
738 intfName2 = 'lo'
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000739 Link.__init__(self, node1, node2, port1=port1, port2=port2,
Brian O'Connoreb27c452014-12-07 02:43:58 -0800740 intfName1=intfName1, intfName2=intfName2, cls1=cls1,
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000741 cls2=cls2, params1=params1, params2=params2)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800742
743 @classmethod
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000744 def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800745 pass
746
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000747 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800748 "build and return the json configuration dictionary for this link"
749 configData = {}
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000750 configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
751 '/%s' % self.intf1.node.ports[ self.intf1 ])
752 configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
753 '/%s' % self.intf2.node.ports[ self.intf2 ])
Brian O'Connoreb27c452014-12-07 02:43:58 -0800754 configData[ 'type' ] = self.linkType
755 configData[ 'annotations' ] = self.annotations
756 return configData
757
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800758 def isCrossConnect(self):
759 if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
760 return True
761
762 return False
763
764 def isTransportLayer(self):
765 if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
766 return True
767
768 return False
769
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000770class LINCIntf(OpticalIntf):
771 """
772 LINC interface class
773 """
774 def __init__(self, name=None, node=None, speed=0,
775 port=None, link=None, **params):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800776 self.node = node
777 self.speed = speed
778 self.port = port
779 self.link = link
780 self.name = name
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000781 node.addIntf(self, port=port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800782 self.params = params
783 self.ip = None
784
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000785 def json(self):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800786 "build and return the JSON information for this interface( not used right now )"
787 configDict = {}
788 configDict[ 'port' ] = self.port
789 configDict[ 'speed' ] = self.speed
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700790 portType = 'COPPER'
791 if isinstance(self.link, LINCLink):
Marc De Leenheer32fc3d22015-12-15 21:36:39 -0800792 portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
Ayaka Koshibe144bab02015-10-22 12:56:59 -0700793 configDict[ 'type' ] = portType
Brian O'Connoreb27c452014-12-07 02:43:58 -0800794 return configDict
795
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000796 def config(self, *args, **kwargs):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800797 "dont configure a dummy interface"
798 pass
799
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000800 def ifconfig(self, status):
801 "configure the status"
802 if status == "up":
803 return self.node.w_port_up(self.port)
804 elif status == "down":
805 return self.node.w_port_down(self.port)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800806
807
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000808class MininetOE(Mininet):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800809 "Mininet with Linc-OE support (starts and stops linc-oe)"
810
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000811 def start(self):
812 Mininet.start(self)
813 LINCSwitch.bootOE(self)
Brian O'Connoreb27c452014-12-07 02:43:58 -0800814
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000815 def stop(self):
816 Mininet.stop(self)
817 LINCSwitch.shutdownOE()
Brian O'Connoreb27c452014-12-07 02:43:58 -0800818
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000819 def addControllers(self, controllers):
Brian O'Connoreb27c452014-12-07 02:43:58 -0800820 i = 0
821 for ctrl in controllers:
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000822 self.addController(RemoteController('c%d' % i, ip=ctrl))
823 i += 1
Brian O'Connoreb27c452014-12-07 02:43:58 -0800824
825if __name__ == '__main__':
Marc De Leenheer6ff97642015-07-08 19:21:16 +0000826 pass