sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #04545
[Merge] ~adam-collard/maas-ci/+git/system-tests:lxd-instance-facade into ~maas-committers/maas-ci/+git/system-tests:master
Adam Collard has proposed merging ~adam-collard/maas-ci/+git/system-tests:lxd-instance-facade into ~maas-committers/maas-ci/+git/system-tests:master.
Commit message:
Add lxd.Instance facade for interacting with a particular LXD instance
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~adam-collard/maas-ci/+git/system-tests/+merge/435762
--
Your team MAAS Committers is requested to review the proposed merge of ~adam-collard/maas-ci/+git/system-tests:lxd-instance-facade into ~maas-committers/maas-ci/+git/system-tests:master.
diff --git a/systemtests/api.py b/systemtests/api.py
index 568d6fa..20cc361 100644
--- a/systemtests/api.py
+++ b/systemtests/api.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import json
-from functools import partial
from logging import getLogger
from subprocess import CalledProcessError
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, TypedDict, Union
@@ -12,7 +11,7 @@ from .utils import wait_for_machine
if TYPE_CHECKING:
from logging import Logger
- from . import lxd
+ from .lxd import Instance
LOG = getLogger("systemtests.api")
@@ -84,34 +83,31 @@ class UnauthenticatedMAASAPIClient:
MAAS_CMD = ["sudo", "-u", "ubuntu", "maas"]
- def __init__(self, url: str, maas_container: str, lxd: lxd.CLILXD):
+ def __init__(self, url: str, maas_container: Instance):
self.url = url
self.maas_container = maas_container
- self.lxd = lxd
- self.pull_file = partial(lxd.pull_file, maas_container)
- self.push_file = partial(lxd.push_file, maas_container)
def __repr__(self) -> str:
return f"<UnauthenticatedMAASAPIClient for {self.url!r}>"
@property
def logger(self) -> Logger:
- return self.lxd.logger
+ return self.maas_container.logger
@logger.setter
def logger(self, logger: Logger) -> None:
- self.lxd.logger = logger
+ self.maas_container.logger = logger
def execute(self, cmd: list[str], base_cmd: Optional[list[str]] = None) -> str:
__tracebackhide__ = True
if base_cmd is None:
base_cmd = self.MAAS_CMD
- result = self.lxd.execute(self.maas_container, base_cmd + cmd)
+ result = self.maas_container.execute(base_cmd + cmd)
return result.stdout
def quietly_execute(self, cmd: list[str]) -> str:
__tracebackhide__ = True
- result = self.lxd.quietly_execute(self.maas_container, self.MAAS_CMD + cmd)
+ result = self.maas_container.quietly_execute(self.MAAS_CMD + cmd)
return result.stdout
def log_in(self, session: str, token: str) -> tuple[str, AuthenticatedAPIClient]:
@@ -133,10 +129,6 @@ class AuthenticatedAPIClient:
return f"<AuthenticatedAPIClient for {self.api_client.url!r}>"
@property
- def lxd(self) -> lxd.CLILXD:
- return self.api_client.lxd
-
- @property
def logger(self) -> Logger:
return self.api_client.logger
diff --git a/systemtests/collect_sos_report/test_collect.py b/systemtests/collect_sos_report/test_collect.py
index ff625da..d8b1e5e 100644
--- a/systemtests/collect_sos_report/test_collect.py
+++ b/systemtests/collect_sos_report/test_collect.py
@@ -1,12 +1,33 @@
import contextlib
import subprocess
-from logging import getLogger
+import tempfile
+from pathlib import Path
-from ..lxd import get_lxd
+from ..lxd import Instance
-def test_collect_sos_report(maas_container: str) -> None:
- lxd = get_lxd(getLogger("collect_sos_report"))
- assert lxd.container_exists(maas_container)
+def collect_sos_report(instance: Instance, output: Path) -> None:
+ container_tmp = "/tmp/sosreport"
+ output_dir = output / "sosreport"
+ instance.execute(["apt", "install", "--yes", "sosreport"])
+ instance.execute(["rm", "-rf", container_tmp])
+ instance.execute(["mkdir", "-p", container_tmp])
+ instance.execute(
+ ["sos", "report", "--batch", "-o", "maas", "--tmp-dir", container_tmp]
+ )
+ output_dir.mkdir(parents=True, exist_ok=True)
+ journalctl = instance.execute(
+ ["journalctl", "--unit=vault", "--no-pager", "--output=cat"]
+ )
+ if journalctl.stdout:
+ (output_dir / "vault-journal").write_text(journalctl.stdout)
+ with tempfile.TemporaryDirectory(prefix="sosreport") as tempdir:
+ instance.files[container_tmp].pull(tempdir)
+ for f in (Path(tempdir) / "sosreport").iterdir():
+ f.rename(output_dir / f.name)
+
+
+def test_collect_sos_report(maas_container: Instance) -> None:
+ assert maas_container.exists()
with contextlib.suppress(subprocess.CalledProcessError):
- lxd.collect_sos_report(maas_container, ".")
+ collect_sos_report(maas_container, Path("."))
diff --git a/systemtests/conftest.py b/systemtests/conftest.py
index 39ef76e..39c59a0 100644
--- a/systemtests/conftest.py
+++ b/systemtests/conftest.py
@@ -30,7 +30,7 @@ from .fixtures import (
vault,
zone,
)
-from .lxd import get_lxd
+from .lxd import Instance, get_lxd
from .machine_config import MachineConfig
from .state import (
authenticated_admin,
@@ -175,20 +175,19 @@ def hardware_sync_machine(
machine = authenticated_admin.list_machines(mac_address=machine_config.mac_address)[
0
]
+ instance = Instance(get_lxd(LOG), machine_config.name)
assert machine["status"] == STATUS_READY
yield HardwareSyncMachine(
name=machine_config.name,
machine=machine,
devices_config=machine_config.devices_config,
+ instance=instance,
)
if machine_config.power_type == "lxd":
- lxd = get_lxd(LOG)
- current_devices = lxd.list_instance_devices(machine_config.name)
+ current_devices = instance.list_devices()
for additional_device in machine_config.devices_config:
if additional_device["device_name"] in current_devices:
- lxd.remove_instance_device(
- machine_config.name, additional_device["device_name"]
- )
+ instance.remove_device(additional_device["device_name"])
authenticated_admin.release_machine(machine)
wait_for_machine(
authenticated_admin,
diff --git a/systemtests/device_config.py b/systemtests/device_config.py
index 41d883b..342f8aa 100644
--- a/systemtests/device_config.py
+++ b/systemtests/device_config.py
@@ -4,6 +4,7 @@ from dataclasses import dataclass
from typing import Dict
from .api import Machine
+from .lxd import Instance
DeviceConfig = Dict[str, str]
@@ -13,3 +14,4 @@ class HardwareSyncMachine:
name: str
machine: Machine
devices_config: tuple[DeviceConfig, ...]
+ instance: Instance
diff --git a/systemtests/env_builder/test_basic.py b/systemtests/env_builder/test_basic.py
index 5f53438..265fa06 100644
--- a/systemtests/env_builder/test_basic.py
+++ b/systemtests/env_builder/test_basic.py
@@ -9,8 +9,10 @@ from urllib.request import urlopen
import pytest
from retry import retry
-from systemtests.lxd import get_lxd
+from systemtests.lxd import Instance, get_lxd
from systemtests.utils import (
+ UnexpectedMachineStatus,
+ debug_lxd_vm,
randomstring,
retries,
wait_for_machine,
@@ -118,13 +120,10 @@ class TestSetup:
"""Ensure that we have a Ready VM at the end."""
lxd = get_lxd(logger=testlog)
vm_name = instance_config.name
- try:
- lxd.instance_status(vm_name)
- except ValueError:
- pass
- else:
+ instance = Instance(lxd, vm_name)
+ if instance.exists():
# Force delete the VM so we know we're starting clean
- lxd.delete(vm_name)
+ instance.delete()
# Need to create a network device with a hwaddr
config: dict[str, str] = {"security.secureboot": "false"}
@@ -133,7 +132,7 @@ class TestSetup:
if instance_config.mac_address:
config["volatile.eth0.hwaddr"] = instance_config.mac_address
- lxd.create_vm(vm_name, config)
+ instance = lxd.create_vm(vm_name, config)
mac_address = instance_config.mac_address
@@ -145,29 +144,33 @@ class TestSetup:
else:
# Machine not registered, let's boot it up
@retry(tries=5, delay=5, backoff=1.2, logger=testlog)
- def _boot_vm(vm_name: str) -> None:
- status = lxd.instance_status(vm_name)
+ def _boot_vm(vm: Instance) -> None:
+ status = instance.status()
if status == "RUNNING":
- testlog.debug(f"{vm_name} is already running, restarting")
- lxd.restart(vm_name)
+ testlog.debug(f"{instance.name} is already running, restarting")
+ instance.restart()
elif status == "STOPPED":
- testlog.debug(f"{vm_name} is stopped, starting")
+ testlog.debug(f"{instance.name} is stopped, starting")
try:
- lxd.start(vm_name)
+ instance.start()
except CalledProcessError:
- lxd._run(["lxc", "info", "--show-log", vm_name])
+ debug_lxd_vm(vm_name, testlog)
raise
else:
assert False, f"Don't know how to handle lxd_vm status: {status}"
- _boot_vm(vm_name)
+ _boot_vm(instance)
try:
- vm_status = lxd.instance_status(vm_name)
+ vm_status = instance.status()
except ValueError:
vm_status = "not available"
testlog.debug(f"{vm_name} is {vm_status}")
- machine = wait_for_new_machine(maas_api_client, mac_address, vm_name)
+ try:
+ machine = wait_for_new_machine(maas_api_client, mac_address, vm_name)
+ except UnexpectedMachineStatus:
+ debug_lxd_vm(vm_name, testlog)
+ raise
# Make sure we have power parameters set
if not machine["power_type"]:
diff --git a/systemtests/fixtures.py b/systemtests/fixtures.py
index eff8651..ba9bbb9 100644
--- a/systemtests/fixtures.py
+++ b/systemtests/fixtures.py
@@ -2,54 +2,50 @@ from __future__ import annotations
import io
import os
-from functools import partial
-from logging import StreamHandler, getLogger
+from logging import Logger, StreamHandler, getLogger
from textwrap import dedent
-from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, TextIO
+from typing import Any, Iterator, Optional, TextIO
import paramiko
import pytest
import yaml
from pytest_steps import one_fixture_per_step
-from .api import UnauthenticatedMAASAPIClient
+from .api import AuthenticatedAPIClient, UnauthenticatedMAASAPIClient
from .config import ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_USER
-from .lxd import CLILXD, get_lxd
+from .lxd import Instance, get_lxd
from .o11y import setup_o11y
from .region import MAASRegion
from .tls import MAAS_CONTAINER_CERTS_PATH, setup_tls
from .vault import Vault, VaultNotReadyError, setup_vault
-if TYPE_CHECKING:
- from logging import Logger
-
- from .api import AuthenticatedAPIClient
LOG_NAME = "systemtests.fixtures"
LXD_PROFILE = os.environ.get("MAAS_SYSTEMTESTS_LXD_PROFILE", "prof-maas-lab")
def _add_maas_ppa(
- exec_on_container: Callable[..., Any],
+ instance: Instance,
maas_ppas: list[str],
) -> None:
"""Add MAAS PPA to the given container."""
for ppa in maas_ppas:
- exec_on_container(
+ instance.execute(
["add-apt-repository", "-y", ppa],
environment={"DEBIAN_FRONTEND": "noninteractive"},
)
@pytest.fixture(scope="session")
-def build_container(config: dict[str, Any]) -> Iterator[str]:
+def build_container(config: dict[str, Any]) -> Iterator[Instance]:
"""Create a container for building MAAS package in."""
log = getLogger(f"{LOG_NAME}.build_container")
lxd = get_lxd(log)
container_name = os.environ.get(
"MAAS_SYSTEMTESTS_BUILD_CONTAINER", "maas-system-build"
)
- if not lxd.container_exists(container_name):
+ instance = Instance(lxd, container_name)
+ if not instance.exists():
cloud_config = {}
http_proxy = config.get("proxy", {}).get("http", "")
@@ -68,19 +64,19 @@ def build_container(config: dict[str, Any]) -> Iterator[str]:
profile=LXD_PROFILE,
)
- yield container_name
+ yield instance
@pytest.fixture(scope="session")
def maas_deb_repo(
- build_container: str, config: dict[str, Any]
+ build_container: Instance, config: dict[str, Any]
) -> Iterator[Optional[str]]:
"""Build maas deb, and setup APT repo."""
if "snap" in config:
yield None
else:
- lxd = get_lxd(getLogger(f"{LOG_NAME}.maas_deb_repo"))
- build_ip = lxd.get_ip_address(build_container)
+ build_container.logger = getLogger(f"{LOG_NAME}.maas_deb_repo")
+ build_ip = build_container.get_ip_address()
http_proxy = config.get("proxy", {}).get("http", "")
proxy_env: Optional[dict[str, str]]
if http_proxy:
@@ -92,14 +88,12 @@ def maas_deb_repo(
else:
proxy_env = None
- if not lxd.file_exists(build_container, "/var/www/html/repo/Packages.gz"):
+ if not build_container.files["/var/www/html/repo/Packages.gz"].exists():
maas_ppas = config.get("deb", {}).get(
"ppa", ["ppa:maas-committers/latest-deps"]
)
- exec_on_container = partial(lxd.execute, build_container)
- _add_maas_ppa(exec_on_container, maas_ppas)
- lxd.execute(
- build_container,
+ _add_maas_ppa(build_container, maas_ppas)
+ build_container.execute(
[
"apt",
"install",
@@ -116,8 +110,7 @@ def maas_deb_repo(
"git_repo", "https://git.launchpad.net/maas"
)
maas_git_branch = str(config.get("deb", {}).get("git_branch", "master"))
- lxd.execute(
- build_container,
+ build_container.execute(
[
"git",
"clone",
@@ -134,8 +127,7 @@ def maas_deb_repo(
],
environment=proxy_env,
)
- lxd.execute(
- build_container,
+ build_container.execute(
[
"mk-build-deps",
"--install",
@@ -147,20 +139,18 @@ def maas_deb_repo(
environment={"DEBIAN_FRONTEND": "noninteractive"},
)
- lxd.execute(
- build_container,
+ build_container.execute(
["make", "-C", "maas", "package"],
environment=proxy_env,
)
- lxd.execute(
- build_container,
+ build_container.execute(
[
"sh",
"-c",
"cd build-area && (dpkg-scanpackages . | gzip -c > Packages.gz)",
],
)
- lxd.execute(build_container, ["mv", "build-area", "/var/www/html/repo"])
+ build_container.execute(["mv", "build-area", "/var/www/html/repo"])
yield f"http://{build_ip}/repo"
@@ -191,29 +181,32 @@ def get_user_data(
@pytest.fixture(scope="session")
-def maas_container(config: dict[str, Any], build_container: str) -> str:
+def maas_container(config: dict[str, Any], build_container: Instance) -> Instance:
"""Build a container for running MAAS in."""
lxd = get_lxd(getLogger(f"{LOG_NAME}.maas_container"))
- container_name = os.environ.get(
+ profile_name = container_name = os.environ.get(
"MAAS_SYSTEMTESTS_MAAS_CONTAINER", "maas-system-maas"
)
- if not lxd.container_exists(container_name):
- if not lxd.profile_exists(container_name):
- lxd.copy_profile(LXD_PROFILE, container_name)
+ instance = Instance(lxd, container_name)
+ # FIXME: loggers could be a stack?
+ build_container.logger = instance.logger = lxd.logger
+ if not instance.exists():
+ if not lxd.profile_exists(profile_name):
+ lxd.copy_profile(LXD_PROFILE, profile_name)
existing_maas_nics = [
device_name
- for device_name in lxd.list_profile_devices(container_name)
+ for device_name in lxd.list_profile_devices(profile_name)
if device_name.startswith("maas-ss-")
]
for device_name in existing_maas_nics:
- lxd.remove_profile_device(container_name, device_name)
+ lxd.remove_profile_device(profile_name, device_name)
maas_networks = config["maas"]["networks"]
devices = {}
for network_name, ip in maas_networks.items():
network = config["networks"][network_name]
device_name = "maas-ss-" + network_name
lxd.add_profile_device(
- container_name,
+ profile_name,
device_name,
"nic",
"name=" + device_name,
@@ -257,7 +250,7 @@ def maas_container(config: dict[str, Any], build_container: str) -> str:
)
if "snap" not in config:
- build_container_ip = lxd.get_ip_address(build_container)
+ build_container_ip = build_container.get_ip_address()
contents = dedent(
f"""\
Package: *
@@ -265,9 +258,7 @@ def maas_container(config: dict[str, Any], build_container: str) -> str:
Pin-Priority: 999
"""
)
- lxd.push_text_file(
- container_name, contents, "/etc/apt/preferences.d/maas-build-pin-999"
- )
+ instance.files["/etc/apt/preferences.d/maas-build-pin-999"].write(contents)
http_proxy = config.get("proxy", {}).get("http", "")
if http_proxy:
@@ -279,27 +270,21 @@ def maas_container(config: dict[str, Any], build_container: str) -> str:
Acquire::https::Proxy::{build_container_ip} "DIRECT";
"""
)
- lxd.push_text_file(
- container_name, contents, "/etc/apt/apt.conf.d/80maas-system-test"
- )
+ instance.files["/etc/apt/apt.conf.d/80maas-system-test"].write(contents)
- return container_name
+ return instance
@pytest.fixture(scope="session")
-def vault(maas_container: str, config: dict[str, Any]) -> Optional[Vault]:
+def vault(maas_container: Instance, config: dict[str, Any]) -> Optional[Vault]:
snap_channel = config.get("vault", {}).get("snap-channel")
if not snap_channel:
return None
- vault_logger = getLogger(f"{LOG_NAME}.vault")
- lxd = get_lxd(vault_logger)
- lxd.execute(maas_container, ["apt", "install", "--yes", "ssl-cert"])
- lxd.execute(
- maas_container, ["snap", "install", "vault", f"--channel={snap_channel}"]
- )
- lxd.execute(
- maas_container,
+ vault_logger = maas_container.logger = getLogger(f"{LOG_NAME}.vault")
+ maas_container.execute(["apt", "install", "--yes", "ssl-cert"])
+ maas_container.execute(["snap", "install", "vault", f"--channel={snap_channel}"])
+ maas_container.execute(
[
"cp",
"/etc/ssl/certs/ssl-cert-snakeoil.pem",
@@ -326,7 +311,7 @@ def vault(maas_container: str, config: dict[str, Any]) -> Optional[Vault]:
"""
)
vault_config_file = "/var/snap/vault/common/vault.hcl"
- lxd.push_text_file(maas_container, vault_config, vault_config_file)
+ maas_container.files[vault_config_file].write(vault_config)
systemd_unit = dedent(
f"""\
@@ -345,23 +330,20 @@ def vault(maas_container: str, config: dict[str, Any]) -> Optional[Vault]:
WantedBy=multi-user.target
"""
)
- lxd.push_text_file(
- maas_container, systemd_unit, "/etc/systemd/system/vault.service"
- )
- lxd.execute(maas_container, ["systemctl", "enable", "--now", "vault"])
+ maas_container.files["/etc/systemd/system/vault.service"].write(systemd_unit)
+ maas_container.execute(["systemctl", "enable", "--now", "vault"])
vault = Vault(
container=maas_container,
secrets_mount="maas-secrets",
secrets_path="maas",
- lxd=lxd,
logger=vault_logger,
)
try:
vault.ensure_initialized()
except VaultNotReadyError as e:
- journalctl = lxd.execute(
- maas_container, ["journalctl", "--unit=vault", "--no-pager"]
+ journalctl = maas_container.execute(
+ ["journalctl", "--unit=vault", "--no-pager"]
)
vault_logger.exception(f"{e}\n{journalctl.stdout}")
raise
@@ -370,21 +352,18 @@ def vault(maas_container: str, config: dict[str, Any]) -> Optional[Vault]:
def install_deb(
- lxd: CLILXD, maas_container: str, maas_deb_repo: str, config: dict[str, Any]
+ maas_container: Instance, maas_deb_repo: str, config: dict[str, Any]
) -> str:
- on_maas_container = partial(lxd.execute, maas_container)
maas_ppas = config.get("deb", {}).get("ppa", ["ppa:maas-committers/latest-deps"])
- lxd.push_text_file(
- maas_container,
- f"deb [trusted=yes] {maas_deb_repo} ./\n",
- "/etc/apt/sources.list.d/maas.list",
+ maas_container.files["/etc/apt/sources.list.d/maas.list"].write(
+ f"deb [trusted=yes] {maas_deb_repo} ./\n"
)
- _add_maas_ppa(on_maas_container, maas_ppas)
- on_maas_container(
+ _add_maas_ppa(maas_container, maas_ppas)
+ maas_container.execute(
["apt", "install", "--yes", "maas"],
environment={"DEBIAN_FRONTEND": "noninteractive"},
)
- policy = on_maas_container(
+ policy = maas_container.execute(
["apt-cache", "policy", "maas"],
environment={"DEBIAN_FRONTEND": "noninteractive"},
) # just to record which version is running.
@@ -397,21 +376,20 @@ def install_deb(
@pytest.fixture(scope="session")
def maas_region(
- maas_container: str,
+ maas_container: Instance,
maas_deb_repo: Optional[str],
vault: Optional[Vault],
config: dict[str, Any],
) -> Iterator[MAASRegion]:
"""Install MAAS region controller in the container."""
lxd = get_lxd(getLogger(f"{LOG_NAME}.maas_region"))
-
- region_ip = lxd.get_ip_address(maas_container)
+ maas_container.logger = lxd.logger
+ region_ip = maas_container.get_ip_address()
installed_from_snap = "snap" in config
# setup self-signed certs, and add them to the trusted list
- lxd.execute(maas_container, ["apt", "install", "--yes", "ssl-cert"])
- lxd.execute(
- maas_container,
+ maas_container.execute(["apt", "install", "--yes", "ssl-cert"])
+ maas_container.execute(
[
"cp",
"-n",
@@ -419,26 +397,25 @@ def maas_region(
"/usr/share/ca-certificates/ssl-cert-snakeoil.crt",
],
)
- certs_list = lxd.get_file_contents(maas_container, "/etc/ca-certificates.conf")
+ ca_certificates = maas_container.files["/etc/ca-certificates.conf"]
+ certs_list = ca_certificates.read()
if "ssl-cert-snakeoil.crt" not in certs_list:
- certs_list += "ssl-cert-snakeoil.crt\n"
- lxd.push_text_file(maas_container, certs_list, "/etc/ca-certificates.conf")
- lxd.execute(maas_container, ["update-ca-certificates"])
+ ca_certificates.write(certs_list + "ssl-cert-snakeoil.crt\n")
+ maas_container.execute(["update-ca-certificates"])
if installed_from_snap:
- maas_already_initialized = lxd.file_exists(
- maas_container, "/var/snap/maas/common/snap_mode"
- )
- snap_list = lxd.execute(
- maas_container, ["snap", "list", "maas"]
- ) # just to record which version is running.
+
+ maas_already_initialized = maas_container.files[
+ "/var/snap/maas/common/snap_mode"
+ ].exists()
+ # just to record which version is running.
+ snap_list = maas_container.execute(["snap", "list", "maas"])
try:
version = snap_list.stdout.split("\n")[1].split()[1]
except IndexError:
version = ""
if not maas_already_initialized:
- lxd.execute(
- maas_container,
+ maas_container.execute(
[
"maas",
"init",
@@ -450,9 +427,8 @@ def maas_region(
],
)
else:
- # TODO: bind the LXD to the maas_container
assert maas_deb_repo is not None
- version = install_deb(lxd, maas_container, maas_deb_repo, config)
+ version = install_deb(maas_container, maas_deb_repo, config)
with open("version_under_test", "w") as fh:
fh.write(f"{version}\n")
@@ -466,9 +442,9 @@ def maas_region(
region_host = region_ip
if vault:
- setup_vault(vault, lxd, maas_container)
+ setup_vault(vault, maas_container)
if "tls" in config:
- region_host, url = setup_tls(lxd, maas_container)
+ region_host, url = setup_tls(maas_container)
region = MAASRegion(
url=url,
http_url=http_url,
@@ -495,20 +471,18 @@ def maas_region(
yaml.dump(credentials, fh)
if o11y := config.get("o11y"):
- setup_o11y(o11y, lxd, maas_container, installed_from_snap)
+ setup_o11y(o11y, maas_container, installed_from_snap)
yield region
@pytest.fixture(scope="session")
def unauthenticated_maas_api_client(
maas_credentials: dict[str, str],
- maas_client_container: str,
+ maas_client_container: Instance,
) -> UnauthenticatedMAASAPIClient:
"""Get an UnauthenticatedMAASAPIClient for interacting with MAAS."""
return UnauthenticatedMAASAPIClient(
- maas_credentials["region_url"],
- maas_client_container,
- get_lxd(getLogger()),
+ maas_credentials["region_url"], maas_client_container
)
@@ -616,40 +590,40 @@ def tag_all(authenticated_admin: AuthenticatedAPIClient) -> Iterator[str]:
@pytest.fixture(scope="session")
def maas_client_container(
maas_credentials: dict[str, str], config: dict[str, Any]
-) -> Iterator[str]:
+) -> Iterator[Instance]:
"""Set up a new LXD container with maas installed (in order to use maas CLI)."""
log = getLogger(f"{LOG_NAME}.client_container")
lxd = get_lxd(log)
container = os.environ.get("MAAS_SYSTEMTESTS_CLIENT_CONTAINER", "maas-client")
- lxd.get_or_create(container, config["containers-image"], profile=LXD_PROFILE)
+ instance = lxd.get_or_create(
+ container, config["containers-image"], profile=LXD_PROFILE
+ )
snap_channel = maas_credentials.get("snap_channel", "latest/edge")
- lxd.execute(container, ["snap", "refresh", "snapd"])
- lxd.execute(container, ["snap", "install", "maas", f"--channel={snap_channel}"])
- lxd.execute(container, ["snap", "list", "maas"])
+ instance.execute(["snap", "refresh", "snapd"])
+ instance.execute(["snap", "install", "maas", f"--channel={snap_channel}"])
+ instance.execute(["snap", "list", "maas"])
ensure_host_ip_mapping(
- lxd, container, maas_credentials["region_host"], maas_credentials["region_ip"]
+ instance, maas_credentials["region_host"], maas_credentials["region_ip"]
)
if "tls" in config:
- lxd.execute(container, ["mkdir", "-p", MAAS_CONTAINER_CERTS_PATH])
- lxd.push_file(
- container,
- config["tls"]["cacerts"],
- f"{MAAS_CONTAINER_CERTS_PATH}cacerts.pem",
+ instance.execute(["mkdir", "-p", MAAS_CONTAINER_CERTS_PATH])
+ instance.files[f"{MAAS_CONTAINER_CERTS_PATH}cacerts.pem"].push(
+ config["tls"]["cacerts"]
)
- yield container
+ yield instance
-def ensure_host_ip_mapping(lxd: CLILXD, container: str, hostname: str, ip: str) -> None:
+def ensure_host_ip_mapping(instance: Instance, hostname: str, ip: str) -> None:
"""Ensure the /etc/hosts file contains the specified host/ip mapping."""
if hostname == ip:
# no need to add the alias
return
line = f"{ip} {hostname}\n"
- content = lxd.get_file_contents(container, "/etc/hosts")
+ hosts_file = instance.files["/etc/hosts"]
+ content = hosts_file.read()
if line in content:
return
- content += line
- lxd.push_text_file(container, content, "/etc/hosts")
+ hosts_file.write(content + line)
diff --git a/systemtests/lxd.py b/systemtests/lxd.py
index 72483f5..f660ffd 100644
--- a/systemtests/lxd.py
+++ b/systemtests/lxd.py
@@ -36,6 +36,130 @@ class BadWebSocketHandshakeError(Exception):
"""Raised when lxc execute gives a bad websocket handshake error."""
+class _FileWrapper:
+ def __init__(self, lxd: CLILXD, instance_name: str, path: str):
+ self._lxd = lxd
+ self._instance_name = instance_name
+ self._path = path
+
+ def __repr__(self) -> str:
+ return f"<FileWrapper for {self._path} on {self._instance_name}>"
+
+ def read(self) -> str:
+ return self._lxd.get_file_contents(self._instance_name, self._path)
+
+ def write(self, content: str, uid: int = 0, gid: int = 0) -> None:
+ return self._lxd.push_text_file(
+ self._instance_name, content, self._path, uid=uid, gid=gid
+ )
+
+ def exists(self) -> bool:
+ return self._lxd.file_exists(self._instance_name, self._path)
+
+ def push(
+ self,
+ local_path: Path,
+ uid: int = 0,
+ gid: int = 0,
+ mode: str = "",
+ create_dirs: bool = False,
+ ) -> None:
+ return self._lxd.push_file(
+ self._instance_name,
+ str(local_path),
+ self._path,
+ uid=uid,
+ gid=gid,
+ mode=mode,
+ create_dirs=create_dirs,
+ )
+
+ def pull(self, local_path: str) -> None:
+ self._lxd.pull_file(self._instance_name, self._path, local_path)
+
+
+class _FilesWrapper:
+ def __init__(self, lxd: CLILXD, instance_name: str):
+ super().__init__()
+ self._lxd = lxd
+ self._instance_name = instance_name
+
+ def __getitem__(self, path: str) -> _FileWrapper:
+ return _FileWrapper(self._lxd, self._instance_name, path)
+
+
+class Instance:
+ """A container or VM on a LXD."""
+
+ def __init__(self, lxd: CLILXD, instance_name: str):
+ self._lxd = lxd
+ self._instance_name = instance_name
+
+ def __repr__(self) -> str:
+ return f"<Instance {self.name}>"
+
+ @property
+ def name(self) -> str:
+ return self._instance_name
+
+ @property
+ def logger(self) -> logging.Logger:
+ return self._lxd.logger
+
+ @logger.setter
+ def logger(self, logger: logging.Logger) -> None:
+ self._lxd.logger = logger
+
+ def exists(self) -> bool:
+ return self._lxd.container_exists(self._instance_name)
+
+ def execute(
+ self, command: list[str], environment: Optional[dict[str, str]] = None
+ ) -> subprocess.CompletedProcess[str]:
+ return self._lxd.execute(self._instance_name, command, environment=environment)
+
+ def quietly_execute(
+ self, command: list[str], environment: Optional[dict[str, str]] = None
+ ) -> subprocess.CompletedProcess[str]:
+ return self._lxd.quietly_execute(
+ self._instance_name, command, environment=environment
+ )
+
+ @property
+ def files(self) -> _FilesWrapper:
+ return _FilesWrapper(self._lxd, self._instance_name)
+
+ def get_ip_address(self) -> str:
+ return self._lxd.get_ip_address(self._instance_name)
+
+ def list_devices(self) -> list[str]:
+ return self._lxd.list_instance_devices(self._instance_name)
+
+ def add_device(self, name: str, device_config: DeviceConfig) -> None:
+ return self._lxd.add_instance_device(self._instance_name, name, device_config)
+
+ def remove_device(self, name: str) -> None:
+ return self._lxd.remove_instance_device(self._instance_name, name)
+
+ def start(self) -> subprocess.CompletedProcess[str]:
+ return self._lxd.start(self._instance_name)
+
+ def stop(self, force: bool = False) -> subprocess.CompletedProcess[str]:
+ return self._lxd.stop(self._instance_name, force=force)
+
+ def restart(self) -> subprocess.CompletedProcess[str]:
+ return self._lxd.restart(self._instance_name)
+
+ def delete(self) -> None:
+ return self._lxd.delete(self._instance_name)
+
+ def is_running(self) -> bool:
+ return self._lxd.is_running(self._instance_name)
+
+ def status(self) -> str:
+ return self._lxd.instance_status(self._instance_name)
+
+
class CLILXD:
"""Backend that uses the CLI to talk to LXD."""
@@ -68,7 +192,7 @@ class CLILXD:
image: str,
user_data: Optional[str] = None,
profile: Optional[str] = None,
- ) -> str:
+ ) -> Instance:
logger = self.logger.getChild(name)
if not self.container_exists(name):
logger.info(f"Creating container {name} (from {image})...")
@@ -88,20 +212,22 @@ class CLILXD:
logger.info(f"Container {name} created.")
logger.info("Waiting for boot to finish...")
+ instance = Instance(self, name)
+
@retry(exceptions=CloudInitDisabled, tries=120, delay=1, logger=logger)
def _cloud_init_wait() -> None:
- process = self.execute(
- name, ["timeout", "2000", "cloud-init", "status", "--wait", "--long"]
+ process = instance.execute(
+ ["timeout", "2000", "cloud-init", "status", "--wait", "--long"]
)
if "Cloud-init disabled by cloud-init-generator" in process.stdout:
raise CloudInitDisabled("Cloud-init is disabled.")
- process = self.execute(
- name, ["timeout", "2000", "snap", "wait", "system", "seed.loaded"]
+ process = instance.execute(
+ ["timeout", "2000", "snap", "wait", "system", "seed.loaded"]
)
_cloud_init_wait()
logger.info("Boot finished.")
- return name
+ return instance
def get_or_create(
self,
@@ -109,10 +235,11 @@ class CLILXD:
image: str,
user_data: Optional[str] = None,
profile: Optional[str] = None,
- ) -> str:
- if not self.container_exists(name):
+ ) -> Instance:
+ instance = Instance(self, name)
+ if not instance.exists():
self.create_container(name, image, user_data=user_data, profile=profile)
- return name
+ return instance
def push_file(
self,
@@ -325,27 +452,6 @@ class CLILXD:
["lxc", "profile", "device", "remove", profile_name, device_name],
)
- def collect_sos_report(self, instance_name: str, output: str) -> None:
- container_tmp = "/tmp/sosreport"
- output_dir = Path(f"{output}/sosreport")
- self.execute(instance_name, ["apt", "install", "--yes", "sosreport"])
- self.execute(instance_name, ["rm", "-rf", container_tmp])
- self.execute(instance_name, ["mkdir", "-p", container_tmp])
- self.execute(
- instance_name,
- ["sos", "report", "--batch", "-o", "maas", "--tmp-dir", container_tmp],
- )
- output_dir.mkdir(parents=True, exist_ok=True)
- journalctl = self.execute(
- instance_name, ["journalctl", "--unit=vault", "--no-pager", "--output=cat"]
- )
- if journalctl.stdout:
- (output_dir / "vault-journal").write_text(journalctl.stdout)
- with tempfile.TemporaryDirectory(prefix="sosreport") as tempdir:
- self.pull_file(instance_name, container_tmp, f"{tempdir}/")
- for f in os.listdir(f"{tempdir}/sosreport"):
- os.rename(os.path.join(f"{tempdir}/sosreport", f), f"{output_dir}/{f}")
-
def list_instance_devices(self, instance_name: str) -> list[str]:
logger = self.logger.getChild(instance_name)
result = self._run(
@@ -395,7 +501,7 @@ class CLILXD:
instances = {instance["name"]: instance for instance in lxc_list}
return instances
- def create_vm(self, instance_name: str, config: dict[str, str]) -> None:
+ def create_vm(self, instance_name: str, config: dict[str, str]) -> Instance:
logger = self.logger.getChild(instance_name)
args: list[str] = []
profile: Optional[str] = config.pop("profile", None)
@@ -417,6 +523,7 @@ class CLILXD:
],
logger=logger,
)
+ return Instance(self, instance_name)
def start(self, instance_name: str) -> subprocess.CompletedProcess[str]:
logger = self.logger.getChild(instance_name)
diff --git a/systemtests/o11y.py b/systemtests/o11y.py
index a5366ff..4dd4342 100644
--- a/systemtests/o11y.py
+++ b/systemtests/o11y.py
@@ -1,28 +1,24 @@
+from pathlib import Path
from textwrap import dedent
from typing import Any
-from .lxd import CLILXD
+from .lxd import Instance
AGENT_PATH = "/opt/agent/agent-linux-amd64"
def setup_o11y(
- o11y: dict[str, Any], lxd: CLILXD, maas_container: str, installed_from_snap: bool
+ o11y: dict[str, Any], maas_container: Instance, installed_from_snap: bool
) -> None:
- if not lxd.file_exists(maas_container, AGENT_PATH):
- host_path_to_agent = o11y["grafana_agent_file_path"].strip()
- lxd.execute(maas_container, ["mkdir", "-p", "/opt/agent"])
- lxd.push_file(
- maas_container,
- host_path_to_agent,
- AGENT_PATH,
- mode="0755",
- )
+ agent_on_container = maas_container.files[AGENT_PATH]
+ if not agent_on_container.exists():
+ host_path_to_agent = Path(o11y["grafana_agent_file_path"].strip())
+ maas_container.execute(["mkdir", "-p", "/opt/agent"])
+ agent_on_container.push(host_path_to_agent, mode="0755")
agent_maas_sample = "/usr/share/maas/grafana_agent/agent.yaml.example"
if installed_from_snap:
agent_maas_sample = f"/snap/maas/current{agent_maas_sample}"
- lxd.execute(
- maas_container,
+ maas_container.execute(
["cp", agent_maas_sample, "/opt/agent/agent.yml"],
)
o11y_ip = o11y["o11y_ip"]
@@ -47,4 +43,4 @@ def setup_o11y(
-server.http.address="0.0.0.0:3100" -server.grpc.address="0.0.0.0:9095"
"""
)
- lxd.execute(maas_container, ["sh", "-c", telemetry_run_cmd])
+ maas_container.execute(["sh", "-c", telemetry_run_cmd])
diff --git a/systemtests/region.py b/systemtests/region.py
index 014cbc8..4b979b6 100644
--- a/systemtests/region.py
+++ b/systemtests/region.py
@@ -4,11 +4,11 @@ import subprocess
from logging import getLogger
from typing import TYPE_CHECKING, Any, Union
-from .lxd import get_lxd
from .utils import retries
if TYPE_CHECKING:
from .api import AuthenticatedAPIClient
+ from .lxd import Instance
LOG = getLogger("systemtests.region")
@@ -19,15 +19,15 @@ class MAASRegion:
url: str,
http_url: str,
host: str,
- maas_container: str,
+ maas_container: Instance,
installed_from_snap: bool,
):
self.url = url
self.http_url = http_url
self.host = host
self.maas_container = maas_container
+ self.maas_container.logger = LOG
self.installed_from_snap = installed_from_snap
- self.lxd = get_lxd(LOG)
def __repr__(self) -> str:
package = "snap" if self.installed_from_snap else "deb"
@@ -36,7 +36,7 @@ class MAASRegion:
)
def execute(self, command: list[str]) -> subprocess.CompletedProcess[str]:
- return self.lxd.execute(self.maas_container, command)
+ return self.maas_container.execute(command)
def get_api_token(self, user: str) -> str:
result = self.execute(["maas", "apikey", "--username", user])
@@ -158,7 +158,7 @@ class MAASRegion:
dhcpd_conf_path = "/var/lib/maas/dhcpd.conf"
if self.installed_from_snap:
dhcpd_conf_path = "/var/snap/maas/common/maas/dhcpd.conf"
- return self.lxd.file_exists(self.maas_container, dhcpd_conf_path)
+ return self.maas_container.files[dhcpd_conf_path].exists()
def set_config(self, key: str, value: str = "") -> None:
if self.installed_from_snap:
diff --git a/systemtests/state.py b/systemtests/state.py
index b79b247..57f0f7c 100644
--- a/systemtests/state.py
+++ b/systemtests/state.py
@@ -3,7 +3,8 @@ from __future__ import annotations
import json
import time
from logging import getLogger
-from typing import TYPE_CHECKING, Any, Iterator, Set, cast
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Iterator, Set
import pytest
from retry import retry
@@ -75,11 +76,8 @@ def import_images_and_wait_until_synced(
if "windows" in osystems:
windows_path = "/home/ubuntu/windows-win2012hvr2-amd64-root-dd"
# 1000/1000 is default uid/gid of ubuntu user
- authenticated_admin.api_client.push_file(
- source_file=config["windows_image_file_path"],
- target_file=windows_path,
- uid=1000,
- gid=1000,
+ authenticated_admin.api_client.maas_container.files[windows_path].push(
+ Path(config["windows_image_file_path"]), uid=1000, gid=1000
)
region_start_point = time.time()
@@ -94,7 +92,7 @@ def import_images_and_wait_until_synced(
)
if "windows" in osystems:
windows_start_point = time.time()
- windows_path = cast(str, windows_path)
+ assert windows_path is not None
authenticated_admin.create_boot_resource(
name="windows/win2012hvr2",
title="Windows2012HVR2",
diff --git a/systemtests/tests_per_machine/test_hardware_sync.py b/systemtests/tests_per_machine/test_hardware_sync.py
index 05533c3..1bab2c4 100644
--- a/systemtests/tests_per_machine/test_hardware_sync.py
+++ b/systemtests/tests_per_machine/test_hardware_sync.py
@@ -11,7 +11,6 @@ from pytest_steps.steps_generator import optional_step
from retry.api import retry, retry_call
from ..device_config import DeviceConfig, HardwareSyncMachine
-from ..lxd import get_lxd
from ..utils import (
retries,
ssh_execute_command,
@@ -25,15 +24,7 @@ if TYPE_CHECKING:
from paramiko import PKey
from ..api import AuthenticatedAPIClient, Machine
- from ..lxd import CLILXD
-
-
-def assert_device_not_added_to_lxd_instance(
- lxd: CLILXD,
- machine_name: str,
- device_config: DeviceConfig,
-) -> None:
- assert device_config["device_name"] not in lxd.list_instance_devices(machine_name)
+ from ..lxd import Instance
def _check_machine_for_device(
@@ -79,27 +70,27 @@ def check_machine_does_not_have_device(
@contextmanager
-def powered_off_vm(lxd: CLILXD, instance_name: str) -> Iterator[None]:
+def powered_off_vm(instance: Instance) -> Iterator[None]:
"""Context-manager to do something with LXD instance off."""
- strategy_iter: Iterator[Callable[[str], CompletedProcess[str]]] = chain(
- repeat(lxd.stop, 3), repeat(partial(lxd.stop, force=True))
+ strategy_iter: Iterator[Callable[[], CompletedProcess[str]]] = chain(
+ repeat(instance.stop, 3), repeat(partial(instance.stop, force=True))
)
- @retry(tries=10, delay=5, logger=lxd.logger)
- def power_off(instance_name: str) -> None:
- if lxd.is_running(instance_name):
+ @retry(tries=10, delay=5, logger=instance.logger)
+ def power_off(instance: Instance) -> None:
+ if instance.is_running():
stop_attempt = next(strategy_iter)
- stop_attempt(instance_name)
- if lxd.is_running(instance_name):
- raise Exception(f"LXD {instance_name} still running.")
+ stop_attempt()
+ if instance.is_running():
+ raise Exception(f"{instance.name} still running.")
- @retry(tries=10, delay=5, backoff=1.2, logger=lxd.logger)
- def power_on(instance_name: str) -> None:
- lxd.start(instance_name)
+ @retry(tries=10, delay=5, backoff=1.2, logger=instance.logger)
+ def power_on(instance: Instance) -> None:
+ instance.start()
- power_off(instance_name)
+ power_off(instance)
yield
- power_on(instance_name)
+ power_on(instance)
@test_steps(
@@ -117,8 +108,7 @@ def test_hardware_sync(
ssh_key: PKey,
testlog: Logger,
) -> Iterator[None]:
- lxd = get_lxd(logger=testlog)
-
+ hardware_sync_machine.instance.logger = testlog
maas_api_client.set_config("hardware_sync_interval", "5s")
maas_api_client.deploy_machine(
@@ -144,21 +134,19 @@ def test_hardware_sync(
testlog.info(f"{hardware_sync_machine.name}: {stdout}")
yield
-
+ instance = hardware_sync_machine.instance
with optional_step("add_device") as add_device:
# we don't have a way of remotely adding physical hardware,
# so this test only tests lxd instances
assert hardware_sync_machine.machine["power_type"] == "lxd"
+ current_devices = instance.list_devices()
for device_config in hardware_sync_machine.devices_config:
- assert_device_not_added_to_lxd_instance(
- lxd, hardware_sync_machine.name, device_config
- )
+ assert device_config["device_name"] not in current_devices
- with powered_off_vm(lxd, hardware_sync_machine.name):
+ with powered_off_vm(instance):
for device_config in hardware_sync_machine.devices_config:
- lxd.add_instance_device(
- hardware_sync_machine.name,
+ instance.add_device(
device_config["device_name"],
device_config,
)
@@ -220,11 +208,9 @@ def test_hardware_sync(
with optional_step("remove_device", depends_on=add_device) as remove_device:
if remove_device.should_run():
- with powered_off_vm(lxd, hardware_sync_machine.name):
+ with powered_off_vm(instance):
for device_config in hardware_sync_machine.devices_config:
- lxd.remove_instance_device(
- hardware_sync_machine.name, device_config["device_name"]
- )
+ instance.remove_device(device_config["device_name"])
for device_config in hardware_sync_machine.devices_config:
retry_call(
@@ -248,5 +234,5 @@ def test_hardware_sync(
break
elif power_state == "on":
with suppress(CalledProcessError):
- lxd.stop(hardware_sync_machine.name, force=True)
+ instance.stop(force=True)
yield
diff --git a/systemtests/tls.py b/systemtests/tls.py
index 47e7cb4..66cbdb4 100644
--- a/systemtests/tls.py
+++ b/systemtests/tls.py
@@ -1,14 +1,13 @@
-from .lxd import CLILXD
+from .lxd import Instance
# Certs must be accessible for MAAS installed by snap, but
# this location is useful also when installed via deb package.
MAAS_CONTAINER_CERTS_PATH = "/var/snap/maas/common/certs/"
-def setup_tls(lxd: CLILXD, maas_container: str) -> tuple[str, str]:
- lxd.execute(maas_container, ["mkdir", "-p", MAAS_CONTAINER_CERTS_PATH])
- lxd.execute(
- maas_container,
+def setup_tls(maas_container: Instance) -> tuple[str, str]:
+ maas_container.execute(["mkdir", "-p", MAAS_CONTAINER_CERTS_PATH])
+ maas_container.execute(
[
"cp",
"-n",
@@ -18,13 +17,10 @@ def setup_tls(lxd: CLILXD, maas_container: str) -> tuple[str, str]:
],
)
# We need the cert to add it as CA in client container.
- lxd.pull_file(
- maas_container,
- "/etc/ssl/certs/ssl-cert-snakeoil.pem",
- "ssl-cert-snakeoil.pem",
+ maas_container.files["/etc/ssl/certs/ssl-cert-snakeoil.pem"].pull(
+ "ssl-cert-snakeoil.pem"
)
- lxd.execute(
- maas_container,
+ maas_container.execute(
[
"maas",
"config-tls",
@@ -36,5 +32,5 @@ def setup_tls(lxd: CLILXD, maas_container: str) -> tuple[str, str]:
"--yes",
],
)
- region_host = lxd.quietly_execute(maas_container, ["hostname", "-f"]).stdout.strip()
+ region_host = maas_container.quietly_execute(["hostname", "-f"]).stdout.strip()
return region_host, f"https://{region_host}:5443/MAAS/"
diff --git a/systemtests/utils.py b/systemtests/utils.py
index d3858bd..1fe7535 100644
--- a/systemtests/utils.py
+++ b/systemtests/utils.py
@@ -7,12 +7,14 @@ import re
import string
import time
from dataclasses import dataclass
+from logging import Logger
from typing import Iterator, Optional, TypedDict, Union
import paramiko
from retry.api import retry_call
from . import api
+from .lxd import get_lxd
class UnexpectedMachineStatus(Exception):
@@ -184,6 +186,15 @@ def wait_for_machine(
raise UnexpectedMachineStatus(machine_id, status, retry_info.elapsed, debug_outputs)
+def debug_lxd_vm(machine_name: str, logger: Logger) -> list[str]:
+ """Grab debug information for a VM failure."""
+ lxd = get_lxd(logger)
+ debug_info = []
+ debug_info.append(repr(lxd.list_instances().get(machine_name, "")))
+ debug_info.append(lxd._run(["lxc", "info", "--show-log", machine_name]).stdout)
+ return debug_info
+
+
# XXX: Move to api.py
def wait_for_new_machine(
api_client: api.AuthenticatedAPIClient, mac_address: str, machine_name: str
@@ -200,8 +211,8 @@ def wait_for_new_machine(
debug_outputs = []
maybe_machines = quiet_client.list_machines(mac_address=mac_address)
debug_outputs.append(repr(maybe_machines))
- if "lxd" in [m["power_type"] for m in machines]:
- debug_outputs.append(repr(api_client.lxd.list_instances().get(machine_name)))
+ if "lxd" in [m["power_type"] for m in maybe_machines]:
+ debug_outputs.extend(debug_lxd_vm(machine_name, api_client.logger))
raise UnexpectedMachineStatus(
machine_name, "New", retry_info.elapsed, debug_outputs
@@ -226,7 +237,7 @@ def wait_for_machine_to_power_off(
debug_outputs = [repr(machine)]
if machine["power_type"] == "lxd":
- debug_outputs.append(repr(api_client.lxd.list_instances()[machine_name]))
+ debug_outputs.extend(debug_lxd_vm(machine_name, api_client.logger))
raise UnexpectedMachineStatus(
machine_name, "power_off", retry_info.elapsed, debug_outputs
)
diff --git a/systemtests/vault.py b/systemtests/vault.py
index 85f86ca..178ea46 100644
--- a/systemtests/vault.py
+++ b/systemtests/vault.py
@@ -10,7 +10,7 @@ from typing import Any, cast
import yaml
from retry.api import retry_call
-from .lxd import CLILXD
+from .lxd import Instance
class VaultNotReadyError(Exception):
@@ -21,10 +21,9 @@ class VaultNotReadyError(Exception):
class Vault:
"""Vault CLI wrapper to be run inside a container."""
- container: str
+ container: Instance
secrets_path: str
secrets_mount: str
- lxd: CLILXD
logger: Logger
root_token: str = ""
@@ -34,9 +33,7 @@ class Vault:
@cached_property
def addr(self) -> str:
"""The Vault address."""
- fqdn = self.lxd.quietly_execute(
- self.container, ["hostname", "-f"]
- ).stdout.strip()
+ fqdn = self.container.quietly_execute(["hostname", "-f"]).stdout.strip()
return f"https://{fqdn}:8200"
def wait_ready(self) -> dict[str, Any]:
@@ -61,8 +58,8 @@ class Vault:
return cast(
dict[str, Any],
json.loads(
- self.lxd.quietly_execute(
- self.container, ["curl", f"{self.addr}/v1/sys/seal-status"]
+ self.container.quietly_execute(
+ ["curl", f"{self.addr}/v1/sys/seal-status"]
).stdout
),
)
@@ -79,8 +76,7 @@ class Vault:
environment = {"VAULT_ADDR": self.addr}
if self.root_token:
environment["VAULT_TOKEN"] = self.root_token
- return self.lxd.quietly_execute(
- self.container,
+ return self.container.quietly_execute(
["vault"] + list(command),
environment=environment,
)
@@ -95,17 +91,13 @@ class Vault:
"""Ensure Vault is initialized and unlocked."""
status = self.wait_ready()
- vault_init_file = "/var/snap/vault/common/init.json"
+ vault_init_file = self.container.files["/var/snap/vault/common/init.json"]
if status["initialized"]:
- init_result = json.loads(
- self.lxd.get_file_contents(self.container, vault_init_file)
- )
+ init_result = json.loads(vault_init_file.read())
self.root_token = init_result["root_token"]
else:
init_result = self.init()
- self.lxd.push_text_file(
- self.container, json.dumps(init_result), vault_init_file
- )
+ vault_init_file.write(json.dumps(init_result))
while (status := self.status())["sealed"]:
index = status["progress"]
@@ -141,9 +133,9 @@ class Vault:
"""
)
tmpfile = f"/root/vault-policy-{uuid.uuid4()}"
- self.lxd.push_text_file(self.container, policy, tmpfile)
+ self.container.files[tmpfile].write(policy)
self.execute("policy", "write", self.MAAS_POLICY_NAME, tmpfile)
- self.lxd.quietly_execute(self.container, ["rm", tmpfile])
+ self.container.quietly_execute(["rm", tmpfile])
def create_approle(self, role_name: str) -> tuple[str, str]:
"""Create an approle with secret and return its ID and wrapped token."""
@@ -165,17 +157,16 @@ class Vault:
return role_id, wrapped_token
-def setup_vault(vault: Vault, lxd: CLILXD, maas_container: str) -> None:
+def setup_vault(vault: Vault, maas_container: Instance) -> None:
"""Configures MAAS to talk to Vault."""
maas_vault_status = yaml.safe_load(
- lxd.quietly_execute(
- maas_container, ["maas", "config-vault", "status"]
+ maas_container.quietly_execute(
+ ["maas", "config-vault", "status"]
).stdout.strip()
)
if maas_vault_status["status"] == "disabled":
- role_id, wrapped_token = vault.create_approle(maas_container)
- lxd.execute(
- maas_container,
+ role_id, wrapped_token = vault.create_approle(maas_container.name)
+ maas_container.execute(
[
"maas",
"config-vault",
@@ -188,8 +179,7 @@ def setup_vault(vault: Vault, lxd: CLILXD, maas_container: str) -> None:
vault.secrets_mount,
],
)
- lxd.execute(
- maas_container,
+ maas_container.execute(
[
"maas",
"config-vault",
Follow ups