Merge ~adam-collard/maas-ci/+git/system-tests:strict-mypy into ~maas-committers/maas-ci/+git/system-tests:master
- Git
- lp:~adam-collard/maas-ci/+git/system-tests
- strict-mypy
- Merge into master
Proposed by
Adam Collard
Status: | Merged |
---|---|
Approved by: | Adam Collard |
Approved revision: | a91b80b938d188ca9c0eab2bd2b6d22b5b728540 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~adam-collard/maas-ci/+git/system-tests:strict-mypy |
Merge into: | ~maas-committers/maas-ci/+git/system-tests:master |
Diff against target: |
558 lines (+132/-78) 10 files modified
pyproject.toml (+1/-3) systemtests/api.py (+94/-49) systemtests/fixtures.py (+5/-5) systemtests/lxd.py (+6/-5) systemtests/region.py (+8/-4) systemtests/subprocess.py (+4/-2) systemtests/tests/conftest.py (+4/-4) systemtests/tests/test_basic.py (+3/-1) systemtests/tests/test_crud.py (+3/-3) systemtests/utils.py (+4/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alberto Donato (community) | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+408010@code.launchpad.net |
Commit message
Add strict mypy validation.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/pyproject.toml b/pyproject.toml | |||
2 | index 0c84d1d..cf33498 100644 | |||
3 | --- a/pyproject.toml | |||
4 | +++ b/pyproject.toml | |||
5 | @@ -13,8 +13,6 @@ log_file_date_format = "%Y-%m-%d %H:%M:%S" | |||
6 | 13 | 13 | ||
7 | 14 | 14 | ||
8 | 15 | [tool.mypy] | 15 | [tool.mypy] |
9 | 16 | disallow_untyped_calls = true | ||
10 | 17 | disallow_untyped_defs = true | ||
11 | 18 | warn_unused_configs = true | ||
12 | 19 | install_types = true | 16 | install_types = true |
13 | 20 | non_interactive = true | 17 | non_interactive = true |
14 | 18 | strict = true | ||
15 | diff --git a/systemtests/api.py b/systemtests/api.py | |||
16 | index 4aac548..a82fde4 100644 | |||
17 | --- a/systemtests/api.py | |||
18 | +++ b/systemtests/api.py | |||
19 | @@ -29,10 +29,10 @@ class MAASAPIClient: | |||
20 | 29 | self.pull_file = partial(lxd.pull_file, maas_container) | 29 | self.pull_file = partial(lxd.pull_file, maas_container) |
21 | 30 | self.push_file = partial(lxd.push_file, maas_container) | 30 | self.push_file = partial(lxd.push_file, maas_container) |
22 | 31 | 31 | ||
24 | 32 | def execute(self, cmd: list[str]) -> CompletedProcess: | 32 | def execute(self, cmd: list[str]) -> CompletedProcess[str]: |
25 | 33 | return self.lxd.execute(self.maas_container, ["maas"] + cmd) | 33 | return self.lxd.execute(self.maas_container, ["maas"] + cmd) |
26 | 34 | 34 | ||
28 | 35 | def log_in(self, session: str, token: str) -> CompletedProcess: | 35 | def log_in(self, session: str, token: str) -> CompletedProcess[str]: |
29 | 36 | return self.execute(["login", session, self.url, token]) | 36 | return self.execute(["login", session, self.url, token]) |
30 | 37 | 37 | ||
31 | 38 | 38 | ||
32 | @@ -44,7 +44,7 @@ class AuthenticatedAPIClient: | |||
33 | 44 | def execute( | 44 | def execute( |
34 | 45 | self, | 45 | self, |
35 | 46 | cmd: list[str], | 46 | cmd: list[str], |
37 | 47 | extra_params: dict[str, str] = None, | 47 | extra_params: Optional[dict[str, str]] = None, |
38 | 48 | json_output: bool = True, | 48 | json_output: bool = True, |
39 | 49 | ) -> Any: | 49 | ) -> Any: |
40 | 50 | if extra_params: | 50 | if extra_params: |
41 | @@ -52,31 +52,47 @@ class AuthenticatedAPIClient: | |||
42 | 52 | result = self.api_client.execute([self.session] + cmd) | 52 | result = self.api_client.execute([self.session] + cmd) |
43 | 53 | return json.loads(result.stdout) if json_output else result.stdout | 53 | return json.loads(result.stdout) if json_output else result.stdout |
44 | 54 | 54 | ||
47 | 55 | def list_subnets(self) -> list[dict]: | 55 | def list_subnets(self) -> list[dict[str, Any]]: |
48 | 56 | return self.execute(["subnets", "read"]) | 56 | subnets: list[dict[str, Any]] = self.execute(["subnets", "read"]) |
49 | 57 | return subnets | ||
50 | 57 | 58 | ||
53 | 58 | def create_subnet(self, name: str, cidr: str) -> dict: | 59 | def create_subnet(self, name: str, cidr: str) -> dict[str, Any]: |
54 | 59 | return self.execute(["subnets", "create", "name=" + name, "cidr=" + cidr]) | 60 | subnet: dict[str, Any] = self.execute( |
55 | 61 | ["subnets", "create", "name=" + name, "cidr=" + cidr] | ||
56 | 62 | ) | ||
57 | 63 | return subnet | ||
58 | 60 | 64 | ||
59 | 61 | def delete_subnet(self, name: str) -> str: | 65 | def delete_subnet(self, name: str) -> str: |
61 | 62 | return self.execute(["subnet", "delete", name], json_output=False) | 66 | result: str = self.execute(["subnet", "delete", name], json_output=False) |
62 | 67 | return result | ||
63 | 63 | 68 | ||
66 | 64 | def list_rack_controllers(self) -> list[dict]: | 69 | def list_rack_controllers(self) -> list[dict[str, Any]]: |
67 | 65 | return self.execute(["rack-controllers", "read"]) | 70 | rack_controllers: list[dict[str, Any]] = self.execute( |
68 | 71 | ["rack-controllers", "read"] | ||
69 | 72 | ) | ||
70 | 73 | return rack_controllers | ||
71 | 66 | 74 | ||
74 | 67 | def list_ip_ranges(self) -> list[dict]: | 75 | def list_ip_ranges(self) -> list[dict[str, Any]]: |
75 | 68 | return self.execute(["ipranges", "read"]) | 76 | ip_ranges: list[dict[str, Any]] = self.execute(["ipranges", "read"]) |
76 | 77 | return ip_ranges | ||
77 | 69 | 78 | ||
78 | 70 | def delete_ip_range(self, range_id: Union[str, int]) -> str: | 79 | def delete_ip_range(self, range_id: Union[str, int]) -> str: |
80 | 71 | return self.execute(["iprange", "delete", str(range_id)], json_output=False) | 80 | result: str = self.execute( |
81 | 81 | ["iprange", "delete", str(range_id)], json_output=False | ||
82 | 82 | ) | ||
83 | 83 | return result | ||
84 | 72 | 84 | ||
87 | 73 | def create_ip_range(self, start: str, end: str, range_type: str) -> dict: | 85 | def create_ip_range(self, start: str, end: str, range_type: str) -> dict[str, Any]: |
88 | 74 | return self.execute( | 86 | ip_range: dict[str, Any] = self.execute( |
89 | 75 | ["ipranges", "create", "type=dynamic", f"start_ip={start}", f"end_ip={end}"] | 87 | ["ipranges", "create", "type=dynamic", f"start_ip={start}", f"end_ip={end}"] |
90 | 76 | ) | 88 | ) |
91 | 89 | return ip_range | ||
92 | 77 | 90 | ||
95 | 78 | def enable_dhcp(self, fabric: str, vlan: str, primary_rack: dict[str, str]) -> dict: | 91 | def enable_dhcp( |
96 | 79 | return self.execute( | 92 | self, fabric: str, vlan: str, primary_rack: dict[str, str] |
97 | 93 | ) -> dict[str, Any]: | ||
98 | 94 | |||
99 | 95 | vlan_obj: dict[str, Any] = self.execute( | ||
100 | 80 | [ | 96 | [ |
101 | 81 | "vlan", | 97 | "vlan", |
102 | 82 | "update", | 98 | "update", |
103 | @@ -86,9 +102,13 @@ class AuthenticatedAPIClient: | |||
104 | 86 | f"primary_rack={primary_rack['system_id']}", | 102 | f"primary_rack={primary_rack['system_id']}", |
105 | 87 | ] | 103 | ] |
106 | 88 | ) | 104 | ) |
107 | 105 | return vlan_obj | ||
108 | 89 | 106 | ||
111 | 90 | def disable_dhcp(self, fabric: str, vlan: str) -> Any: | 107 | def disable_dhcp(self, fabric: str, vlan: str) -> dict[str, Any]: |
112 | 91 | return self.execute(["vlan", "update", fabric, vlan, "dhcp_on=False"]) | 108 | vlan_obj: dict[str, Any] = self.execute( |
113 | 109 | ["vlan", "update", fabric, vlan, "dhcp_on=False"] | ||
114 | 110 | ) | ||
115 | 111 | return vlan_obj | ||
116 | 92 | 112 | ||
117 | 93 | def import_boot_resources(self) -> str: | 113 | def import_boot_resources(self) -> str: |
118 | 94 | LOG.debug("Getting latest debug event for watermark") | 114 | LOG.debug("Getting latest debug event for watermark") |
119 | @@ -124,46 +144,55 @@ class AuthenticatedAPIClient: | |||
120 | 124 | return result | 144 | return result |
121 | 125 | 145 | ||
122 | 126 | def is_importing_boot_resources(self) -> str: | 146 | def is_importing_boot_resources(self) -> str: |
124 | 127 | return self.execute(["boot-resources", "is-importing"]) | 147 | result: str = self.execute(["boot-resources", "is-importing"]) |
125 | 148 | return result | ||
126 | 128 | 149 | ||
127 | 129 | def list_machines(self, **kwargs: str) -> list[dict[str, Any]]: | 150 | def list_machines(self, **kwargs: str) -> list[dict[str, Any]]: |
128 | 130 | """ | 151 | """ |
129 | 131 | machines read -h to know parameters available for kwargs | 152 | machines read -h to know parameters available for kwargs |
130 | 132 | """ | 153 | """ |
133 | 133 | cmd = ["machines", "read"] | 154 | machines: list[dict[str, Any]] = self.execute( |
134 | 134 | return self.execute(cmd, extra_params=kwargs) | 155 | ["machines", "read"], extra_params=kwargs |
135 | 156 | ) | ||
136 | 157 | return machines | ||
137 | 135 | 158 | ||
138 | 136 | def list_boot_images(self, rack_controller: dict[str, str]) -> dict[str, str]: | 159 | def list_boot_images(self, rack_controller: dict[str, str]) -> dict[str, str]: |
140 | 137 | return self.execute( | 160 | boot_images: dict[str, str] = self.execute( |
141 | 138 | ["rack-controller", "list-boot-images", rack_controller["system_id"]] | 161 | ["rack-controller", "list-boot-images", rack_controller["system_id"]] |
142 | 139 | ) | 162 | ) |
143 | 163 | return boot_images | ||
144 | 140 | 164 | ||
145 | 141 | def import_boot_resources_in_rack(self, rack_controller: dict[str, str]) -> str: | 165 | def import_boot_resources_in_rack(self, rack_controller: dict[str, str]) -> str: |
147 | 142 | return self.execute( | 166 | result: str = self.execute( |
148 | 143 | ["rack-controller", "import-boot-images", rack_controller["system_id"]], | 167 | ["rack-controller", "import-boot-images", rack_controller["system_id"]], |
149 | 144 | json_output=False, | 168 | json_output=False, |
150 | 145 | ) | 169 | ) |
151 | 170 | return result | ||
152 | 146 | 171 | ||
153 | 147 | def commission_machine(self, machine: dict[str, str]) -> dict[str, str]: | 172 | def commission_machine(self, machine: dict[str, str]) -> dict[str, str]: |
155 | 148 | result = self.execute(["machine", "commission", machine["system_id"]]) | 173 | result: dict[str, str] = self.execute( |
156 | 174 | ["machine", "commission", machine["system_id"]] | ||
157 | 175 | ) | ||
158 | 149 | assert result["status_name"] == "Commissioning" | 176 | assert result["status_name"] == "Commissioning" |
159 | 150 | return result | 177 | return result |
160 | 151 | 178 | ||
161 | 152 | def deploy_machine(self, machine: dict[str, str], **kwargs: str) -> dict[str, str]: | 179 | def deploy_machine(self, machine: dict[str, str], **kwargs: str) -> dict[str, str]: |
163 | 153 | result = self.execute( | 180 | result: dict[str, str] = self.execute( |
164 | 154 | ["machine", "deploy", machine["system_id"]], extra_params=kwargs | 181 | ["machine", "deploy", machine["system_id"]], extra_params=kwargs |
165 | 155 | ) | 182 | ) |
166 | 156 | assert result["status_name"] == "Deploying" | 183 | assert result["status_name"] == "Deploying" |
167 | 157 | return result | 184 | return result |
168 | 158 | 185 | ||
169 | 159 | def create_ssh_key(self, public_key: str) -> None: | 186 | def create_ssh_key(self, public_key: str) -> None: |
171 | 160 | result = self.execute(["sshkeys", "create", f"key={public_key}"]) | 187 | result: dict[str, str] = self.execute( |
172 | 188 | ["sshkeys", "create", f"key={public_key}"] | ||
173 | 189 | ) | ||
174 | 161 | assert result["key"] == public_key | 190 | assert result["key"] == public_key |
175 | 162 | return | 191 | return |
176 | 163 | 192 | ||
177 | 164 | def release_machine(self, machine: dict[str, str]) -> None: | 193 | def release_machine(self, machine: dict[str, str]) -> None: |
178 | 165 | system_id: str = machine["system_id"] | 194 | system_id: str = machine["system_id"] |
180 | 166 | result: dict = self.execute(["machine", "release", system_id]) | 195 | result: dict[str, Any] = self.execute(["machine", "release", system_id]) |
181 | 167 | assert result["status_name"] == "Releasing" | 196 | assert result["status_name"] == "Releasing" |
182 | 168 | wait_for_machines( | 197 | wait_for_machines( |
183 | 169 | self, | 198 | self, |
184 | @@ -238,11 +267,13 @@ class AuthenticatedAPIClient: | |||
185 | 238 | ["zones", "create", f"name={name}", f"description={description}"] | 267 | ["zones", "create", f"name={name}", f"description={description}"] |
186 | 239 | ) | 268 | ) |
187 | 240 | 269 | ||
190 | 241 | def list_zones(self) -> list[dict]: | 270 | def list_zones(self) -> list[dict[str, Any]]: |
191 | 242 | return self.execute(["zones", "read"]) | 271 | zones: list[dict[str, Any]] = self.execute(["zones", "read"]) |
192 | 272 | return zones | ||
193 | 243 | 273 | ||
194 | 244 | def read_zone(self, zone_name: str) -> Any: | 274 | def read_zone(self, zone_name: str) -> Any: |
196 | 245 | return self.execute(["zone", "read", zone_name]) | 275 | zone: dict[str, Any] = self.execute(["zone", "read", zone_name]) |
197 | 276 | return zone | ||
198 | 246 | 277 | ||
199 | 247 | def update_zone( | 278 | def update_zone( |
200 | 248 | self, | 279 | self, |
201 | @@ -256,41 +287,49 @@ class AuthenticatedAPIClient: | |||
202 | 256 | if new_description is not None: | 287 | if new_description is not None: |
203 | 257 | cmd.append(f"description={new_description}") | 288 | cmd.append(f"description={new_description}") |
204 | 258 | 289 | ||
206 | 259 | return self.execute(cmd) | 290 | zone: dict[str, Any] = self.execute(cmd) |
207 | 291 | return zone | ||
208 | 260 | 292 | ||
209 | 261 | def delete_zone(self, zone_name: str) -> str: | 293 | def delete_zone(self, zone_name: str) -> str: |
210 | 262 | try: | 294 | try: |
212 | 263 | return self.execute(["zone", "delete", zone_name], json_output=False) | 295 | result: str = self.execute(["zone", "delete", zone_name], json_output=False) |
213 | 264 | except CalledProcessError as err: | 296 | except CalledProcessError as err: |
214 | 265 | if "cannot be deleted" in err.stdout: | 297 | if "cannot be deleted" in err.stdout: |
215 | 266 | raise CannotDeleteError(err.stdout) | 298 | raise CannotDeleteError(err.stdout) |
216 | 267 | else: | 299 | else: |
217 | 268 | raise | 300 | raise |
218 | 301 | else: | ||
219 | 302 | return result | ||
220 | 269 | 303 | ||
221 | 270 | def create_pool(self, name: str, description: str) -> Any: | 304 | def create_pool(self, name: str, description: str) -> Any: |
222 | 271 | return self.execute( | 305 | return self.execute( |
223 | 272 | ["resource-pools", "create", f"name={name}", f"description={description}"] | 306 | ["resource-pools", "create", f"name={name}", f"description={description}"] |
224 | 273 | ) | 307 | ) |
225 | 274 | 308 | ||
228 | 275 | def list_pools(self) -> list[dict]: | 309 | def list_pools(self) -> list[dict[str, Any]]: |
229 | 276 | return self.execute(["resource-pools", "read"]) | 310 | resource_pools: list[dict[str, Any]] = self.execute(["resource-pools", "read"]) |
230 | 311 | return resource_pools | ||
231 | 277 | 312 | ||
234 | 278 | def read_pool(self, pool: dict[str, Any]) -> dict: | 313 | def read_pool(self, pool: dict[str, Any]) -> dict[str, Any]: |
235 | 279 | return self.execute(["resource-pool", "read", str(pool["id"])]) | 314 | resource_pool: dict[str, Any] = self.execute( |
236 | 315 | ["resource-pool", "read", str(pool["id"])] | ||
237 | 316 | ) | ||
238 | 317 | return resource_pool | ||
239 | 280 | 318 | ||
240 | 281 | def update_pool( | 319 | def update_pool( |
241 | 282 | self, | 320 | self, |
242 | 283 | pool: dict[str, Any], | 321 | pool: dict[str, Any], |
243 | 284 | new_name: Optional[str] = None, | 322 | new_name: Optional[str] = None, |
244 | 285 | new_description: Optional[str] = None, | 323 | new_description: Optional[str] = None, |
246 | 286 | ) -> Any: | 324 | ) -> dict[str, Any]: |
247 | 287 | cmd = ["resource-pool", "update", str(pool["id"])] | 325 | cmd = ["resource-pool", "update", str(pool["id"])] |
248 | 288 | if new_name is not None: | 326 | if new_name is not None: |
249 | 289 | cmd.append(f"name={new_name}") | 327 | cmd.append(f"name={new_name}") |
250 | 290 | if new_description is not None: | 328 | if new_description is not None: |
251 | 291 | cmd.append(f"description={new_description}") | 329 | cmd.append(f"description={new_description}") |
252 | 292 | 330 | ||
254 | 293 | return self.execute(cmd) | 331 | pool_obj: dict[str, Any] = self.execute(cmd) |
255 | 332 | return pool_obj | ||
256 | 294 | 333 | ||
257 | 295 | def delete_pool(self, pool: dict[str, Any]) -> Any: | 334 | def delete_pool(self, pool: dict[str, Any]) -> Any: |
258 | 296 | try: | 335 | try: |
259 | @@ -303,27 +342,31 @@ class AuthenticatedAPIClient: | |||
260 | 303 | else: | 342 | else: |
261 | 304 | raise | 343 | raise |
262 | 305 | 344 | ||
265 | 306 | def create_space(self, name: str) -> Any: | 345 | def create_space(self, name: str) -> dict[str, Any]: |
266 | 307 | return self.execute(["spaces", "create", f"name={name}"]) | 346 | space: dict[str, Any] = self.execute(["spaces", "create", f"name={name}"]) |
267 | 347 | return space | ||
268 | 308 | 348 | ||
271 | 309 | def list_spaces(self) -> list[dict]: | 349 | def list_spaces(self) -> list[dict[str, Any]]: |
272 | 310 | return self.execute(["spaces", "read"]) | 350 | spaces: list[dict[str, Any]] = self.execute(["spaces", "read"]) |
273 | 351 | return spaces | ||
274 | 311 | 352 | ||
277 | 312 | def read_space(self, space: dict[str, Any]) -> Any: | 353 | def read_space(self, space: dict[str, Any]) -> dict[str, Any]: |
278 | 313 | return self.execute(["space", "read", str(space["id"])]) | 354 | space_obj: dict[str, Any] = self.execute(["space", "read", str(space["id"])]) |
279 | 355 | return space_obj | ||
280 | 314 | 356 | ||
281 | 315 | def update_space( | 357 | def update_space( |
282 | 316 | self, space: dict[str, Any], new_name: Optional[str] = None | 358 | self, space: dict[str, Any], new_name: Optional[str] = None |
284 | 317 | ) -> Any: | 359 | ) -> dict[str, Any]: |
285 | 318 | cmd = ["space", "update", str(space["id"])] | 360 | cmd = ["space", "update", str(space["id"])] |
286 | 319 | if new_name is not None: | 361 | if new_name is not None: |
287 | 320 | cmd.append(f"name={new_name}") | 362 | cmd.append(f"name={new_name}") |
288 | 321 | 363 | ||
290 | 322 | return self.execute(cmd) | 364 | space_obj: dict[str, Any] = self.execute(cmd) |
291 | 365 | return space_obj | ||
292 | 323 | 366 | ||
294 | 324 | def delete_space(self, space: dict[str, Any]) -> Any: | 367 | def delete_space(self, space: dict[str, Any]) -> str: |
295 | 325 | try: | 368 | try: |
297 | 326 | return self.execute( | 369 | result: str = self.execute( |
298 | 327 | ["space", "delete", str(space["id"])], json_output=False | 370 | ["space", "delete", str(space["id"])], json_output=False |
299 | 328 | ) | 371 | ) |
300 | 329 | except CalledProcessError as err: | 372 | except CalledProcessError as err: |
301 | @@ -331,3 +374,5 @@ class AuthenticatedAPIClient: | |||
302 | 331 | raise CannotDeleteError(err.stdout) | 374 | raise CannotDeleteError(err.stdout) |
303 | 332 | else: | 375 | else: |
304 | 333 | raise | 376 | raise |
305 | 377 | else: | ||
306 | 378 | return result | ||
307 | diff --git a/systemtests/fixtures.py b/systemtests/fixtures.py | |||
308 | index fa50527..70278a7 100644 | |||
309 | --- a/systemtests/fixtures.py | |||
310 | +++ b/systemtests/fixtures.py | |||
311 | @@ -8,7 +8,7 @@ import tempfile | |||
312 | 8 | from logging import StreamHandler, getLogger | 8 | from logging import StreamHandler, getLogger |
313 | 9 | from pathlib import Path | 9 | from pathlib import Path |
314 | 10 | from textwrap import dedent | 10 | from textwrap import dedent |
316 | 11 | from typing import IO, TYPE_CHECKING, Any, Iterator, Optional | 11 | from typing import TYPE_CHECKING, Any, Iterator, Optional, TextIO |
317 | 12 | 12 | ||
318 | 13 | import paramiko | 13 | import paramiko |
319 | 14 | import pytest | 14 | import pytest |
320 | @@ -137,7 +137,7 @@ def maas_deb_repo( | |||
321 | 137 | 137 | ||
322 | 138 | 138 | ||
323 | 139 | def get_user_data( | 139 | def get_user_data( |
325 | 140 | devices: dict[str, dict[str, Any]], cloud_config: Optional[dict] = None | 140 | devices: dict[str, dict[str, Any]], cloud_config: Optional[dict[str, Any]] = None |
326 | 141 | ) -> str: | 141 | ) -> str: |
327 | 142 | ethernets = {} | 142 | ethernets = {} |
328 | 143 | for name, device in sorted(devices.items()): | 143 | for name, device in sorted(devices.items()): |
329 | @@ -160,7 +160,7 @@ def get_user_data( | |||
330 | 160 | } | 160 | } |
331 | 161 | ) | 161 | ) |
332 | 162 | 162 | ||
334 | 163 | user_data = "#cloud-config\n" + yaml.dump(cloud_config, default_style="|") | 163 | user_data: str = "#cloud-config\n" + yaml.dump(cloud_config, default_style="|") |
335 | 164 | return user_data | 164 | return user_data |
336 | 165 | 165 | ||
337 | 166 | 166 | ||
338 | @@ -325,12 +325,12 @@ def maas_api_client(maas_region: MAASRegion) -> Iterator[MAASAPIClient]: | |||
339 | 325 | 325 | ||
340 | 326 | 326 | ||
341 | 327 | @pytest.fixture | 327 | @pytest.fixture |
343 | 328 | def logstream() -> Iterator[IO]: | 328 | def logstream() -> Iterator[TextIO]: |
344 | 329 | yield io.StringIO() | 329 | yield io.StringIO() |
345 | 330 | 330 | ||
346 | 331 | 331 | ||
347 | 332 | @pytest.fixture(autouse=True) | 332 | @pytest.fixture(autouse=True) |
349 | 333 | def testlog(logstream: IO) -> Iterator[Logger]: | 333 | def testlog(logstream: TextIO) -> Iterator[Logger]: |
350 | 334 | current_test = os.environ.get("PYTEST_CURRENT_TEST") | 334 | current_test = os.environ.get("PYTEST_CURRENT_TEST") |
351 | 335 | logger = getLogger(f"systemtests.{current_test}") | 335 | logger = getLogger(f"systemtests.{current_test}") |
352 | 336 | handler = StreamHandler(logstream) | 336 | handler = StreamHandler(logstream) |
353 | diff --git a/systemtests/lxd.py b/systemtests/lxd.py | |||
354 | index a783f95..2c49809 100644 | |||
355 | --- a/systemtests/lxd.py | |||
356 | +++ b/systemtests/lxd.py | |||
357 | @@ -35,7 +35,7 @@ class CLILXD: | |||
358 | 35 | except subprocess.CalledProcessError: | 35 | except subprocess.CalledProcessError: |
359 | 36 | return False | 36 | return False |
360 | 37 | 37 | ||
362 | 38 | def _run(self, cmd: Union[str, list[str]]) -> subprocess.CompletedProcess: | 38 | def _run(self, cmd: Union[str, list[str]]) -> subprocess.CompletedProcess[str]: |
363 | 39 | return run_with_logging(cmd, self.logger) | 39 | return run_with_logging(cmd, self.logger) |
364 | 40 | 40 | ||
365 | 41 | def create_container( | 41 | def create_container( |
366 | @@ -89,7 +89,7 @@ class CLILXD: | |||
367 | 89 | 89 | ||
368 | 90 | def pull_file( | 90 | def pull_file( |
369 | 91 | self, container: str, file_path: str, local_path: str | 91 | self, container: str, file_path: str, local_path: str |
371 | 92 | ) -> subprocess.CompletedProcess: | 92 | ) -> subprocess.CompletedProcess[str]: |
372 | 93 | return self._run( | 93 | return self._run( |
373 | 94 | [ | 94 | [ |
374 | 95 | "lxc", | 95 | "lxc", |
375 | @@ -122,9 +122,9 @@ class CLILXD: | |||
376 | 122 | def execute( | 122 | def execute( |
377 | 123 | self, | 123 | self, |
378 | 124 | container: str, | 124 | container: str, |
380 | 125 | command: list, | 125 | command: list[str], |
381 | 126 | environment: Optional[dict[str, str]] = None, | 126 | environment: Optional[dict[str, str]] = None, |
383 | 127 | ) -> subprocess.CompletedProcess: | 127 | ) -> subprocess.CompletedProcess[str]: |
384 | 128 | lxc_command = ["lxc", "exec", "--force-noninteractive", container] | 128 | lxc_command = ["lxc", "exec", "--force-noninteractive", container] |
385 | 129 | if environment is not None: | 129 | if environment is not None: |
386 | 130 | for key, value in environment.items(): | 130 | for key, value in environment.items(): |
387 | @@ -146,7 +146,8 @@ class CLILXD: | |||
388 | 146 | for address in entry["state"]["network"]["eth0"]["addresses"]: | 146 | for address in entry["state"]["network"]["eth0"]["addresses"]: |
389 | 147 | self.logger.info(f"Considering address: {address}") | 147 | self.logger.info(f"Considering address: {address}") |
390 | 148 | if address["family"] == "inet": | 148 | if address["family"] == "inet": |
392 | 149 | return address["address"] | 149 | ip: str = address["address"] |
393 | 150 | return ip | ||
394 | 150 | else: | 151 | else: |
395 | 151 | raise RuntimeError("Couldn't find an IP address") | 152 | raise RuntimeError("Couldn't find an IP address") |
396 | 152 | 153 | ||
397 | diff --git a/systemtests/region.py b/systemtests/region.py | |||
398 | index 4e96f81..5ca3901 100644 | |||
399 | --- a/systemtests/region.py | |||
400 | +++ b/systemtests/region.py | |||
401 | @@ -21,7 +21,7 @@ class MAASRegion: | |||
402 | 21 | self.maas_container = maas_container | 21 | self.maas_container = maas_container |
403 | 22 | self.installed_from_snap = installed_from_snap | 22 | self.installed_from_snap = installed_from_snap |
404 | 23 | 23 | ||
406 | 24 | def execute(self, command: list[str]) -> subprocess.CompletedProcess: | 24 | def execute(self, command: list[str]) -> subprocess.CompletedProcess[str]: |
407 | 25 | lxd = get_lxd(LOG) | 25 | lxd = get_lxd(LOG) |
408 | 26 | return lxd.execute(self.maas_container, command) | 26 | return lxd.execute(self.maas_container, command) |
409 | 27 | 27 | ||
410 | @@ -61,7 +61,9 @@ class MAASRegion: | |||
411 | 61 | "enable_http_proxy": enable_http_proxy, | 61 | "enable_http_proxy": enable_http_proxy, |
412 | 62 | } | 62 | } |
413 | 63 | 63 | ||
415 | 64 | def enable_dhcp(self, config: dict, client: api.AuthenticatedAPIClient) -> None: | 64 | def enable_dhcp( |
416 | 65 | self, config: dict[str, Any], client: api.AuthenticatedAPIClient | ||
417 | 66 | ) -> None: | ||
418 | 65 | rack_controllers = get_rack_controllers(client) | 67 | rack_controllers = get_rack_controllers(client) |
419 | 66 | for network in config["networks"].values(): | 68 | for network in config["networks"].values(): |
420 | 67 | primary_controller, link = get_dhcp_controller( | 69 | primary_controller, link = get_dhcp_controller( |
421 | @@ -134,7 +136,9 @@ class MAASRegion: | |||
422 | 134 | return self.set_config("debug", "True") | 136 | return self.set_config("debug", "True") |
423 | 135 | 137 | ||
424 | 136 | 138 | ||
426 | 137 | def get_dhcp_controller(rack_controllers: list[dict], cidr: str) -> tuple[dict, dict]: | 139 | def get_dhcp_controller( |
427 | 140 | rack_controllers: list[dict[str, Any]], cidr: str | ||
428 | 141 | ) -> tuple[dict[str, Any], dict[str, Any]]: | ||
429 | 138 | for rack_controller in rack_controllers: | 142 | for rack_controller in rack_controllers: |
430 | 139 | for interface in rack_controller["interface_set"]: | 143 | for interface in rack_controller["interface_set"]: |
431 | 140 | for link in interface["links"]: | 144 | for link in interface["links"]: |
432 | @@ -143,7 +147,7 @@ def get_dhcp_controller(rack_controllers: list[dict], cidr: str) -> tuple[dict, | |||
433 | 143 | raise AssertionError(f"Couldn't find rack controller managing DHCP for {cidr}") | 147 | raise AssertionError(f"Couldn't find rack controller managing DHCP for {cidr}") |
434 | 144 | 148 | ||
435 | 145 | 149 | ||
437 | 146 | def get_rack_controllers(client: api.AuthenticatedAPIClient) -> list[dict]: | 150 | def get_rack_controllers(client: api.AuthenticatedAPIClient) -> list[dict[str, Any]]: |
438 | 147 | """Repeatedly attempt to get rack controllers""" | 151 | """Repeatedly attempt to get rack controllers""" |
439 | 148 | attempts = count(1) | 152 | attempts = count(1) |
440 | 149 | for elapsed, _ in retries(timeout=300, delay=10): | 153 | for elapsed, _ in retries(timeout=300, delay=10): |
441 | diff --git a/systemtests/subprocess.py b/systemtests/subprocess.py | |||
442 | index 2c34389..8e4219f 100644 | |||
443 | --- a/systemtests/subprocess.py | |||
444 | +++ b/systemtests/subprocess.py | |||
445 | @@ -8,8 +8,10 @@ if TYPE_CHECKING: | |||
446 | 8 | 8 | ||
447 | 9 | 9 | ||
448 | 10 | def run_with_logging( | 10 | def run_with_logging( |
451 | 11 | cmd: Union[str, list[str]], logger: logging.Logger, env: Optional[dict] = None | 11 | cmd: Union[str, list[str]], |
452 | 12 | ) -> subprocess.CompletedProcess: | 12 | logger: logging.Logger, |
453 | 13 | env: Optional[dict[str, str]] = None, | ||
454 | 14 | ) -> subprocess.CompletedProcess[str]: | ||
455 | 13 | logger.info("Running command: " + " ".join(cmd)) | 15 | logger.info("Running command: " + " ".join(cmd)) |
456 | 14 | process = subprocess.Popen( | 16 | process = subprocess.Popen( |
457 | 15 | cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env | 17 | cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env |
458 | diff --git a/systemtests/tests/conftest.py b/systemtests/tests/conftest.py | |||
459 | index 7fa063d..7b8163a 100644 | |||
460 | --- a/systemtests/tests/conftest.py | |||
461 | +++ b/systemtests/tests/conftest.py | |||
462 | @@ -1,7 +1,7 @@ | |||
463 | 1 | from __future__ import annotations | 1 | from __future__ import annotations |
464 | 2 | 2 | ||
465 | 3 | import argparse | 3 | import argparse |
467 | 4 | from typing import TYPE_CHECKING, Any, Iterator | 4 | from typing import TYPE_CHECKING, Any, Iterator, cast |
468 | 5 | 5 | ||
469 | 6 | import pytest | 6 | import pytest |
470 | 7 | import yaml | 7 | import yaml |
471 | @@ -52,14 +52,14 @@ def pytest_addoption(parser: Parser) -> None: | |||
472 | 52 | 52 | ||
473 | 53 | 53 | ||
474 | 54 | @pytest.fixture(scope="session") | 54 | @pytest.fixture(scope="session") |
476 | 55 | def config(request: pytest.FixtureRequest) -> dict: | 55 | def config(request: pytest.FixtureRequest) -> dict[str, Any]: |
477 | 56 | config_file = request.config.getoption("--ss-config") | 56 | config_file = request.config.getoption("--ss-config") |
478 | 57 | if not config_file: | 57 | if not config_file: |
479 | 58 | config_file = open("config.yaml", "r") | 58 | config_file = open("config.yaml", "r") |
481 | 59 | return yaml.safe_load(config_file) if config_file else {} | 59 | return cast(dict[str, Any], yaml.safe_load(config_file)) if config_file else {} |
482 | 60 | 60 | ||
483 | 61 | 61 | ||
485 | 62 | @pytest.hookimpl(tryfirst=True, hookwrapper=True) | 62 | @pytest.hookimpl(tryfirst=True, hookwrapper=True) # type: ignore |
486 | 63 | def pytest_runtest_makereport(item: Any, call: Any) -> Iterator[Any]: | 63 | def pytest_runtest_makereport(item: Any, call: Any) -> Iterator[Any]: |
487 | 64 | # execute all other hooks to obtain the report object | 64 | # execute all other hooks to obtain the report object |
488 | 65 | outcome: _Result | 65 | outcome: _Result |
489 | diff --git a/systemtests/tests/test_basic.py b/systemtests/tests/test_basic.py | |||
490 | index a94c69a..a653921 100644 | |||
491 | --- a/systemtests/tests/test_basic.py | |||
492 | +++ b/systemtests/tests/test_basic.py | |||
493 | @@ -19,7 +19,9 @@ if TYPE_CHECKING: | |||
494 | 19 | 19 | ||
495 | 20 | class TestSetup: | 20 | class TestSetup: |
496 | 21 | @pytest.mark.skip_if_installed_from_snap("Prometheus is installed in the snap") | 21 | @pytest.mark.skip_if_installed_from_snap("Prometheus is installed in the snap") |
498 | 22 | def test_setup_prometheus(self, maas_region: MAASRegion, config: dict) -> None: | 22 | def test_setup_prometheus( |
499 | 23 | self, maas_region: MAASRegion, config: dict[str, str] | ||
500 | 24 | ) -> None: | ||
501 | 23 | result = maas_region.execute( | 25 | result = maas_region.execute( |
502 | 24 | ["apt", "install", "python3-prometheus-client", "-y"] | 26 | ["apt", "install", "python3-prometheus-client", "-y"] |
503 | 25 | ) | 27 | ) |
504 | diff --git a/systemtests/tests/test_crud.py b/systemtests/tests/test_crud.py | |||
505 | index 366f815..0cb76f8 100644 | |||
506 | --- a/systemtests/tests/test_crud.py | |||
507 | +++ b/systemtests/tests/test_crud.py | |||
508 | @@ -11,7 +11,7 @@ if TYPE_CHECKING: | |||
509 | 11 | from ..api import AuthenticatedAPIClient | 11 | from ..api import AuthenticatedAPIClient |
510 | 12 | 12 | ||
511 | 13 | 13 | ||
513 | 14 | @test_steps("create", "update", "delete") | 14 | @test_steps("create", "update", "delete") # type: ignore |
514 | 15 | def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: | 15 | def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: |
515 | 16 | authenticated_admin.create_zone( | 16 | authenticated_admin.create_zone( |
516 | 17 | name="test-zone", description="A zone created by system-tests" | 17 | name="test-zone", description="A zone created by system-tests" |
517 | @@ -48,7 +48,7 @@ def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: | |||
518 | 48 | yield | 48 | yield |
519 | 49 | 49 | ||
520 | 50 | 50 | ||
522 | 51 | @test_steps("create", "update", "delete") | 51 | @test_steps("create", "update", "delete") # type: ignore |
523 | 52 | def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: | 52 | def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: |
524 | 53 | authenticated_admin.create_pool( | 53 | authenticated_admin.create_pool( |
525 | 54 | name="test-pool", description="A resource pool created by system-tests" | 54 | name="test-pool", description="A resource pool created by system-tests" |
526 | @@ -87,7 +87,7 @@ def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[ | |||
527 | 87 | yield | 87 | yield |
528 | 88 | 88 | ||
529 | 89 | 89 | ||
531 | 90 | @test_steps("create", "update", "delete") | 90 | @test_steps("create", "update", "delete") # type: ignore |
532 | 91 | def test_spaces(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: | 91 | def test_spaces(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]: |
533 | 92 | authenticated_admin.create_space(name="test-space") | 92 | authenticated_admin.create_space(name="test-space") |
534 | 93 | spaces = authenticated_admin.list_spaces() | 93 | spaces = authenticated_admin.list_spaces() |
535 | diff --git a/systemtests/utils.py b/systemtests/utils.py | |||
536 | index 68e17e9..7959492 100644 | |||
537 | --- a/systemtests/utils.py | |||
538 | +++ b/systemtests/utils.py | |||
539 | @@ -3,7 +3,7 @@ from __future__ import annotations | |||
540 | 3 | import random | 3 | import random |
541 | 4 | import string | 4 | import string |
542 | 5 | import time | 5 | import time |
544 | 6 | from typing import TYPE_CHECKING, Any, Callable, Generator, Union | 6 | from typing import TYPE_CHECKING, Any, Callable, Iterator, Union |
545 | 7 | 7 | ||
546 | 8 | import paramiko | 8 | import paramiko |
547 | 9 | from retry.api import retry_call | 9 | from retry.api import retry_call |
548 | @@ -42,7 +42,9 @@ def randomstring(length: int = 10) -> str: | |||
549 | 42 | return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) | 42 | return "".join(random.choice(string.ascii_lowercase) for _ in range(length)) |
550 | 43 | 43 | ||
551 | 44 | 44 | ||
553 | 45 | def retries(timeout: Union[int, float] = 30, delay: Union[int, float] = 1) -> Generator: | 45 | def retries( |
554 | 46 | timeout: Union[int, float] = 30, delay: Union[int, float] = 1 | ||
555 | 47 | ) -> Iterator[tuple[float, float]]: | ||
556 | 46 | """Helper for retrying something, sleeping between attempts. | 48 | """Helper for retrying something, sleeping between attempts. |
557 | 47 | 49 | ||
558 | 48 | Yields ``(elapsed, remaining)`` tuples, giving times in seconds. | 50 | Yields ``(elapsed, remaining)`` tuples, giving times in seconds. |
UNIT TESTS
-b strict-mypy lp:~adam-collard/maas-ci/+git/system-tests into -b master lp:~maas-committers/maas-ci/+git/system-tests
STATUS: SUCCESS a9c0eab2bd2b6d2 2b5b728540
COMMIT: a91b80b938d188c