import yaml import sys from functools import wraps from . import network, container def requires_parsed(func): @wraps(func) def aux(self, *args, **kwargs): if not self.parsed: raise self.__class__.NotParsed return func(self, *args, **kwargs) return aux def requires_id_in_dom_range(func): @wraps(func) def aux(self, dom_id, *args, **kwargs): if dom_id not in range(len(self.domains)): raise IndexError("bad domain id: {}".format(dom_id)) return func(self, dom_id, *args, **kwargs) return aux def requires_id_in_link_range(func): @wraps(func) def aux(self, link_id, *args, **kwargs): if link_id not in range(len(self.links)): raise IndexError("bad domain id: {}".format(link_id)) return func(self, link_id, *args, **kwargs) return aux class Topology: """ A generic network topology """ class InvalidConfiguration(Exception): def __init__(self, reason): self.reason = reason def __str__(self): return "Bad topology configuration: {}".format(self.reason) class NotParsed(Exception): def __str__(self): return "This topology is not parsed yet" def __init__(self, conn): self.conn = conn self.parsed = False self.domains = [] self.links = [] @requires_parsed @requires_id_in_link_range def net_setstate_single(self, link_id, state, verbose=False, verbose_inline=False): """ Start or stop a single network. * `net_id`: the link to be affected * `verbose`: if true, prints progress """ link = self.links[link_id] print_endchar = "\t" if verbose_inline else "\n" if link.up == state: if verbose: print( "{} already {}.".format(link.name, "up" if state else "down"), end=print_endchar, ) sys.stdout.flush() return if verbose: print("{}... ".format(link.name), end="") sys.stdout.flush() if state is True: link.create() else: link.cleanup() if verbose: print("done.", end=print_endchar) sys.stdout.flush() @requires_parsed @requires_id_in_dom_range def dom_setstate_single(self, dom_id, state, verbose=False, verbose_inline=False): """ Start or stop a single domain. * `dom_id`: the domain to be affected * `verbose`: if true, prints progress """ dom = self.domains[dom_id] print_endchar = "\t" if verbose_inline else "\n" if dom.up == state: if verbose: print( "{} already {}.".format(dom.name, "up" if state else "down"), end=print_endchar, ) sys.stdout.flush() return if verbose: print("{}... ".format(dom.name), end="") sys.stdout.flush() if state is True: dom.create() else: dom.cleanup() if verbose: print("done.", end=print_endchar) sys.stdout.flush() def net_start_single(self, link_id, verbose=False, verbose_inline=False): self.net_setstate_single(link_id, True, verbose, verbose_inline) def net_stop_single(self, link_id, verbose=False, verbose_inline=False): self.net_setstate_single(link_id, False, verbose, verbose_inline) def dom_start_single(self, dom_id, verbose=False, verbose_inline=False): self.dom_setstate_single(dom_id, True, verbose, verbose_inline) def dom_stop_single(self, dom_id, verbose=False, verbose_inline=False): self.dom_setstate_single(dom_id, False, verbose, verbose_inline) @requires_parsed def net_setstate(self, state, verbose=False): """ Start or stop the networks. * `verbose`: if true, prints progress """ if verbose: print("{} networks: ".format("Starting" if state else "Stopping"), end="\t") sys.stdout.flush() for link_id in range(len(self.links)): self.net_setstate_single(link_id, state, verbose, verbose_inline=True) if verbose: print("") # New line @requires_parsed def dom_setstate(self, state, verbose=False): """ Start or stop the domains. * `verbose`: if true, prints progress """ if verbose: print("{} domains: ".format("Starting" if state else "Stopping"), end="\t") sys.stdout.flush() if state is False: for dom in self.domains: dom.notify_cleanup() for dom_id in range(len(self.domains)): self.dom_setstate_single(dom_id, state, verbose, verbose_inline=True) if verbose: print("") # New line def net_start(self, verbose=False): self.net_setstate(True, verbose) def net_stop(self, verbose=False): self.net_setstate(False, verbose) def dom_start(self, verbose=False): self.dom_setstate(True, verbose) def dom_stop(self, verbose=False): self.dom_setstate(False, verbose) del requires_id_in_link_range del requires_id_in_dom_range del requires_parsed class YamlTopology(Topology): """ Parse a YAML description of a network topology. The networks' links and domains are contained in the `links` and `domains` attributes, but their `create` methods are not called. """ def __init__(self, path, conn): super().__init__(conn) self.path = path self._parse() def _parse(self): with open(self.path, "r") as handle: topology = yaml.safe_load(handle) if "links" not in topology: raise self.InvalidConfiguration("links definition is mandatory") link_descr = topology["links"] self.links = [] dom_descr = {} for link_conf in link_descr: if "domains" not in link_conf: raise self.InvalidConfiguration( "a 'domains' attribute is mandatory for each link" ) cur_link = network.Network( self.conn, enable_v4=link_conf.get("enable_v4", True), ) self.links.append(cur_link) for dom in link_conf["domains"]: if dom not in dom_descr: dom_descr[dom] = {"links": []} dom_descr[dom]["links"].append(cur_link) for dom_conf_name in topology.get("domains", {}): if dom_conf_name not in dom_descr: # Domain does not participate in any link: warn and ignore print( ( "WARNING: domain {} does not participate in any link. " "Ignored." ).format(dom_conf_name), file=sys.stderr, ) continue dom_conf = topology["domains"][dom_conf_name] dom_descr[dom_conf_name]["enable_v4"] = dom_conf.get("enable_v4", True) sorted_dom_names = sorted(list(dom_descr.keys())) self.domains = [ container.Container( self.conn, dom_descr[dom]["links"], enable_v4=dom_descr[dom].get("enable_v4", True), ) for dom in sorted_dom_names ] self.parsed = True