mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-14 17:01:02 +02:00
827 lines
30 KiB
Python
827 lines
30 KiB
Python
#!/usr/bin/python
|
|
|
|
'''
|
|
Notes:
|
|
|
|
This file contains classes and methods useful for integrating LincOE with Mininet,
|
|
such as startOE, stopOE, LINCLink, and OpticalSwitch
|
|
|
|
- $ONOS_ROOT ust be set
|
|
- Need to run with sudo -E to preserve ONOS_ROOT env var
|
|
- We assume LINC-Config-Generator is named LINC-Config-Generator
|
|
- We also assume linc-oe is named linc-oe
|
|
- LINC-config-generator and linc-oe must be subdirectories of the user's
|
|
home directory
|
|
|
|
TODO
|
|
-----------
|
|
- clean up files after runtime
|
|
- maybe save the old files in a separate directory?
|
|
- modify script to allow startOE to run before net.start()
|
|
- add ONOS as a controller in script
|
|
|
|
Usage:
|
|
------------
|
|
- import LINCLink and OpticalSwitch from this module
|
|
- import startOE and stopOE from this module
|
|
- create topology as you would a normal topology. when
|
|
to an optical switch with topo.addLink, always specify cls=LINCLink
|
|
- when creating an optical switch, use cls=OpticalSwitch in topo.addSwitch
|
|
- for annotations on links and switches, a dictionary must be passed in as
|
|
the annotations argument
|
|
- startOE must be run AFTER net.start() with net as an argument.
|
|
- stopOE can be run at any time
|
|
|
|
I created a separate function to start lincOE to avoid subclassing Mininet.
|
|
In case anyone wants to write something that DOES subclass Mininet, I
|
|
thought I would outline how:
|
|
|
|
If we want an object that starts lincOE within the mininet class itself,
|
|
we need to add another object to Mininet that contains all of the json object
|
|
information for each switch. We would still subclass switch and link, but these
|
|
classes would basically be dummy classes that store their own json information
|
|
in the Mininet class object. We may also change the default switch class to add
|
|
it's tap interfaces from lincOE during startup. The start() method for mininet would
|
|
grab all of the information from these switches and links, write configuration files
|
|
for lincOE using the json module, start lincOE, then run the start methodfor each
|
|
switch. The new start() method for each switch would parse through the sys.config
|
|
file that was created and find the tap interface it needs to connect to, similar
|
|
to the findTap function that I currently use. After all of the controllers and
|
|
switches have been started, the new Mininet start() method should also push the
|
|
Topology configuration file to ONOS.
|
|
|
|
'''
|
|
import sys
|
|
import re
|
|
import json
|
|
import os
|
|
from time import sleep
|
|
import urllib2
|
|
|
|
from mininet.node import Switch, OVSSwitch, RemoteController
|
|
from mininet.topo import Topo
|
|
from mininet.util import quietRun
|
|
from mininet.net import Mininet
|
|
from mininet.log import setLogLevel, info, error, warn
|
|
from mininet.link import Link, Intf
|
|
from mininet.cli import CLI
|
|
|
|
# Sleep time and timeout values in seconds
|
|
SLEEP_TIME = 2
|
|
TIMEOUT = 60
|
|
|
|
### method, mapping dpid to LINC switchId ###
|
|
def dpids_to_ids(sysConfig):
|
|
'''
|
|
return the dict containing switch dpids as key and LINC switch id as values
|
|
'''
|
|
dpids_to_ids = {}
|
|
fd = None
|
|
try:
|
|
with open(sysConfig, 'r', 0) as fd:
|
|
switch_id = 1
|
|
for line in fd:
|
|
dpid = re.search(r'([0-9A-Fa-f]{2}[:-]){7}([0-9A-Fa-f]{2})+', line, re.I)
|
|
if dpid:
|
|
dpids_to_ids[dpid.group().replace(':', '')] = switch_id
|
|
switch_id += 1
|
|
return dpids_to_ids
|
|
except:
|
|
print "Error working with {}\nError: {}\n".format(sysConfig, sys.exc_info())
|
|
fd.close()
|
|
return None
|
|
|
|
def findDir(directory, userName):
|
|
"finds and returns the path of any directory in the user's home directory"
|
|
homeDir = '/home/' + userName
|
|
Dir = quietRun('find %s -maxdepth 1 -name %s -type d' % (homeDir, directory)).strip('\n')
|
|
DirList = Dir.split('\n')
|
|
if not Dir:
|
|
return None
|
|
elif len(DirList) > 1 :
|
|
warn('***WARNING: Found multiple instances of %s; using %s\n'
|
|
% (directory, DirList[ 0 ]))
|
|
return DirList[ 0 ]
|
|
else:
|
|
return Dir
|
|
|
|
def switchJSON(switch):
|
|
"Returns the json configuration for a packet switch"
|
|
configDict = {}
|
|
configDict[ 'uri' ] = 'of:' + switch.dpid
|
|
configDict[ 'type' ] = 'SWITCH'
|
|
annotations = switch.params.get('annotations', {})
|
|
annotations.setdefault('name', switch.name)
|
|
configDict[ 'annotations' ] = annotations
|
|
ports = []
|
|
for port, intf in switch.intfs.items():
|
|
if intf.name == 'lo':
|
|
continue
|
|
portDict = {}
|
|
portDict[ 'port' ] = port
|
|
portType = 'COPPER'
|
|
if isinstance(intf.link, LINCLink):
|
|
portType = 'OCH' if intf.link.isCrossConnect() else 'OMS'
|
|
portDict[ 'type' ] = portType
|
|
intfList = [ intf.link.intf1, intf.link.intf2 ]
|
|
intfList.remove(intf)
|
|
portDict[ 'speed' ] = intfList[ 0 ].speed if isinstance(intf.link, LINCLink) else 0
|
|
ports.append(portDict)
|
|
configDict[ 'ports' ] = ports
|
|
return configDict
|
|
|
|
def dpId(id):
|
|
nodeDpid = ""
|
|
id = id.split("/", 1)[0]
|
|
for i in range(3, len(id) - 1, 2):
|
|
nodeDpid += (id[i:(i + 2):]) + ":"
|
|
return nodeDpid[0:-1]
|
|
|
|
def getSwitchConfig(switches):
|
|
switchConfig = []
|
|
|
|
# Iterate through all switches and convert the ROADM switches to linc-oe format
|
|
for switch in switches:
|
|
if isinstance(switch, LINCSwitch):
|
|
builtSwitch = {}
|
|
|
|
# Set basic switch params based on annotations
|
|
builtSwitch["allowed"] = True
|
|
builtSwitch["latitude"] = switch.annotations.get("latitude", 0.0)
|
|
builtSwitch["longitude"] = switch.annotations.get("longitude", 0.0)
|
|
|
|
# Convert dpid to linc-oe format
|
|
builtSwitch["name"] = switch.name
|
|
builtSwitch["nodeDpid"] = dpId('of:' + switch.dpid)
|
|
|
|
# Set switch params and type
|
|
builtSwitch["params"] = {}
|
|
builtSwitch["params"]["numregens"] = switch.annotations.get("optical.regens", 0)
|
|
builtSwitch["type"] = "Roadm"
|
|
|
|
switchConfig.append(builtSwitch)
|
|
|
|
return switchConfig
|
|
|
|
def getLinkConfig(links):
|
|
linkConfig = []
|
|
|
|
# Iterate through all non-edge links and convert them to linc-oe format
|
|
for link in links:
|
|
if isinstance(link, LINCLink):
|
|
builtLink = {}
|
|
|
|
# Set basic link params for src and dst
|
|
builtLink["allowed"] = True
|
|
builtLink["nodeDpid1"] = dpId('of:' + link.intf1.node.dpid)
|
|
builtLink["nodeDpid2"] = dpId('of:' + link.intf2.node.dpid)
|
|
|
|
# Set more params such as name/bandwidth/port if they exist
|
|
params = {}
|
|
params["nodeName1"] = link.intf1.node.name
|
|
params["nodeName2"] = link.intf2.node.name
|
|
|
|
params["port1"] = link.port1
|
|
params["port2"] = link.port2
|
|
|
|
if "bandwidth" in link.annotations:
|
|
params["bandwidth"] = link.annotations["bandwidth"]
|
|
|
|
builtLink["params"] = params
|
|
|
|
# Set link type to WDM or packet (LINC-config-generator relies on it)
|
|
if link.isTransportLayer():
|
|
builtLink["type"] = "wdmLink"
|
|
else:
|
|
builtLink["type"] = "pktOptLink"
|
|
|
|
linkConfig.append(builtLink)
|
|
|
|
return linkConfig
|
|
|
|
def waitStarted(net, timeout=TIMEOUT):
|
|
"wait until all tap interfaces are available"
|
|
tapCount = 0
|
|
time = 0
|
|
for link in net.links:
|
|
if isinstance(link, LINCLink) and link.isCrossConnect():
|
|
tapCount += 1
|
|
|
|
while True:
|
|
# tapCount can be less than the actual number of taps if the optical network
|
|
# is a subgraph of a larger multidomain network.
|
|
tapNum = int(quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'))
|
|
if tapCount <= tapNum:
|
|
return True
|
|
if timeout:
|
|
if time >= TIMEOUT:
|
|
error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
|
|
return False
|
|
time += SLEEP_TIME
|
|
sleep(SLEEP_TIME)
|
|
|
|
def setupInts(intfs):
|
|
'''
|
|
add taps and bring them up.
|
|
'''
|
|
for i in intfs:
|
|
quietRun('ip tuntap add dev %s mode tap' % i)
|
|
quietRun('ip link set dev %s up' % i)
|
|
info('*** Intf %s set\n' % i)
|
|
|
|
class OpticalSwitch(Switch):
|
|
"""
|
|
For now, same as Switch class.
|
|
"""
|
|
pass
|
|
|
|
class OpticalIntf(Intf):
|
|
"""
|
|
For now,same as Intf class.
|
|
"""
|
|
pass
|
|
|
|
class OpticalLink(Link):
|
|
"""
|
|
For now, same as Link.
|
|
"""
|
|
pass
|
|
|
|
class LINCSwitch(OpticalSwitch):
|
|
"""
|
|
LINCSwitch class
|
|
"""
|
|
# FIXME:Sometimes LINC doesn't remove pipes and on restart increase the pipe
|
|
# number from erlang.pipe.1.* to erlang.pipe.2.*, so should read and write
|
|
# from latest pipe files. For now we are removing all the pipes before
|
|
# starting LINC.
|
|
### User Name ###
|
|
user = os.getlogin()
|
|
### pipes ###
|
|
readPipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.r".format(user)
|
|
writePipe = "/tmp/home/{}/linc-oe/rel/linc/erlang.pipe.1.w".format(user)
|
|
### sys.config path ###
|
|
sysConfig = "/home/{}/linc-oe/rel/linc/releases/1.0/sys.config".format(user)
|
|
### dict of containing dpids as key and corresponding LINC switchId as values ###
|
|
dpidsToLINCSwitchId = dpids_to_ids(sysConfig)
|
|
|
|
### ONOS Directory ###
|
|
try:
|
|
onosDir = os.environ[ 'ONOS_ROOT' ]
|
|
except:
|
|
onosDir = findDir('onos', user)
|
|
if not onosDir:
|
|
error('Please set ONOS_ROOT environment variable!\n')
|
|
else:
|
|
os.environ[ 'ONOS_ROOT' ] = onosDir
|
|
### ONOS-netcfg directory ###
|
|
try:
|
|
runPackDir = os.environ[ 'RUN_PACK_PATH' ]
|
|
except:
|
|
runPackDir = onosDir+"/tools/package/runtime/bin"
|
|
os.environ[ 'RUN_PACK_PATH' ] = runPackDir
|
|
### REST USER/PASS ###
|
|
try:
|
|
restUser = os.environ[ 'ONOS_WEB_USER' ]
|
|
restPass = os.environ[ 'ONOS_WEB_PASS' ]
|
|
except:
|
|
error('***WARNING: $ONOS_WEB_USER and $ONOS_WEB_PASS aren\'t set!\n')
|
|
error('***WARNING: Setting (probably) sane WEB user/pass values\n')
|
|
restUser = 'onos'
|
|
restPass = 'rocks'
|
|
os.environ[ 'ONOS_WEB_USER' ] = restUser
|
|
os.environ[ 'ONOS_WEB_PASS' ] = restPass
|
|
### LINC-directory
|
|
lincDir = findDir('linc-oe', user)
|
|
if not lincDir:
|
|
error("***ERROR: Could not find linc-oe in user's home directory\n")
|
|
### LINC config generator directory###
|
|
configGen = findDir('LINC-config-generator', user)
|
|
if not configGen:
|
|
error("***ERROR: Could not find LINC-config-generator in user's home directory\n")
|
|
# list of all the controllers
|
|
controllers = None
|
|
def __init__(self, name, dpid=None, allowed=True,
|
|
switchType='ROADM', topo=None, annotations={}, controller=None, **params):
|
|
params[ 'inNamespace' ] = False
|
|
Switch.__init__(self, name, dpid=dpid, **params)
|
|
self.name = name
|
|
self.annotations = annotations
|
|
self.allowed = allowed
|
|
self.switchType = switchType
|
|
self.configDict = {} # dictionary that holds all of the JSON configuration data
|
|
self.crossConnects = []
|
|
self.deletedCrossConnects = []
|
|
self.controller = controller
|
|
self.lincId = self._get_linc_id() # use to communicate with LINC
|
|
self.lincStarted = False
|
|
|
|
def start(self, *opts, **params):
|
|
'''Instead of starting a virtual switch, we build the JSON
|
|
dictionary for the emulated optical switch'''
|
|
# TODO:Once LINC has the ability to spawn network element dynamically
|
|
# we need to use this method to spawn new logical LINC switch rather then
|
|
# bulding JSON.
|
|
# if LINC is started then we can start and stop logical switches else create JSON
|
|
if self.lincStarted:
|
|
return self.start_oe()
|
|
self.configDict[ 'uri' ] = 'of:' + self.dpid
|
|
self.configDict[ 'annotations' ] = self.annotations
|
|
self.configDict[ 'annotations' ].setdefault('name', self.name)
|
|
self.configDict[ 'type' ] = self.switchType
|
|
self.configDict[ 'ports' ] = []
|
|
for port, intf in self.intfs.items():
|
|
if intf.name == 'lo':
|
|
continue
|
|
else:
|
|
self.configDict[ 'ports' ].append(intf.json())
|
|
self.lincStarted = True
|
|
|
|
def stop(self, deleteIntfs=False):
|
|
'''
|
|
stop the existing switch
|
|
'''
|
|
# TODO:Add support for deleteIntf
|
|
self.stop_oe()
|
|
|
|
def dpctl( self, *args ):
|
|
"Run dpctl command: ignore for now"
|
|
pass
|
|
|
|
def write_to_cli(self, command):
|
|
'''
|
|
send command to LINC
|
|
'''
|
|
fd = None
|
|
try:
|
|
fd = open(self.writePipe, 'w', 0)
|
|
fd.write(command)
|
|
fd.close()
|
|
except:
|
|
print "Error working with {}\nError: {}\n".format(self.writePipe, sys.exc_info())
|
|
if fd:
|
|
fd.close()
|
|
|
|
def read_from_cli(self):
|
|
'''
|
|
read the output from the LINC CLI
|
|
'''
|
|
response = None
|
|
fd = None
|
|
try:
|
|
fd = open(self.readPipe, 'r', 0)
|
|
fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # for non-blocking read
|
|
# FIXME:Due to non-blocking read most for the time we read nothing
|
|
response = fd.read()
|
|
fd.close()
|
|
except :
|
|
# print "Error working with {}\nError: {}\n".format(self.readPipe, sys.exc_info())
|
|
if fd:
|
|
fd.close()
|
|
return response
|
|
|
|
def _get_linc_id(self):
|
|
'''
|
|
return the corresponding LINC switchId.
|
|
'''
|
|
return LINCSwitch.dpidsToLINCSwitchId.get(self.dpid)
|
|
#--------------------------------------------------------------------------
|
|
# LINC CLI commands
|
|
#--------------------------------------------------------------------------
|
|
def start_oe(self):
|
|
'''
|
|
existing LINC switch
|
|
'''
|
|
#starting Switch
|
|
cmd = "linc:start_switch({}).\r\n".format(self.lincId)
|
|
self.write_to_cli(cmd)
|
|
#hanlding taps interfaces related to the switch
|
|
crossConnectJSON = {}
|
|
linkConfig = []
|
|
for i in range(0,len(self.deletedCrossConnects)):
|
|
crossConnect = self.deletedCrossConnects.pop()
|
|
tap = None
|
|
if isinstance(crossConnect.intf1.node, LINCSwitch):
|
|
intf = crossConnect.intf2
|
|
tapPort = crossConnect.intf1.port
|
|
else:
|
|
intf = crossConnect.intf1
|
|
tapPort = crossConnect.intf2.port
|
|
tap = LINCSwitch.findTap(self, tapPort)
|
|
if tap:
|
|
setupInts([tap])
|
|
intf.node.attach(tap)
|
|
self.crossConnects.append(crossConnect)
|
|
linkConfig.append(crossConnect.json())
|
|
#Sending crossConnect info to the ONOS.
|
|
crossConnectJSON['links'] = linkConfig
|
|
with open("crossConnect.json", 'w') as fd:
|
|
json.dump(crossConnectJSON, fd, indent=4, separators=(',', ': '))
|
|
info('*** Pushing crossConnect.json to ONOS\n')
|
|
output = quietRun('%s/onos-netcfg %s\
|
|
Topology.json' % (self.runPackDir, self.controllers[ 0 ].ip), shell=True)
|
|
|
|
def stop_oe(self):
|
|
'''
|
|
stop the existing LINC switch
|
|
'''
|
|
cmd = "linc:stop_switch({}).\r\n".format(self.lincId)
|
|
self.write_to_cli(cmd)
|
|
#handling taps if any
|
|
for i in range(0, len(self.crossConnects)):
|
|
crossConnect = self.crossConnects.pop()
|
|
if isinstance(crossConnect.intf1.node, LINCSwitch):
|
|
intf = crossConnect.intf2
|
|
tapPort = crossConnect.intf1.port
|
|
else:
|
|
intf = crossConnect.intf1
|
|
tapPort = crossConnect.intf2.port
|
|
intf.node.detach(LINCSwitch.findTap(self, tapPort))
|
|
self.deletedCrossConnects.append(crossConnect)
|
|
|
|
def w_port_up(self, port):
|
|
'''
|
|
port_up
|
|
'''
|
|
cmd = "linc:port_up({},{}).\r\n".format(self.lincId, port)
|
|
self.write_to_cli(cmd)
|
|
|
|
def w_port_down(self, port):
|
|
'''
|
|
port_down
|
|
'''
|
|
cmd = "linc:port_down({},{}).\r\n".format(self.lincId, port)
|
|
self.write_to_cli(cmd)
|
|
|
|
# helper functions
|
|
@staticmethod
|
|
def bootOE(net, domain=None):
|
|
"""
|
|
Start the LINC optical emulator within a mininet instance
|
|
|
|
This involves 1. converting the information stored in Linc* to configs
|
|
for both LINC and the network config system, 2. starting Linc, 3. connecting
|
|
cross-connects, and finally pushing the network configs to ONOS.
|
|
|
|
Inevitably, there are times when we have OVS switches that should not be
|
|
under the control of the controller in charge of the Linc switches. We
|
|
hint at these by passing domain information.
|
|
"""
|
|
LINCSwitch.opticalJSON = {}
|
|
linkConfig = []
|
|
devices = []
|
|
#setting up the controllers for LINCSwitch class
|
|
LINCSwitch.controllers = net.controllers
|
|
|
|
for switch in net.switches:
|
|
if domain and switch not in domain:
|
|
continue
|
|
if isinstance(switch, OpticalSwitch):
|
|
devices.append(switch.json())
|
|
elif isinstance(switch, OVSSwitch):
|
|
devices.append(switchJSON(switch))
|
|
LINCSwitch.opticalJSON[ 'devices' ] = devices
|
|
|
|
for link in net.links:
|
|
if isinstance(link, LINCLink) :
|
|
linkConfig.append(link.json())
|
|
LINCSwitch.opticalJSON[ 'links' ] = linkConfig
|
|
|
|
info('*** Writing Topology.json file\n')
|
|
topoJSON = LINCSwitch.makeTopoJSON()
|
|
with open('Topology.json', 'w') as outfile:
|
|
json.dump(topoJSON, outfile, indent=4, separators=(',', ': '))
|
|
|
|
info('*** Converting Topology.json to linc-oe format (TopoConfig.json) file (no oecfg) \n')
|
|
|
|
topoConfigJson = {}
|
|
|
|
topoConfigJson["switchConfig"] = getSwitchConfig(net.switches)
|
|
topoConfigJson["linkConfig"] = getLinkConfig(net.links)
|
|
|
|
#Writing to TopoConfig.json
|
|
with open( 'TopoConfig.json', 'w' ) as outfile:
|
|
json.dump( topoConfigJson, outfile, indent=4, separators=(',', ': ') )
|
|
|
|
info('*** Creating sys.config...\n')
|
|
output = quietRun('%s/config_generator TopoConfig.json %s/sys.config.template %s %s'
|
|
% (LINCSwitch.configGen, LINCSwitch.configGen, LINCSwitch.controllers[ 0 ].ip, LINCSwitch.controllers[ 0 ].port), shell=True)
|
|
if output:
|
|
error('***ERROR: Error creating sys.config file: %s\n' % output)
|
|
return False
|
|
|
|
info ('*** Setting multiple controllers in sys.config...\n')
|
|
searchStr = '\[{"Switch.*$'
|
|
ctrlStr = ''
|
|
for index in range(len(LINCSwitch.controllers)):
|
|
ctrlStr += '{"Switch%d-Controller","%s",%d,tcp},' % (index, net.controllers[index].ip, net.controllers[index].port)
|
|
replaceStr = '[%s]},' % ctrlStr[:-1] # Cut off last comma
|
|
sedCmd = 'sed -i \'s/%s/%s/\' sys.config' % (searchStr, replaceStr)
|
|
output = quietRun(sedCmd, shell=True)
|
|
|
|
info('*** Copying sys.config to linc-oe directory: ', output + '\n')
|
|
output = quietRun('cp -v sys.config %s/rel/linc/releases/1.0/' % LINCSwitch.lincDir, shell=True).strip('\n')
|
|
info(output + '\n')
|
|
|
|
info('*** Adding taps and bringing them up...\n')
|
|
setupInts(LINCSwitch.getTaps())
|
|
|
|
info('*** removing pipes if any \n')
|
|
quietRun('rm /tmp/home/%s/linc-oe/rel/linc/*' % LINCSwitch.user, shell=True)
|
|
|
|
info('*** Starting linc OE...\n')
|
|
output = quietRun('%s/rel/linc/bin/linc start' % LINCSwitch.lincDir, shell=True)
|
|
if output:
|
|
error('***ERROR: LINC-OE: %s' % output + '\n')
|
|
quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
|
|
return False
|
|
|
|
info('*** Waiting for linc-oe to start...\n')
|
|
waitStarted(net)
|
|
|
|
info('*** Adding cross-connect (tap) interfaces to packet switches...\n')
|
|
for link in net.links:
|
|
if isinstance(link, LINCLink) and link.isCrossConnect():
|
|
for intf in [ link.intf1, link.intf2 ]:
|
|
if not isinstance(intf, LINCIntf):
|
|
intfList = [ intf.link.intf1, intf.link.intf2 ]
|
|
intfList.remove(intf)
|
|
intf2 = intfList[ 0 ]
|
|
intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
|
|
|
|
info('*** Waiting for all devices to be available in ONOS...\n')
|
|
url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
|
|
time = 0
|
|
# Set up password authentication
|
|
pw_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
|
pw_mgr.add_password(None, url, LINCSwitch.restUser, LINCSwitch.restPass)
|
|
handler = urllib2.HTTPBasicAuthHandler(pw_mgr)
|
|
opener = urllib2.build_opener(handler)
|
|
opener.open(url)
|
|
urllib2.install_opener(opener)
|
|
# focus on just checking the state of devices we're interested in
|
|
# expected devices availability map
|
|
devMap = dict.fromkeys(map( lambda x: x['uri'], devices ), False)
|
|
while True:
|
|
response = json.load(urllib2.urlopen(url))
|
|
devs = response.get('devices')
|
|
|
|
# update availability map
|
|
for d in devs:
|
|
if devMap.has_key(d['id']):
|
|
devMap[d['id']] = d['available']
|
|
|
|
# Check if all devices we're interested became available
|
|
if all(devMap.viewvalues()):
|
|
break;
|
|
|
|
if (time >= TIMEOUT):
|
|
error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
|
|
break
|
|
|
|
time += SLEEP_TIME
|
|
sleep(SLEEP_TIME)
|
|
|
|
info('*** Pushing Topology.json to ONOS\n')
|
|
for index in range(len(LINCSwitch.controllers)):
|
|
output = quietRun('%s/onos-netcfg %s Topology.json &'\
|
|
% (LINCSwitch.runPackDir, LINCSwitch.controllers[ index ].ip), shell=True)
|
|
# successful output contains the two characters '{}'
|
|
# if there is more output than this, there is an issue
|
|
if output.strip('{}'):
|
|
warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
|
|
|
|
|
|
@staticmethod
|
|
def makeTopoJSON():
|
|
"""
|
|
Builds ONOS network config system compatible dicts to be written as Topology.json file.
|
|
"""
|
|
topology = {}
|
|
links = {}
|
|
devices = {}
|
|
ports = {}
|
|
BasicDevConfigKeys = ['name', 'type', 'latitude', 'longitude', 'allowed',
|
|
'rackAddress', 'owner', 'driver', 'manufacturer',
|
|
'hwVersion', 'swVersion', 'serial',
|
|
'managementAddress']
|
|
|
|
for switch in LINCSwitch.opticalJSON[ 'devices' ]:
|
|
# Build device entries - keyed on uri (DPID) and config key 'basic'
|
|
# 'type' is necessary field, else ONOS assumes it's a SWITCH
|
|
# Annotations hold switch name and latitude/longitude
|
|
devDict = {}
|
|
devDict[ 'type' ] = switch[ 'type' ]
|
|
devDict.update({k: v for k, v in switch[ 'annotations' ].iteritems() if k in BasicDevConfigKeys})
|
|
devSubj = switch[ 'uri' ]
|
|
devices[ devSubj ] = { 'basic': devDict }
|
|
|
|
# Build port entries - keyed on "uri/port" and config key 'optical'
|
|
for port in switch[ 'ports' ]:
|
|
portSubj = devSubj + '/' + str(port[ 'port' ])
|
|
ports[ portSubj ] = { 'optical': port }
|
|
|
|
# Build link entries - keyed on "uri/port-uri/port" and config key 'basic'
|
|
# Annotations hold the 'durable' field, which is necessary as long as we don't discover optical links
|
|
for link in LINCSwitch.opticalJSON[ 'links' ]:
|
|
linkDict = {}
|
|
linkDict[ 'type' ] = link[ 'type' ]
|
|
linkDict.update(link[ 'annotations' ])
|
|
linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
|
|
links[ linkSubj ] = { 'basic': linkDict }
|
|
|
|
topology[ 'links' ] = links
|
|
topology[ 'devices' ] = devices
|
|
topology[ 'ports' ] = ports
|
|
|
|
return topology
|
|
|
|
|
|
@staticmethod
|
|
def shutdownOE():
|
|
"stop the optical emulator"
|
|
info('*** Stopping linc OE...\n')
|
|
quietRun('%s/rel/linc/bin/linc stop' % LINCSwitch.lincDir, shell=True)
|
|
|
|
@staticmethod
|
|
def getTaps(path=None):
|
|
'''
|
|
return list of all the taps in sys.config
|
|
'''
|
|
if path is None:
|
|
path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
|
|
fd = open(path, 'r', 0)
|
|
sys_data = fd.read()
|
|
taps = re.findall('tap\d+', sys_data)
|
|
fd.close()
|
|
return taps
|
|
|
|
@staticmethod
|
|
def findTap(node, port, path=None):
|
|
'''utility function to parse through a sys.config
|
|
file to find tap interfaces for a switch'''
|
|
switch = False
|
|
portLine = ''
|
|
intfLines = []
|
|
|
|
if path is None:
|
|
path = '%s/rel/linc/releases/1.0/sys.config' % LINCSwitch.lincDir
|
|
|
|
with open(path) as f:
|
|
for line in f:
|
|
if 'tap' in line:
|
|
intfLines.append(line)
|
|
if node.dpid in line.translate(None, ':'):
|
|
switch = True
|
|
continue
|
|
if switch:
|
|
if 'switch' in line:
|
|
switch = False
|
|
if 'port_no,%s}' % port in line:
|
|
portLine = line
|
|
break
|
|
|
|
if portLine:
|
|
m = re.search('port,\d+', portLine)
|
|
port = m.group(0).split(',')[ 1 ]
|
|
else:
|
|
error('***ERROR: Could not find any ports in sys.config\n')
|
|
return
|
|
|
|
for intfLine in intfLines:
|
|
if 'port,%s' % port in intfLine:
|
|
return re.findall('tap\d+', intfLine)[ 0 ]
|
|
|
|
def json(self):
|
|
"return json configuration dictionary for switch"
|
|
return self.configDict
|
|
|
|
def terminate(self):
|
|
pass
|
|
|
|
|
|
|
|
class LINCLink(Link):
|
|
"""
|
|
LINC link class
|
|
"""
|
|
def __init__(self, node1, node2, port1=None, port2=None, allowed=True,
|
|
intfName1=None, intfName2=None, linkType='OPTICAL',
|
|
annotations={}, speed1=0, speed2=0, **params):
|
|
"Creates a dummy link without a virtual ethernet pair."
|
|
self.allowed = allowed
|
|
self.annotations = annotations
|
|
self.linkType = linkType
|
|
self.port1 = port1
|
|
self.port2 = port2
|
|
params1 = { 'speed': speed1 }
|
|
params2 = { 'speed': speed2 }
|
|
if isinstance(node1, LINCSwitch) and isinstance(node2, LINCSwitch):
|
|
self.isXC = False
|
|
else:
|
|
self.isXC = True
|
|
if isinstance(node1, LINCSwitch):
|
|
cls1 = LINCIntf
|
|
if self.isXC:
|
|
node1.crossConnects.append(self)
|
|
else:
|
|
cls1 = Intf
|
|
# bad hack to stop error message from appearing when we try to set up intf in a packet switch,
|
|
# and there is no interface there( because we do not run makeIntfPair ). This way, we just set lo up
|
|
intfName1 = 'lo'
|
|
if isinstance(node2, LINCSwitch):
|
|
cls2 = LINCIntf
|
|
if self.isXC:
|
|
node2.crossConnects.append(self)
|
|
else:
|
|
cls2 = Intf
|
|
intfName2 = 'lo'
|
|
Link.__init__(self, node1, node2, port1=port1, port2=port2,
|
|
intfName1=intfName1, intfName2=intfName2, cls1=cls1,
|
|
cls2=cls2, params1=params1, params2=params2)
|
|
|
|
@classmethod
|
|
def makeIntfPair(_cls, intfName1, intfName2, *args, **kwargs):
|
|
pass
|
|
|
|
def json(self):
|
|
"build and return the json configuration dictionary for this link"
|
|
configData = {}
|
|
configData[ 'src' ] = ('of:' + self.intf1.node.dpid +
|
|
'/%s' % self.intf1.node.ports[ self.intf1 ])
|
|
configData[ 'dst' ] = ('of:' + self.intf2.node.dpid +
|
|
'/%s' % self.intf2.node.ports[ self.intf2 ])
|
|
configData[ 'type' ] = self.linkType
|
|
configData[ 'annotations' ] = self.annotations
|
|
return configData
|
|
|
|
def isCrossConnect(self):
|
|
if isinstance(self.intf1.node, LINCSwitch) ^ isinstance(self.intf2.node, LINCSwitch):
|
|
return True
|
|
|
|
return False
|
|
|
|
def isTransportLayer(self):
|
|
if isinstance(self.intf1.node, LINCSwitch) and isinstance(self.intf2.node, LINCSwitch):
|
|
return True
|
|
|
|
return False
|
|
|
|
class LINCIntf(OpticalIntf):
|
|
"""
|
|
LINC interface class
|
|
"""
|
|
def __init__(self, name=None, node=None, speed=0,
|
|
port=None, link=None, **params):
|
|
self.node = node
|
|
self.speed = speed
|
|
self.port = port
|
|
self.link = link
|
|
self.name = name
|
|
node.addIntf(self, port=port)
|
|
self.params = params
|
|
self.ip = None
|
|
|
|
def json(self):
|
|
"build and return the JSON information for this interface( not used right now )"
|
|
configDict = {}
|
|
configDict[ 'port' ] = self.port
|
|
configDict[ 'speed' ] = self.speed
|
|
portType = 'COPPER'
|
|
if isinstance(self.link, LINCLink):
|
|
portType = 'OCH' if self.link.isCrossConnect() else 'OMS'
|
|
configDict[ 'type' ] = portType
|
|
return configDict
|
|
|
|
def config(self, *args, **kwargs):
|
|
"dont configure a dummy interface"
|
|
pass
|
|
|
|
def ifconfig(self, status):
|
|
"configure the status"
|
|
if status == "up":
|
|
return self.node.w_port_up(self.port)
|
|
elif status == "down":
|
|
return self.node.w_port_down(self.port)
|
|
|
|
|
|
class MininetOE(Mininet):
|
|
"Mininet with Linc-OE support (starts and stops linc-oe)"
|
|
|
|
def start(self):
|
|
Mininet.start(self)
|
|
LINCSwitch.bootOE(self)
|
|
|
|
def stop(self):
|
|
Mininet.stop(self)
|
|
LINCSwitch.shutdownOE()
|
|
|
|
def addControllers(self, controllers):
|
|
i = 0
|
|
for ctrl in controllers:
|
|
self.addController(RemoteController('c%d' % i, ip=ctrl))
|
|
i += 1
|
|
|
|
if __name__ == '__main__':
|
|
pass
|