sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #08589
[Merge] ~lloydwaltersj/maas-ci/+git/system-tests:system-test-image-test into ~maas-committers/maas-ci/+git/system-tests:master
Jack Lloyd-Walters has proposed merging ~lloydwaltersj/maas-ci/+git/system-tests:system-test-image-test into ~maas-committers/maas-ci/+git/system-tests:master.
Commit message:
allow tests_per_machine to use custom images
Requested reviews:
MAAS Lander (maas-lander)
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~lloydwaltersj/maas-ci/+git/system-tests/+merge/443284
--
Your team MAAS Committers is requested to review the proposed merge of ~lloydwaltersj/maas-ci/+git/system-tests:system-test-image-test into ~maas-committers/maas-ci/+git/system-tests:master.
diff --git a/image_mapping.yaml.sample b/image_mapping.yaml.sample
new file mode 100644
index 0000000..33b8e25
--- /dev/null
+++ b/image_mapping.yaml.sample
@@ -0,0 +1,13 @@
+---
+# Mapping between the names of built custom images, and the
+# details required to download/deploy them in systemtests
+
+# An example of a mapping is:
+# images:
+# $IMAGE_NAME:
+# url: $IMAGE_URL
+# filetype: $IMAGE_FILETYPE
+# architecture: $IMAGE_ARCH
+# osystem: $IMAGE_OSYSTEM
+
+images:
diff --git a/systemtests/conftest.py b/systemtests/conftest.py
index 0db6532..78c27c9 100644
--- a/systemtests/conftest.py
+++ b/systemtests/conftest.py
@@ -54,6 +54,7 @@ from .fixtures import (
vault,
zone,
)
+from .image_config import TestableImage
from .lxd import Instance, get_lxd
from .machine_config import MachineConfig
from .o11y import add_o11y_header
@@ -287,6 +288,19 @@ def hardware_sync_machine(
@pytest.fixture(scope="module")
+def images_to_test(request: Any) -> Iterator[TestableImage]:
+ images_to_test = request.param
+ yield images_to_test
+
+
+def generate_images(config: dict[str, Any]) -> list[TestableImage]:
+ return [
+ TestableImage.from_config(name=name, config=cfg)
+ for name, cfg in config.get("image-tests", {}).items()
+ ]
+
+
+@pytest.fixture(scope="module")
def instance_config(
authenticated_admin: AuthenticatedAPIClient, request: Any
) -> Iterator[MachineConfig]:
@@ -329,3 +343,9 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
if len(instance.devices_config) > 0
]
metafunc.parametrize("instance_config", instance_config, ids=str, indirect=True)
+
+ # if we're testing im
+ if "images_to_test" in metafunc.fixturenames:
+ images_to_test = [image for image in generate_images(cfg) if image.url]
+ LOG.info(f"Testing images: {', '.join(str(image) for image in images_to_test)}")
+ metafunc.parametrize("images_to_test", images_to_test, ids=str, indirect=True)
diff --git a/systemtests/image_config.py b/systemtests/image_config.py
new file mode 100644
index 0000000..15f5178
--- /dev/null
+++ b/systemtests/image_config.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Dict, Union, cast
+
+
+@dataclass(frozen=True)
+class TestableImage:
+ name: str
+ url: str
+ filetype: str = "targz"
+ architecture: str = "amd64"
+ osystem: str = "ubuntu"
+
+ def __str__(self) -> str:
+ return self.osystem
+
+ @classmethod
+ def from_config(
+ cls,
+ name: str,
+ config: dict[str, Union[str, dict[str, str]]],
+ ) -> TestableImage:
+ details = cast(Dict[str, str], config.copy())
+ return cls(
+ name=name,
+ **details,
+ )
diff --git a/systemtests/state.py b/systemtests/state.py
index 38494d0..7bb6faf 100644
--- a/systemtests/state.py
+++ b/systemtests/state.py
@@ -2,9 +2,11 @@ from __future__ import annotations
import json
import time
+from contextlib import closing
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterator, Set
+from urllib.request import urlopen
import pytest
from retry import retry
@@ -79,6 +81,19 @@ def import_images_and_wait_until_synced(
authenticated_admin.api_client.maas_container.files[windows_path].push(
Path(config["windows_image_file_path"]), uid=1000, gid=1000
)
+ image_cfg = config.get("image-tests")
+ if image_cfg:
+ for image, cfg in image_cfg.items():
+ cfg["ftype"], ext = (
+ ("tgz", ".tar.gz") if "tar" in cfg["filetype"] else ("ddgz", ".dd.gz")
+ )
+ cfg["fpath"] = f"/home/ubuntu/{image}{ext}"
+ image_file = authenticated_admin.api_client.maas_container.files[
+ cfg["fpath"]
+ ]
+ LOG.debug(f"Downloading {image}{ext}")
+ with closing(urlopen(cfg["url"])) as response:
+ image_file.write(response.read().decode("utf-8"), uid=1000, gid=1000)
region_start_point = time.time()
while authenticated_admin.is_importing_boot_resources():
@@ -102,6 +117,23 @@ def import_images_and_wait_until_synced(
)
windows_time_taken = time.time() - windows_start_point
LOG.info(f"Took {windows_time_taken:0.1f}s to upload Windows")
+ if image_cfg:
+ custom_image_start_point = time.time()
+ for image, cfg in image_cfg.items():
+ image_type = "Raw" if cfg["ftype"] == "ddgz" else "TGZ"
+ authenticated_admin.create_boot_resource(
+ name=f"custom/{image}-{cfg['ftype']}",
+ title=f"{image.capitalize()} Custom {image_type}",
+ architecture=cfg["architecture"],
+ filetype=cfg["ftype"],
+ image_file_path=cfg["fpath"],
+ )
+ custom_images_time_taken = time.time() - custom_image_start_point
+ LOG.info(
+ f"Took {custom_images_time_taken:0.1f}s to upload {len(image_cfg.keys())} "
+ + f"custom image{'s' if len(image_cfg.keys())>1 else ''}"
+ )
+
rack_start_point = time.time()
for rack_controller in get_rack_controllers(authenticated_admin):
boot_images = authenticated_admin.list_boot_images(rack_controller)
diff --git a/systemtests/tests_per_machine/test_machine.py b/systemtests/tests_per_machine/test_machine.py
index 9241fed..4631504 100644
--- a/systemtests/tests_per_machine/test_machine.py
+++ b/systemtests/tests_per_machine/test_machine.py
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
from paramiko import PKey
from ..api import AuthenticatedAPIClient
+ from ..image_config import TestableImage
from ..machine_config import MachineConfig
@@ -31,11 +32,13 @@ def test_full_circle(
machine_config: MachineConfig,
ready_remote_maas: None,
maas_without_machine: None,
+ import_images_and_wait_until_synced: None,
ssh_key: PKey,
testlog: Logger,
zone: str,
pool: str,
tag_all: str,
+ images_to_test: TestableImage,
) -> Iterator[None]:
maas_ip_ranges = maas_api_client.list_ip_ranges()
reserved_ranges = []
@@ -109,8 +112,12 @@ def test_full_circle(
assert machine["ip_addresses"][0] in dynamic_range
yield
+ deploy_osystem = (
+ machine_config.osystem if not images_to_test else images_to_test.osystem
+ )
+
maas_api_client.deploy_machine(
- machine, osystem=machine_config.osystem, distro_series=machine_config.osystem
+ machine, osystem=deploy_osystem, distro_series=deploy_osystem
)
machine = wait_for_machine(
@@ -126,14 +133,14 @@ def test_full_circle(
for reserved_range in reserved_ranges:
assert machine["ip_addresses"][0] not in reserved_range
- if machine_config.osystem == "ubuntu":
+ if deploy_osystem == "ubuntu":
stdout = ssh_execute_command(
machine, "ubuntu", ssh_key, "cat /etc/cloud/build.info"
)
testlog.info(f"{machine_config.name}: {stdout}")
yield
- if machine_config.osystem == "windows":
+ if deploy_osystem == "windows":
# We need ssh access in order to test rescue mode.
pytest.skip("rescue mode not tested in windows.")
diff --git a/utils/gen_config.py b/utils/gen_config.py
index 4a8a5b5..18ad8c0 100755
--- a/utils/gen_config.py
+++ b/utils/gen_config.py
@@ -113,6 +113,20 @@ def main(argv: list[str]) -> int:
metavar="GIT_BRANCH",
help="Git branch to clone Ansible Playbooks from",
)
+ image_tests_group = parser.add_argument_group(
+ "Image Tests", "Config options for Automated OS Image testing"
+ )
+ image_tests_group.add_argument(
+ "image_mapping",
+ type=argparse.FileType("r"),
+ help="Path to image_mapping.yaml",
+ )
+ image_tests_group.add_argument(
+ "--image-name",
+ action="append",
+ metavar="IMAGE_NAME",
+ help="Run tests for this image; can be repeated",
+ )
filter_machines_group = parser.add_argument_group(
"filters", "Use these options to filter machines to test"
)
@@ -202,6 +216,19 @@ def main(argv: list[str]) -> int:
else:
config.pop("ansible-playbooks", None)
+ if args.image_tests:
+ config["image-tests"] = config.get("image-tests", {})
+ with args.image_mapping as fh:
+ image_cfg: dict[str, Any] = yaml.load(fh)
+ # fetch details from the image mapping cfg file
+ for image in args.image_name:
+ assert image in image_cfg.get(
+ "images", {}
+ ), "Image does not have a known mapping"
+ config["image-tests"][image] = image_cfg["images"][image]
+ else:
+ config.pop("image-tests", None)
+
machines = config.get("machines", {})
vms = machines.get("vms", {})
hardware = machines.get("hardware", {})
@@ -224,11 +251,19 @@ def main(argv: list[str]) -> int:
if hardware:
if args.architecture:
+ # if running custom image tests, only use compatible machines
+ target_arches = (
+ args.architecture
+ if not args.image_tests
+ else [
+ image["architecture"] for _, image in config["image-tests"].items()
+ ]
+ )
# Filter out machines with architectures not matching specified ones.
machines["hardware"] = {
name: details
for name, details in hardware.items()
- if details.get("architecture", "amd64") in args.architecture
+ if details.get("architecture", "amd64") in target_arches
}
if args.machine:
Follow ups