3 changed files with 139 additions and 0 deletions
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
import yaml |
||||
import sys |
||||
from . import network, container |
||||
|
||||
|
||||
class YamlTopology: |
||||
""" 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. """ |
||||
|
||||
class InvalidConfiguration(Exception): |
||||
def __init__(self, reason): |
||||
self.reason = reason |
||||
|
||||
def __str__(self): |
||||
return "Bad topology configuration: {}".format(self.reason) |
||||
|
||||
def __init__(self, path, conn): |
||||
self.path = path |
||||
self.conn = conn |
||||
|
||||
self.domains = None |
||||
self.links = None |
||||
|
||||
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", None): |
||||
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 |
||||
] |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
from lxc_net import parse_network |
||||
import sys |
||||
import signal |
||||
import libvirt |
||||
import argparse |
||||
|
||||
|
||||
def parse_args(): |
||||
parser = argparse.ArgumentParser(description="Spawns a network of LXC containers.") |
||||
parser.add_argument("topology", help="A YAML file defining the network topology") |
||||
|
||||
args = parser.parse_args() |
||||
return args |
||||
|
||||
|
||||
def main(): |
||||
args = parse_args() |
||||
|
||||
received_sigint = False |
||||
|
||||
def handle_sigint(signum, frame): |
||||
""" Called upon SIGINT (^C) """ |
||||
nonlocal received_sigint |
||||
|
||||
print(" >> Received SIGINT, stopping network...") |
||||
received_sigint = True |
||||
|
||||
signal.signal(signal.SIGINT, handle_sigint) |
||||
|
||||
conn = libvirt.open("lxc:///") |
||||
|
||||
topology = parse_network.YamlTopology(args.topology, conn) |
||||
|
||||
print(">> Spawning networks: ", end="") |
||||
sys.stdout.flush() |
||||
for link in topology.links: |
||||
if received_sigint: |
||||
return |
||||
print(link.name, end="... ") |
||||
sys.stdout.flush() |
||||
link.create() |
||||
print("Done.") |
||||
|
||||
print(">> Spawning containers: ", end="") |
||||
sys.stdout.flush() |
||||
for c_dom in topology.domains: |
||||
if received_sigint: |
||||
return |
||||
print(c_dom.name, end="... ") |
||||
sys.stdout.flush() |
||||
c_dom.create() |
||||
print("Done.") |
||||
|
||||
print("Network running. Press ^C to terminate.") |
||||
while not received_sigint: # Wait for SIGINT |
||||
signal.pause() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
Loading…
Reference in new issue