mirror of
				https://github.com/opennetworkinglab/onos.git
				synced 2025-10-31 00:01:01 +01:00 
			
		
		
		
	- Fixing opticalUtils.py, which was broken due to additional constraint added by Change-Id: I372e6c7e6c0fa6fa52301568af73342aaae6347b Change-Id: I4233550c9e483448599a2dd501a50309ceb2e4c8
		
			
				
	
	
		
			826 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			826 lines
		
	
	
		
			31 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
 | |
| 
 | |
| 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)
 | |
|     ### method, mapping dpid to LINC switchId ###
 | |
|     @staticmethod
 | |
|     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
 | |
|     ### dict of containing dpids as key and corresponding LINC switchId as values ###
 | |
|     dpidsToLINCSwitchId = dpids_to_ids.__func__(sysConfig)
 | |
|     @staticmethod
 | |
|     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
 | |
|     ### 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:
 | |
|                 LINCSwitch.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 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
 | |
| 
 | |
|     @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(LINCSwitch.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"] = LINCSwitch.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')
 | |
|         LINCSwitch.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')
 | |
|         LINCSwitch.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)
 | |
| 
 | |
|     #converts node ids to linc-oe format, with colons every two chars
 | |
|     @staticmethod
 | |
|     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]
 | |
| 
 | |
|     @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 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"] = LINCSwitch.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
 | |
| 
 | |
|     @staticmethod
 | |
|     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"] = LINCSwitch.dpId('of:' + link.intf1.node.dpid)
 | |
|                 builtLink["nodeDpid2"] = LINCSwitch.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
 | |
| 
 | |
| 
 | |
|     @staticmethod
 | |
|     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)
 | |
| 
 | |
|     @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 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)
 | |
| 
 | |
|     @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
 |