Handle net/dom start/stop in Topology

This commit is contained in:
Théophile Bastian 2020-03-19 19:06:44 +01:00
parent 9d71490264
commit 71b5d661c0
4 changed files with 191 additions and 34 deletions

View file

@ -154,6 +154,10 @@ class Container(util.LibvirtObject):
self.lxc_container = None self.lxc_container = None
@property
def up(self):
return self.lxc_container is not None
def get_jinja_networks(self): def get_jinja_networks(self):
""" Get a jinja2 environment-compatible network list """ """ Get a jinja2 environment-compatible network list """
return [ return [

View file

@ -25,6 +25,10 @@ class Network(util.LibvirtObject):
self.ipv6 = util.Addrv6(self.id, None, host_address=True) self.ipv6 = util.Addrv6(self.id, None, host_address=True)
self.lxc_network = None self.lxc_network = None
@property
def up(self):
return self.lxc_network is not None
def create(self): def create(self):
if self.lxc_network: if self.lxc_network:
raise self.AlreadyExists() raise self.AlreadyExists()
@ -50,6 +54,7 @@ class Network(util.LibvirtObject):
def cleanup(self): def cleanup(self):
if self.lxc_network: if self.lxc_network:
self.lxc_network.destroy() self.lxc_network.destroy()
self.lxc_network = None
def __enter__(self): def __enter__(self):
self.create() self.create()

View file

@ -1,12 +1,41 @@
import yaml import yaml
import sys import sys
from functools import wraps
from . import network, container from . import network, container
class YamlTopology: def requires_parsed(func):
""" Parse a YAML description of a network topology. The networks' links and domains @wraps(func)
are contained in the `links` and `domains` attributes, but their `create` methods def aux(self, *args, **kwargs):
are not called. """ 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): class InvalidConfiguration(Exception):
def __init__(self, reason): def __init__(self, reason):
@ -15,13 +44,153 @@ class YamlTopology:
def __str__(self): def __str__(self):
return "Bad topology configuration: {}".format(self.reason) return "Bad topology configuration: {}".format(self.reason)
def __init__(self, path, conn): class NotParsed(Exception):
self.path = path def __str__(self):
return "This topology is not parsed yet"
def __init__(self, conn):
self.conn = conn self.conn = conn
self.parsed = False
self.domains = None self.domains = None
self.links = 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() self._parse()
def _parse(self): def _parse(self):
@ -74,3 +243,5 @@ class YamlTopology:
) )
for dom in sorted_dom_names for dom in sorted_dom_names
] ]
self.parsed = True

View file

@ -1,11 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from lxc_net import parse_network from lxc_net import parse_network
import sys
import signal import signal
import libvirt import libvirt
import argparse import argparse
import asyncio
def parse_args(): def parse_args():
@ -16,18 +14,6 @@ def parse_args():
return args return args
async def spawn_network(link):
await link.async_create()
print(link.name, end="... ")
sys.stdout.flush()
async def spawn_networks(links):
link_tasks = [asyncio.create_task(spawn_network(link)) for link in links]
for link_task in link_tasks:
await link_task
def main(): def main():
args = parse_args() args = parse_args()
@ -46,25 +32,16 @@ def main():
topology = parse_network.YamlTopology(args.topology, conn) topology = parse_network.YamlTopology(args.topology, conn)
print(">> Spawning networks: ", end="") topology.net_start(verbose=True)
sys.stdout.flush() topology.dom_start(verbose=True)
asyncio.run(spawn_networks(topology.links))
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.") print("Network running. Press ^C to terminate.")
while not received_sigint: # Wait for SIGINT while not received_sigint: # Wait for SIGINT
signal.pause() signal.pause()
topology.dom_stop(verbose=True)
topology.net_stop(verbose=True)
if __name__ == "__main__": if __name__ == "__main__":
main() main()