def start(self): """Start the router: Configure the daemons, set the relevant sysctls, and fire up all needed processes""" self.cmd('ip', 'link', 'set', 'dev', 'lo', 'up') # Build the config self.config.build() # Check them err_code = False for d in self.config.daemons: out, err, code = self._processes.pexec(shlex.split(d.dry_run)) err_code = err_code or code if code: lg.error(d.NAME, 'configuration check failed [' 'rcode:', str(code), ']\n' 'stdout:', str(out), '\n' 'stderr:', str(err)) if err_code: lg.error('Config checks failed, aborting!') mininet.clean.cleanup() sys.exit(1) # Set relevant sysctls for opt, val in self.config.sysctl: self._old_sysctl[opt] = self._set_sysctl(opt, val) # Fire up all daemons for d in self.config.daemons: self._processes.popen(shlex.split(d.startup_line)) # Busy-wait if the daemon needs some time before being started while not d.has_started(): time.sleep(.001)
def addRouters(self, *routers: Union[str, Tuple[str, Dict[str, Any]]], **common_opts) -> List['RouterDescription']: """Add several routers in one go. :param routers: router names or tuples (each containing the router name and options only applying to this router) :param common_opts: common router options (optional)""" new_routers = [] for router_info in routers: # Accept either router names or tuple containing both a router name # and the specific options of the router n, opt = router_info if is_container(router_info) \ else (router_info, {}) # Merge router options by giving precedence to specific ones router_opts = { k: v for k, v in itertools.chain(common_opts.items(), opt.items()) } try: new_routers.append(self.addRouter(n, **router_opts)) except Exception as e: lg.error("Cannot create router '{}' with options '{}'".format( n, router_opts)) raise e return new_routers
def _parse_addresses( out: str ) -> Tuple[Optional[str], List[IPv4Interface], List[IPv6Interface]]: """Parse the output of an ip address command :return: mac, [ipv4], [ipv6]""" # 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state ... # link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 # inet 127.0.0.1/8 scope host lo # valid_lft forever preferred_lft forever # inet6 ::1/128 scope host # valid_lft forever preferred_lft forever mac = None v4 = [] v6 = [] for line in out.strip(' \n\t\r').split('\n'): parts = line.strip(' \n\t\r').split(' ') try: t = parts[0] if t == 'inet': v4.append(IPv4Interface(parts[1])) elif t == 'inet6': v6.append(IPv6Interface(parts[1])) elif 'link/' in t: mac = parts[1] except IndexError: log.error('Malformed ip-address line:', line) return mac, v4, v6
def start(self): """Start the node: Configure the daemons, set the relevant sysctls, and fire up all needed processes""" # Build the config self.nconfig.build() # Check them err_code = False for d in self.nconfig.daemons: out, err, code = self._processes.pexec(shlex.split(d.dry_run)) err_code = err_code or code if code: lg.error(d.NAME, 'configuration check failed [' 'rcode:', code, ']\n' 'stdout:', out, '\n' 'stderr:', err) if err_code: lg.error('Config checks failed, aborting!') mininet.clean.cleanup() sys.exit(1) # Set relevant sysctls for opt, val in self.nconfig.sysctl: self._old_sysctl[opt] = self._set_sysctl(opt, val) # Fire up all daemons for d in self.nconfig.daemons: self._processes.popen(shlex.split(d.startup_line)) # Busy-wait if the daemon needs some time before being started while not d.has_started(): time.sleep(.001)
def _parse_addresses(out): """Parse the output of an ip address command :return: mac, [ipv4], [ipv6]""" # 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state ... # link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 # inet 127.0.0.1/8 scope host lo # valid_lft forever preferred_lft forever # inet6 ::1/128 scope host # valid_lft forever preferred_lft forever mac = None v4 = [] v6 = [] for line in out.strip(' \n\t\r').split('\n'): parts = line.strip(' \n\t\r').split(' ') try: t = parts[0] if t == 'inet': v4.append(IPv4Interface(unicode(parts[1]))) elif t == 'inet6': v6.append(IPv6Interface(unicode(parts[1]))) elif 'link/' in t: mac = parts[1] except IndexError: log.error('Malformed ip-address line:', line) return mac, v4, v6
def render(self, cfg, **kwargs) -> Dict[str, str]: """Render the configuration content for each config file of this daemon :param cfg: The global config for the node :param kwargs: Additional keywords args. will be passed directly to the template""" self.files.extend(self.cfg_filenames) cfg_content = {} for i, filename in enumerate(self.cfg_filenames): log.debug('Generating %s\n' % filename) try: cfg.current_filename = filename kwargs["node"] = cfg kwargs["ip_statement"] = ip_statement kwargs["family_translate"] = family_translate template = self.template_lookup.get_template( self.template_filenames[i]) cfg_content[filename] = template.render(**kwargs) except Exception: # Display template errors in a less cryptic way log.error('Couldn''t render a config file(', self.template_filenames[i], ')') log.error(mako.exceptions.text_error_template().render()) raise ValueError('Cannot render a configuration [%s: %s]' % ( self._node.name, self.NAME)) return cfg_content
def dest_prefixes(self) -> List[str]: prefixes = [] try: IPv6Network(str(self.destination)) # This is an IPv6 address prefixes.append(str(self.destination)) except (AddressValueError, NetmaskValueError): if isinstance(self.destination, str): try: self.destination = self.net[self.destination] except KeyError: pass if isinstance(self.destination, IPNode): for itf in self.destination.intfList(): for ip6 in itf.ip6s(exclude_lls=True, exclude_lbs=True): prefixes.append(ip6.network.with_prefixlen) elif isinstance(self.destination, IPIntf): for ip6 in self.destination.ip6s(exclude_lls=True, exclude_lbs=True): prefixes.append(ip6.network.with_prefixlen) if len(prefixes) == 0: log.error("Cannot install SRv6Route", self, "because the destination", self.destination, "does not have a global IPv6 address\n") return prefixes
def sample_rxbytes(net, rxbytes): """ For each host, parse received bytes from /proc/net/dev, which has the format: Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3_1_3-eth0: 33714765732 1133072 0 0 0 0 0 0 25308223734 1110457 0 0 0 0 0 0 """ for name in HOST_NAMES: host = net.get(name) iface = '%s-eth0:' % name bytes = None now = time() res = host.cmd('cat /proc/net/dev') lines = res.split('\n') for line in lines: if iface in line: bytes = int(line.split()[1]) rxbytes[name].append(bytes) break if bytes is None: lg.error('Couldn\'t parse rxbytes for host %s!\n' % name)
def build_zone(self, zone: 'DNSZone') -> ConfigDict: master_ips = [] for s_name in zone.servers + [zone.dns_master] + zone.dns_slaves: server_itf = find_node(self._node, s_name) if server_itf is None: lg.error("Cannot find the server node {name} of DNS zone" " {zone}. Are you sure that they are connected to " "the current node {current}?".format( name=s_name, zone=zone.name, current=self._node.name)) continue server = server_itf.node for itf in realIntfList(server): for ip in itf.ips(): if ".arpa" not in zone.name: # Not a Reverse zone zone.soa_record.add_record( ARecord(s_name, ip.ip.compressed)) if s_name == zone.dns_master: master_ips.append(ip.ip.compressed) for ip6 in itf.ip6s(exclude_lls=True): if ".arpa" not in zone.name: # Not a Reverse zone zone.soa_record.add_record( AAAARecord(s_name, ip6.ip.compressed)) if s_name == zone.dns_master: master_ips.append(ip6.ip.compressed) return ConfigDict(name=zone.soa_record.domain_name, soa_record=zone.soa_record, records=zone.soa_record.records, master=self._node.name == zone.dns_master, master_ips=master_ips)
def __init__(self, name: str, config: Union[Type[NodeConfig], Tuple[Type[NodeConfig], Dict]] = NodeConfig, cwd='/tmp', process_manager: Type[ProcessHelper] = ProcessHelper, use_v4=True, use_v6=True, *args, **kwargs): """Most of the heavy lifting for this node should happen in the associated config object. :param config: The configuration generator for this node. Either a class or a tuple (class, kwargs) :param cwd: The base directory for temporary files such as configs :param process_manager: The class that will manage all the associated processes for this node :param use_v4: Whether this node has IPv4 :param use_v6: Whether this node has IPv6""" super().__init__(name, *args, **kwargs) self.use_v4 = use_v4 self.use_v6 = use_v6 self.cwd = cwd self._old_sysctl = {} # type: Dict[str, Union[str, int]] if isinstance(config, tuple): try: self.nconfig = config[0](self, **config[1]) except ValueError: lg.error("Expected a tuple (class, kwargs) for the config " "parameter but got instead %s" % str(config)) else: self.nconfig = config(self) self._processes = process_manager(self)
def addLinks(self, *links: Union[Tuple[str, str], Tuple[str, str, Dict[str, Any]]], **common_opts) -> List['LinkDescription']: """Add several links in one go. :param links: link description tuples, either only both node names or nodes names with link-specific options :param common_opts: common link options (optional)""" new_links = [] for u, v, *opt in links: # Merge link options by giving precedence to specific ones opt = opt[0] if opt else {} link_opts = { k: v for k, v in itertools.chain(common_opts.items(), opt.items()) } try: new_links.append(self.addLink(u, v, **link_opts)) except Exception as e: lg.error("Cannot create link between '{}' and '{}'" " with options '{}'".format(u, v, link_opts)) raise e return new_links
def sample_rxbytes(net, rxbytes): """ For each host, parse received bytes from /proc/net/dev, which has the format: Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3_1_3-eth0: 33714765732 1133072 0 0 0 0 0 0 25308223734 1110457 0 0 0 0 0 0 """ for name in HOST_NAMES: host = net.get(name) iface = '%s-eth0:' % name bytes = None res = host.cmd('cat /proc/net/dev') lines = res.split('\n') for line in lines: if iface in line: bytes = int(line.split()[1]) rxbytes[name].append(bytes) break if bytes is None: lg.error('Couldn\'t parse rxbytes for host %s!\n' % name)
def build(self, *args, **kwargs): for o in self.overlays: o.apply(self) for o in self.overlays: if not o.check_consistency(self): lg.error('Consistency checks for', str(o), 'overlay have failed!\n') super(IPTopo, self).build(*args, **kwargs)
def parseIperf(iperfOutput): """Parse iperf output and return bandwidth. iperfOutput: string returns: result string""" r = r'([\d\.]+ \w+/sec)' m = re.findall(r, iperfOutput) if m: return m[-1] else: # was: raise Exception(...) lg.error('could not parse iperf output: ' + iperfOutput) return ''
def parseIperf( iperfOutput ): """Parse iperf output and return bandwidth. iperfOutput: string returns: result string""" r = r'([\d\.]+ \w+/sec)' m = re.findall( r, iperfOutput ) if m: return m[-1] else: # was: raise Exception(...) lg.error( 'could not parse iperf output: ' + iperfOutput ) return ''
def require_cmd(cmd, help_str=None): """ Ensures that a command is available in $PATH :param cmd: the command to test :param help_str: an optional help string to display if cmd is not found """ if has_cmd(cmd): return if help_str: log.error(help_str) raise RuntimeError('[%s] is not available in $PATH' % cmd)
def __init__(self, name: str, dns_master: str, dns_slaves: Sequence[str] = (), records: Sequence[DNSRecord] = (), nodes: Sequence[str] = (), refresh_time=DNS_REFRESH, retry_time=DNS_RETRY, expire_time=DNS_EXPIRE, min_ttl=DNS_MIN_TTL, ns_domain_name: Optional[str] = None): """ :param name: The domain name of the zone :param dns_master: The name of the master DNS server :param dns_slaves: The list of names of DNS slaves :param records: The list of DNS Records to be included in the zone :param nodes: The list of nodes for which one A/AAAA record has to be created for each of their IPv4/IPv6 addresses :param refresh_time: The number of seconds before the zone should be refreshed :param retry_time: The number of seconds before a failed refresh should be retried :param expire_time: The upper limit in seconds before a zone is considered no longer authoritative :param min_ttl: The negative result TTL :param ns_domain_name: If it is defined, it is the suffix of the domain of the name servers, otherwise, parameter 'name' is used. """ self.name = name self.dns_master = dns_master self.dns_slaves = list(dns_slaves) self.records = records self.servers = list(nodes) self.soa_record = SOARecord(name, refresh_time=refresh_time, retry_time=retry_time, expire_time=expire_time, min_ttl=min_ttl, records=records) super().__init__(nodes=[dns_master] + list(dns_slaves)) self.consistent = True for node_name in [dns_master] + self.dns_slaves + self.servers: if "." in node_name: lg.error("Cannot create zone {name} because the node name" " {node_name} contains a '.'".format( name=name, node_name=node_name)) self.consistent = False self.ns_domain_name = ns_domain_name if ns_domain_name is not None \ else self.name
def stop(self): try: self.topo.pre_stop(self) except AttributeError as e: log.error('*** Skipping pre_stop():', e, '\n') log.info('*** Stopping', len(self.hosts), 'hosts\n') log.info('*** Stopping', len(self.routers), 'routers\n') for router in self.routers: log.info(router.name + ' ') router.terminate() log.info('\n') super().stop()
def _run_cmds(self, prefix: str = "ip -6 route add ") -> int: for cmd in self.cmds: cmd = prefix + cmd if self.table is not None: cmd = cmd + " table {num}".format(num=self.table.num) out, err, code = self.source.pexec(shlex.split(cmd)) log.debug("Installing route on router %s: '%s'\n" % (self.source.name, cmd)) if code: log.error('Cannot install SRv6Route', self, '[rcode:', str(code), ']:\n', cmd, '\nstdout:', str(out), '\nstderr:', str(err)) return code return -1
def _check_subnets(self): """ :return: True if there is enough addresses in each subnet for each node in the overlay and that each subnet is valid """ try: for subnet in self.subnets: if ip_network(str(subnet)).num_addresses - 1 < len(self.nodes): lg.error("The subnet %s does not contain enough addresses." " We need %s addresses\n" % (subnet, len(self.nodes))) return False except ValueError as e: lg.error("One of the subnet is invalid: %s\n" % e.message) return False return True
def _mklogdirs(self, logdir) -> Tuple[str, str, int]: """Creates directories for the given logdir. :param logdir: The log directory path to create :return: (stdout, stderr, return_code) """ lg.debug('{}: Creating logdir {}.\n'.format(self.name, logdir)) cmd = 'mkdir -p {}'.format(logdir) stdout, stderr, return_code = self._processes.pexec(shlex.split(cmd)) if not return_code: lg.debug('{}: Logdir {} successfully created.\n'.format( self.name, logdir)) else: lg.error('{}: Could not create logdir {}. Stderr: \n' '{}\n'.format(self.name, logdir, stderr)) return (stdout, stderr, return_code)
def start(self): """Start the node: Configure the daemons, set the relevant sysctls, and fire up all needed processes""" # Build the config self.nconfig.build() # Check them err_code = False for d in self.nconfig.daemons: if self.create_logdirs and d.logdir: self._mklogdirs(d.logdir) out, err, code = self._processes.pexec(shlex.split(d.dry_run)) err_code = err_code or code if code: lg.error(d.NAME, 'configuration check failed [' 'rcode:', code, ']\n' 'stdout:', out, '\n' 'stderr:', err) if err_code: lg.error('Config checks failed, aborting!') mininet.clean.cleanup() sys.exit(1) # Set relevant sysctls for opt, val in self.nconfig.sysctl: self._old_sysctl[opt] = self._set_sysctl(opt, val) # wait until NDP has finished to check each IPv6 addresses assigned # to the interface of the node. # The command lists addresses failing duplicate address detection (IPv6) # If any, it waits until all addresses has been checked lg.debug(self._processes.node.name, 'Checking for any "tentative" addresses') tentative_cmd = "ip addr show tentative" tentative_chk = self._processes.call(tentative_cmd) while tentative_chk is not None and tentative_chk != '': time.sleep(.5) tentative_chk = self._processes.call(tentative_cmd) lg.debug( self._processes.node.name, 'All IPv6 addresses has passed the Duplicate address detection mechanism' ) # Fire up all daemons for d in self.nconfig.daemons: self._processes.popen(shlex.split(d.startup_line)) # Busy-wait if the daemon needs some time before being started while not d.has_started(): time.sleep(.001)
def default(self, line): """Called on an input line when the command prefix is not recognized. Overridden to run shell commands when a node is the first CLI argument. Past the first CLI argument, node names are automatically replaced with corresponding addresses if possible. We select only one IP version for these automatic replacements. The chosen IP version chosen is first restricted by the addresses available on the first node. Then, we choose the IP version that enables every replacement. We use IPv4 as a tie-break.""" first, args, line = self.parseline(line) if first in self.mn: if not args: print("*** Enter a command for node: %s <cmd>" % first) return node = self.mn[first] rest = args.split(' ') hops = [h for h in rest if h in self.mn] v4_support, v6_support = address_pair(self.mn[first]) v4_map = {} v6_map = {} for hop in hops: ip, ip6 = address_pair(self.mn[hop], v4_support is not None, v6_support is not None) if ip is not None: v4_map[hop] = ip if ip6 is not None: v6_map[hop] = ip6 ip_map = v4_map if len(v4_map) >= len(v6_map) else v6_map if len(ip_map) < len(hops): missing = filter(lambda h: h not in ip_map, hops) version = 'IPv4' if v4_support else 'IPv6' lg.error('*** Nodes', missing, 'have no', version, 'address! Cannot execute the command.\n') return node.sendCmd(' '.join([ip_map.get(r, r) for r in rest])) self.waitForNode(node) else: lg.error('*** Unknown command: %s\n' % line)
def create(self): self.clean() for prefix in self.prefixes: cmd = "ip -6 rule add to {prefix} table {num}"\ .format(prefix=prefix, num=self.num) out, err, exitcode = self.node.pexec(shlex.split(cmd)) if exitcode != 0: log.error("Cannot install rule for new LocalSIDTable:\n" "{cmd}\nstdout:{out}\nstderr:{err}\n".format( cmd=cmd, out=out, err=err)) cmd = "ip -6 route add blackhole default table {num}"\ .format(num=self.num) out, err, exitcode = self.node.pexec(cmd) if exitcode != 0: log.error("Cannot install blackhole rule for new LocalSIDTable:\n" "{cmd}\nstdout:{out}\nstderr:{err}\n".format(cmd=cmd, out=out, err=err))
def default(self, line): """Called on an input line when the command prefix is not recognized. Overridden to run shell commands when a node is the first CLI argument. Past the first CLI argument, node names are automatically replaced with corresponding addresses if possible. We select only one IP version for these automatic replacements. The chosen IP version chosen is first restricted by the addresses available on the first node. Then, we choose the IP version that enables every replacement. We use IPv4 as a tie-break.""" first, args, line = self.parseline(line) if first in self.mn: if not args: print("*** Enter a command for node: %s <cmd>" % first) return node = self.mn[first] rest = args.split(' ') hops = [h for h in rest if h in self.mn] v4_support, v6_support = address_pair(self.mn[first]) v4_map = {} v6_map = {} for hop in hops: ip, ip6 = address_pair(self.mn[hop], v4_support is not None, v6_support is not None) if ip is not None: v4_map[hop] = ip if ip6 is not None: v6_map[hop] = ip6 ip_map = v4_map if len(v4_map) >= len(v6_map) else v6_map if len(ip_map) < len(hops): missing = [h for h in hops if h not in ip_map] version = 'IPv4' if v4_support else 'IPv6' lg.error('*** Nodes', missing, 'have no', version, 'address! Cannot execute the command.\n') return node.sendCmd(' '.join([ip_map.get(r, r) for r in rest])) self.waitForNode(node) else: lg.error('*** Unknown command: %s\n' % line)
def build(self): super().build() self.broadcast_domains = self._broadcast_domains() log.info("*** Found", len(self.broadcast_domains), "broadcast domains\n") if self.allocate_IPs: self._allocate_IPs() # Physical interfaces are their own broadcast domain for itf_name, n in self.physical_interface.items(): try: itf = PhysicalInterface(itf_name, node=self[n]) log.info('\n*** Adding Physical interface', itf_name, 'to', n, '\n') self.broadcast_domains.append(BroadcastDomain(itf)) except KeyError: log.error('!!! Node', n, 'not found!\n') try: self.topo.post_build(self) except AttributeError as e: log.error('*** Skipping post_build():', e, '\n')
def render(self, cfg, **kwargs): """Render the configuration file for this daemon :param cfg: The global config for the node :param kwargs: Additional keywords args. will be passed directly to the template""" self.files.append(self.cfg_filename) log.debug('Generating %s\n' % self.cfg_filename) try: return template_lookup.get_template(self.template_filename)\ .render(node=cfg, ip_statement=ip_statement, **kwargs) except: # Display template errors in a less cryptic way log.error('Couldn''t render a config file(', self.template_filename, ')') log.error(mako.exceptions.text_error_template().render()) raise ValueError('Cannot render a configuration [%s: %s]' % ( self._node.name, self.NAME))
def build(self): super(IPNet, self).build() self.broadcast_domains = self._broadcast_domains() log.info("*** Found", len(self.broadcast_domains), "broadcast domains\n") if self.allocate_IPs: self._allocate_IPs() # Physical interfaces are their own broadcast domain for itf_name, n in self.physical_interface.iteritems(): try: itf = PhysicalInterface(itf_name, node=self[n]) log.info('\n*** Adding Physical interface', itf_name, 'to', n, '\n') self.broadcast_domains.append(BroadcastDomain(itf)) except KeyError: log.error('!!! Node', n, 'not found!\n') try: self.topo.post_build(self) except AttributeError as e: log.error('*** Skipping post_build():', str(e), '\n')
def render(self, cfg, **kwargs): """Render the configuration file for this daemon :param cfg: The global config for the node :param kwargs: Additional keywords args. will be passed directly to the template""" self.files.append(self.cfg_filename) log.debug('Generating %s\n' % self.cfg_filename) try: return template_lookup.get_template(self.template_filename)\ .render(node=cfg, ip_statement=ip_statement, **kwargs) except: # Display template errors in a less cryptic way log.error('Couldn' 't render a config file(', self.template_filename, ')') log.error(mako.exceptions.text_error_template().render()) raise ValueError('Cannot render a configuration [%s: %s]' % (self._node.name, self.NAME))
def __init__(self, name, *args, **kw): try: node = kw['node'] except KeyError: raise ValueError('PhysicalInterface() requires a node= argument') # Save the addresses from the root namespace try: _, v4, v6 = _addresses_of(name, node=None) except (subprocess.CalledProcessError, OSError): log.error('Cannot retrieve the addresses of interface', name, '!') raise ValueError('Unknown physical interface name') if node.inNamespace: # cfr man ip-link; some devices cannot change of net ns if 'netns-local: on' in subprocess.check_output( ('ethtool', '-k', name)): log.error('Cannot move interface', name, 'into another network' ' namespace!') super(PhysicalInterface, self).__init__(name, *args, **kw) # Exclude link locals ... v4.extend(ip for ip in v6 if not ip.is_link_local) # Apply saved addresses self.setIP(v4)
def WinWin_run(self, host, command, delay=10, width=350, height=250, posx=None, posy=None, destroy=0): """ A convenience method which starts a Sub Window next to host and executes command inside an xterm after delay seconds. :param host: Host as String e.g. "h1" :param command: bash command as STring e.g. "ping -c 1 10.0.0.2" :param delay: delay in seconds e.g. 3 or 0.5 :param destroy: destroy's Sub Window after destroy seconds. '0' does not destroy SubWindow. """ # Figure out a good position for SubWindow if not posx or not posy: # get all positions host_pos = self.locations[host] if not host_pos: lg.error("no location for host %s defined" % (host)) return # put window on Bottom of Host if not posx: posx = host_pos[0] - (width / 2) if not posy: posy = host_pos[1] + 80 if delay: cmd = "termdown --no-figlet -W -f big -T \"" + command.split(";")[0].replace('"','\\"') + "\" " + str(delay) + "; sleep 0.1; " + command subWinReady = threading.Event() subWin = [None] self.app.runCreateSubWindow(width=width, height=height, posx=posx, posy=posy, contentfunc=self.WinWin_xterm_custom(cmd), host=host, events=True, subWinReady=subWinReady, subWin=subWin) subWinReady.wait() # wait until SubWindow ready if delay: sleep(delay+0.1) if destroy: sleep(destroy) self.app.runDestroySubWindow(subWin[0])
def runFailurePlan(self, failure_plan: List[Tuple[str, str]]) \ -> List[IPIntf]: """Run a failure plan :param: A list of pairs of node names: links connecting these two links will be downed :return: A list of interfaces that were downed """ log.output("** Starting failure plan\n") interfaces_down = [] for node1, node2 in failure_plan: try: links = self[node1].connectionsTo(self[node2]) for link in links: interfaces_down.extend(link) except KeyError as e: log.error("Node " + str(e) + " does not exist\n") interfaces_down = [] for interface in interfaces_down: interface.down(backup=True) log.output("** Interface " + str(interface) + " down\n") return interfaces_down
def __init__(self, name: str, *args, **kw): try: node = kw['node'] except KeyError: raise ValueError('PhysicalInterface() requires a node= argument') # Save the addresses from the root namespace try: _, v4, v6 = _addresses_of(name, node=None) except (subprocess.CalledProcessError, OSError): log.error('Cannot retrieve the addresses of interface', name, '!') raise ValueError('Unknown physical interface name') if node.inNamespace: # cfr man ip-link; some devices cannot change of net ns if 'netns-local: on' in subprocess.check_output( ('ethtool', '-k', name)).decode("utf-8"): log.error('Cannot move interface', name, 'into another network' ' namespace!') super().__init__(name, *args, **kw) # Exclude link locals ... v4.extend(ip for ip in v6 if not ip.is_link_local) # Apply saved addresses self.setIP(v4)
def randomFailure(self, n: int, weak_links: Optional[List[IPLink]] = None)\ -> List[IPIntf]: """Randomly down 'n' link :param n: the number of link to be downed :param weak_links: the list of links that can be downed; if set to None, every network link can be downed :return: the list of interfaces which were downed """ all_links = weak_links if weak_links is not None else self.links number_of_links = len(all_links) if n > number_of_links: log.error("More link down requested than number of link" " that can be downed\n") return [] downed_interfaces = [] down_links = random.sample(all_links, k=n) for link in down_links: for intf in [link.intf1, link.intf2]: intf.down(backup=True) log.output("** Interface " + str(intf) + " down\n") downed_interfaces.append(intf) return downed_interfaces
parser.add_argument('--log', choices=LEVELS.keys(), default='info', help='The level of details in the logs.') parser.add_argument('--args', help='Additional arguments to give' 'to the topology constructor (key=val, key=val, ...)', default='') return parser.parse_args() if __name__ == '__main__': args = parse_args() lg.setLogLevel(args.log) if args.log == 'debug': ipmininet.DEBUG_FLAG = True kwargs = {} for arg in args.args.strip(' \r\t\n').split(','): arg = arg.strip(' \r\t\n') if not arg: continue try: k, v = arg.split('=') kwargs[k] = v except ValueError: lg.error('Ignoring args:', arg) net = IPNet(topo=TOPOS[args.topo](**kwargs), **NET_ARGS.get(args.topo, {})) net.start() IPCLI(net) net.stop()
def __error__(self, content, pre, post): mnLog.error(pre + content + post)
def _find_nodes_in_lan(self, topo: 'IPTopo', nodes: List[str]) -> bool: """Checks that all nodes are in one same LAN. It also fills a map for each node name, the link on which an address should be set :return: True if all nodes are in the same LAN""" if len(nodes) == 0: return True # Build adjacency list for each node adjacencies = self._build_adjacency_list(topo) # Try to identify a LAN that includes every node among the LANs # attached to nodes[0] node_links = {} count_nodes = 0 count_links = 0 for previous, n_start, k_start, n_start_value in adjacencies[nodes[0]]: nodes_0_value = [ x for x in adjacencies[n_start] if x[1] == nodes[0] and x[2] == k_start ][0][3] node_links = {nodes[0]: [nodes_0_value]} count_nodes = 1 # to_visit is a list of tuples giving, in order, the previously # visited node, the current node that we explore, the key of the # link from which we are coming and the attributes of the interface. # The first three values identify an interface in the topology. to_visit = [(previous, n_start, k_start, n_start_value)] # Contains a tuple identifying an interface of the graph visited = {(n_start, previous, k_start)} # Includes one of the requested links if (n_start, previous) in self.links: count_links += 1 # Go through the LAN while to_visit: prev, curr, curr_k, curr_value = to_visit.pop() curr_itf = (prev, curr, curr_k) if curr_itf in visited: continue visited.add(curr_itf) # Includes one of the requested links if (prev, curr) in self.links: count_links += 1 if topo.isSwitch(curr): # Look at neighbors to_visit.extend(adjacencies[curr]) elif curr in self.nodes: # Add to node_links if node_links.get(curr, None) is None: count_nodes += 1 node_links.setdefault(curr, []).append(curr_value) if count_nodes == len(nodes): break # Found the LAN that includes all the nodes if count_nodes != len(nodes): lg.error("The nodes of %s are not in the same LAN\n" % self) return False self.node_links = node_links return True
def __init__(self, name: str, dns_master: str, dns_slaves: Sequence[str] = (), records: Sequence[DNSRecord] = (), nodes: Sequence[str] = (), refresh_time=DNS_REFRESH, retry_time=DNS_RETRY, expire_time=DNS_EXPIRE, min_ttl=DNS_MIN_TTL, ns_domain_name: Optional[str] = None, subdomain_delegation=True, delegated_zones: Sequence['DNSZone'] = ()): """ :param name: The domain name of the zone :param dns_master: The name of the master DNS server :param dns_slaves: The list of names of DNS slaves :param records: The list of DNS Records to be included in the zone :param nodes: The list of nodes for which one A/AAAA record has to be created for each of their IPv4/IPv6 addresses :param refresh_time: The number of seconds before the zone should be refreshed :param retry_time: The number of seconds before a failed refresh should be retried :param expire_time: The upper limit in seconds before a zone is considered no longer authoritative :param min_ttl: The negative result TTL :param ns_domain_name: If it is defined, it is the suffix of the domain of the name servers, otherwise, parameter 'name' is used. :param subdomain_delegation: If set, additional records for subdomain name servers are added to guarantee correct delegation :param delegated_zones: Additional delegated zones """ self.name = name + "." if name[-1:] != "." else name self.dns_master = dns_master self.dns_slaves = list(dns_slaves) self._records = list(records) self.servers = list(nodes) self.soa_record = SOARecord(self.name, refresh_time=refresh_time, retry_time=retry_time, expire_time=expire_time, min_ttl=min_ttl) super().__init__(nodes=[dns_master] + list(dns_slaves)) self.consistent = True for node_name in [dns_master] + self.dns_slaves + self.servers: if "." in node_name: lg.error("Cannot create zone {name} because the node name" " {node_name} contains a '.'".format( name=self.name, node_name=node_name)) self.consistent = False self.ns_domain_name = ns_domain_name if ns_domain_name is not None \ else self.name if self.ns_domain_name[-1:] != ".": self.ns_domain_name = self.ns_domain_name + "." # Add NS Records (if not already present) for n in self.nodes: server_name = dns_join_name(n, self.ns_domain_name) self.add_record(NSRecord(self.name, server_name)) self.subdomain_delegation = subdomain_delegation self.delegated_zones = list(delegated_zones) self.delegation_servers = []