def _start_mininet(self, opts=None): if not opts: opts = {} self._info("***Starting mininet***") opts['controller'] = Controller opts['autoSetMacs'] = True self.net = MininetWithControlNet(**opts)
def __init__ (self, network=None, topo_desc=None): """ Initialize Mininet implementation with proper attributes. Use network as the hided Mininet topology if it's given. :param topo_desc: static topology description e.g. the related NFFG :type topo_desc: :any:`NFFG` :param network: use this specific Mininet object for init (default: None) :type network: :class:`mininet.net.MininetWithControlNet` :return: None """ log.debug("Init ESCAPENetworkBridge with topo description: %s" % topo_desc) if network is not None: self.__mininet = network else: log.warning( "Network implementation object is missing! Use Builder class instead " "of direct initialization. Creating bare Mininet object anyway...") self.__mininet = MininetWithControlNet() # Topology description which is emulated by the Mininet self.topo_desc = topo_desc # Duplicate static links for ensure undirected neighbour relationship if self.topo_desc is not None: back_links = [l.id for u, v, l in self.topo_desc.network.edges_iter(data=True) if l.backward is True] if len(back_links) == 0: log.debug("No backward link has been detected! Duplicate STATIC links " "to ensure undirected relationship for mapping...") self.topo_desc.duplicate_static_links() # Need to clean after shutdown self._need_clean = None # There is no such flag in the Mininet class so using this self.started = False self.xterms = []
def __init__(self, net=None, opts=None, fallback=True, run_dry=True): """ Initialize NetworkBuilder. If the topology definition is not found, an exception will be raised or an empty :class:`mininet.net.Mininet` topology will be created if ``run_dry`` is set. :param net: update given Mininet object instead of creating a new one :type net: :class:`mininet.net.Mininet` :param opts: update default options with the given opts :type opts: dict :param fallback: search for fallback topology (default: True) :type fallback: bool :param run_dry: do not raise an Exception and return with bare Mininet obj. :type run_dry: bool :return: None """ self.opts = dict(self.default_opts) if opts is not None: self.opts.update(opts) self.fallback = fallback self.run_dry = run_dry if net is not None: if isinstance(net, Mininet): # Initial settings - Create new Mininet object if necessary self.mn = net else: raise TopologyBuilderException( "Network object's type must be a derived class of Mininet!" ) else: # self.mn = Mininet(**self.opts) try: self.mn = MininetWithControlNet(**self.opts) except KeyboardInterrupt: quit_with_error( msg="Assembly of Mininet network was interrupted by user!", logger=log) # Basically a wrapper for mn to offer helping functions self.mn_bridge = None # Cache of the topology description as an NFFG which is parsed during # initialization self.topo_desc = None self.__dpid_cntr = self.dpidBase
class NetworkManagerMininet(NetworkManager, GenericEventNotifyer, LoggerHelper): __shared_state = {} def __init__(self): 'lazy init' self.__dict__ = self.__shared_state if self.__dict__: # already initialized return GenericEventNotifyer.__init__(self) self.net = None # Initial topo to create Mininet topology self.initial_topo = None # Network state self.state = NetworkManager.DOWN self.port_map = {} self.dpid = {} # Running topo to manage/store running topo obtained from Mininet - dummy object self.network = Store() # Active link list self.network.links = {} # Active node list self.network.nodes = {} # Queue for parallel event processing self.of_event_queue = [] # Timer daemon process for periodic polling self.process = None self.vnf_manager = None self.last_status_poll = 0 pox.core.core.listen_to_dependencies(self) # Start periodic scan self.periodic_scan() ######### ### Mininet topology compilation ######### def build_topo_network(self, network_topo, appPrefs): """ Filling Mininet "topo" with app specific data network_topo - Topology object need to update appPrefs - global startup parameters No return """ dpctl = None if not appPrefs['dpctl'] else int(appPrefs['dpctl']) #dpctl = int(appPrefs['dpctl']) if 'dpctl' in appPrefs else None network_topo['netopts'].update({'listenPort': dpctl, 'topo': None, 'build': False, 'ipBase': appPrefs['ipBase'], 'autoSetMacs': True, 'autoStaticArp': True}) self._debug('Add global parameters: %s' % network_topo['netopts']) def build_topo_switch(self, network_topo, name, opts, appPrefs): """ Build switch object with given name and "opts" params network_topo - Topology object need to update name - switch name opts - switch instance params appPrefs - global startup parameters No return """ required_keys = {'switchType'} if not opts.viewkeys() & required_keys: raise KeyError('Required argument is missing!\nCheck: ' + repr(required_keys)) switch = dict() switchParms = {'name': name} if 'dpctl' in opts: switchParms['listenPort'] = int(opts['dpctl']) if 'dpid' in opts: switchParms['dpid'] = opts['dpid'] # Get switch type or default if opts['switchType'] == 'default': sw_type = appPrefs['switchType'] else: sw_type = opts['switchType'] # Get the correct switch class if sw_type == 'ivs': switchParms['cls'] = IVSSwitch elif sw_type == 'user': switchParms['cls'] = CustomUserSwitch elif sw_type == 'userns': switchParms['inNamespace'] = True switchParms['cls'] = CustomUserSwitch else: switchParms['cls'] = customOvs switch['openflowver'] = appPrefs['openFlowVersions'] switch['controllers'] = opts.get('controllers', None) switch['netflow'] = opts.get('netflow', None) switch['sflow'] = opts.get('sflow', None) # Are these ifs important or switch instances can contain empty ip, extintf attributes? # Attach external interfaces if 'externalInterfaces' in opts: switch['extintf'] = opts['externalInterfaces'] if 'ip' in opts: switch['ip'] = opts['switchIP'] # Add new switch param and switch switch['params'] = switchParms network_topo['switches'][opts['_id']] = switch self._debug('Add %s Switch to mininet topo with parameters %s' % (name, network_topo['switches'][opts['_id']])) def build_topo_ee(self, network_topo, name, opts): """ Build VNF Container object with given name and "opts" params network_topo - Topology object need to update name - Container name opts - Container instance params No return """ settings = {} ip = opts.get('ip', None) if ip: settings['ip'] = ip defaultRoute = opts.get('defaultRoute', None) if defaultRoute: settings['defaultRoute'] = 'via ' + defaultRoute # Create the correct host class hostCls = EE params = {'name': name, 'cls': hostCls, 'cpu': opts['res']['cpu'], 'mem': opts['res']['mem'], 'ee_type': opts.get('ee_type', 'static'), } for o in ['remote_dpid', 'remote_port', 'remote_conf_ip', 'remote_netconf_port', 'netconf_username', 'netconf_passwd', 'local_intf_name']: params[o] = opts.get(o) params.update(settings) network_topo['ee'][opts['_id']]={'params': params} if False: # Set the CPULimitedHost specific options if 'cores' in opts: network_topo['ee'][opts['_id']]['cores'] = opts['cores'] if 'cpu' in opts: network_topo['ee'][opts['_id']]['frac']={'f':opts['res']['cpu'], 'sched':opts['sched'] } # Attach external interfaces if 'externalInterfaces' in opts: network_topo['ee'][opts['_id']]['extintf'] = opts['externalInterfaces'] vlanif = opts.get('vlanInterfaces', None) if vlanif: self._debug('Checking that OS is VLAN prepared') self.pathCheck('vconfig', moduleName='vlan package') moduleDeps( add='8021q' ) network_topo['ee'][opts['_id']]['vlanif'] = vlanif self._debug("Add %s EE to mininet topo with parameters %s" % (name, network_topo['ee'][opts['_id']])) def pathCheck( self, *args, **kwargs ): """Make sure each program in *args can be found in $PATH.""" moduleName = kwargs.get( 'moduleName', 'it' ) for arg in args: if not quietRun( 'which ' + arg ): showerror(title="Error", message= 'Cannot find required executable %s.\n' % arg + 'Please make sure that %s is installed ' % moduleName + 'and available in your $PATH.' ) def build_topo_controller(self, network_topo, name, opts): """ Build Controller object with given name and "opts" params network_topo - Topology object need to update name - Controller name opts - Controller instance params No return """ # Get controller info from panel controllerType = opts['controllerType'] # Make controller self._info('*** Getting controller selection: %s' % controllerType) if controllerType == 'remote': c = RemoteController elif controllerType == 'inband': c = InbandController elif controllerType == 'ovsc': c = OVSController else: c = Controller params = {'name': name, 'ip': opts['remoteIP'], 'port': opts['remotePort'], 'controller': c } network_topo['controllers'][opts['_id']] = {'params': params} self._debug("Add %s Controller(s) to mininet topo with parameters %s" % (name, network_topo['controllers'][opts['_id']])) def build_topo_sap(self, network_topo, name, opts): settings = {} ip = opts.get('ip', None) if ip: settings['ip'] = ip # else: # nodeNum = canvas.startpointOpts[name]['nodeNum'] # settings['nodeNum']= nodeNum # ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) # settings['ipBaseNum'] = ipBaseNum # settings['prefixLen'] = prefixLen # ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) defaultRoute = opts.get('defaultRoute', None) if defaultRoute: settings['defaultRoute'] = 'via ' + defaultRoute # Create the correct host class hostCls = Host if 'cores' in opts or 'cpu' in opts: hostCls=CPULimitedHost params = {'name': name, 'cls': hostCls } params.update(settings) network_topo['saps'][opts['_id']]={'params': params} # Set the CPULimitedHost specific options if 'cores' in opts: network_topo['saps'][opts['_id']]['cores'] = opts['cores'] if 'cpu' in opts: network_topo['saps'][opts['_id']].update({'f':opts['res']['cpu'], 'sched':opts['sched'] }) # Attach external interfaces if 'externalInterfaces' in opts: network_topo['saps'][opts['_id']]['extintf'] = \ opts['externalInterfaces'] vlanif = opts.get('vlanInterfaces', None) if vlanif: pass # self._debug('Checking that OS is VLAN prepared') # self.pathCheck('vconfig', moduleName='vlan package') # moduleDeps( add='8021q' ) self._debug("Add %s SAP to mininet topo with parameters %s" % (name, network_topo['saps'][opts['_id']])) def build_topo_links(self, network_topo, phy_g): for node1, node2, params in list(phy_g.edges_iter(data=True)): if params.get('type', None) == 'data': network_topo['links'][(node1, node2)] = {'node1': node1, 'node2': node2, 'cls': TCLink} if 'delay' in params: network_topo['links'][(node1, node2)]['delay'] = params.get('delay', 5) if 'bw' in params: network_topo['links'][(node1, node2)]['bw'] = params['bw'] self._debug("Create link between %s : %s with parameters %s" % (node1, node2, network_topo['links'][(node1, node2)])) # TODO: don't use appPrefs and canvas, references and bindings to GUI should be removed # check imports to eliminate unnecessary ones def build_topo(self, phy_g, appPrefs): """ Generate and set mininet topo No return """ from Utils import dump #dump(phy_g) self.initial_topo = self.generate_topo(phy_g, appPrefs) def generate_topo(self, phy_g, appPrefs): """ Build the topology according to the GUI widget params appPrefs - global params: ipBase, switchType, openFlowVersions Return: topo object """ self._info("*** Build network based on our topology.") # Empty topo network_topo = { 'netopts': dict(), 'ee': dict(), 'saps': dict(), 'switches': dict(), 'controllers': dict(), 'links': dict() } # Set global params self.build_topo_network(network_topo, appPrefs) # Make nodes self._info("*** Getting Hosts and Switches.") for node in phy_g.nodes(): if phy_g.node[node]['node_type'] == NODE_TYPE['SWITCH']: # Adding specific switch object to "topo" self.build_topo_switch(network_topo, node, phy_g.node[node], appPrefs) # TODO: Need to handle 'LegacySwitch' ??? # elif 'LegacySwitch' in tags: # opts = canvas.switchOpts[name] # params = {'params':{'name': name, # 'cls': LegacySwitch} # } # # Adding specific switch object to "topo" # network_topo['switches'][opts['_id']] = params elif phy_g.node[node]['node_type'] == NODE_TYPE['NODE']: # Adding specific EE object to "topo" self.build_topo_ee(network_topo, node, phy_g.node[node]) elif phy_g.node[node]['node_type'] == NODE_TYPE['CONTROLLER']: # Adding specific controller object to "topo" self.build_topo_controller(network_topo, node, phy_g.node[node]) # TODO: Need to handle 'LegacyRouter' ??? # elif 'LegacyRouter' in tags: # opts = canvas.switchOpts[name] # params = {'params':{'name': name, # 'cls': LegacyRouter} # } # # Adding specific router object to "topo" # network_topo['hosts'][opts['_id']] = params elif phy_g.node[node]['node_type'] == NODE_TYPE['SAP']: # Adding specific SAP object to "topo" self.build_topo_sap(network_topo, node, phy_g.node[node]) else: raise TypeError('Cannot create mystery node: ' + node) # Adding the links self._info("*** Getting Links.") self.build_topo_links(network_topo, phy_g) return network_topo ######### ### Mininet topology creation ######### # Use this function instead of direct access to initial_topo def get_initial_topology(self): """ General function to return the observed topology in the NetworkX format """ return self._convert_to_NetworkX_format() def _convert_to_NetworkX_format(self): """ Convert the "topo" dictionary to NetworkX format for Orchestration module Keep only the relevant node information Return: graph - networkx.classes.graph.Graph @author: czentye """ # Create empty graph graph = nx.Graph() # Return if topo is not set if not self.initial_topo: return graph from Utils import dump #dump(self.initial_topo, 'NetMen initial_topo') # Convert "controllers" to node controllers = self.initial_topo['controllers'] for c in controllers: node_name = controllers[c]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = c graph.node[node_name]['node_type'] = NODE_TYPE['CONTROLLER'] graph.node[node_name]['hostname'] = node_name graph.node[node_name]['controllerType'] = None #TODO cut from controllers[c]['params']['controller'] graph.node[node_name]['canvas_id'] = None graph.node[node_name]['remoteIP'] = controllers[c]['params']['ip'] graph.node[node_name]['remotePort'] = controllers[c]['params']['port'] # Convert "ee" to node ee = self.initial_topo['ee'] for container in ee: node_name = ee[container]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = container graph.node[node_name]['node_type'] = NODE_TYPE['NODE'] graph.node[node_name]['hostname'] = node_name graph.node[node_name]['nodeNum'] = None graph.node[node_name]['canvas_id'] = None # graph.node[node_name]['ee_type'] = ee[container]['params']['ee_type'] # graph.node[node_name]['cpu'] = ee[container]['params']['cpu'] # graph.node[node_name]['mem'] = ee[container]['params']['mem'] # Duplicated data, SHOULD be removed !!! graph.node[node_name]['res'] = {'cpu': ee[container]['params']['cpu'], 'mem': ee[container]['params']['mem']} # Convert "saps" to node saps = self.initial_topo['saps'] for sap in saps: node_name = saps[sap]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = sap graph.node[node_name]['node_type'] = NODE_TYPE['SAP'] graph.node[node_name]['name'] = node_name graph.node[node_name]['canvas_id'] = None graph.node[node_name]['nodeNum'] = None # Convert "switches" to node switches = self.initial_topo['switches'] for switch in switches: node_name = switches[switch]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = switch graph.node[node_name]['node_type'] = NODE_TYPE['SWITCH'] graph.node[node_name]['hostname'] = node_name graph.node[node_name]['canvas_id'] = None graph.node[node_name]['nodeNum'] = None graph.node[node_name]['controllers'] = switches[switch]['controllers'] graph.node[node_name]['netflow'] = switches[switch]['netflow'] graph.node[node_name]['sflow'] = switches[switch]['sflow'] #graph.node[node_name]['switchIP'] = '' graph.node[node_name]['switchType'] = None #TODO cut from switches[switch]['params']['cls'] # Convert "links" to edges links = self.initial_topo['links'] for node1, node2 in links: options = {'weight': 1} if graph.node[node1]['node_type'] == 'C' or graph.node[node2]['node_type'] == 'C': # Control links are missing from topo -> this branch is useless options['type'] = LINK_TYPE['CONTROL'] else: if 'bw' in links[(node1, node2)]: options['bw'] = links[(node1, node2)]['bw'] options['type'] = LINK_TYPE['DATA'] if 'delay' in links[(node1, node2)]: options['delay'] = links[(node1, node2)].get('delay', 5) graph.add_edge(node1, node2, attr_dict=options) # Adding control channel links # TODO improve if there is multiple controller for node in self.initial_topo['switches']: graph.add_edge(self.initial_topo['switches'][node]['controllers'][0], node, attr_dict={'type': LINK_TYPE['CONTROL'], 'weight': 1}) return graph def _start_mininet(self, opts=None): if not opts: opts = {} self._info("***Starting mininet***") opts['controller'] = Controller opts['autoSetMacs'] = True self.net = MininetWithControlNet(**opts) def _create_ee(self, ees): self._info('**** Create %d execution environment(s)' % len(ees)) for id, ee in ees.iteritems(): params = ee['params'] name = params['name'] self._debug('\tCreate %s EE with params %s' % (name, ee)) if params['ee_type'] == 'netconf': sw = self.net.addSwitch(name) agt = self.net.addAgent('agt_' + name) agt.setSwitch(sw) continue elif params['ee_type'] == 'remote': p = copy.deepcopy(params) p['cls'] = None p['inNamespace'] = False p['dpid'] = p['remote_dpid'] p['username'] = p['netconf_username'] p['passwd'] = p['netconf_passwd'] p['conf_ip'] = p['remote_conf_ip'] p['agentPort'] = p['remote_netconf_port'] del p['name'] sw = self.net.addRemoteSwitch(name, **p) agt = self.net.addAgent('agt_' + name, **p) agt.setSwitch(sw) continue else: # params['ee_type'] == 'static': # normal case h = self.net.addEE(**params) if 'cores' in ee: h.setCPUs(**ee['cores']) if 'frac' in ee: h.setCPUFrac(**ee['frac']) if 'vlanif' in ee: for vif in ee['vlaninf']: # TODO: In miniedit it was after self.net.build() h.cmdPrint('vconfig add '+name+'-eth0 '+vif[1]) h.cmdPrint('ifconfig '+name+'-eth0.'+vif[1]+' '+vif[0]) def _create_switches(self, switches): self._info('**** Create %d switch(es)'%len(switches)) for id, switch in switches.iteritems(): self._debug('\tCreate %s switch with params %s' % (switch['params']['name'], switch)) sw = self.net.addSwitch(**switch['params']) if 'openflowver' in switch: sw.setOpenFlowVersion(switch['openflowver']) if 'ip' in switch: sw.setSwitchIP(switch['ip']) def _create_controllers(self, controllers): self._info('**** Create %d controller(s)'%len(controllers)) for id, controller in controllers.iteritems(): self._debug('\tCreate %s controller with params %s' % (controller['params']['name'], controller)) self.net.addController(**controller['params']) def _create_saps(self, saps): self._info('**** Create %d SAP(s)'%len(saps)) for id, sap in saps.iteritems(): self._debug('\tCreate %s SAP with params %s' % (sap['params']['name'], sap)) self.net.addHost(**sap['params']) def _create_links(self, links): def is_remote(node): return isinstance(node, RemoteSwitch) def is_local(node): return not is_remote(node) self._info('**** Create %d link(s)' % len(links)) for id, link in links.iteritems(): self._debug('\tCreate link %s with params: %s' % (id, link)) node1 = self.net.get(link['node1']) node2 = self.net.get(link['node2']) name_to_node = {'node1': node1, 'node2': node2} link.update(name_to_node) remote = filter(is_remote, [node1, node2]) local = filter(is_local, [node1, node2]) if not remote: self.net.addLink(**link) else: sw = local[0] r = remote[0] intfName = r.params['local_intf_name'] r_mac = None # unknown, r.params['remote_mac'] r_port = r.params['remote_port'] self._debug('\tadd hw interface (%s) to node (%s)' % (intfName, sw.name)) # This hack avoids calling __init__ which always makeIntfPair() link = Link.__new__(Link) i1 = Intf(intfName, node=sw, link=link) i2 = Intf(intfName, node=r, mac=r_mac, port=r_port, link=link) i2.mac = r_mac # mn runs 'ifconfig', which resets mac to None # link.intf1, link.intf2 = i1, i2 def _start_controllers(self): self._info('**** Start controller(s)') for controller in self.net.controllers: controller.start() def _start_switches(self, switches): for id, switch in switches.iteritems(): controllers = [] #with legacySwitch there is no controller in miniedit if switch['controllers']: controllers.append(self.net.get(*switch['controllers'])) self.net.get(switch['params']['name']).start(controllers) def start_topo(self, **kwargs): """ Start the physical topology (using Mininet) topo - Set and use this topology nflow - sflow - startcli - No return """ self.change_network_state(NetworkManager.STARTING) # Save given topo (created by MiniEdit) if 'topo' in kwargs: self.initial_topo = kwargs['topo'] if self._is_initial_topo_empty(): raise AttributeError("Initial topology is missing!!!") self._start_mininet(self.initial_topo['netopts']) self._create_ee(self.initial_topo['ee']) self._create_switches(self.initial_topo['switches']) self._create_controllers(self.initial_topo['controllers']) self._create_saps(self.initial_topo['saps']) self._create_links(self.initial_topo['links']) self.net.build() self.net.start() if 'nflow' in kwargs and kwargs['nflow'].get('nflowTarget'): self.start_nflow(kwargs['nflow']['nflowTarget'], kwargs['nflow']['nflowTimeout'], kwargs['nflow']['nflowAddId']) if 'sflow' in kwargs and kwargs['sflow'].get('sflowTarget'): self.start_sflow(kwargs['sflow']['sflowTarget'], kwargs['sflow']['sflowHeader'], kwargs['sflow']['sflowSampling'], kwargs['sflow']['sflowPolling']) if 'startcli' in kwargs and kwargs['startcli'] == '1': self.start_cli() self.change_network_state(NetworkManager.UP) def _is_initial_topo_empty(self): return self.initial_topo is None or ('ee' in self.initial_topo and 'saps' in self.initial_topo and 'switches' in self.initial_topo and 'controller' in self.initial_topo) def stop_network(self): self.change_network_state(NetworkManager.STOPPING) if self.net is not None: self.net.stop() self.net = None self.initial_topo = None self.network.links = {} self.network.nodes = {} self.dpid = {} self.port_map = {} # self._debug('Cleaning up Mininet...') # mininet.clean.cleanup() # time.sleep(4) self.change_network_state(NetworkManager.DOWN) def network_alive(self): return self.state in [NetworkManager.UP] def start_sflow(self, target, header, sampling, polling): sflowEnabled = False sflowSwitches = '' for switch in self.initial_topo['switches'].itervalues(): name = switch['params']['name'] if switch.get('sflow', None) == '1': self._info('%s has sflow enabled' % name) sflowSwitches += ' -- set Bridge '+name+' sflow=@MiniEditSF' sflowEnabled=True if sflowEnabled: sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '\ 'target=\\\"'+target+'\\\" '\ 'header='+header+' '+ \ 'sampling='+sampling+' '+\ 'polling='+polling self._debug('sFlow command: cmd = %s%s' % (sflowCmd,sflowSwitches)) call(sflowCmd+sflowSwitches, shell=True) else: self._info('No switches with sflow') def start_nflow(self, nflowTarget, nflowTimeout, nflowAddId): nflowSwitches = '' nflowEnabled = False for switch in self.initial_topo['switches'].itervalues(): name = switch['params']['name'] if switch.get('netflow', None) == '1': self._info('%s has Netflow enabled'% name) nflowSwitches += ' -- set Bridge '+name+' netflow=@MiniEditNF' nflowEnabled=True if nflowEnabled: nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '\ 'target=\\\"'+nflowTarget+'\\\" '\ 'active-timeout='+nflowTimeout if nflowAddId == 1: nflowCmd += ' add_id_to_interface=true' else: nflowCmd += ' add_id_to_interface=false' self._debug('nFlowcmd = %s'%(nflowCmd+nflowSwitches)) call(nflowCmd+nflowSwitches, shell=True) else: self._info('No switches with Netflow') def start_cli(self): CLI(self.net) def start_clicky(self, vnf_name): instances = self.vnf_manager.start_clicky(vnf_name) self.net.clickys += instances # self._debug('CLICKY: %s' % instances) ######### ### Scan the Mininet network and updating the running topology ######### def periodic_scan(self, wait = 1): """Scan the Mininet topo and recall itself after a period of time (wait)""" self.scan_network() self.process = threading.Timer(wait, self.periodic_scan) self.process.daemon = True self.process.start() def scan_network(self, forced = False): self.process_event_queue() if not self.net or self.state == NetworkManager.STOPPING: return self.poll_netconf_agents(forced) checked = [] # net -> Mininet network representation for name, node in self.net.items(): checked.append(name) if not self.found_node(node): return deleted = [] for name, opts in self.network.nodes.iteritems(): if opts.get('parent'): # netconf-controlled vnf node, mininet doesn't know about it continue if name not in checked: deleted.append(name) if self.state != NetworkManager.STARTING: for node_name in deleted: self._debug('remove node (%s) from network table' % node_name) del self.network.nodes[node_name] # TODO: send event deleted = [] for link_id, link in self.network.links.iteritems(): node_names = [link['node1'], link['node2']] for node_name in node_names: if node_name not in self.network.nodes: self._debug('delete link: %s' % node_names) deleted.append(link_id) for link_id in deleted: del self.network.links[link_id] self._fire_dpid_update(self.dpid) self._fire_port_map_update(self.port_map) def poll_netconf_agents(self, forced = False): "Poll netconf agents for VNF status updates" poll_period = 10 if (time.time() - self.last_status_poll < poll_period) and not forced: return for sw in self.net.switches: if not sw.getAgent(): continue i = self.vnf_manager.get_vnf_info_on_node(sw.name) # self._debug('VNF_INFO: %s' % i) visited = [] for vnf_name, new_opts in i.iteritems(): if vnf_name is None: continue new_opts['parent'] = sw.name visited.append(vnf_name) orig = self.network.nodes.get(vnf_name, {}) old_status = orig.get('status') if orig == new_opts: # nothing's changed continue orig.update(new_opts) self.network.nodes[vnf_name] = orig links = new_opts.get('link', []) if type(links) != list: links = [links] self.found_vnf_links(vnf_name, links) if old_status != new_opts['status']: self._fire_vnf_update(vnf_name, new_opts['status']) deleted = [] for node_name, opts in self.network.nodes.iteritems(): # self._debug('node_name: %s opts: %s' % (node_name, opts)) if opts.get('parent') != sw.name: continue if node_name not in visited: deleted.append(node_name) for vnf_name in deleted: del self.network.nodes[vnf_name] # self._debug('vnf_name: %s' % vnf_name) sw = self.net.nameToNode[vnf_name] self.net.switches.remove(sw) del self.net.nameToNode[vnf_name] neighbours = self.port_map[vnf_name].keys() for n in neighbours: del self.port_map[n][vnf_name] # Assuming there can be only one link between n and vnf. link = {'node1': n, 'node2': vnf_name, 'intf1': None, 'intf2': None, 'delete': True} pox.core.core.raiseLater(self, LinkChange, **link) del self.port_map[vnf_name] self._fire_vnf_update(vnf_name, 'STOPPED') self.last_status_poll = time.time() def found_vnf_links(self, vnf_id, links): def get_intf_by_name(node, intf_name): for i in node.intfList(): if str(i) == intf_name: return i return None def get_or_create_intf(dev_name, obj, port): if port not in obj.intfs: # does not exist, let's create it return Intf(dev_name, node=obj, port=port) if str(obj.intfs[port]) != dev_name: # intf exists, but port is invalid return Intf(dev_name, node=obj, port=port) return obj.intfs[port] for i, link in enumerate(links): if int(link['sw_port']) == -1: # disconnected links (with port==-1) are omitted continue sw_name = link.get('sw_id', 'sw_id'+str(i)) sw_dev = link.get('sw_dev', 'sw_dev'+str(i)) sw_port = int(link['sw_port']) nf_name = vnf_id nf_dev = link.get('vnf_dev', 'vnf_dev'+str(i)) nf_port = int(link['vnf_port']) nf_mac = link['vnf_dev_mac'] sw_obj = self.net.getNodeByName(sw_name) try: nf_obj = self.net.getNodeByName(nf_name) except KeyError: # this is a VNF not managed by mininet, yet we have to # add to the mininet 'database'. TODO: Ideally, it # would be a RemoteHost, but for now it is a # RemoteSwitch. nf_obj = self.net.addRemoteSwitch(nf_name, dpid="-1") sw_i = get_or_create_intf(sw_dev, sw_obj, sw_port) if nf_dev in nf_obj.intfNames(): nf_i = get_intf_by_name(nf_obj, nf_dev) else: nf_i = Intf(nf_dev, node=nf_obj, port=nf_port, mac=nf_mac) nf_i.mac = nf_mac # mn runs 'ifconfig', which resets mac to None self.found_link(sw_obj, nf_obj, sw_i, nf_i) def found_link(self, node_a, node_b, intf_a, intf_b): # link "A -> B" is the same as link "B -> A" link = [(node_a, intf_a), (node_b, intf_b)] link = sorted(link, key=lambda x: x[1]) [(node1, intf1), (node2, intf2)] = link link_id = ''.join([intf1.name, intf2.name]) orig_link = self.network.links.get(link_id, {}) link = { 'node1': node1.name, 'node2': node2.name, 'intf1': intf1, 'intf2': intf2 } try: self.port_map[node1.name][node2.name] = node1.ports[intf1] except KeyError: self.port_map[node1.name] = {node2.name: node1.ports[intf1]} try: self.port_map[node2.name][node1.name] = node2.ports[intf2] except KeyError: self.port_map[node2.name] = {node1.name: node2.ports[intf2]} if not cmp(orig_link, link) == 0: self.network.links[link_id] = link pox.core.core.raiseLater(self, LinkChange, **link) def found_node(self, node): orig = self.network.nodes.get(node.name, {}) new_opts = copy.deepcopy(orig) new_opts['name'] = node.name new_opts['intf'] = {} for intf in node.intfList(): new_opts['intf'][intf.name] = {'ip': node.IP(intf), 'mac': node.MAC(intf), 'port': node.ports[intf]} try: # taken form Node.connectionsTo for intf in node.intfList(): link = intf.link if not intf.link: continue node1, node2 = link.intf1.node, link.intf2.node if node1 == node or node2 == node: self.found_link(node1, node2, link.intf1, link.intf2) except AttributeError: # network is not running return False if getattr(node, 'dpid', None): new_opts['dpid'] = int(node.dpid, base=16) self.dpid[node.name] = int(node.dpid, base=16) if not cmp(orig, new_opts) == 0: self.network.nodes[node.name] = new_opts pox.core.core.raiseLater(self, NodeChange, NodeChange.TYPE_DUMMY, **new_opts) return True def dpid_to_name(self, dpid): for name, name_dpid in self.dpid.iteritems(): if dpid == name_dpid: return name return None def process_event_queue(self): processed = [] for e in self.of_event_queue: name = self.dpid_to_name(e.dpid) if not name: continue e.name = name processed.append(e) self.fire(e.type, e) for e in processed: self.of_event_queue.remove(e) def change_network_state(self, new_state): if self.state == new_state: return self.state = new_state self._fire_network_state_change() ######### ### Change Event generation and OpenFlow event handling ######### def _fire_network_state_change(self): """Signalling the node/link params are changed""" event = Store() event.state = self.state self.fire('network_state_change', event) def _fire_dpid_update(self, dpids): event = Store() event.dpids = dpids self.fire('dpid_update', event) def _fire_port_map_update(self, port_map): event = Store() event.port_map = port_map self.fire('port_map_update', event) def _fire_switch_connection_up(self): pass def _fire_switch_connection_down(self): pass def _fire_vnf_update(self, vnf_name, status): m = {'FAILED': 'failed', 'UP_AND_RUNNING': 'running', 'INITIALIZING': 'starting', 'STOPPED': 'stopped', } event = Store() event.name = vnf_name try: event.on_node = self.network.nodes[vnf_name]['parent'] except KeyError: event.on_node = None event.status = m.get(status, 'failed') self.fire('vnf_update', event) def _handle_openflow_ConnectionUp(self, event): event.type = 'switch_connection_up' self.of_event_queue.append(event) def _handle_openflow_ConnectionDown(self, event): event.type = 'switch_connection_down' self.of_event_queue.append(event)
class ESCAPENetworkBuilder(object): """ Builder class for topology. Update the network object based on the parameters if it's given or create an empty instance. Always return with an ESCAPENetworkBridge instance which offer a generic interface for created :class:`mininet.net.Mininet` object and hide implementation's nature. Follows Builder design pattern. """ # Default initial options for Mininet default_opts = { "controller": InternalControllerProxy, # Use own Controller 'build': False, # Not build during init 'inNamespace': False, # Not start element in namespace 'autoSetMacs': False, # Set simple MACs 'autoStaticArp': True, # Set static ARP entries 'listenPort': None, # Add listen port to OVS switches 'link': TCLink # Add default link } # Default internal storing format for NFFG parsing/reading from file DEFAULT_NFFG_FORMAT = "NFFG" # Constants TYPE_EE_LOCAL = "LOCAL" TYPE_EE_REMOTE = "REMOTE" # Constants for DPID generation dpidBase = 1 # Switches start with port 1 in OpenFlow dpidLen = 16 # digits in dpid passed to switch def __init__ (self, net=None, opts=None, fallback=True, run_dry=True): """ Initialize NetworkBuilder. If the topology definition is not found, an exception will be raised or an empty :class:`mininet.net.Mininet` topology will be created if ``run_dry`` is set. :param net: update given Mininet object instead of creating a new one :type net: :class:`mininet.net.Mininet` :param opts: update default options with the given opts :type opts: dict :param fallback: search for fallback topology (default: True) :type fallback: bool :param run_dry: do not raise an Exception and return with bare Mininet obj. :type run_dry: bool :return: None """ self.opts = dict(self.default_opts) if opts is not None: self.opts.update(opts) self.fallback = fallback self.run_dry = run_dry if net is not None: if isinstance(net, Mininet): # Initial settings - Create new Mininet object if necessary self.mn = net else: raise TopologyBuilderException( "Network object's type must be a derived class of Mininet!") else: # self.mn = Mininet(**self.opts) try: self.mn = MininetWithControlNet(**self.opts) except KeyboardInterrupt: quit_with_error( msg="Assembly of Mininet network was interrupted by user!", logger=log) # Basically a wrapper for mn to offer helping functions self.mn_bridge = None # Cache of the topology description as an NFFG which is parsed during # initialization self.topo_desc = None self.__dpid_cntr = self.dpidBase def __get_new_dpid (self): """ Generate a new DPID and return the valid format for Mininet/OVS. :return: new DPID :rtype: str """ dpid = hex(int(self.__dpid_cntr))[2:] dpid = '0' * (self.dpidLen - len(dpid)) + dpid self.__dpid_cntr += 1 return dpid ############################################################################## # Topology initializer functions ############################################################################## def __init_from_NFFG (self, nffg): """ Initialize topology from an :any:`NFFG` representation. :param nffg: topology object structure :type nffg: :any:`NFFG` :return: None """ # pprint(nffg.network.__dict__) log.info("Start topology creation from NFFG(name: %s)..." % nffg.name) created_mn_nodes = {} # created nodes as 'NFFG-id': <node> created_mn_links = {} # created links as 'NFFG-id': <link> # If not set then cache the given NFFG as the topology description self.topo_desc = nffg # Create a Controller which will be the default internal POX controller try: self.create_Controller("ESCAPE") except SystemExit: raise TopologyBuilderException("Controller creations was unsuccessful!") # Convert INFRAs for infra in nffg.infras: # Create EE if infra.infra_type == NodeInfra.TYPE_EE: if infra.domain == "INTERNAL": ee_type = self.TYPE_EE_LOCAL else: log.warning( "Detected domain of infra: %s is not INTERNAL! Remote EE creation " "for domains other than INTERNAL is not supported yet!" % infra) # ee_type = self.TYPE_EE_REMOTE ee_type = self.TYPE_EE_LOCAL # FIXME - set resource info in MN EE if can - cpu,mem,delay,bandwidth? agt, sw = self.create_NETCONF_EE(name=infra.id, type=ee_type) created_mn_nodes[infra.id] = sw # Create Switch elif infra.infra_type == NodeInfra.TYPE_SDN_SWITCH: switch = self.create_Switch(name=infra.id) created_mn_nodes[infra.id] = switch elif infra.infra_type == NodeInfra.TYPE_STATIC_EE: static_ee = self.create_static_EE(name=infra.id) created_mn_nodes[infra.id] = static_ee else: quit_with_error( msg="Type: %s in %s is not supported by the topology creation " "process in %s!" % ( infra.infra_type, infra, self.__class__.__name__), logger=log) # Create SAPs - skip the temporary, inter-domain SAPs for sap in {s for s in nffg.saps if not s.binding}: # Create SAP sap_host = self.create_SAP(name=sap.id) created_mn_nodes[sap.id] = sap_host # Convert VNFs # TODO - implement --> currently the default Mininet topology does not # TODO contain NFs but it could be possible # Convert connections - copy link ref in a list and iter over it for edge in [l for l in nffg.links]: # Skip initiation of links which connected to an inter-domain SAP if (edge.src.node.type == NFFG.TYPE_SAP and edge.src.node.binding is not None) or ( edge.dst.node.type == NFFG.TYPE_SAP and edge.dst.node.binding is not None): continue # Create Links mn_src_node = created_mn_nodes.get(edge.src.node.id) mn_dst_node = created_mn_nodes.get(edge.dst.node.id) if mn_src_node is None or mn_dst_node is None: raise TopologyBuilderException( "Created topology node is missing! Something really went wrong!") src_port = int(edge.src.id) if int(edge.src.id) < 65535 else None if src_port is None: log.warning( "Source port id of Link: %s is generated dynamically! Using " "automatic port assignment based on internal Mininet " "implementation!" % edge) dst_port = int(edge.dst.id) if int(edge.dst.id) < 65535 else None if dst_port is None: log.warning( "Destination port id of Link: %s is generated dynamically! Using " "automatic port assignment based on internal Mininet " "implementation!" % edge) link = self.create_Link(src=mn_src_node, src_port=src_port, dst=mn_dst_node, dst_port=dst_port, bw=edge.bandwidth, delay=str(edge.delay) + 'ms') created_mn_links[edge.id] = link # Set port properties of SAP nodes. # A possible excerpt from a escape-mn-topo.nffg file: # "ports": [{ "id": 1, # "property": ["ip:10.0.10.1/24"] }] # for n in {s for s in nffg.saps if not s.binding}: mn_node = self.mn.getNodeByName(n.id) for port in n.ports: # ip should be something like '10.0.123.1/24'. if len(port.l3): if len(port.l3) == 1: ip = port.l3.container[0].provided else: log.warning( "Multiple L3 address is detected! Skip explicit IP address " "definition...") ip = None else: # or None ip = port.get_property('ip') if port.l2: mac = port.l2 else: mac = port.get_property('mac') intf = mn_node.intfs.get(port.id) if intf is None: log.warn(("Port %s of node %s is not connected," "it will remain unconfigured!") % (port.id, n.name)) continue if intf == mn_node.defaultIntf(): # Workaround a bug in Mininet mn_node.params.update({'ip': ip}) mn_node.params.update({'mac': mac}) if ip is not None: mn_node.setIP(ip, intf=intf) log.debug("Use explicit IP: %s for node: %s" % (ip, n)) if mac is not None: mn_node.setMAC(mac, intf=intf) log.debug("Use explicit MAC: %s for node: %s" % (mac, n)) # For inter-domain SAPs no need to create host/xterm just add the SAP as # a port to the border Node # Iterate inter-domain SAPs self.bind_inter_domain_SAPs(nffg=nffg) log.info("Topology creation from NFFG has been finished!") def __init_from_AbstractTopology (self, topo_class): """ Build topology from pre-defined Topology class. :param topo_class: topology :type topo_class: :any:`AbstractTopology` :return: None """ log.info("Load topology from class: %s" % topo_class.__name__) if topo_class.TYPE == "STATIC": self.mn.topo = topo_class().construct() self.mn.build() elif topo_class.TYPE == "DYNAMIC": # self.mn = topo_class().construct() topo_class().construct(builder=self) else: raise TopologyBuilderException( "TYPE field of the Topology class need to be set!") self.topo_desc = topo_class.get_topo_desc() def __init_from_CONFIG (self, format=DEFAULT_NFFG_FORMAT): """ Build a pre-defined topology from an NFFG stored in a file. The file path is searched in CONFIG with tha name ``TOPO``. :param format: NF-FG storing format (default: internal NFFG representation) :type format: str :return: None """ path = CONFIG.get_mininet_topology() if path is None: raise TopologyBuilderException("Missing Topology!") self.__init_from_file(path=path, format=format) def __init_from_file (self, path, format=DEFAULT_NFFG_FORMAT): """ Build a pre-defined topology from an NFFG stored in a file. The file path is searched in CONFIG with tha name ``TOPO``. :param path: file path :type path: str :param format: NF-FG storing format (default: internal NFFG representation) :type format: str :return: None """ if path is None: log.error("Missing file path of Topology description") return try: with open(path, 'r') as f: log.info("Load topology from file: %s" % path) if format == self.DEFAULT_NFFG_FORMAT: log.info("Using file format: %s" % format) self.__init_from_NFFG(nffg=NFFG.parse(f.read())) else: raise TopologyBuilderException("Unsupported file format: %s!" % format) except IOError: log.warning("Additional topology file not found: %s" % path) raise TopologyBuilderException("Missing topology file!") except ValueError as e: log.error("An error occurred when load topology from file: %s" % e.message) raise TopologyBuilderException("File parsing error!") # except SystemExit: # raise TopologyBuilderException("Got exit exception from Mininet!") def get_network (self): """ Return the bridge to the constructed network. :return: object representing the emulated network :rtype: :any:`ESCAPENetworkBridge` """ if self.mn_bridge is None: # Create the Interface object and set the topology description as the # original NFFG self.mn_bridge = ESCAPENetworkBridge(network=self.mn, topo_desc=self.topo_desc) # Additional settings self.mn_bridge._need_clean = CONFIG.get_clean_after_shutdown() return self.mn_bridge ############################################################################## # Builder functions ############################################################################## def create_static_EE (self, name, cls=None, **params): """ Create and add a new EE to Mininet in the static way. This function is for only backward compatibility. .. warning:: Not tested yet! :param name: name of the Execution Environment :type name: str :param cls: custom EE class/constructor (optional) :type cls: :class:`mininet.node.EE` :param cores: Specify (real) cores that our cgroup can run on (optional) :type cores: list :param frac: Set overall CPU fraction for this EE (optional) :type frac: list :param vlanif: set vlan interfaces (optional) :type vlanif: list :return: newly created EE object :rtype: :class:`mininet.node.EE` """ # create static EE cfg = CONFIG.get_EE_params() cfg.update(params) cfg['dpid'] = self.__get_new_dpid() log.debug("Create static EE with name: %s" % name) ee = self.mn.addEE(name=name, cls=cls, **cfg) if 'cores' in cfg: ee.setCPUs(**cfg['cores']) if 'frac' in cfg: ee.setCPUFrac(**cfg['frac']) if 'vlanif' in cfg: for vif in cfg['vlaninf']: ee.cmdPrint('vconfig add ' + name + '-eth0 ' + vif[1]) ee.cmdPrint('ifconfig ' + name + '-eth0.' + vif[1] + ' ' + vif[0]) return ee def create_NETCONF_EE (self, name, type=TYPE_EE_LOCAL, **params): """ Create and add a new EE to Mininet network. The type of EE can be {local|remote} NETCONF-based. :param name: name of the EE: switch: name, agent: agt_+'name' :type name: str :param type: type of EE {local|remote} :type type: str :param opts: additional options for the switch in EE :type opts: str :param dpid: remote switch DPID (remote only) :param username: NETCONF username (remote only) :param passwd: NETCONF password (remote only) :param ip: control Interface for the agent (optional) :param agentPort: port to listen on for NETCONF connections, (else set \ automatically) :param minPort: first VNF control port which can be used (else set \ automatically) :param cPort: number of VNF control ports (and VNFs) which can be used ( \ default: 10) :return: tuple of newly created :class:`mininet.node.Agent` and \ :class:`mininet.node.Switch` object :rtype: tuple """ type = type.upper() cfg = CONFIG.get_EE_params() cfg.update(params) cfg['dpid'] = self.__get_new_dpid() if type == self.TYPE_EE_LOCAL: # create local NETCONF-based log.debug("Create local NETCONF EE with name: %s" % name) sw = self.mn.addSwitch(name, **cfg) elif type == self.TYPE_EE_REMOTE: # create remote NETCONF-based log.debug("Create remote NETCONF EE with name: %s" % name) cfg["inNamespace"] = False sw = self.mn.addRemoteSwitch(name, cls=None, **cfg) else: raise TopologyBuilderException( "Unsupported NETCONF-based EE type: %s!" % type) agt = self.mn.addAgent('agt_' + name, cls=None, **cfg) agt.setSwitch(sw) return agt, sw def create_Switch (self, name, cls=None, **params): """ Create and add a new OF switch instance to Mininet network. Additional parameters are keyword arguments depend on and forwarded to the initiated Switch class type. :param name: name of switch :type name: str :param cls: custom switch class/constructor (optional) :type cls: :class:`mininet.node.Switch` :param dpid: DPID for switch (default: derived from name) :type dpid: str :param opts: additional switch options :type opts: str :param listenPort: custom listening port (optional) :type listenPort: int :param inNamespace: override the switch spawn in namespace (optional) :type inNamespace: bool :param of_ver: override OpenFlow version (optional) :type of_ver: int :param ip: set IP address for the switch (optional) :type ip: :return: newly created Switch object :rtype: :class:`mininet.node.Switch` """ log.debug("Create Switch with name: %s" % name) cfg = CONFIG.get_Switch_params() cfg.update(params) cfg['dpid'] = self.__get_new_dpid() sw = self.mn.addSwitch(name=name, cls=cls, **cfg) if 'of_ver' in cfg: sw.setOpenFlowVersion(cfg['of_ver']) if 'ip' in cfg: sw.setSwitchIP(cfg['ip']) return sw def create_Controller (self, name, controller=None, **params): """ Create and add a new OF controller to Mininet network. Additional parameters are keyword arguments depend on and forwarded to the initiated Controller class type. .. warning:: Should not call this function and use the default InternalControllerProxy! :param name: name of controller :type name: str :param controller: custom controller class/constructor (optional) :type controller: :class:`mininet.node.Controller` :param inNamespace: override the controller spawn in namespace (optional) :type inNamespace: bool :return: newly created Controller object :rtype: :class:`mininet.node.Controller` """ log.debug("Create Controller with name: %s" % name) cfg = CONFIG.get_Controller_params() cfg.update(params) return self.mn.addController(name=name, controller=controller, **cfg) def create_SAP (self, name, cls=None, **params): """ Create and add a new SAP to Mininet network. Additional parameters are keyword arguments depend on and forwarded to the initiated Host class type. :param name: name of SAP :type name: str :param cls: custom hosts class/constructor (optional) :type cls: :class:`mininet.node.Host` :return: newly created Host object as the SAP :rtype: :class:`mininet.node.Host` """ log.debug("Create SAP with name: %s" % name) cfg = CONFIG.get_SAP_params() cfg.update(params) return self.mn.addHost(name=name, cls=cls, **cfg) def bind_inter_domain_SAPs (self, nffg): """ Search for inter-domain SAPs in given :any:`NFFG`, create them as a switch port and bind them to a physical interface given in sap.domain attribute. :param nffg: topology description :type nffg: :any:`NFFG` :return: None """ log.debug("Search for inter-domain SAPs...") # Create the inter-domain SAP ports for sap in {s for s in nffg.saps if s.binding is not None}: # NFFG is the raw NFFG without link duplication --> iterate over every # edges in or out there should be only one link in this case # e = (u, v, data) sap_switch_links = [e for e in nffg.network.edges_iter(data=True) if sap.id in e] try: if sap_switch_links[0][0] == sap.id: border_node = sap_switch_links[0][1] else: border_node = sap_switch_links[0][0] except IndexError: log.error("Link for inter-domain SAP: %s is not found. " "Skip SAP creation..." % sap) continue log.debug("Detected inter-domain SAP: %s connected to border Node: %s" % (sap, border_node)) # if sap.delay or sap.bandwidth: # log.debug("Detected resource values for inter-domain connection: " # "delay: %s, bandwidth: %s" % (sap.delay, sap.bandwidth)) sw_name = nffg.network.node[border_node].id for sw in self.mn.switches: # print sw.name if sw.name == sw_name: if sap.binding not in get_ifaces(): log.warning( "Physical interface: %s is not found! Skip binding..." % sap.binding) continue log.debug("Add physical port as inter-domain SAP: %s -> %s" % (sap.binding, sap.id)) # Add interface to border switch in Mininet # os.system('ovs-vsctl add-port %s %s' % (sw_name, sap.domain)) sw.addIntf(intf=Intf(name=sap.binding, node=sw)) def create_Link (self, src, dst, src_port=None, dst_port=None, **params): """ Create an undirected connection between src and dst. Source and destination ports can be given optionally: :param src: source Node :param dst: destination Node :param src_port: source Port (optional) :param dst_port: destination Port (optional) :param params: additional link parameters :return: """ log.debug("Create Link %s%s <--> %s%s" % ( src, ":%s" % src_port if src_port is not None else "", dst, ":%s" % dst_port if dst_port is not None else "")) remote = filter(lambda n: isinstance(n, RemoteSwitch), [src, dst]) local = filter(lambda n: not isinstance(n, RemoteSwitch), [src, dst]) cfg = CONFIG.get_Link_params() cfg.update(params) if not remote: self.mn.addLink(src, dst, src_port, dst_port, **cfg) else: # sw = local[0] # one of the local Node # r = remote[0] # other Node which is the remote # intfName = r.params['local_intf_name'] # r_mac = None # unknown, r.params['remote_mac'] # r_port = r.params['remote_port'] # # self._debug('\tadd hw interface (%s) to node (%s)' % (intfName, # # sw.name)) # # This hack avoids calling __init__ which always makeIntfPair() # link = Link.__new__(Link) # i1 = Intf(intfName, node=sw, link=link) # i2 = Intf(intfName, node=r, mac=r_mac, port=r_port, link=link) # i2.mac = r_mac # mn runs 'ifconfig', which resets mac to None # link.intf1, link.intf2 = i1, i2 raise TopologyBuilderException( "Remote Link creation is not supported yet!") def build (self, topo=None): """ Initialize network. 1. If the additional ``topology`` is given then using that for init. 2. If TOPO is not given, search topology description in CONFIG with the \ name 'TOPO'. 3. If TOPO not found or an Exception was raised, search for the fallback \ topo with the name ``FALLBACK-TOPO``. 4. If FALLBACK-TOPO not found raise an exception or run a bare Mininet \ object if the run_dry attribute is set :param topo: optional topology representation :type topo: :any:`NFFG` or :any:`AbstractTopology` or ``None`` :return: object representing the emulated network :rtype: :any:`ESCAPENetworkBridge` """ log.debug("Init emulated topology based on Mininet v%s" % MNVERSION) # Load topology try: if topo is None: log.info("Get Topology description from CONFIG...") self.__init_from_CONFIG() elif isinstance(topo, NFFG): log.info("Get Topology description from given NFFG...") self.__init_from_NFFG(nffg=topo) elif isinstance(topo, basestring) and topo.startswith('/'): log.info("Get Topology description from given file...") self.__init_from_file(path=topo) elif isinstance(topo, AbstractTopology): log.info("Get Topology description based on Topology class...") self.__init_from_AbstractTopology(topo_class=topo) else: raise TopologyBuilderException( "Unsupported topology format: %s - %s" % (type(topo), topo)) return self.get_network() except SystemExit as e: quit_with_error(msg="Mininet exited unexpectedly!", logger=log, exception=e) except TopologyBuilderException: if self.fallback: # Search for fallback topology fallback = CONFIG.get_fallback_topology() if fallback: log.info("Load topo from fallback topology description...") self.__init_from_AbstractTopology(fallback) return self.get_network() # fallback topo is not found or set if self.run_dry: # Return with the bare Mininet object log.warning("Topology description is not found! Running dry...") return self.get_network() else: # Re-raise the exception raise except KeyboardInterrupt: quit_with_error( msg="Assembly of Mininet network was interrupted by user!", logger=log)
class ESCAPENetworkBridge(object): """ Internal class for representing the emulated topology. Represents a container class for network elements such as switches, nodes, execution environments, links etc. Contains network management functions similar to Mininet's mid-level API extended with ESCAPEv2 related capabilities Separate the interface using internally from original Mininet object to implement loose coupling and avoid changes caused by Mininet API changes e.g. 2.1.0 -> 2.2.0. Follows Bridge design pattern. """ def __init__ (self, network=None, topo_desc=None): """ Initialize Mininet implementation with proper attributes. Use network as the hided Mininet topology if it's given. :param topo_desc: static topology description e.g. the related NFFG :type topo_desc: :any:`NFFG` :param network: use this specific Mininet object for init (default: None) :type network: :class:`mininet.net.MininetWithControlNet` :return: None """ log.debug("Init ESCAPENetworkBridge with topo description: %s" % topo_desc) if network is not None: self.__mininet = network else: log.warning( "Network implementation object is missing! Use Builder class instead " "of direct initialization. Creating bare Mininet object anyway...") self.__mininet = MininetWithControlNet() # Topology description which is emulated by the Mininet self.topo_desc = topo_desc # Duplicate static links for ensure undirected neighbour relationship if self.topo_desc is not None: back_links = [l.id for u, v, l in self.topo_desc.network.edges_iter(data=True) if l.backward is True] if len(back_links) == 0: log.debug("No backward link has been detected! Duplicate STATIC links " "to ensure undirected relationship for mapping...") self.topo_desc.duplicate_static_links() # Need to clean after shutdown self._need_clean = None # There is no such flag in the Mininet class so using this self.started = False self.xterms = [] @property def network (self): """ Internal network representation. :return: network representation :rtype: :class:`mininet.net.MininetWithControlNet` """ return self.__mininet def runXTerms (self): """ Start an xterm to every SAP if it's enabled in the global config. SAP are stored as hosts in the Mininet class. :return: None """ if CONFIG.get_SAP_xterms(): log.debug("Starting xterm on SAPS...") terms = makeTerms(nodes=self.__mininet.hosts, title='SAP', term="xterm") self.xterms.extend(terms) else: log.warning("Skip starting xterms on SAPS according to global config") def start_network (self): """ Start network. """ log.debug("Starting Mininet network...") if self.__mininet is not None: if not self.started: try: self.__mininet.start() except SystemExit: quit_with_error(msg="Mininet emulation requires root privileges!", logger=LAYER_NAME) except KeyboardInterrupt: quit_with_error( msg="Initiation of Mininet network was interrupted by user!", logger=log) self.started = True log.debug("Mininet network has been started!") self.runXTerms() else: log.warning( "Mininet network has already started! Skipping start task...") else: log.error("Missing topology! Skipping emulation...") def stop_network (self): """ Stop network. """ log.debug("Shutting down Mininet network...") if self.__mininet is not None: if self.started: self.__mininet.stop() self.started = False log.debug("Mininet network has been stopped!") else: log.warning("Mininet network is not started yet! Skipping stop task...") if self._need_clean: self.cleanup() def cleanup (self): """ Clean up junk which might be left over from old runs. ..seealso:: :func:`mininet.clean.cleanup() <mininet.clean.cleanup>` """ if self.started: log.warning( "Mininet network is not stopped yet! Skipping cleanup task...") else: log.info("Schedule cleanup task after Mininet emulation...") # Kill remained xterms log.debug("Close SAP xterms...") import os import signal for term in self.xterms: os.killpg(term.pid, signal.SIGTERM) # Schedule a cleanup as a coop task to avoid threading issues from escape.util.misc import remove_junks # call_as_coop_task(remove_junks, log=log) # threading.Thread(target=remove_junks, name="cleanup", args=(log, # )).start() # multiprocessing.Process(target=remove_junks, name="cleanup", # args=(log,)).start() remove_junks(log=log) def get_agent_to_switch (self, switch_name): """ Return the agent to which the given switch is tided.. :param switch_name: name of the switch :type switch_name: str :return: the agent :rtype: :class:`mininet.node.NetconfAgent` """ for switch in self.__mininet.switches: if switch.name == switch_name: return switch.agent return None
def netWithVNFs(netconf=False): "Create an empty network and add nodes to it." #ctl = InbandController( 'ctl', ip='192.168.123.1' ) #ctl = InbandController( 'ctl', ip='127.0.0.1' ) #net = MininetWithControlNet( ) net = MininetWithControlNet(controller=Controller, autoSetMacs=True) #net = Mininet( controller=Controller ) info('*** Adding controller\n') ctl = net.addController('c0', controller=RemoteController) #ctl = net.addController( 'c0' ) #import pdb; pdb.set_trace(); info('*** Adding hosts \n') h1 = net.addHost('h1') h2 = net.addHost('h2') info('*** Adding VNFs \n') if netconf: ee1 = net.addEE('ee1') ee1.setVNF(vnf_name='netconf') ee2 = net.addEE('ee2') ee2.setVNF(vnf_name='netconf') #[ exe1_sw, exe1_container ] = net.addManagedExe( 'exe1', nintf=5) #exe1_container.cmd = netconf.makeNetConfCmd() else: ee1 = net.addEE('ee1', cpu=0.5) # ee1.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') # ee1.setVNF(vnf_name='simpleForwarder', name=ee1.name) ee1.setVNF(vnf_name='headerCompressor', name=ee1.name) ee2 = net.addEE('ee2', cpu=0.5) ee2.setVNF(vnf_name='headerDecompressor', name=ee2.name) # ee2.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') info('*** Adding switches\n') s3 = net.addSwitch('s3') s4 = net.addSwitch('s4') info('*** Creating links\n') net.addLink(h1, s3) net.addLink(h2, s4) net.addLink(s3, s4) if netconf: net.addLink(exe1_sw, s3) else: net.addLink(ee1, s3) net.addLink(ee2, s4) info('*** Starting network\n') net.start() info('*** Running CLI\n') CLI(net) info('*** Stopping network') net.stop()
def netWithVNFs(netconf=False): "Create an empty network and add nodes to it." #ctl = InbandController( 'ctl', ip='192.168.123.1' ) #ctl = InbandController( 'ctl', ip='127.0.0.1' ) #net = MininetWithControlNet( ) net = MininetWithControlNet(controller=Controller, autoSetMacs=True) #net = Mininet( controller=Controller ) info('*** Adding controller\n') ctl = net.addController('c0', controller=RemoteController) #import pdb; pdb.set_trace(); info('*** Adding hosts \n') h1 = net.addHost('h1') h2 = net.addHost('h2') info('*** Adding VNFs \n') if netconf: ee1 = net.addEE('ee1') ee1.setVNF(vnf_name='netconf') ee2 = net.addEE('ee2') ee2.setVNF(vnf_name='netconf') #[ exe1_sw, exe1_container ] = net.addManagedExe( 'exe1', nintf=5) #exe1_container.cmd = netconf.makeNetConfCmd() else: ee1 = net.addEE('ee1', cpu=0.1) #ee1.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') ee1.setVNF(vnf_name='simpleForwarder', device=ee1.name + '_eth1', name=ee1.name) ee2 = net.addEE('ee2', cpu=0.1) #example for NAT with two ports connected to internal hosts (private addresses) and one port connected to the Internet (public address) device = [{ 'index': 0, 'name': 'eth1', 'ip1': '1.0.0.1', 'ip2': '1.0.0.10' }, { 'index': 1, 'name': 'eth2', 'ip1': '1.0.0.20', 'ip2': '1.0.0.30' }] public = {'index': 2, 'name': 'eth2'} ee2.setVNF(vnf_name='nat', device=device, public=public) # ee2.setVNF(vnf_name='simpleObservationPoint', name=ee2.name) #ee2.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') #ee2.setVNF(vnf_name='lookbusy', # mem_util='5MB', cpu_util='8-20', cpu_mode='curve', # cpu_curve_period='5m', cpu_curve_peak='2m' ) info('*** Adding switches\n') s3 = net.addSwitch('s3') s4 = net.addSwitch('s4') info('*** Creating links\n') net.addLink(h1, s3) net.addLink(h2, s4) net.addLink(s3, s4) if netconf: net.addLink(exe1_sw, s3) else: net.addLink(ee1, s3) net.addLink(ee2, s4) info('*** Starting network\n') net.start() info('*** Running CLI\n') CLI(net) info('*** Stopping network') net.stop()
def netWithVNFs(netconf=False): "Create an empty network and add nodes to it." #ctl = InbandController( 'ctl', ip='192.168.123.1' ) #ctl = InbandController( 'ctl', ip='127.0.0.1' ) #net = MininetWithControlNet( ) net = MininetWithControlNet(controller=Controller, autoSetMacs=True) #net = Mininet( controller=Controller ) info('*** Adding controller\n') ctl = net.addController('c0', controller=RemoteController) #import pdb; pdb.set_trace(); info('*** Adding hosts \n') h1 = net.addHost('h1') h2 = net.addHost('h2') info('*** Adding VNFs \n') if netconf: ee1 = net.addEE('ee1') ee1.setVNF(vnf_name='netconf') ee2 = net.addEE('ee2') ee2.setVNF(vnf_name='netconf') #[ exe1_sw, exe1_container ] = net.addManagedExe( 'exe1', nintf=5) #exe1_container.cmd = netconf.makeNetConfCmd() else: ee1 = net.addEE('ee1', cpu=0.1) #ee1.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') ee1.setVNF( vnf_name='simpleForwarder', device=ee1.name + '_eth1', name=ee1.name) ee2 = net.addEE('ee2', cpu=0.1) #example for NAT with two ports connected to internal hosts (private addresses) and one port connected to the Internet (public address) device = [{ 'index': 0, 'name': 'eth1', 'ip1': '1.0.0.1', 'ip2': '1.0.0.10' }, { 'index': 1, 'name': 'eth2', 'ip1': '1.0.0.20', 'ip2': '1.0.0.30' }] public = {'index': 2, 'name': 'eth2'} ee2.setVNF(vnf_name='nat', device=device, public=public) # ee2.setVNF(vnf_name='simpleObservationPoint', name=ee2.name) #ee2.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') #ee2.setVNF(vnf_name='lookbusy', # mem_util='5MB', cpu_util='8-20', cpu_mode='curve', # cpu_curve_period='5m', cpu_curve_peak='2m' ) info('*** Adding switches\n') s3 = net.addSwitch('s3') s4 = net.addSwitch('s4') info('*** Creating links\n') net.addLink(h1, s3) net.addLink(h2, s4) net.addLink(s3, s4) if netconf: net.addLink(exe1_sw, s3) else: net.addLink(ee1, s3) net.addLink(ee2, s4) info('*** Starting network\n') net.start() info('*** Running CLI\n') CLI(net) info('*** Stopping network') net.stop()
class NetworkManagerMininet(NetworkManager, GenericEventNotifyer, LoggerHelper): __shared_state = {} def __init__(self): 'lazy init' self.__dict__ = self.__shared_state if self.__dict__: # already initialized return GenericEventNotifyer.__init__(self) self.net = None # Initial topo to create Mininet topology self.initial_topo = None # Network state self.state = NetworkManager.DOWN self.port_map = {} self.dpid = {} # Running topo to manage/store running topo obtained from Mininet - dummy object self.network = Store() # Active link list self.network.links = {} # Active node list self.network.nodes = {} # Queue for parallel event processing self.of_event_queue = [] # Timer daemon process for periodic polling self.process = None self.vnf_manager = None self.last_status_poll = 0 pox.core.core.listen_to_dependencies(self) # Start periodic scan self.periodic_scan() ######### ### Mininet topology compilation ######### def build_topo_network(self, network_topo, appPrefs): """ Filling Mininet "topo" with app specific data network_topo - Topology object need to update appPrefs - global startup parameters No return """ dpctl = None if not appPrefs['dpctl'] else int(appPrefs['dpctl']) #dpctl = int(appPrefs['dpctl']) if 'dpctl' in appPrefs else None network_topo['netopts'].update({ 'listenPort': dpctl, 'topo': None, 'build': False, 'ipBase': appPrefs['ipBase'], 'autoSetMacs': True, 'autoStaticArp': True }) self._debug('Add global parameters: %s' % network_topo['netopts']) def build_topo_switch(self, network_topo, name, opts, appPrefs): """ Build switch object with given name and "opts" params network_topo - Topology object need to update name - switch name opts - switch instance params appPrefs - global startup parameters No return """ required_keys = {'switchType'} if not opts.viewkeys() & required_keys: raise KeyError('Required argument is missing!\nCheck: ' + repr(required_keys)) switch = dict() switchParms = {'name': name} if 'dpctl' in opts: switchParms['listenPort'] = int(opts['dpctl']) if 'dpid' in opts: switchParms['dpid'] = opts['dpid'] # Get switch type or default if opts['switchType'] == 'default': sw_type = appPrefs['switchType'] else: sw_type = opts['switchType'] # Get the correct switch class if sw_type == 'ivs': switchParms['cls'] = IVSSwitch elif sw_type == 'user': switchParms['cls'] = CustomUserSwitch elif sw_type == 'userns': switchParms['inNamespace'] = True switchParms['cls'] = CustomUserSwitch else: switchParms['cls'] = customOvs switch['openflowver'] = appPrefs['openFlowVersions'] switch['controllers'] = opts.get('controllers', None) switch['netflow'] = opts.get('netflow', None) switch['sflow'] = opts.get('sflow', None) # Are these ifs important or switch instances can contain empty ip, extintf attributes? # Attach external interfaces if 'externalInterfaces' in opts: switch['extintf'] = opts['externalInterfaces'] if 'ip' in opts: switch['ip'] = opts['switchIP'] # Add new switch param and switch switch['params'] = switchParms network_topo['switches'][opts['_id']] = switch self._debug('Add %s Switch to mininet topo with parameters %s' % (name, network_topo['switches'][opts['_id']])) def build_topo_ee(self, network_topo, name, opts): """ Build VNF Container object with given name and "opts" params network_topo - Topology object need to update name - Container name opts - Container instance params No return """ settings = {} ip = opts.get('ip', None) if ip: settings['ip'] = ip defaultRoute = opts.get('defaultRoute', None) if defaultRoute: settings['defaultRoute'] = 'via ' + defaultRoute # Create the correct host class hostCls = EE params = { 'name': name, 'cls': hostCls, 'cpu': opts['res']['cpu'], 'mem': opts['res']['mem'], 'ee_type': opts.get('ee_type', 'static'), } for o in [ 'remote_dpid', 'remote_port', 'remote_conf_ip', 'remote_netconf_port', 'netconf_username', 'netconf_passwd', 'local_intf_name' ]: params[o] = opts.get(o) params.update(settings) network_topo['ee'][opts['_id']] = {'params': params} if False: # Set the CPULimitedHost specific options if 'cores' in opts: network_topo['ee'][opts['_id']]['cores'] = opts['cores'] if 'cpu' in opts: network_topo['ee'][opts['_id']]['frac'] = { 'f': opts['res']['cpu'], 'sched': opts['sched'] } # Attach external interfaces if 'externalInterfaces' in opts: network_topo['ee'][ opts['_id']]['extintf'] = opts['externalInterfaces'] vlanif = opts.get('vlanInterfaces', None) if vlanif: self._debug('Checking that OS is VLAN prepared') self.pathCheck('vconfig', moduleName='vlan package') moduleDeps(add='8021q') network_topo['ee'][opts['_id']]['vlanif'] = vlanif self._debug("Add %s EE to mininet topo with parameters %s" % (name, network_topo['ee'][opts['_id']])) def pathCheck(self, *args, **kwargs): """Make sure each program in *args can be found in $PATH.""" moduleName = kwargs.get('moduleName', 'it') for arg in args: if not quietRun('which ' + arg): showerror( title="Error", message='Cannot find required executable %s.\n' % arg + 'Please make sure that %s is installed ' % moduleName + 'and available in your $PATH.') def build_topo_controller(self, network_topo, name, opts): """ Build Controller object with given name and "opts" params network_topo - Topology object need to update name - Controller name opts - Controller instance params No return """ # Get controller info from panel controllerType = opts['controllerType'] # Make controller self._info('*** Getting controller selection: %s' % controllerType) if controllerType == 'remote': c = RemoteController elif controllerType == 'inband': c = InbandController elif controllerType == 'ovsc': c = OVSController else: c = Controller params = { 'name': name, 'ip': opts['remoteIP'], 'port': opts['remotePort'], 'controller': c } network_topo['controllers'][opts['_id']] = {'params': params} self._debug("Add %s Controller(s) to mininet topo with parameters %s" % (name, network_topo['controllers'][opts['_id']])) def build_topo_sap(self, network_topo, name, opts): settings = {} ip = opts.get('ip', None) if ip: settings['ip'] = ip # else: # nodeNum = canvas.startpointOpts[name]['nodeNum'] # settings['nodeNum']= nodeNum # ipBaseNum, prefixLen = netParse( self.appPrefs['ipBase'] ) # settings['ipBaseNum'] = ipBaseNum # settings['prefixLen'] = prefixLen # ip = ipAdd(i=nodeNum, prefixLen=prefixLen, ipBaseNum=ipBaseNum) defaultRoute = opts.get('defaultRoute', None) if defaultRoute: settings['defaultRoute'] = 'via ' + defaultRoute # Create the correct host class hostCls = Host if 'cores' in opts or 'cpu' in opts: hostCls = CPULimitedHost params = {'name': name, 'cls': hostCls} params.update(settings) network_topo['saps'][opts['_id']] = {'params': params} # Set the CPULimitedHost specific options if 'cores' in opts: network_topo['saps'][opts['_id']]['cores'] = opts['cores'] if 'cpu' in opts: network_topo['saps'][opts['_id']].update({ 'f': opts['res']['cpu'], 'sched': opts['sched'] }) # Attach external interfaces if 'externalInterfaces' in opts: network_topo['saps'][opts['_id']]['extintf'] = \ opts['externalInterfaces'] vlanif = opts.get('vlanInterfaces', None) if vlanif: pass # self._debug('Checking that OS is VLAN prepared') # self.pathCheck('vconfig', moduleName='vlan package') # moduleDeps( add='8021q' ) self._debug("Add %s SAP to mininet topo with parameters %s" % (name, network_topo['saps'][opts['_id']])) def build_topo_links(self, network_topo, phy_g): for node1, node2, params in list(phy_g.edges_iter(data=True)): if params.get('type', None) == 'data': network_topo['links'][(node1, node2)] = { 'node1': node1, 'node2': node2, 'cls': TCLink } if 'delay' in params: network_topo['links'][(node1, node2)]['delay'] = params.get( 'delay', 5) if 'bw' in params: network_topo['links'][(node1, node2)]['bw'] = params['bw'] self._debug( "Create link between %s : %s with parameters %s" % (node1, node2, network_topo['links'][(node1, node2)])) # TODO: don't use appPrefs and canvas, references and bindings to GUI should be removed # check imports to eliminate unnecessary ones def build_topo(self, phy_g, appPrefs): """ Generate and set mininet topo No return """ from Utils import dump #dump(phy_g) self.initial_topo = self.generate_topo(phy_g, appPrefs) def generate_topo(self, phy_g, appPrefs): """ Build the topology according to the GUI widget params appPrefs - global params: ipBase, switchType, openFlowVersions Return: topo object """ self._info("*** Build network based on our topology.") # Empty topo network_topo = { 'netopts': dict(), 'ee': dict(), 'saps': dict(), 'switches': dict(), 'controllers': dict(), 'links': dict() } # Set global params self.build_topo_network(network_topo, appPrefs) # Make nodes self._info("*** Getting Hosts and Switches.") for node in phy_g.nodes(): if phy_g.node[node]['node_type'] == NODE_TYPE['SWITCH']: # Adding specific switch object to "topo" self.build_topo_switch(network_topo, node, phy_g.node[node], appPrefs) # TODO: Need to handle 'LegacySwitch' ??? # elif 'LegacySwitch' in tags: # opts = canvas.switchOpts[name] # params = {'params':{'name': name, # 'cls': LegacySwitch} # } # # Adding specific switch object to "topo" # network_topo['switches'][opts['_id']] = params elif phy_g.node[node]['node_type'] == NODE_TYPE['NODE']: # Adding specific EE object to "topo" self.build_topo_ee(network_topo, node, phy_g.node[node]) elif phy_g.node[node]['node_type'] == NODE_TYPE['CONTROLLER']: # Adding specific controller object to "topo" self.build_topo_controller(network_topo, node, phy_g.node[node]) # TODO: Need to handle 'LegacyRouter' ??? # elif 'LegacyRouter' in tags: # opts = canvas.switchOpts[name] # params = {'params':{'name': name, # 'cls': LegacyRouter} # } # # Adding specific router object to "topo" # network_topo['hosts'][opts['_id']] = params elif phy_g.node[node]['node_type'] == NODE_TYPE['SAP']: # Adding specific SAP object to "topo" self.build_topo_sap(network_topo, node, phy_g.node[node]) else: raise TypeError('Cannot create mystery node: ' + node) # Adding the links self._info("*** Getting Links.") self.build_topo_links(network_topo, phy_g) return network_topo ######### ### Mininet topology creation ######### # Use this function instead of direct access to initial_topo def get_initial_topology(self): """ General function to return the observed topology in the NetworkX format """ return self._convert_to_NetworkX_format() def _convert_to_NetworkX_format(self): """ Convert the "topo" dictionary to NetworkX format for Orchestration module Keep only the relevant node information Return: graph - networkx.classes.graph.Graph @author: czentye """ # Create empty graph graph = nx.Graph() # Return if topo is not set if not self.initial_topo: return graph from Utils import dump #dump(self.initial_topo, 'NetMen initial_topo') # Convert "controllers" to node controllers = self.initial_topo['controllers'] for c in controllers: node_name = controllers[c]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = c graph.node[node_name]['node_type'] = NODE_TYPE['CONTROLLER'] graph.node[node_name]['hostname'] = node_name graph.node[node_name][ 'controllerType'] = None #TODO cut from controllers[c]['params']['controller'] graph.node[node_name]['canvas_id'] = None graph.node[node_name]['remoteIP'] = controllers[c]['params']['ip'] graph.node[node_name]['remotePort'] = controllers[c]['params'][ 'port'] # Convert "ee" to node ee = self.initial_topo['ee'] for container in ee: node_name = ee[container]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = container graph.node[node_name]['node_type'] = NODE_TYPE['NODE'] graph.node[node_name]['hostname'] = node_name graph.node[node_name]['nodeNum'] = None graph.node[node_name]['canvas_id'] = None # graph.node[node_name]['ee_type'] = ee[container]['params']['ee_type'] # graph.node[node_name]['cpu'] = ee[container]['params']['cpu'] # graph.node[node_name]['mem'] = ee[container]['params']['mem'] # Duplicated data, SHOULD be removed !!! graph.node[node_name]['res'] = { 'cpu': ee[container]['params']['cpu'], 'mem': ee[container]['params']['mem'] } # Convert "saps" to node saps = self.initial_topo['saps'] for sap in saps: node_name = saps[sap]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = sap graph.node[node_name]['node_type'] = NODE_TYPE['SAP'] graph.node[node_name]['name'] = node_name graph.node[node_name]['canvas_id'] = None graph.node[node_name]['nodeNum'] = None # Convert "switches" to node switches = self.initial_topo['switches'] for switch in switches: node_name = switches[switch]['params']['name'] graph.add_node(node_name) graph.node[node_name]['_id'] = switch graph.node[node_name]['node_type'] = NODE_TYPE['SWITCH'] graph.node[node_name]['hostname'] = node_name graph.node[node_name]['canvas_id'] = None graph.node[node_name]['nodeNum'] = None graph.node[node_name]['controllers'] = switches[switch][ 'controllers'] graph.node[node_name]['netflow'] = switches[switch]['netflow'] graph.node[node_name]['sflow'] = switches[switch]['sflow'] #graph.node[node_name]['switchIP'] = '' graph.node[node_name][ 'switchType'] = None #TODO cut from switches[switch]['params']['cls'] # Convert "links" to edges links = self.initial_topo['links'] for node1, node2 in links: options = {'weight': 1} if graph.node[node1]['node_type'] == 'C' or graph.node[node2][ 'node_type'] == 'C': # Control links are missing from topo -> this branch is useless options['type'] = LINK_TYPE['CONTROL'] else: if 'bw' in links[(node1, node2)]: options['bw'] = links[(node1, node2)]['bw'] options['type'] = LINK_TYPE['DATA'] if 'delay' in links[(node1, node2)]: options['delay'] = links[(node1, node2)].get('delay', 5) graph.add_edge(node1, node2, attr_dict=options) # Adding control channel links # TODO improve if there is multiple controller for node in self.initial_topo['switches']: graph.add_edge( self.initial_topo['switches'][node]['controllers'][0], node, attr_dict={ 'type': LINK_TYPE['CONTROL'], 'weight': 1 }) return graph def _start_mininet(self, opts=None): if not opts: opts = {} self._info("***Starting mininet***") opts['controller'] = Controller opts['autoSetMacs'] = True self.net = MininetWithControlNet(**opts) def _create_ee(self, ees): self._info('**** Create %d execution environment(s)' % len(ees)) for id, ee in ees.iteritems(): params = ee['params'] name = params['name'] self._debug('\tCreate %s EE with params %s' % (name, ee)) if params['ee_type'] == 'netconf': sw = self.net.addSwitch(name) agt = self.net.addAgent('agt_' + name) agt.setSwitch(sw) continue elif params['ee_type'] == 'remote': p = copy.deepcopy(params) p['cls'] = None p['inNamespace'] = False p['dpid'] = p['remote_dpid'] p['username'] = p['netconf_username'] p['passwd'] = p['netconf_passwd'] p['conf_ip'] = p['remote_conf_ip'] p['agentPort'] = p['remote_netconf_port'] del p['name'] sw = self.net.addRemoteSwitch(name, **p) agt = self.net.addAgent('agt_' + name, **p) agt.setSwitch(sw) continue else: # params['ee_type'] == 'static': # normal case h = self.net.addEE(**params) if 'cores' in ee: h.setCPUs(**ee['cores']) if 'frac' in ee: h.setCPUFrac(**ee['frac']) if 'vlanif' in ee: for vif in ee['vlaninf']: # TODO: In miniedit it was after self.net.build() h.cmdPrint('vconfig add ' + name + '-eth0 ' + vif[1]) h.cmdPrint('ifconfig ' + name + '-eth0.' + vif[1] + ' ' + vif[0]) def _create_switches(self, switches): self._info('**** Create %d switch(es)' % len(switches)) for id, switch in switches.iteritems(): self._debug('\tCreate %s switch with params %s' % (switch['params']['name'], switch)) sw = self.net.addSwitch(**switch['params']) if 'openflowver' in switch: sw.setOpenFlowVersion(switch['openflowver']) if 'ip' in switch: sw.setSwitchIP(switch['ip']) def _create_controllers(self, controllers): self._info('**** Create %d controller(s)' % len(controllers)) for id, controller in controllers.iteritems(): self._debug('\tCreate %s controller with params %s' % (controller['params']['name'], controller)) self.net.addController(**controller['params']) def _create_saps(self, saps): self._info('**** Create %d SAP(s)' % len(saps)) for id, sap in saps.iteritems(): self._debug('\tCreate %s SAP with params %s' % (sap['params']['name'], sap)) self.net.addHost(**sap['params']) def _create_links(self, links): def is_remote(node): return isinstance(node, RemoteSwitch) def is_local(node): return not is_remote(node) self._info('**** Create %d link(s)' % len(links)) for id, link in links.iteritems(): self._debug('\tCreate link %s with params: %s' % (id, link)) node1 = self.net.get(link['node1']) node2 = self.net.get(link['node2']) name_to_node = {'node1': node1, 'node2': node2} link.update(name_to_node) remote = filter(is_remote, [node1, node2]) local = filter(is_local, [node1, node2]) if not remote: self.net.addLink(**link) else: sw = local[0] r = remote[0] intfName = r.params['local_intf_name'] r_mac = None # unknown, r.params['remote_mac'] r_port = r.params['remote_port'] self._debug('\tadd hw interface (%s) to node (%s)' % (intfName, sw.name)) # This hack avoids calling __init__ which always makeIntfPair() link = Link.__new__(Link) i1 = Intf(intfName, node=sw, link=link) i2 = Intf(intfName, node=r, mac=r_mac, port=r_port, link=link) i2.mac = r_mac # mn runs 'ifconfig', which resets mac to None # link.intf1, link.intf2 = i1, i2 def _start_controllers(self): self._info('**** Start controller(s)') for controller in self.net.controllers: controller.start() def _start_switches(self, switches): for id, switch in switches.iteritems(): controllers = [ ] #with legacySwitch there is no controller in miniedit if switch['controllers']: controllers.append(self.net.get(*switch['controllers'])) self.net.get(switch['params']['name']).start(controllers) def start_topo(self, **kwargs): """ Start the physical topology (using Mininet) topo - Set and use this topology nflow - sflow - startcli - No return """ self.change_network_state(NetworkManager.STARTING) # Save given topo (created by MiniEdit) if 'topo' in kwargs: self.initial_topo = kwargs['topo'] if self._is_initial_topo_empty(): raise AttributeError("Initial topology is missing!!!") self._start_mininet(self.initial_topo['netopts']) self._create_ee(self.initial_topo['ee']) self._create_switches(self.initial_topo['switches']) self._create_controllers(self.initial_topo['controllers']) self._create_saps(self.initial_topo['saps']) self._create_links(self.initial_topo['links']) self.net.build() self.net.start() if 'nflow' in kwargs and kwargs['nflow'].get('nflowTarget'): self.start_nflow(kwargs['nflow']['nflowTarget'], kwargs['nflow']['nflowTimeout'], kwargs['nflow']['nflowAddId']) if 'sflow' in kwargs and kwargs['sflow'].get('sflowTarget'): self.start_sflow(kwargs['sflow']['sflowTarget'], kwargs['sflow']['sflowHeader'], kwargs['sflow']['sflowSampling'], kwargs['sflow']['sflowPolling']) if 'startcli' in kwargs and kwargs['startcli'] == '1': self.start_cli() self.change_network_state(NetworkManager.UP) def _is_initial_topo_empty(self): return self.initial_topo is None or ( 'ee' in self.initial_topo and 'saps' in self.initial_topo and 'switches' in self.initial_topo and 'controller' in self.initial_topo) def stop_network(self): self.change_network_state(NetworkManager.STOPPING) if self.net is not None: self.net.stop() self.net = None self.initial_topo = None self.network.links = {} self.network.nodes = {} self.dpid = {} self.port_map = {} # self._debug('Cleaning up Mininet...') # mininet.clean.cleanup() # time.sleep(4) self.change_network_state(NetworkManager.DOWN) def network_alive(self): return self.state in [NetworkManager.UP] def start_sflow(self, target, header, sampling, polling): sflowEnabled = False sflowSwitches = '' for switch in self.initial_topo['switches'].itervalues(): name = switch['params']['name'] if switch.get('sflow', None) == '1': self._info('%s has sflow enabled' % name) sflowSwitches += ' -- set Bridge ' + name + ' sflow=@MiniEditSF' sflowEnabled = True if sflowEnabled: sflowCmd = 'ovs-vsctl -- --id=@MiniEditSF create sFlow '\ 'target=\\\"'+target+'\\\" '\ 'header='+header+' '+ \ 'sampling='+sampling+' '+\ 'polling='+polling self._debug('sFlow command: cmd = %s%s' % (sflowCmd, sflowSwitches)) call(sflowCmd + sflowSwitches, shell=True) else: self._info('No switches with sflow') def start_nflow(self, nflowTarget, nflowTimeout, nflowAddId): nflowSwitches = '' nflowEnabled = False for switch in self.initial_topo['switches'].itervalues(): name = switch['params']['name'] if switch.get('netflow', None) == '1': self._info('%s has Netflow enabled' % name) nflowSwitches += ' -- set Bridge ' + name + ' netflow=@MiniEditNF' nflowEnabled = True if nflowEnabled: nflowCmd = 'ovs-vsctl -- --id=@MiniEditNF create NetFlow '\ 'target=\\\"'+nflowTarget+'\\\" '\ 'active-timeout='+nflowTimeout if nflowAddId == 1: nflowCmd += ' add_id_to_interface=true' else: nflowCmd += ' add_id_to_interface=false' self._debug('nFlowcmd = %s' % (nflowCmd + nflowSwitches)) call(nflowCmd + nflowSwitches, shell=True) else: self._info('No switches with Netflow') def start_cli(self): CLI(self.net) def start_clicky(self, vnf_name): instances = self.vnf_manager.start_clicky(vnf_name) self.net.clickys += instances # self._debug('CLICKY: %s' % instances) ######### ### Scan the Mininet network and updating the running topology ######### def periodic_scan(self, wait=1): """Scan the Mininet topo and recall itself after a period of time (wait)""" self.scan_network() self.process = threading.Timer(wait, self.periodic_scan) self.process.daemon = True self.process.start() def scan_network(self, forced=False): self.process_event_queue() if not self.net or self.state == NetworkManager.STOPPING: return self.poll_netconf_agents(forced) checked = [] # net -> Mininet network representation for name, node in self.net.items(): checked.append(name) if not self.found_node(node): return deleted = [] for name, opts in self.network.nodes.iteritems(): if opts.get('parent'): # netconf-controlled vnf node, mininet doesn't know about it continue if name not in checked: deleted.append(name) if self.state != NetworkManager.STARTING: for node_name in deleted: self._debug('remove node (%s) from network table' % node_name) del self.network.nodes[node_name] # TODO: send event deleted = [] for link_id, link in self.network.links.iteritems(): node_names = [link['node1'], link['node2']] for node_name in node_names: if node_name not in self.network.nodes: self._debug('delete link: %s' % node_names) deleted.append(link_id) for link_id in deleted: del self.network.links[link_id] self._fire_dpid_update(self.dpid) self._fire_port_map_update(self.port_map) def poll_netconf_agents(self, forced=False): "Poll netconf agents for VNF status updates" poll_period = 10 if (time.time() - self.last_status_poll < poll_period) and not forced: return for sw in self.net.switches: if not sw.getAgent(): continue i = self.vnf_manager.get_vnf_info_on_node(sw.name) # self._debug('VNF_INFO: %s' % i) visited = [] for vnf_name, new_opts in i.iteritems(): if vnf_name is None: continue new_opts['parent'] = sw.name visited.append(vnf_name) orig = self.network.nodes.get(vnf_name, {}) old_status = orig.get('status') if orig == new_opts: # nothing's changed continue orig.update(new_opts) self.network.nodes[vnf_name] = orig links = new_opts.get('link', []) if type(links) != list: links = [links] self.found_vnf_links(vnf_name, links) if old_status != new_opts['status']: self._fire_vnf_update(vnf_name, new_opts['status']) deleted = [] for node_name, opts in self.network.nodes.iteritems(): # self._debug('node_name: %s opts: %s' % (node_name, opts)) if opts.get('parent') != sw.name: continue if node_name not in visited: deleted.append(node_name) for vnf_name in deleted: del self.network.nodes[vnf_name] # self._debug('vnf_name: %s' % vnf_name) sw = self.net.nameToNode[vnf_name] self.net.switches.remove(sw) del self.net.nameToNode[vnf_name] neighbours = self.port_map[vnf_name].keys() for n in neighbours: del self.port_map[n][vnf_name] # Assuming there can be only one link between n and vnf. link = { 'node1': n, 'node2': vnf_name, 'intf1': None, 'intf2': None, 'delete': True } pox.core.core.raiseLater(self, LinkChange, **link) del self.port_map[vnf_name] self._fire_vnf_update(vnf_name, 'STOPPED') self.last_status_poll = time.time() def found_vnf_links(self, vnf_id, links): def get_intf_by_name(node, intf_name): for i in node.intfList(): if str(i) == intf_name: return i return None def get_or_create_intf(dev_name, obj, port): if port not in obj.intfs: # does not exist, let's create it return Intf(dev_name, node=obj, port=port) if str(obj.intfs[port]) != dev_name: # intf exists, but port is invalid return Intf(dev_name, node=obj, port=port) return obj.intfs[port] for i, link in enumerate(links): if int(link['sw_port']) == -1: # disconnected links (with port==-1) are omitted continue sw_name = link.get('sw_id', 'sw_id' + str(i)) sw_dev = link.get('sw_dev', 'sw_dev' + str(i)) sw_port = int(link['sw_port']) nf_name = vnf_id nf_dev = link.get('vnf_dev', 'vnf_dev' + str(i)) nf_port = int(link['vnf_port']) nf_mac = link['vnf_dev_mac'] sw_obj = self.net.getNodeByName(sw_name) try: nf_obj = self.net.getNodeByName(nf_name) except KeyError: # this is a VNF not managed by mininet, yet we have to # add to the mininet 'database'. TODO: Ideally, it # would be a RemoteHost, but for now it is a # RemoteSwitch. nf_obj = self.net.addRemoteSwitch(nf_name, dpid="-1") sw_i = get_or_create_intf(sw_dev, sw_obj, sw_port) if nf_dev in nf_obj.intfNames(): nf_i = get_intf_by_name(nf_obj, nf_dev) else: nf_i = Intf(nf_dev, node=nf_obj, port=nf_port, mac=nf_mac) nf_i.mac = nf_mac # mn runs 'ifconfig', which resets mac to None self.found_link(sw_obj, nf_obj, sw_i, nf_i) def found_link(self, node_a, node_b, intf_a, intf_b): # link "A -> B" is the same as link "B -> A" link = [(node_a, intf_a), (node_b, intf_b)] link = sorted(link, key=lambda x: x[1]) [(node1, intf1), (node2, intf2)] = link link_id = ''.join([intf1.name, intf2.name]) orig_link = self.network.links.get(link_id, {}) link = { 'node1': node1.name, 'node2': node2.name, 'intf1': intf1, 'intf2': intf2 } try: self.port_map[node1.name][node2.name] = node1.ports[intf1] except KeyError: self.port_map[node1.name] = {node2.name: node1.ports[intf1]} try: self.port_map[node2.name][node1.name] = node2.ports[intf2] except KeyError: self.port_map[node2.name] = {node1.name: node2.ports[intf2]} if not cmp(orig_link, link) == 0: self.network.links[link_id] = link pox.core.core.raiseLater(self, LinkChange, **link) def found_node(self, node): orig = self.network.nodes.get(node.name, {}) new_opts = copy.deepcopy(orig) new_opts['name'] = node.name new_opts['intf'] = {} for intf in node.intfList(): new_opts['intf'][intf.name] = { 'ip': node.IP(intf), 'mac': node.MAC(intf), 'port': node.ports[intf] } try: # taken form Node.connectionsTo for intf in node.intfList(): link = intf.link if not intf.link: continue node1, node2 = link.intf1.node, link.intf2.node if node1 == node or node2 == node: self.found_link(node1, node2, link.intf1, link.intf2) except AttributeError: # network is not running return False if getattr(node, 'dpid', None): new_opts['dpid'] = int(node.dpid, base=16) self.dpid[node.name] = int(node.dpid, base=16) if not cmp(orig, new_opts) == 0: self.network.nodes[node.name] = new_opts pox.core.core.raiseLater(self, NodeChange, NodeChange.TYPE_DUMMY, **new_opts) return True def dpid_to_name(self, dpid): for name, name_dpid in self.dpid.iteritems(): if dpid == name_dpid: return name return None def process_event_queue(self): processed = [] for e in self.of_event_queue: name = self.dpid_to_name(e.dpid) if not name: continue e.name = name processed.append(e) self.fire(e.type, e) for e in processed: self.of_event_queue.remove(e) def change_network_state(self, new_state): if self.state == new_state: return self.state = new_state self._fire_network_state_change() ######### ### Change Event generation and OpenFlow event handling ######### def _fire_network_state_change(self): """Signalling the node/link params are changed""" event = Store() event.state = self.state self.fire('network_state_change', event) def _fire_dpid_update(self, dpids): event = Store() event.dpids = dpids self.fire('dpid_update', event) def _fire_port_map_update(self, port_map): event = Store() event.port_map = port_map self.fire('port_map_update', event) def _fire_switch_connection_up(self): pass def _fire_switch_connection_down(self): pass def _fire_vnf_update(self, vnf_name, status): m = { 'FAILED': 'failed', 'UP_AND_RUNNING': 'running', 'INITIALIZING': 'starting', 'STOPPED': 'stopped', } event = Store() event.name = vnf_name try: event.on_node = self.network.nodes[vnf_name]['parent'] except KeyError: event.on_node = None event.status = m.get(status, 'failed') self.fire('vnf_update', event) def _handle_openflow_ConnectionUp(self, event): event.type = 'switch_connection_up' self.of_event_queue.append(event) def _handle_openflow_ConnectionDown(self, event): event.type = 'switch_connection_down' self.of_event_queue.append(event)
def netWithVNFs(netconf=False): "Create an empty network and add nodes and VNFs to it." net = MininetWithControlNet(controller=Controller, autoSetMacs=True) info('*** Adding controller\n') ctl = net.addController('c0', controller=RemoteController) info('*** Adding hosts \n') h1 = net.addHost('h1') h2 = net.addHost('h2') info('*** Adding VNFs \n') if netconf: ee1 = net.addEE('ee1') ee1.setVNF(vnf_name='netconf') ee2 = net.addEE('ee2') ee2.setVNF(vnf_name='netconf') else: ee1 = net.addEE('ee1', cpu=0.1) ee1.setVNF(vnf_name='simpleForwarder', name=ee1.name) ee2 = net.addEE('ee2', cpu=0.1) #ee2.setVNF(vnf_name='fakeload', name=ee2.name, cpu='8', mem='5MB') ee2.setVNF(vnf_name='simpleForwarder') info('*** Adding switches\n') s3 = net.addSwitch('s3') s4 = net.addSwitch('s4') info('*** Creating links\n') net.addLink(h1, s3) net.addLink(h2, s4) net.addLink(s3, s4) if netconf: net.addLink(exe1_sw, s3) else: net.addLink(ee1, s3) net.addLink(ee2, s4) info('*** Starting network\n') net.start() info('*** Running CLI\n') CLI(net) info('*** Stopping network') net.stop()
def netWithVNFs(netconf = False): "Create an empty network and add nodes and VNFs to it." net = MininetWithControlNet( controller=Controller, autoSetMacs=True ) info( '*** Adding controller\n' ) ctl = net.addController( 'c0' , controller=RemoteController ) info( '*** Adding hosts \n' ) h1 = net.addHost( 'h1') h2 = net.addHost( 'h2') info( '*** Adding VNFs \n' ) if netconf: ee1 = net.addEE( 'ee1' ) ee1.setVNF(vnf_name='netconf') ee2 = net.addEE( 'ee2' ) ee2.setVNF(vnf_name='netconf') else: ee1 = net.addEE( 'ee1',cpu=0.1) ee1.setVNF(vnf_name='simpleForwarder', name=ee1.name) ee2 = net.addEE( 'ee2',cpu=0.1) #ee2.setVNF(vnf_name='fakeload', name=ee2.name, cpu='8', mem='5MB') ee2.setVNF(vnf_name='simpleForwarder') info( '*** Adding switches\n' ) s3 = net.addSwitch( 's3' ) s4 = net.addSwitch( 's4' ) info( '*** Creating links\n' ) net.addLink( h1, s3 ) net.addLink( h2, s4 ) net.addLink( s3, s4 ) if netconf: net.addLink( exe1_sw, s3 ) else: net.addLink( ee1, s3 ) net.addLink( ee2, s4 ) info( '*** Starting network\n' ) net.start() info( '*** Running CLI\n' ) CLI( net ) info( '*** Stopping network' ) net.stop()
def netWithVNFs(netconf = False): "Create an empty network and add nodes to it." #ctl = InbandController( 'ctl', ip='192.168.123.1' ) #ctl = InbandController( 'ctl', ip='127.0.0.1' ) #net = MininetWithControlNet( ) net = MininetWithControlNet( controller=Controller, autoSetMacs=True ) #net = Mininet( controller=Controller ) info( '*** Adding controller\n' ) ctl = net.addController( 'c0' , controller=RemoteController ) #ctl = net.addController( 'c0' ) #import pdb; pdb.set_trace(); info( '*** Adding hosts \n' ) h1 = net.addHost( 'h1') h2 = net.addHost( 'h2') info( '*** Adding VNFs \n' ) if netconf: ee1 = net.addEE( 'ee1' ) ee1.setVNF(vnf_name='netconf') ee2 = net.addEE( 'ee2' ) ee2.setVNF(vnf_name='netconf') #[ exe1_sw, exe1_container ] = net.addManagedExe( 'exe1', nintf=5) #exe1_container.cmd = netconf.makeNetConfCmd() else: ee1 = net.addEE( 'ee1', cpu=0.5) # ee1.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') # ee1.setVNF(vnf_name='simpleForwarder', name=ee1.name) ee1.setVNF(vnf_name='headerCompressor', name=ee1.name) ee2 = net.addEE( 'ee2', cpu=0.5) ee2.setVNF(vnf_name='headerDecompressor', name=ee2.name) # ee2.setVNF(vnf_name='fakeLoad', cpu='8', mem='5MB') info( '*** Adding switches\n' ) s3 = net.addSwitch( 's3' ) s4 = net.addSwitch( 's4' ) info( '*** Creating links\n' ) net.addLink( h1, s3 ) net.addLink( h2, s4 ) net.addLink( s3, s4 ) if netconf: net.addLink( exe1_sw, s3 ) else: net.addLink( ee1, s3 ) net.addLink( ee2, s4 ) info( '*** Starting network\n' ) net.start() info( '*** Running CLI\n' ) CLI( net ) info( '*** Stopping network' ) net.stop()