| #!/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.__func__(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 |
| ### 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.__func__('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.__func__('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/tools/test/bin/onos-netcfg %s\ |
| Topology.json' % (self.onosDir, 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"] = LINCSwitch.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/tools/test/bin/onos-netcfg %s Topology.json &'\ |
| % (LINCSwitch.onosDir, 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 |