Merge ~skatsaounis/maas-ci/+git/system-tests:download-boot-images into ~maas-committers/maas-ci/+git/system-tests:master
- Git
- lp:~skatsaounis/maas-ci/+git/system-tests
- download-boot-images
- Merge into master
Status: | Merged |
---|---|
Merge reported by: | Stamatis Katsaounis |
Merged at revision: | 40f5ee8ece9b0d1684205574b1f536fd7d811ebb |
Proposed branch: | ~skatsaounis/maas-ci/+git/system-tests:download-boot-images |
Merge into: | ~maas-committers/maas-ci/+git/system-tests:master |
Diff against target: |
355 lines (+137/-10) 8 files modified
pyproject.toml (+2/-1) systemtests/api.py (+53/-1) systemtests/conftest.py (+2/-0) systemtests/fixtures.py (+11/-0) systemtests/machine_config.py (+18/-0) systemtests/state.py (+35/-4) systemtests/tests_per_machine/test_machine.py (+3/-2) utils/gen_config.py (+13/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jack Lloyd-Walters | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+454705@code.launchpad.net |
Commit message
Add oseries support
Description of the change
Adam Collard (adam-collard) : | # |
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b download-
STATUS: FAILED
LOG: http://
COMMIT: a2399387bfe20d5
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b download-
STATUS: FAILED
LOG: http://
COMMIT: c5372e8511c8780
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b download-
STATUS: SUCCESS
COMMIT: deb013d04f91a14
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b download-
STATUS: SUCCESS
COMMIT: 40f5ee8ece9b0d1
Preview Diff
1 | diff --git a/pyproject.toml b/pyproject.toml |
2 | index 4a38e7a..a3b6850 100644 |
3 | --- a/pyproject.toml |
4 | +++ b/pyproject.toml |
5 | @@ -5,7 +5,8 @@ markers = [ |
6 | "skip_if_dns_unconfigured", # skips tests if DNS configuration isn't present |
7 | "skip_if_installed_from_snap", # Skips tests if MAAS is installed as a snap. |
8 | "skip_if_installed_from_deb_package", # Skips tests if MAAS is installed from package. |
9 | - "skip_if_packer_unconfigured" # Skips tests if Packer-MAAS is not configured. |
10 | + "skip_if_packer_unconfigured", # Skips tests if Packer-MAAS is not configured. |
11 | + "skip_if_os_not_supported" # Skips tests if the OS chosen is not compatible with the machine architecture |
12 | ] |
13 | log_level = "INFO" |
14 | log_file = "systemtests.log" |
15 | diff --git a/systemtests/api.py b/systemtests/api.py |
16 | index 4abf1c2..83163d1 100644 |
17 | --- a/systemtests/api.py |
18 | +++ b/systemtests/api.py |
19 | @@ -6,7 +6,7 @@ from subprocess import CalledProcessError |
20 | from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, TypedDict, Union |
21 | |
22 | from .tls import MAAS_CONTAINER_CERTS_PATH |
23 | -from .utils import wait_for_machine |
24 | +from .utils import retries, wait_for_machine |
25 | |
26 | if TYPE_CHECKING: |
27 | from logging import Logger |
28 | @@ -32,6 +32,16 @@ class BootImages(TypedDict): |
29 | status: str |
30 | |
31 | |
32 | +class BootResource(TypedDict): |
33 | + id: int |
34 | + type: str |
35 | + name: str |
36 | + architecture: str |
37 | + resource_uri: str |
38 | + last_deployed: str | None |
39 | + subarches: str |
40 | + |
41 | + |
42 | class Version(TypedDict): |
43 | capabilities: list[str] |
44 | version: str |
45 | @@ -276,6 +286,10 @@ class AuthenticatedAPIClient: |
46 | result: str = self.execute(["boot-resources", "import"], json_output=False) |
47 | return result |
48 | |
49 | + def list_boot_resources(self) -> list[BootResource]: |
50 | + result: list[BootResource] = self.execute(["boot-resources", "read"]) |
51 | + return result |
52 | + |
53 | def stop_importing_boot_resources(self) -> str: |
54 | result: str = self.execute(["boot-resources", "stop-import"], json_output=False) |
55 | return result |
56 | @@ -284,6 +298,20 @@ class AuthenticatedAPIClient: |
57 | result: bool = self.execute(["boot-resources", "is-importing"]) |
58 | return result |
59 | |
60 | + def wait_for_images(self) -> None: |
61 | + for retry_info in retries(timeout=20 * 60, delay=10): |
62 | + if not self.is_importing_boot_resources(): |
63 | + break |
64 | + else: |
65 | + raise AssertionError( |
66 | + f"Image couldn't be imported after {retry_info.attempt} attempts" |
67 | + f" over {retry_info.elapsed} seconds." |
68 | + ) |
69 | + |
70 | + def import_and_wait_for_images(self) -> None: |
71 | + self.import_boot_resources() |
72 | + self.wait_for_images() |
73 | + |
74 | def list_machines(self, **kwargs: str) -> list[Machine]: |
75 | """ |
76 | machines read -h to know parameters available for kwargs |
77 | @@ -336,6 +364,8 @@ class AuthenticatedAPIClient: |
78 | assert result["status_name"] == "Deploying" |
79 | if expected_osystem := kwargs.get("osystem"): |
80 | assert result["osystem"] == expected_osystem |
81 | + if expected_distro_series := kwargs.get("distro_series"): |
82 | + assert result["distro_series"] == expected_distro_series |
83 | return result |
84 | |
85 | def create_ssh_key(self, public_key: str) -> SSHKey: |
86 | @@ -390,6 +420,28 @@ class AuthenticatedAPIClient: |
87 | ] |
88 | ) |
89 | |
90 | + def create_boot_source_selection( |
91 | + self, |
92 | + architectures: Iterable[str], |
93 | + osystem: str = "ubuntu", |
94 | + oseries: str = "lunar", |
95 | + ) -> None: |
96 | + arches = [f"arches={arch}" for arch in architectures] |
97 | + boot_sources = self.execute(["boot-sources", "read"]) |
98 | + for boot_source in boot_sources: |
99 | + self.execute( |
100 | + [ |
101 | + "boot-source-selections", |
102 | + "create", |
103 | + str(boot_source["id"]), |
104 | + f"os={osystem}", |
105 | + f"release={oseries}", |
106 | + *arches, |
107 | + "subarches=*", |
108 | + "labels=*", |
109 | + ] |
110 | + ) |
111 | + |
112 | def rescue_machine(self, machine: Machine) -> None: |
113 | result = self.execute(["machine", "rescue-mode", machine["system_id"]]) |
114 | assert result["status_name"] == "Entering rescue mode" |
115 | diff --git a/systemtests/conftest.py b/systemtests/conftest.py |
116 | index 60dfa4a..72957ee 100644 |
117 | --- a/systemtests/conftest.py |
118 | +++ b/systemtests/conftest.py |
119 | @@ -50,6 +50,7 @@ from .fixtures import ( |
120 | skip_if_dns_unconfigured, |
121 | skip_if_installed_from_deb_package, |
122 | skip_if_installed_from_snap, |
123 | + skip_if_os_not_supported, |
124 | ssh_key, |
125 | tag_all, |
126 | testlog, |
127 | @@ -102,6 +103,7 @@ __all__ = [ |
128 | "skip_if_dns_unconfigured", |
129 | "skip_if_installed_from_deb_package", |
130 | "skip_if_installed_from_snap", |
131 | + "skip_if_os_not_supported", |
132 | "ssh_key", |
133 | "tag_all", |
134 | "testlog", |
135 | diff --git a/systemtests/fixtures.py b/systemtests/fixtures.py |
136 | index e53ba45..1046980 100644 |
137 | --- a/systemtests/fixtures.py |
138 | +++ b/systemtests/fixtures.py |
139 | @@ -17,6 +17,7 @@ from .api import AuthenticatedAPIClient, UnauthenticatedMAASAPIClient |
140 | from .config import ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_USER |
141 | from .dnstester import DNSTester |
142 | from .lxd import Instance, get_lxd |
143 | +from .machine_config import MachineConfig |
144 | from .o11y import is_o11y_enabled, setup_o11y |
145 | from .packer import PackerMain |
146 | from .region import MAASRegion |
147 | @@ -634,6 +635,16 @@ def skip_if_ansible_playbooks_unconfigured( |
148 | |
149 | |
150 | @pytest.fixture(autouse=True) |
151 | +def skip_if_os_not_supported(request: Any) -> None: |
152 | + if marker := request.node.get_closest_marker("skip_if_os_not_supported"): |
153 | + # Avoid instantiating machine configurations unless we have the marker applied |
154 | + machine_config: MachineConfig = request.getfixturevalue("machine_config") |
155 | + reason = marker.args[0] |
156 | + if machine_config.not_supported: |
157 | + pytest.skip(reason) |
158 | + |
159 | + |
160 | +@pytest.fixture(autouse=True) |
161 | def skip_if_packer_unconfigured(request: Any, config: dict[str, Any]) -> None: |
162 | """Skip tests that require Packer configuration.""" |
163 | marker = request.node.get_closest_marker("skip_if_packer_unconfigured") |
164 | diff --git a/systemtests/machine_config.py b/systemtests/machine_config.py |
165 | index e567d12..31c249b 100644 |
166 | --- a/systemtests/machine_config.py |
167 | +++ b/systemtests/machine_config.py |
168 | @@ -54,6 +54,8 @@ class MachineConfig: |
169 | devices_config: tuple[DeviceConfig, ...] = field(compare=False) |
170 | architecture: str = "amd64" |
171 | osystem: str = "ubuntu" |
172 | + oseries: Optional[str] = None |
173 | + cpu_model: Optional[str] = None |
174 | lxd_profile: Optional[str] = None |
175 | |
176 | def __str__(self) -> str: |
177 | @@ -86,6 +88,22 @@ class MachineConfig: |
178 | ) |
179 | |
180 | @property |
181 | + def not_supported(self) -> bool: |
182 | + # XXX config.yaml doesn't contain a CPU model yet |
183 | + # we need to update that for this to be useful |
184 | + return ( |
185 | + ( |
186 | + (self.architecture, self.osystem) |
187 | + == ( |
188 | + "ppc64el", |
189 | + "ubuntu", |
190 | + ) |
191 | + ) |
192 | + and isinstance(self.oseries, str) |
193 | + and self.oseries >= "jammy" |
194 | + ) |
195 | + |
196 | + @property |
197 | def maas_power_cmd_power_parameters(self) -> list[str]: |
198 | return maas_power_cmd_power_parameters(self.power_type, self.power_parameters) |
199 | |
200 | diff --git a/systemtests/state.py b/systemtests/state.py |
201 | index 7ca5be8..0af9641 100644 |
202 | --- a/systemtests/state.py |
203 | +++ b/systemtests/state.py |
204 | @@ -65,9 +65,15 @@ def import_images_and_wait_until_synced( |
205 | ) -> None: |
206 | architectures = set() |
207 | osystems = set() |
208 | + oseriess = set() |
209 | for machine, power_config in config.get("machines", {}).get("hardware", {}).items(): |
210 | architectures.add(power_config.get("architecture", "amd64")) |
211 | - osystems.add(power_config.get("osystem", "ubuntu")) |
212 | + osystem = power_config.get("osystem", "ubuntu") |
213 | + osystems.add(osystem) |
214 | + if osystem == "ubuntu": |
215 | + oseries = power_config.get("oseries") |
216 | + if oseries: |
217 | + oseriess.add(oseries) |
218 | |
219 | started_importing_regex = "^Started importing of boot images from" |
220 | # Sometimes the region has already started importing images, we |
221 | @@ -77,12 +83,15 @@ def import_images_and_wait_until_synced( |
222 | # TODO Arguably MAAS should do this for us! |
223 | if authenticated_admin.is_importing_boot_resources(): |
224 | authenticated_admin.stop_importing_boot_resources() |
225 | + LOG.info("Aborted importing of unconfigured boot resources.") |
226 | if stream_url := config.get("images", {}).get("stream-url"): |
227 | LOG.info(f"Setting image source to {stream_url}") |
228 | boot_sources = authenticated_admin.list_boot_sources() |
229 | # XXX: Why do we have >1 boot source?! |
230 | for boot_source in boot_sources: |
231 | authenticated_admin.update_boot_source_url(boot_source["id"], stream_url) |
232 | + # wait for the image stream to switch |
233 | + authenticated_admin.import_and_wait_for_images() |
234 | with waits_for_event_after( |
235 | authenticated_admin, |
236 | event_type="Region import info", |
237 | @@ -91,6 +100,20 @@ def import_images_and_wait_until_synced( |
238 | authenticated_admin.update_architectures_in_boot_source_selections( |
239 | architectures |
240 | ) |
241 | + for oseries in oseriess: |
242 | + authenticated_admin.create_boot_source_selection( |
243 | + architectures, oseries=oseries |
244 | + ) |
245 | + boot_sources = authenticated_admin.list_boot_sources() |
246 | + boot_resources = ", ".join( |
247 | + sorted( |
248 | + set( |
249 | + resource["name"] |
250 | + for resource in authenticated_admin.list_boot_resources() |
251 | + ) |
252 | + ) |
253 | + ) |
254 | + LOG.info(f"Available boot resources: {boot_resources}") |
255 | authenticated_admin.import_boot_resources() |
256 | windows_path = None |
257 | if "windows" in osystems: |
258 | @@ -115,9 +138,16 @@ def import_images_and_wait_until_synced( |
259 | LOG.error(f"Unsupported file-store scheme '{scheme}'") |
260 | |
261 | region_start_point = time.time() |
262 | - while authenticated_admin.is_importing_boot_resources(): |
263 | - LOG.debug("Sleeping for 10s for region image import") |
264 | - time.sleep(10) |
265 | + authenticated_admin.import_and_wait_for_images() |
266 | + boot_resources = ", ".join( |
267 | + sorted( |
268 | + set( |
269 | + resource["name"] |
270 | + for resource in authenticated_admin.list_boot_resources() |
271 | + ) |
272 | + ) |
273 | + ) |
274 | + LOG.info(f"available images: {boot_resources}") |
275 | |
276 | region_time_taken = time.time() - region_start_point |
277 | LOG.info( |
278 | @@ -136,6 +166,7 @@ def import_images_and_wait_until_synced( |
279 | ) |
280 | windows_time_taken = time.time() - windows_start_point |
281 | LOG.info(f"Took {windows_time_taken:0.1f}s to upload Windows") |
282 | + # testing of custom (packer) images |
283 | for img in custom_images: |
284 | custom_image_start_point = time.time() |
285 | img_params = { |
286 | diff --git a/systemtests/tests_per_machine/test_machine.py b/systemtests/tests_per_machine/test_machine.py |
287 | index e244b17..0ae7bdc 100644 |
288 | --- a/systemtests/tests_per_machine/test_machine.py |
289 | +++ b/systemtests/tests_per_machine/test_machine.py |
290 | @@ -28,6 +28,7 @@ if TYPE_CHECKING: |
291 | from ..machine_config import MachineConfig |
292 | |
293 | |
294 | +@pytest.mark.skip_if_os_not_supported("Machine hardware is not supported by OS") |
295 | @test_steps("enlist", "metadata", "commission", "deploy", "test_image", "rescue") |
296 | def test_full_circle( |
297 | maas_api_client: AuthenticatedAPIClient, |
298 | @@ -116,7 +117,7 @@ def test_full_circle( |
299 | if image_to_test is None: |
300 | deploy_osystem, deploy_oseries = ( |
301 | machine_config.osystem, |
302 | - None, |
303 | + machine_config.oseries, |
304 | ) |
305 | ssh_username = "ubuntu" |
306 | else: |
307 | @@ -133,7 +134,7 @@ def test_full_circle( |
308 | status="Deployed", |
309 | abort_status="Failed deployment", |
310 | machine_id=machine_config.name, |
311 | - timeout=TIMEOUT, |
312 | + timeout=TIMEOUT * 2, |
313 | ) |
314 | |
315 | assert machine["ip_addresses"][0] not in dynamic_range |
316 | diff --git a/utils/gen_config.py b/utils/gen_config.py |
317 | index 041b1af..17d84c8 100755 |
318 | --- a/utils/gen_config.py |
319 | +++ b/utils/gen_config.py |
320 | @@ -193,6 +193,12 @@ def main(argv: list[str]) -> int: |
321 | metavar="URL", |
322 | help="Configure MAAS to consume images from this URL", |
323 | ) |
324 | + parser.add_argument( |
325 | + "--oseries", |
326 | + type=str, |
327 | + metavar="SERIES", |
328 | + help="Deploy and test this SERIES", |
329 | + ) |
330 | |
331 | args = parser.parse_args(argv) |
332 | |
333 | @@ -208,8 +214,8 @@ def main(argv: list[str]) -> int: |
334 | "or in --containers-image option" |
335 | ) |
336 | |
337 | - if args.image_stream_url: |
338 | - config.setdefault("images", {})["stream-url"] = args.image_stream_url |
339 | + if stream_url := args.image_stream_url: |
340 | + config.setdefault("images", {})["stream-url"] = stream_url.strip() |
341 | else: |
342 | if "images" not in config or "stream-url" not in config["images"]: |
343 | parser.error( |
344 | @@ -339,6 +345,11 @@ def main(argv: list[str]) -> int: |
345 | for name, details in hardware.items() |
346 | if name in args.machine |
347 | } |
348 | + if oseries := args.oseries: |
349 | + for details in machines["hardware"].values(): |
350 | + details["oseries"] = oseries |
351 | + if details.get("osystem", ""): |
352 | + details["osystem"] = "ubuntu" |
353 | |
354 | if vms: |
355 | # Filter out VMs with name not listed in specified vm_machines |
UNIT TESTS boot-images lp:~skatsaounis/maas-ci/+git/system-tests into -b master lp:~maas-committers/maas-ci/+git/system-tests
-b download-
STATUS: SUCCESS 12d32ad211f3590 d69104ca8f
COMMIT: 215a3c407e1f9c7