Merge ~ltrager/maas:lp1851622 into maas:master
- Git
- lp:~ltrager/maas
- lp1851622
- Merge into master
Proposed by
Lee Trager
Status: | Merged |
---|---|
Approved by: | Lee Trager |
Approved revision: | 3ac1df98cd7957d6e4bfd6dea44a195ec99d4371 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~ltrager/maas:lp1851622 |
Merge into: | maas:master |
Diff against target: |
437 lines (+199/-34) 5 files modified
src/maasserver/api/machines.py (+1/-3) src/maasserver/api/tests/test_machine.py (+1/-2) src/maasserver/models/node.py (+77/-17) src/maasserver/models/tests/test_node.py (+115/-11) src/metadataserver/tests/test_vendor_data.py (+5/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Newell Jensen (community) | Approve | ||
MAAS Lander | Needs Fixing | ||
Review via email: mp+375462@code.launchpad.net |
Commit message
LP: #1851622 - Validate netplan usage before use in ephemeral environments.
Netplan is only supported in Bionic+. MAAS now prevents ephemeral deployments
and testing in Xenial. This also fixes a bug where MAAS attempted to apply
network configuration when commissioning a machine that had all of its
disks deleted.
Description of the change
To post a comment you must log in.
~ltrager/maas:lp1851622
updated
- 75c3d57... by Lee Trager
-
Fix lint
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1851622 lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 75c3d57e3ae9ffb
review:
Needs Fixing
~ltrager/maas:lp1851622
updated
- 3ac1df9... by Lee Trager
-
Fix broken tests
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py |
2 | index 4f71844..5444773 100644 |
3 | --- a/src/maasserver/api/machines.py |
4 | +++ b/src/maasserver/api/machines.py |
5 | @@ -725,9 +725,7 @@ class MachineHandler(NodeHandler, OwnerDataMixin, PowerMixin): |
6 | NodePermission.admin, machine |
7 | ): |
8 | raise PermissionDenied() |
9 | - if options.install_kvm and ( |
10 | - machine.ephemeral_deployment or options.ephemeral_deploy |
11 | - ): |
12 | + if options.install_kvm and machine.ephemeral_deployment: |
13 | raise MAASAPIBadRequest( |
14 | "Cannot install KVM host for ephemeral deployments." |
15 | ) |
16 | diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py |
17 | index faea37a..8b62ea3 100644 |
18 | --- a/src/maasserver/api/tests/test_machine.py |
19 | +++ b/src/maasserver/api/tests/test_machine.py |
20 | @@ -554,9 +554,8 @@ class TestMachineAPI(APITestCase.ForUser): |
21 | distro_series=distro_series, |
22 | osystem=osystem, |
23 | architecture=make_usable_architecture(self), |
24 | + ephemeral_deploy=True, |
25 | ) |
26 | - for bd in machine.blockdevice_set.all(): |
27 | - bd.delete() |
28 | response = self.client.post( |
29 | self.get_machine_uri(machine), |
30 | {"op": "deploy", "install_kvm": True}, |
31 | diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py |
32 | index b679963..d087f37 100644 |
33 | --- a/src/maasserver/models/node.py |
34 | +++ b/src/maasserver/models/node.py |
35 | @@ -16,7 +16,7 @@ from collections import defaultdict, namedtuple, OrderedDict |
36 | import copy |
37 | from datetime import datetime, timedelta |
38 | from functools import partial |
39 | -from itertools import count |
40 | +from itertools import chain, count |
41 | import json |
42 | import logging |
43 | from operator import attrgetter |
44 | @@ -1463,6 +1463,16 @@ class Node(CleanSave, TimestampedModel): |
45 | # Devices should always local boot. |
46 | if self.is_device: |
47 | return False |
48 | + # Only machines which are deploying can be deployed ephemerally. Allow |
49 | + # ALLOCATED and READY as this is checked before transitioning in the |
50 | + # API. |
51 | + if self.status not in { |
52 | + NODE_STATUS.DEPLOYING, |
53 | + NODE_STATUS.DEPLOYED, |
54 | + NODE_STATUS.ALLOCATED, |
55 | + NODE_STATUS.READY, |
56 | + }: |
57 | + return False |
58 | return self.is_diskless or self.ephemeral_deploy |
59 | |
60 | def retrieve_storage_layout_issues( |
61 | @@ -2216,21 +2226,55 @@ class Node(CleanSave, TimestampedModel): |
62 | ) |
63 | |
64 | # Create a new ScriptSet for this commissioning run. |
65 | - script_set = ScriptSet.objects.create_commissioning_script_set( |
66 | + commis_script_set = ScriptSet.objects.create_commissioning_script_set( |
67 | self, commissioning_scripts, script_input |
68 | ) |
69 | - self.current_commissioning_script_set = script_set |
70 | |
71 | # Create a new ScriptSet for any tests to be run after commissioning. |
72 | try: |
73 | - script_set = ScriptSet.objects.create_testing_script_set( |
74 | + testing_script_set = ScriptSet.objects.create_testing_script_set( |
75 | self, testing_scripts, script_input |
76 | ) |
77 | - self.current_testing_script_set = script_set |
78 | except NoScriptsFound: |
79 | # Commissioning can run without running tests after. |
80 | + testing_script_set = [] |
81 | pass |
82 | |
83 | + config = Config.objects.get_configs( |
84 | + [ |
85 | + "commissioning_osystem", |
86 | + "commissioning_distro_series", |
87 | + "default_osystem", |
88 | + "default_distro_series", |
89 | + "default_min_hwe_kernel", |
90 | + ] |
91 | + ) |
92 | + |
93 | + # Testing configured networking requires netplan which is only |
94 | + # available in 18.04+ |
95 | + if config["commissioning_distro_series"] == "xenial": |
96 | + apply_configured_networking_found = False |
97 | + for script_result in chain(commis_script_set, testing_script_set): |
98 | + if ( |
99 | + script_result.script |
100 | + and script_result.script.apply_configured_networking |
101 | + ): |
102 | + apply_configured_networking_found = True |
103 | + break |
104 | + if apply_configured_networking_found: |
105 | + commis_script_set.delete() |
106 | + if testing_script_set: |
107 | + testing_script_set.delete() |
108 | + raise ValidationError( |
109 | + "Unable to run script which configures custom networking " |
110 | + "when using Ubuntu Xenial 16.04 as the ephemeral " |
111 | + "operating system." |
112 | + ) |
113 | + |
114 | + self.current_commissioning_script_set = commis_script_set |
115 | + if testing_script_set: |
116 | + self.current_testing_script_set = testing_script_set |
117 | + |
118 | # Clear the current storage configuration if networking is not being |
119 | # skipped during commissioning. |
120 | if not self.skip_storage: |
121 | @@ -2247,15 +2291,7 @@ class Node(CleanSave, TimestampedModel): |
122 | old_status = self.status |
123 | self.status = NODE_STATUS.COMMISSIONING |
124 | self.owner = user |
125 | - config = Config.objects.get_configs( |
126 | - [ |
127 | - "commissioning_osystem", |
128 | - "commissioning_distro_series", |
129 | - "default_osystem", |
130 | - "default_distro_series", |
131 | - "default_min_hwe_kernel", |
132 | - ] |
133 | - ) |
134 | + |
135 | # Set min_hwe_kernel to default_min_hwe_kernel. |
136 | # This makes sure that the min_hwe_kernel is up to date |
137 | # with what is stored in the settings. |
138 | @@ -2377,6 +2413,9 @@ class Node(CleanSave, TimestampedModel): |
139 | script_set = ScriptSet.objects.create_testing_script_set( |
140 | self, testing_scripts, script_input |
141 | ) |
142 | + commissioning_distro_series = Config.objects.get_config( |
143 | + "commissioning_distro_series" |
144 | + ) |
145 | # Additional validation for when starting testing and not |
146 | # commissioning + testing. |
147 | for script_result in script_set: |
148 | @@ -2401,6 +2440,17 @@ class Node(CleanSave, TimestampedModel): |
149 | raise ValidationError( |
150 | "Unable to run destructive test while deployed!" |
151 | ) |
152 | + if ( |
153 | + commissioning_distro_series == "xenial" |
154 | + and script_result.script |
155 | + and script_result.script.apply_configured_networking |
156 | + ): |
157 | + script_set.delete() |
158 | + raise ValidationError( |
159 | + "Unable to run script which configures custom networking " |
160 | + "when using Ubuntu Xenial 16.04 as the ephemeral " |
161 | + "operating system." |
162 | + ) |
163 | |
164 | self.current_testing_script_set = script_set |
165 | |
166 | @@ -3612,13 +3662,13 @@ class Node(CleanSave, TimestampedModel): |
167 | |
168 | def set_ephemeral_deploy(self, on=False): |
169 | """Set ephermal deploy on or off.""" |
170 | + # self.ephemeral_deployment returns True when the system is diskless. |
171 | + self.ephemeral_deploy = on or self.is_diskless |
172 | log.info( |
173 | "{hostname}: Turning ephemeral deploy %s for node" |
174 | - % ("off" if on is False else "on"), |
175 | + % ("on" if self.ephemeral_deploy else "off"), |
176 | hostname=self.hostname, |
177 | ) |
178 | - self.ephemeral_deploy = on |
179 | - self.save() |
180 | |
181 | def split_arch(self): |
182 | """Return architecture and subarchitecture, as a tuple.""" |
183 | @@ -4986,6 +5036,7 @@ class Node(CleanSave, TimestampedModel): |
184 | # If ephemeral is in purposes, this comes from Caringo OS. |
185 | # Set the ephemeral_deploy field on the node. |
186 | self.set_ephemeral_deploy(True) |
187 | + self.save() |
188 | return "ephemeral" |
189 | else: |
190 | return "xinstall" |
191 | @@ -5160,6 +5211,9 @@ class Node(CleanSave, TimestampedModel): |
192 | bridge_stp=None, |
193 | bridge_fd=None, |
194 | ): |
195 | + # Avoid circular imports |
196 | + from maasserver.utils.osystems import release_a_newer_than_b |
197 | + |
198 | if not user.has_perm(NodePermission.edit, self): |
199 | # You can't start a node you don't own unless you're an admin. |
200 | raise PermissionDenied() |
201 | @@ -5227,6 +5281,12 @@ class Node(CleanSave, TimestampedModel): |
202 | raise ValidationError( |
203 | "Cannot install KVM host for ephemeral deployments." |
204 | ) |
205 | + if self.ephemeral_deployment and not release_a_newer_than_b( |
206 | + self.distro_series, "bionic" |
207 | + ): |
208 | + raise ValidationError( |
209 | + "Ephemeral deployments only supported on Ubuntu Bionic 18.04+" |
210 | + ) |
211 | self._register_request_event( |
212 | user, event, action="start", comment=comment |
213 | ) |
214 | diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py |
215 | index 2f0ec78..e98fa74 100644 |
216 | --- a/src/maasserver/models/tests/test_node.py |
217 | +++ b/src/maasserver/models/tests/test_node.py |
218 | @@ -1096,33 +1096,62 @@ class TestNode(MAASServerTestCase): |
219 | self.assertFalse(node.is_diskless) |
220 | |
221 | def test_no_ephemeral_deploy(self): |
222 | - node = factory.make_Node(ephemeral_deploy=False) |
223 | + node = factory.make_Node( |
224 | + ephemeral_deploy=False, status=NODE_STATUS.DEPLOYING |
225 | + ) |
226 | self.assertFalse(node.ephemeral_deploy) |
227 | |
228 | def test_ephemeral_deploy(self): |
229 | - node = factory.make_Node(ephemeral_deploy=True) |
230 | + node = factory.make_Node( |
231 | + ephemeral_deploy=True, status=NODE_STATUS.DEPLOYING |
232 | + ) |
233 | self.assertTrue(node.ephemeral_deploy) |
234 | |
235 | def test_ephemeral_deployment_checks_diskless(self): |
236 | - node = factory.make_Node(with_boot_disk=True) |
237 | + node = factory.make_Node( |
238 | + with_boot_disk=True, status=NODE_STATUS.DEPLOYING |
239 | + ) |
240 | self.assertFalse(node.ephemeral_deployment) |
241 | |
242 | def test_ephemeral_deployment_checks_not_diskless(self): |
243 | - node = factory.make_Node(with_boot_disk=False) |
244 | + node = factory.make_Node( |
245 | + with_boot_disk=False, status=NODE_STATUS.DEPLOYING |
246 | + ) |
247 | self.assertTrue(node.ephemeral_deployment) |
248 | |
249 | def test_ephemeral_deployment_checks_ephemeral_deploy(self): |
250 | - node = factory.make_Node(ephemeral_deploy=True) |
251 | + node = factory.make_Node( |
252 | + ephemeral_deploy=True, status=NODE_STATUS.DEPLOYING |
253 | + ) |
254 | self.assertTrue(node.ephemeral_deployment) |
255 | |
256 | def test_ephemeral_deployment_checks_no_ephemeral_deploy(self): |
257 | - node = factory.make_Node(ephemeral_deploy=False) |
258 | + node = factory.make_Node( |
259 | + ephemeral_deploy=False, status=NODE_STATUS.DEPLOYING |
260 | + ) |
261 | self.assertFalse(node.ephemeral_deployment) |
262 | |
263 | def test_ephemeral_deployment_checks_is_device(self): |
264 | - device = factory.make_Device() |
265 | + device = factory.make_Device( |
266 | + ephemeral_deploy=True, status=NODE_STATUS.DEPLOYING |
267 | + ) |
268 | self.assertFalse(device.ephemeral_deployment) |
269 | |
270 | + def test_ephemeral_deployment_no_if_not_deploying(self): |
271 | + node = factory.make_Node( |
272 | + status=factory.pick_choice( |
273 | + NODE_STATUS_CHOICES, |
274 | + but_not=[ |
275 | + NODE_STATUS.DEPLOYING, |
276 | + NODE_STATUS.DEPLOYED, |
277 | + NODE_STATUS.ALLOCATED, |
278 | + NODE_STATUS.READY, |
279 | + ], |
280 | + ), |
281 | + ephemeral_deploy=True, |
282 | + ) |
283 | + self.assertFalse(node.ephemeral_deployment) |
284 | + |
285 | def test_system_id_is_a_valid_znum(self): |
286 | node = factory.make_Node() |
287 | self.assertThat( |
288 | @@ -3456,6 +3485,39 @@ class TestNode(MAASServerTestCase): |
289 | ) |
290 | self.assertEqual(events[1].type.name, EVENT_TYPES.COMMISSIONING) |
291 | |
292 | + def test_start_commissioning_fails_on_xenial_with_network_testing_c(self): |
293 | + Config.objects.set_config("commissioning_distro_series", "xenial") |
294 | + node = factory.make_Node(status=NODE_STATUS.NEW) |
295 | + admin = factory.make_admin() |
296 | + script = factory.make_Script( |
297 | + script_type=SCRIPT_TYPE.COMMISSIONING, |
298 | + apply_configured_networking=True, |
299 | + ) |
300 | + self.assertRaises( |
301 | + ValidationError, |
302 | + node.start_commissioning, |
303 | + user=admin, |
304 | + commissioning_scripts=[script.name], |
305 | + ) |
306 | + self.assertEquals(0, ScriptSet.objects.count()) |
307 | + self.assertEquals(0, ScriptResult.objects.count()) |
308 | + |
309 | + def test_start_commissioning_fails_on_xenial_with_network_testing_t(self): |
310 | + Config.objects.set_config("commissioning_distro_series", "xenial") |
311 | + node = factory.make_Node(status=NODE_STATUS.NEW) |
312 | + admin = factory.make_admin() |
313 | + script = factory.make_Script( |
314 | + script_type=SCRIPT_TYPE.TESTING, apply_configured_networking=True |
315 | + ) |
316 | + self.assertRaises( |
317 | + ValidationError, |
318 | + node.start_commissioning, |
319 | + user=admin, |
320 | + testing_scripts=[script.name], |
321 | + ) |
322 | + self.assertEquals(0, ScriptSet.objects.count()) |
323 | + self.assertEquals(0, ScriptResult.objects.count()) |
324 | + |
325 | def test_abort_commissioning_reverts_to_sane_state_on_error(self): |
326 | # If abort commissioning hits an error when trying to stop the |
327 | # node, it will revert the node to the state it was in before |
328 | @@ -3920,6 +3982,21 @@ class TestNode(MAASServerTestCase): |
329 | ) |
330 | self.assertFalse(ScriptSet.objects.filter(node=node).exists()) |
331 | |
332 | + def test_start_testing_prevents_network_testing_with_xenial(self): |
333 | + script = factory.make_Script( |
334 | + script_type=SCRIPT_TYPE.TESTING, apply_configured_networking=True |
335 | + ) |
336 | + admin = factory.make_admin() |
337 | + node = factory.make_Node(status=NODE_STATUS.READY) |
338 | + self.assertRaises( |
339 | + ValidationError, |
340 | + node.start_testing, |
341 | + admin, |
342 | + testing_scripts=[script.name], |
343 | + ) |
344 | + self.assertEquals(0, ScriptSet.objects.count()) |
345 | + self.assertEquals(0, ScriptResult.objects.count()) |
346 | + |
347 | def test_save_logs_node_status_transition(self): |
348 | self.disable_node_query() |
349 | node = factory.make_Node( |
350 | @@ -5212,7 +5289,11 @@ class TestNode(MAASServerTestCase): |
351 | |
352 | def test_storage_layout_issues_is_valid_when_ephemeral_deployment(self): |
353 | # A diskless node is one that it is ephemerally deployed. |
354 | - node = factory.make_Node(with_boot_disk=False, osystem="ubuntu") |
355 | + node = factory.make_Node( |
356 | + with_boot_disk=False, |
357 | + osystem="ubuntu", |
358 | + status=NODE_STATUS.DEPLOYING, |
359 | + ) |
360 | self.assertEqual([], node.storage_layout_issues()) |
361 | |
362 | def test_storage_layout_issues_is_invalid_when_no_disks_non_ubuntu(self): |
363 | @@ -6397,6 +6478,11 @@ class NodeManagerTest(MAASServerTestCase): |
364 | node.set_ephemeral_deploy(True) |
365 | self.assertTrue(node.ephemeral_deploy) |
366 | |
367 | + def test_ephemeral_deploy_auto_on_when_diskless(self): |
368 | + node = factory.make_Node(ephemeral_deploy=False, with_boot_disk=False) |
369 | + node.set_ephemeral_deploy(False) |
370 | + self.assertTrue(node.ephemeral_deploy) |
371 | + |
372 | def test_ephemeral_deploy_off(self): |
373 | node = factory.make_Node(ephemeral_deploy=True) |
374 | node.set_ephemeral_deploy(False) |
375 | @@ -8013,6 +8099,8 @@ class TestNode_Start(MAASTransactionServerTestCase): |
376 | power_state=POWER_STATE.OFF, |
377 | network=None, |
378 | with_boot_disk=True, |
379 | + install_kvm=False, |
380 | + ephemeral_deploy=False, |
381 | ): |
382 | if network is None: |
383 | network = factory.make_ip4_or_6_network() |
384 | @@ -8041,6 +8129,8 @@ class TestNode_Start(MAASTransactionServerTestCase): |
385 | cidr=cidr, |
386 | osystem=osystem, |
387 | distro_series=distro_series, |
388 | + install_kvm=install_kvm, |
389 | + ephemeral_deploy=ephemeral_deploy, |
390 | ) |
391 | node.acquire(user) |
392 | return node |
393 | @@ -8073,10 +8163,24 @@ class TestNode_Start(MAASTransactionServerTestCase): |
394 | def test__raises_ValidationError_if_ephemeral_deploy_and_install_kvm(self): |
395 | admin = factory.make_admin() |
396 | node = self.make_acquired_node_with_interface( |
397 | - admin, power_type="manual", with_boot_disk=False |
398 | + admin, |
399 | + power_type="manual", |
400 | + with_boot_disk=False, |
401 | + ephemeral_deploy=True, |
402 | + install_kvm=True, |
403 | ) |
404 | - node.install_kvm = True |
405 | - node.save() |
406 | + with ExpectedException(ValidationError): |
407 | + node.start(admin) |
408 | + |
409 | + def test__raises_ValidationError_if_ephemeral_deployment_less_bionic(self): |
410 | + admin = factory.make_admin() |
411 | + node = self.make_acquired_node_with_interface( |
412 | + admin, |
413 | + power_type="manual", |
414 | + with_boot_disk=False, |
415 | + ephemeral_deploy=True, |
416 | + ) |
417 | + node.distro_series = "xenial" |
418 | with ExpectedException(ValidationError): |
419 | node.start(admin) |
420 | |
421 | diff --git a/src/metadataserver/tests/test_vendor_data.py b/src/metadataserver/tests/test_vendor_data.py |
422 | index 24f1175..d57282f 100644 |
423 | --- a/src/metadataserver/tests/test_vendor_data.py |
424 | +++ b/src/metadataserver/tests/test_vendor_data.py |
425 | @@ -384,7 +384,11 @@ class TestGenerateEphemeralDeploymentNetworkConfiguration(MAASServerTestCase): |
426 | self.assertThat(dict(configuration), Equals({})) |
427 | |
428 | def test_yields_configuration_when_node_is_ephemeral_deployment(self): |
429 | - node = factory.make_Node(with_boot_disk=False) |
430 | + node = factory.make_Node( |
431 | + with_boot_disk=False, |
432 | + ephemeral_deploy=True, |
433 | + status=NODE_STATUS.DEPLOYING, |
434 | + ) |
435 | configuration = get_vendor_data(node, None) |
436 | config = dict(configuration) |
437 | self.assertThat( |
UNIT TESTS
-b lp1851622 lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 6973/console 9f71e5b1eaac9a4 756356f13f
LOG: http://
COMMIT: 21ccdd41d667e11