Merge ~ltrager/maas:fix_architecture into maas:master

Proposed by Lee Trager
Status: Rejected
Rejected by: Lee Trager
Proposed branch: ~ltrager/maas:fix_architecture
Merge into: maas:master
Prerequisite: ~ltrager/maas:enable_uefi_http_boot
Diff against target: 466 lines (+95/-40)
8 files modified
src/provisioningserver/boot/__init__.py (+14/-2)
src/provisioningserver/boot/grub.py (+2/-2)
src/provisioningserver/boot/powernv.py (+4/-1)
src/provisioningserver/boot/s390x.py (+4/-1)
src/provisioningserver/boot/s390x_partition.py (+4/-1)
src/provisioningserver/boot/tests/test_boot.py (+35/-3)
src/provisioningserver/kernel_opts.py (+6/-5)
src/provisioningserver/tests/test_kernel_opts.py (+26/-25)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
MAAS Lander Approve
Review via email: mp+401498@code.launchpad.net

Commit message

Automatically fix an incorrect architecture.

When adding a new machine if a user selects the wrong architecture booting will fail. Every boot architecture except PXE only supports one Debian
architecture. Automatically swap the user selected architecture for the
one detected during boot. Commissioning will properly set the
architecture.

Commissioning may still fail due to the archive being set to
ports.ubuntu.com however 50-maas-01-commissioning will still run which
will fix the machine architecture. When a user retries commissioning will
work. This is preferable to failing to load the kernel from the bootloader
which can give a cryptic error message.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b fix_architecture lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: a529235ba36266901d37684cb615e0a2ec744bcf

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

LGTM

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

Unmerged commits

a529235... by Lee Trager

Automatically fix an incorrect architecture.

When adding a new machine if a user selects the wrong architecture booting will
fail. Every boot architecture except PXE only supports one Debian
architecture. Automatically swap the user selected architecture for the one
detected during boot. Commissioning will properly set the architecture.

Commissioning may still fail due to the archive being set to ports.ubuntu.com
however 50-maas-01-commissioning will still run which will fix the
machine architecture. When a user retries commissioning will work. This is
preferable to failing to load the kernel from the bootloader which can give
a cryptic error message.

a06daa6... by Lee Trager

Reenable AMD64 UEFI HTTP booting and enable ARM64 UEFI booting.

