Compare commits
2 commits
9d71490264
...
b9f9e7d1b0
Author | SHA1 | Date | |
---|---|---|---|
Théophile Bastian | b9f9e7d1b0 | ||
Théophile Bastian | 71b5d661c0 |
|
@ -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 [
|
||||||
|
|
105
lxc_net/libvirt_error.py
Normal file
105
lxc_net/libvirt_error.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
from collections import namedtuple
|
||||||
|
import libvirt
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LibvirtErrorCode:
|
||||||
|
reverse_errors = {
|
||||||
|
getattr(libvirt, err_name): err_name
|
||||||
|
for err_name in filter(lambda x: x.startswith("VIR_ERR_"), libvirt.__dir__())
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, code):
|
||||||
|
self.code = code
|
||||||
|
self.err = self.reverse_errors.get(code, None)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}".format(self.code)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} [{}]".format(self.err, self.code)
|
||||||
|
|
||||||
|
def __eq__(self, val):
|
||||||
|
return self.code == val
|
||||||
|
|
||||||
|
|
||||||
|
class LibvirtErrorDomain:
|
||||||
|
reverse_domains = {
|
||||||
|
getattr(libvirt, err_name): err_name
|
||||||
|
for err_name in filter(lambda x: x.startswith("VIR_DOMAIN_"), libvirt.__dir__())
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, code):
|
||||||
|
self.code = code
|
||||||
|
self.domain = self.reverse_domains.get(code, None)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}".format(self.code)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} [{}]".format(self.domain, self.code)
|
||||||
|
|
||||||
|
def __eq__(self, val):
|
||||||
|
return self.code == val
|
||||||
|
|
||||||
|
|
||||||
|
class LibvirtError:
|
||||||
|
params = (
|
||||||
|
"code",
|
||||||
|
"domain",
|
||||||
|
"message",
|
||||||
|
"level",
|
||||||
|
"str1",
|
||||||
|
"str2",
|
||||||
|
"str3",
|
||||||
|
"int1",
|
||||||
|
"int2",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, code, domain, message, level, str1, str2, str3, int1, int2):
|
||||||
|
self.code = LibvirtErrorCode(code)
|
||||||
|
self.domain = LibvirtErrorDomain(domain)
|
||||||
|
self.message = message
|
||||||
|
self.level = level
|
||||||
|
self.str1 = str1
|
||||||
|
self.str2 = str2
|
||||||
|
self.str3 = str3
|
||||||
|
self.int1 = int1
|
||||||
|
self.int2 = int2
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
out = "{}(".format(self.__class__.__name__)
|
||||||
|
for param in self.__class__.params:
|
||||||
|
out += "{}={}, ".format(param, getattr(self, param))
|
||||||
|
out += ")"
|
||||||
|
return out
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Libvirt error {}, domain {}. {}".format(
|
||||||
|
self.code, self.domain, self.message
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loglevel(self):
|
||||||
|
levelmap = {
|
||||||
|
0: logging.INFO,
|
||||||
|
1: logging.WARNING,
|
||||||
|
2: logging.ERROR,
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelmap.get(self.level, logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
|
def libvirt_error_handler(_, raw_error):
|
||||||
|
""" Called upon libvirt error """
|
||||||
|
|
||||||
|
error = LibvirtError(*raw_error)
|
||||||
|
loglevel = error.loglevel
|
||||||
|
|
||||||
|
if error.code == libvirt.VIR_ERR_NO_DOMAIN:
|
||||||
|
loglevel = logging.DEBUG
|
||||||
|
|
||||||
|
logger.log(loglevel, str(error))
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from lxc_net import parse_network
|
from lxc_net import parse_network, libvirt_error
|
||||||
import sys
|
|
||||||
import signal
|
import signal
|
||||||
import libvirt
|
import libvirt
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
|
@ -16,18 +15,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()
|
||||||
|
|
||||||
|
@ -42,29 +29,23 @@ def main():
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, handle_sigint)
|
signal.signal(signal.SIGINT, handle_sigint)
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
libvirt.registerErrorHandler(libvirt_error.libvirt_error_handler, None)
|
||||||
|
|
||||||
conn = libvirt.open("lxc:///")
|
conn = libvirt.open("lxc:///")
|
||||||
|
|
||||||
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()
|
||||||
|
|
Loading…
Reference in a new issue