247 lines
7.2 KiB
Python
247 lines
7.2 KiB
Python
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 = None
|
|
self.links = None
|
|
|
|
@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()
|
|
|
|
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)
|
|
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
|