MAAS will now respond to AMD64 and ARM64 UEFI HTTP boot requests. When booting
over HTTP MAAS will now send a GRUB configuration which will continue to use
the UEFI firmware's internal HTTP stack instead of GRUB's. While various bugs
have been fixed in GRUB's HTTP stack this should avoid any future ones entirely.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/boot/__init__.py b/src/provisioningserver/boot/__init__.py
2index 9a38f11..994901f 100644
3--- a/src/provisioningserver/boot/__init__.py
4+++ b/src/provisioningserver/boot/__init__.py
5@@ -352,15 +352,27 @@ class BootMethod(metaclass=ABCMeta):
6 """
7 dtb_subarchs = ["xgene-uboot-mustang"]
8
9+ def get_arch(params):
10+ # Most boot loader arches are for a specific architecture. The
11+ # exception is 00:00(PXE) which is for i386 and AMD64. Use the
12+ # defined architecture incase the user selected the wrong one.
13+ # Subarches are identical across architectures except for armhf
14+ # which additonaly has generic-lpae.
15+ if len(self.bootloader_arches) == 1:
16+ return self.bootloader_arches[0]
17+ else:
18+ return params.arch
19+
20 def fs_host(params):
21 return "http://%s:5248/images/" % (
22 convert_host_to_uri_str(params.fs_host)
23 )
24
25 def image_dir(params):
26+
27 return compose_image_path(
28 params.osystem,
29- params.arch,
30+ get_arch(params),
31 params.subarch,
32 params.release,
33 params.label,
34@@ -403,7 +415,7 @@ class BootMethod(metaclass=ABCMeta):
35 return None
36
37 def kernel_command(params):
38- return compose_kernel_command_line(params)
39+ return compose_kernel_command_line(params, get_arch(params))
40
41 namespace = {
42 "fs_host": fs_host,
43diff --git a/src/provisioningserver/boot/grub.py b/src/provisioningserver/boot/grub.py
44index 286e350..56436ac 100644
45--- a/src/provisioningserver/boot/grub.py
46+++ b/src/provisioningserver/boot/grub.py
47@@ -119,7 +119,7 @@ class UEFIAMD64BootMethod(BootMethod):
48 return re.sub(
49 r"cc:{(?P<inner>[^}]*)}end_cc",
50 r"cc:\{\g<inner>\}end_cc",
51- compose_kernel_command_line(params),
52+ compose_kernel_command_line(params, self.bootloader_arches[0]),
53 )
54
55 template = self.get_template(
56@@ -225,7 +225,7 @@ class OpenFirmwarePPC64ELBootMethod(UEFIAMD64BootMethod):
57
58 name = "open-firmware_ppc64el"
59 bios_boot_method = "open-firmware"
60- bootloader_arches = ["ppc64el", "ppc64"]
61+ bootloader_arches = ["ppc64el"]
62 bootloader_path = "bootppc64.bin"
63 bootloader_files = ["bootppc64.bin"]
64 arch_octet = "00:0C"
65diff --git a/src/provisioningserver/boot/powernv.py b/src/provisioningserver/boot/powernv.py
66index e1e870e..b81aaba 100644
67--- a/src/provisioningserver/boot/powernv.py
68+++ b/src/provisioningserver/boot/powernv.py
69@@ -73,6 +73,7 @@ class PowerNVBootMethod(BootMethod):
70 name = "powernv"
71 bios_boot_method = "powernv"
72 template_subdir = "pxe"
73+ bootloader_arches = ["ppc64el"]
74 bootloader_path = "pxelinux.0"
75 arch_octet = "00:0E"
76 user_class = None
77@@ -138,7 +139,9 @@ class PowerNVBootMethod(BootMethod):
78 # Modify the kernel_command to inject the BOOTIF. PowerNV fails to
79 # support the IPAPPEND pxelinux flag.
80 def kernel_command(params):
81- cmd_line = compose_kernel_command_line(params)
82+ cmd_line = compose_kernel_command_line(
83+ params, self.bootloader_arches[0]
84+ )
85 if mac is not None:
86 return "%s BOOTIF=%s" % (cmd_line, format_bootif(mac))
87 return cmd_line
88diff --git a/src/provisioningserver/boot/s390x.py b/src/provisioningserver/boot/s390x.py
89index 310cf0a..947c287 100644
90--- a/src/provisioningserver/boot/s390x.py
91+++ b/src/provisioningserver/boot/s390x.py
92@@ -72,6 +72,7 @@ class S390XBootMethod(BootMethod):
93 name = "s390x"
94 bios_boot_method = "s390x"
95 template_subdir = "pxe"
96+ bootloader_arches = ["s390x"]
97 # boots390x.bin is a place holder to allow the path_prefix to be set.
98 # s390x KVM uses a bootloader shipped with KVM.
99 bootloader_path = "boots390x.bin"
100@@ -139,7 +140,9 @@ class S390XBootMethod(BootMethod):
101 # Modify the kernel_command to inject the BOOTIF. S390x fails to
102 # support the IPAPPEND pxelinux flag.
103 def kernel_command(params):
104- cmd_line = compose_kernel_command_line(params)
105+ cmd_line = compose_kernel_command_line(
106+ params, self.bootloader_arches[0]
107+ )
108 if mac is not None:
109 return "%s BOOTIF=%s" % (cmd_line, format_bootif(mac))
110 return cmd_line
111diff --git a/src/provisioningserver/boot/s390x_partition.py b/src/provisioningserver/boot/s390x_partition.py
112index c45e348..cb43e0d 100644
113--- a/src/provisioningserver/boot/s390x_partition.py
114+++ b/src/provisioningserver/boot/s390x_partition.py
115@@ -15,6 +15,7 @@ class S390XPartitionBootMethod(BootMethod):
116 name = "s390x_partition"
117 bios_boot_method = "s390x_partition"
118 template_subdir = "s390x_partition"
119+ bootloader_arches = ["s390x"]
120 # S390X partitions has its bootloader built into the firmware. The
121 # "bootloader" provided must be the bootloader configuration file. The
122 # file format is similar to pxelinux.cfg but supports limited options
123@@ -56,7 +57,9 @@ class S390XPartitionBootMethod(BootMethod):
124 namespace = self.compose_template_namespace(kernel_params)
125
126 def kernel_command(params):
127- cmd_line = compose_kernel_command_line(params)
128+ cmd_line = compose_kernel_command_line(
129+ params, self.bootloader_arches[0]
130+ )
131 # Modify the kernel_command to inject the BOOTIF. S390X doesn't
132 # support the IPAPPEND pxelinux flag.
133 if mac is not None:
134diff --git a/src/provisioningserver/boot/tests/test_boot.py b/src/provisioningserver/boot/tests/test_boot.py
135index eb61cab..5eae9b2 100644
136--- a/src/provisioningserver/boot/tests/test_boot.py
137+++ b/src/provisioningserver/boot/tests/test_boot.py
138@@ -255,7 +255,37 @@ class TestBootMethod(MAASTestCase):
139 template_namespace["initrd_path"](kernel_params),
140 )
141 self.assertEqual(
142- compose_kernel_command_line(kernel_params),
143+ compose_kernel_command_line(kernel_params, kernel_params.arch),
144+ template_namespace["kernel_command"](kernel_params),
145+ )
146+ self.assertEqual(kernel_params, template_namespace["kernel_params"])
147+ self.assertEqual(
148+ "%s/%s" % (image_dir, kernel_params.kernel),
149+ template_namespace["kernel_path"](kernel_params),
150+ )
151+ self.assertIsNone(template_namespace["dtb_path"](kernel_params))
152+
153+ def test_compose_template_namespace_fixes_arch(self):
154+ kernel_params = make_kernel_parameters()
155+ method = FakeBootMethod()
156+ arch = factory.make_name("arch")
157+ method.bootloader_arches = [arch]
158+ image_dir = compose_image_path(
159+ kernel_params.osystem,
160+ arch,
161+ kernel_params.subarch,
162+ kernel_params.release,
163+ kernel_params.label,
164+ )
165+
166+ template_namespace = method.compose_template_namespace(kernel_params)
167+
168+ self.assertEqual(
169+ "%s/%s" % (image_dir, kernel_params.initrd),
170+ template_namespace["initrd_path"](kernel_params),
171+ )
172+ self.assertEqual(
173+ compose_kernel_command_line(kernel_params, arch),
174 template_namespace["kernel_command"](kernel_params),
175 )
176 self.assertEqual(kernel_params, template_namespace["kernel_params"])
177@@ -288,7 +318,7 @@ class TestBootMethod(MAASTestCase):
178 template_namespace["initrd_path"](kernel_params),
179 )
180 self.assertEqual(
181- compose_kernel_command_line(kernel_params),
182+ compose_kernel_command_line(kernel_params, kernel_params.arch),
183 template_namespace["kernel_command"](kernel_params),
184 )
185 self.assertEqual(kernel_params, template_namespace["kernel_params"])
186@@ -319,7 +349,7 @@ class TestBootMethod(MAASTestCase):
187 template_namespace["initrd_path"](kernel_params),
188 )
189 self.assertEqual(
190- compose_kernel_command_line(kernel_params),
191+ compose_kernel_command_line(kernel_params, kernel_params.arch),
192 template_namespace["kernel_command"](kernel_params),
193 )
194 self.assertEqual(kernel_params, template_namespace["kernel_params"])
195@@ -334,6 +364,8 @@ class TestBootMethod(MAASTestCase):
196
197 def test_compose_template_namespace_include_debug(self):
198 debug = factory.pick_bool()
199+ boot.debug_enabled.cache_clear()
200+ self.addCleanup(boot.debug_enabled.cache_clear)
201 self.useFixture(ClusterConfigurationFixture(debug=debug))
202 kernel_params = make_kernel_parameters()
203 method = FakeBootMethod()
204diff --git a/src/provisioningserver/kernel_opts.py b/src/provisioningserver/kernel_opts.py
205index 47fd85c..f7082d7 100644
206--- a/src/provisioningserver/kernel_opts.py
207+++ b/src/provisioningserver/kernel_opts.py
208@@ -1,4 +1,4 @@
209-# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
210+# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
211 # GNU Affero General Public License version 3 (see the file LICENSE).
212
213 """Generate kernel command-line options for inclusion in PXE configs."""
214@@ -77,8 +77,9 @@ def get_last_directory(root):
215 return max(dirs)
216
217
218-def compose_purpose_opts(params):
219+def compose_purpose_opts(params, bootloader_arch=None):
220 """Return the list of the purpose-specific kernel options."""
221+ arch = bootloader_arch if bootloader_arch else params.arch
222 kernel_params = [
223 "ro",
224 "root=squash:http://%s:5248/images/%s/%s/%s/%s/%s/squashfs"
225@@ -89,7 +90,7 @@ def compose_purpose_opts(params):
226 else params.fs_host
227 ),
228 params.osystem,
229- params.arch,
230+ arch,
231 params.subarch,
232 params.release,
233 params.label,
234@@ -137,7 +138,7 @@ def get_curtin_kernel_cmdline_sep():
235 return getattr(curtin, CURTIN_KERNEL_CMDLINE_NAME, "--")
236
237
238-def compose_kernel_command_line(params):
239+def compose_kernel_command_line(params, bootloader_arch):
240 """Generate a line of kernel options for booting `node`.
241
242 :type params: `KernelParameters`.
243@@ -145,7 +146,7 @@ def compose_kernel_command_line(params):
244 options = []
245 # nomodeset prevents video mode switching.
246 options += ["nomodeset"]
247- options += compose_purpose_opts(params)
248+ options += compose_purpose_opts(params, bootloader_arch)
249 # Note: logging opts are not respected by ephemeral images, so
250 # these are actually "purpose_opts" but were left generic
251 # as it would be nice to have.
252diff --git a/src/provisioningserver/tests/test_kernel_opts.py b/src/provisioningserver/tests/test_kernel_opts.py
253index 0985ed6..53450ed 100644
254--- a/src/provisioningserver/tests/test_kernel_opts.py
255+++ b/src/provisioningserver/tests/test_kernel_opts.py
256@@ -1,4 +1,4 @@
257-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
258+# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
259 # GNU Affero General Public License version 3 (see the file LICENSE).
260
261 """Test composition of kernel command lines."""
262@@ -121,7 +121,7 @@ class TestKernelOpts(MAASTestCase):
263 params = self.make_kernel_parameters(
264 purpose=random.choice(["commissioning", "xinstall", "enlist"])
265 )
266- cmdline = compose_kernel_command_line(params)
267+ cmdline = compose_kernel_command_line(params, params.arch)
268 self.assertThat(cmdline, Contains("overlayroot_cfgdisk=disabled"))
269
270 def test_xinstall_compose_kernel_command_line_inc_purpose_opts4(self):
271@@ -130,7 +130,7 @@ class TestKernelOpts(MAASTestCase):
272 params = self.make_kernel_parameters(
273 purpose="xinstall", fs_host=factory.make_ipv4_address()
274 )
275- cmdline = compose_kernel_command_line(params)
276+ cmdline = compose_kernel_command_line(params, params.arch)
277 self.assertThat(
278 cmdline,
279 ContainsAll(
280@@ -149,7 +149,7 @@ class TestKernelOpts(MAASTestCase):
281 params = self.make_kernel_parameters(
282 purpose="xinstall", fs_host=factory.make_ipv6_address()
283 )
284- cmdline = compose_kernel_command_line(params)
285+ cmdline = compose_kernel_command_line(params, params.arch)
286 self.assertThat(
287 cmdline,
288 ContainsAll(
289@@ -168,7 +168,7 @@ class TestKernelOpts(MAASTestCase):
290 params = self.make_kernel_parameters(
291 purpose="xinstall", fs_host=factory.make_ipv4_address()
292 )
293- cmdline = compose_kernel_command_line(params)
294+ cmdline = compose_kernel_command_line(params, params.arch)
295 self.assertThat(
296 cmdline,
297 ContainsAll(
298@@ -185,7 +185,7 @@ class TestKernelOpts(MAASTestCase):
299 params = self.make_kernel_parameters(
300 purpose="ephemeral", fs_host=factory.make_ipv4_address()
301 )
302- cmdline = compose_kernel_command_line(params)
303+ cmdline = compose_kernel_command_line(params, params.arch)
304 self.assertThat(
305 cmdline,
306 ContainsAll(
307@@ -202,7 +202,7 @@ class TestKernelOpts(MAASTestCase):
308 params = self.make_kernel_parameters(
309 purpose="commissioning", fs_host=factory.make_ipv4_address()
310 )
311- cmdline = compose_kernel_command_line(params)
312+ cmdline = compose_kernel_command_line(params, params.arch)
313 self.assertThat(
314 cmdline,
315 ContainsAll(
316@@ -221,7 +221,7 @@ class TestKernelOpts(MAASTestCase):
317 params = self.make_kernel_parameters(
318 purpose="commissioning", fs_host=factory.make_ipv6_address()
319 )
320- cmdline = compose_kernel_command_line(params)
321+ cmdline = compose_kernel_command_line(params, params.arch)
322 self.assertThat(
323 cmdline,
324 ContainsAll(
325@@ -240,7 +240,7 @@ class TestKernelOpts(MAASTestCase):
326 params = self.make_kernel_parameters(
327 purpose="commissioning", fs_host=factory.make_ipv4_address()
328 )
329- cmdline = compose_kernel_command_line(params)
330+ cmdline = compose_kernel_command_line(params, params.arch)
331 self.assertThat(
332 cmdline,
333 ContainsAll(
334@@ -257,7 +257,7 @@ class TestKernelOpts(MAASTestCase):
335 params = self.make_kernel_parameters(
336 purpose="enlist", fs_host=factory.make_ipv4_address()
337 )
338- cmdline = compose_kernel_command_line(params)
339+ cmdline = compose_kernel_command_line(params, params.arch)
340 self.assertThat(
341 cmdline,
342 ContainsAll(
343@@ -276,7 +276,7 @@ class TestKernelOpts(MAASTestCase):
344 params = self.make_kernel_parameters(
345 purpose="enlist", fs_host=factory.make_ipv6_address()
346 )
347- cmdline = compose_kernel_command_line(params)
348+ cmdline = compose_kernel_command_line(params, params.arch)
349 self.assertThat(
350 cmdline,
351 ContainsAll(
352@@ -295,7 +295,7 @@ class TestKernelOpts(MAASTestCase):
353 params = self.make_kernel_parameters(
354 purpose="enlist", fs_host=factory.make_ipv4_address()
355 )
356- cmdline = compose_kernel_command_line(params)
357+ cmdline = compose_kernel_command_line(params, params.arch)
358 self.assertThat(
359 cmdline,
360 ContainsAll(
361@@ -312,7 +312,7 @@ class TestKernelOpts(MAASTestCase):
362 params = self.make_kernel_parameters(
363 purpose="enlist", fs_host=factory.make_ipv4_address()
364 )
365- cmdline = compose_kernel_command_line(params)
366+ cmdline = compose_kernel_command_line(params, params.arch)
367 self.assertThat(cmdline, ContainsAll(["apparmor=0"]))
368
369 def test_commissioning_compose_kernel_command_line_apparmor_disabled(self):
370@@ -321,7 +321,7 @@ class TestKernelOpts(MAASTestCase):
371 params = self.make_kernel_parameters(
372 purpose="commissioning", fs_host=factory.make_ipv4_address()
373 )
374- cmdline = compose_kernel_command_line(params)
375+ cmdline = compose_kernel_command_line(params, params.arch)
376 self.assertThat(cmdline, ContainsAll(["apparmor=0"]))
377
378 def test_commissioning_compose_kernel_command_line_inc_extra_opts(self):
379@@ -332,14 +332,14 @@ class TestKernelOpts(MAASTestCase):
380 mock_get_curtin_sep.return_value = sep
381 extra_opts = "special console=ABCD -- options to pass"
382 params = self.make_kernel_parameters(extra_opts=extra_opts)
383- cmdline = compose_kernel_command_line(params)
384+ cmdline = compose_kernel_command_line(params, params.arch)
385 # There should be KERNEL_CMDLINE_COPY_TO_INSTALL_SEP surrounded by
386 # spaces before the options, but otherwise added verbatim.
387 self.assertThat(cmdline, Contains(" %s " % sep + extra_opts))
388
389 def test_commissioning_compose_kernel_handles_extra_opts_None(self):
390 params = self.make_kernel_parameters(extra_opts=None)
391- cmdline = compose_kernel_command_line(params)
392+ cmdline = compose_kernel_command_line(params, params.arch)
393 self.assertNotIn(cmdline, "None")
394
395 def test_compose_kernel_command_line_inc_common_opts(self):
396@@ -350,15 +350,15 @@ class TestKernelOpts(MAASTestCase):
397 params = self.make_kernel_parameters(
398 purpose="commissioning", arch="i386"
399 )
400- cmdline = compose_kernel_command_line(params)
401+ cmdline = compose_kernel_command_line(params, params.arch)
402 self.assertThat(cmdline, ContainsAll(expected))
403
404 params = self.make_kernel_parameters(purpose="xinstall", arch="i386")
405- cmdline = compose_kernel_command_line(params)
406+ cmdline = compose_kernel_command_line(params, params.arch)
407 self.assertThat(cmdline, ContainsAll(expected))
408
409 params = self.make_kernel_parameters(purpose="install", arch="i386")
410- cmdline = compose_kernel_command_line(params)
411+ cmdline = compose_kernel_command_line(params, params.arch)
412 self.assertThat(cmdline, ContainsAll(expected))
413
414 def test_compose_kernel_command_line_inc_purpose_opts_xinstall_node(self):
415@@ -366,7 +366,7 @@ class TestKernelOpts(MAASTestCase):
416 # options for a "xinstall" node.
417 params = self.make_kernel_parameters(purpose="xinstall")
418 self.assertThat(
419- compose_kernel_command_line(params),
420+ compose_kernel_command_line(params, params.arch),
421 ContainsAll(["root=squash:http://"]),
422 )
423
424@@ -375,20 +375,21 @@ class TestKernelOpts(MAASTestCase):
425 # options for a "commissioning" node.
426 params = self.make_kernel_parameters(purpose="commissioning")
427 self.assertThat(
428- compose_kernel_command_line(params),
429+ compose_kernel_command_line(params, params.arch),
430 ContainsAll(["root=squash:http://"]),
431 )
432
433 def test_compose_kernel_command_line_inc_arm_specific_option(self):
434 params = self.make_kernel_parameters(arch="armhf", subarch="highbank")
435 self.assertThat(
436- compose_kernel_command_line(params), Contains("console=ttyAMA0")
437+ compose_kernel_command_line(params, params.arch),
438+ Contains("console=ttyAMA0"),
439 )
440
441 def test_compose_kernel_command_line_not_inc_arm_specific_option(self):
442 params = self.make_kernel_parameters(arch="i386")
443 self.assertThat(
444- compose_kernel_command_line(params),
445+ compose_kernel_command_line(params, params.arch),
446 Not(Contains("console=ttyAMA0")),
447 )
448
449@@ -405,7 +406,7 @@ class TestKernelOpts(MAASTestCase):
450 def test_compose_rootfs_over_http_ipv4(self):
451 params = make_kernel_parameters(fs_host=factory.make_ipv4_address())
452 self.assertThat(
453- compose_kernel_command_line(params),
454+ compose_kernel_command_line(params, params.arch),
455 ContainsAll(
456 [
457 "ro",
458@@ -425,7 +426,7 @@ class TestKernelOpts(MAASTestCase):
459 def test_compose_rootfs_over_http_ipv6(self):
460 params = make_kernel_parameters(fs_host=factory.make_ipv6_address())
461 self.assertThat(
462- compose_kernel_command_line(params),
463+ compose_kernel_command_line(params, params.arch),
464 ContainsAll(
465 [
466 "ro",

Subscribers

People subscribed via source and target branches