Merge ~ltrager/maas:lp1923268_2.7 into maas:2.7

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 6815de6bd61fd47a3e6c785e2ae7047e6b3bf53f
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:lp1923268_2.7
Merge into: maas:2.7
Prerequisite: ~ltrager/maas:grub_debug_2.7
Diff against target: 277 lines (+100/-41)
4 files modified
dev/null (+0/-30)
src/provisioningserver/__main__.py (+1/-3)
src/provisioningserver/boot/tests/test_uefi_amd64.py (+83/-0)
src/provisioningserver/boot/uefi_amd64.py (+16/-8)
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Review via email: mp+402538@code.launchpad.net

Commit message

LP: #1923268 - Make default grub.cfg architecture agnostic.

Backport of f606805

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/provisioningserver/__main__.py b/src/provisioningserver/__main__.py
2index 6cf81d9..5f22393 100644
3--- a/src/provisioningserver/__main__.py
4+++ b/src/provisioningserver/__main__.py
5@@ -1,5 +1,5 @@
6 #!/usr/bin/env python3
7-# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
8+# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
9 # GNU Affero General Public License version 3 (see the file LICENSE).
10
11 """Command-line interface for the MAAS provisioning component."""
12@@ -7,7 +7,6 @@
13 import sys
14
15 from provisioningserver import security
16-import provisioningserver.boot.install_grub
17 import provisioningserver.cluster_config_command
18 import provisioningserver.dns.commands.edit_named_options
19 import provisioningserver.dns.commands.get_named_conf
20@@ -39,7 +38,6 @@ RACK_ONLY_COMMANDS = {
21 "check-for-shared-secret": security.CheckForSharedSecretScript,
22 "config": provisioningserver.cluster_config_command,
23 "install-shared-secret": security.InstallSharedSecretScript,
24- "install-uefi-config": provisioningserver.boot.install_grub,
25 "register": provisioningserver.register_command,
26 "support-dump": provisioningserver.support_dump,
27 "upgrade-cluster": provisioningserver.upgrade_cluster,
28diff --git a/src/provisioningserver/boot/install_grub.py b/src/provisioningserver/boot/install_grub.py
29deleted file mode 100644
30index b210f5d..0000000
31--- a/src/provisioningserver/boot/install_grub.py
32+++ /dev/null
33@@ -1,37 +0,0 @@
34-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
35-# GNU Affero General Public License version 3 (see the file LICENSE).
36-
37-"""Install a GRUB2 pre-boot loader config for TFTP download."""
38-
39-__all__ = ["add_arguments", "run"]
40-
41-import os
42-
43-from provisioningserver.config import ClusterConfiguration
44-from provisioningserver.utils.fs import write_text_file
45-
46-CONFIG_FILE = """
47-# MAAS GRUB2 pre-loader configuration file
48-
49-# Load based on MAC address first.
50-configfile /grub/grub.cfg-${net_default_mac}
51-
52-# Failed to load based on MAC address.
53-# Load amd64 by default, UEFI only supported by 64-bit
54-configfile /grub/grub.cfg-default-amd64
55-"""
56-
57-
58-def add_arguments(parser):
59- pass
60-
61-
62-def run(args):
63- """Install a GRUB2 pre-boot loader config into the TFTP
64- directory structure.
65- """
66- with ClusterConfiguration.open() as config:
67- if not os.path.exists(config.grub_root):
68- os.makedirs(config.grub_root)
69- destination_file = os.path.join(config.grub_root, "grub.cfg")
70- write_text_file(destination_file, CONFIG_FILE)
71diff --git a/src/provisioningserver/boot/tests/test_install_grub.py b/src/provisioningserver/boot/tests/test_install_grub.py
72deleted file mode 100644
73index 9593069..0000000
74--- a/src/provisioningserver/boot/tests/test_install_grub.py
75+++ /dev/null
76@@ -1,30 +0,0 @@
77-# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
78-# GNU Affero General Public License version 3 (see the file LICENSE).
79-
80-"""Tests for the install_grub command."""
81-
82-__all__ = []
83-
84-import os.path
85-
86-from testtools.matchers import FileExists
87-
88-from maastesting.factory import factory
89-from maastesting.testcase import MAASTestCase
90-import provisioningserver.boot.install_grub
91-from provisioningserver.testing.config import ClusterConfigurationFixture
92-from provisioningserver.utils.script import MainScript
93-
94-
95-class TestInstallGrub(MAASTestCase):
96- def test_integration(self):
97- tftproot = self.make_dir()
98- self.useFixture(ClusterConfigurationFixture(tftp_root=tftproot))
99-
100- action = factory.make_name("action")
101- script = MainScript(action)
102- script.register(action, provisioningserver.boot.install_grub)
103- script.execute((action,))
104-
105- config_filename = os.path.join("grub", "grub.cfg")
106- self.assertThat(os.path.join(tftproot, config_filename), FileExists())
107diff --git a/src/provisioningserver/boot/tests/test_uefi_amd64.py b/src/provisioningserver/boot/tests/test_uefi_amd64.py
108index 33df035..c71f42e 100644
109--- a/src/provisioningserver/boot/tests/test_uefi_amd64.py
110+++ b/src/provisioningserver/boot/tests/test_uefi_amd64.py
111@@ -6,6 +6,7 @@
112 __all__ = []
113
114 import os
115+import random
116 import re
117 from unittest.mock import sentinel
118
119@@ -270,6 +271,8 @@ class TestUEFIAMD64BootMethodRegex(MAASTestCase):
120 )
121
122 def test_re_config_file_does_not_match_default_grub_config_file(self):
123+ # The default grub.cfg is on the filesystem let the normal handler
124+ # grab it.
125 self.assertIsNone(re_config_file.match(b"grub/grub.cfg"))
126
127 def test_re_config_file_with_default(self):
128@@ -302,6 +305,86 @@ class TestUEFIAMD64BootMethodRegex(MAASTestCase):
129 class TestUEFIAMD64BootMethod(MAASTestCase):
130 """Tests `provisioningserver.boot.uefi_amd64.UEFIAMD64BootMethod`."""
131
132+ def test_match_path_none(self):
133+ method = UEFIAMD64BootMethod()
134+ backend = random.choice(["http", "tftp"])
135+ self.assertIsNone(
136+ method.match_path(backend, factory.make_string().encode())
137+ )
138+
139+ def test_match_path_mac_colon(self):
140+ method = UEFIAMD64BootMethod()
141+ backend = random.choice(["http", "tftp"])
142+ mac = factory.make_mac_address()
143+ self.assertEqual(
144+ {"mac": mac.replace(":", "-")},
145+ method.match_path(backend, f"/grub/grub.cfg-{mac}".encode()),
146+ )
147+
148+ def test_match_path_mac_dash(self):
149+ method = UEFIAMD64BootMethod()
150+ backend = random.choice(["http", "tftp"])
151+ mac = factory.make_mac_address().replace(":", "-")
152+ self.assertEqual(
153+ {"mac": mac},
154+ method.match_path(backend, f"/grub/grub.cfg-{mac}".encode()),
155+ )
156+
157+ def test_match_path_arch(self):
158+ method = UEFIAMD64BootMethod()
159+ backend = random.choice(["http", "tftp"])
160+ arch = factory.make_string()
161+ self.assertEqual(
162+ {"arch": arch},
163+ method.match_path(
164+ backend, f"/grub/grub.cfg-default-{arch}".encode()
165+ ),
166+ )
167+
168+ def test_match_path_arch_x86_64(self):
169+ method = UEFIAMD64BootMethod()
170+ backend = random.choice(["http", "tftp"])
171+ self.assertEqual(
172+ {"arch": "amd64"},
173+ method.match_path(backend, b"/grub/grub.cfg-default-x86_64"),
174+ )
175+
176+ def test_match_path_arch_powerpc(self):
177+ method = UEFIAMD64BootMethod()
178+ backend = random.choice(["http", "tftp"])
179+ self.assertEqual(
180+ {"arch": "ppc64el"},
181+ method.match_path(backend, b"/grub/grub.cfg-default-powerpc"),
182+ )
183+
184+ def test_match_path_arch_ppc64(self):
185+ method = UEFIAMD64BootMethod()
186+ backend = random.choice(["http", "tftp"])
187+ self.assertEqual(
188+ {"arch": "ppc64el"},
189+ method.match_path(backend, b"/grub/grub.cfg-default-ppc64"),
190+ )
191+
192+ def test_match_path_arch_ppc64le(self):
193+ method = UEFIAMD64BootMethod()
194+ backend = random.choice(["http", "tftp"])
195+ self.assertEqual(
196+ {"arch": "ppc64el"},
197+ method.match_path(backend, b"/grub/grub.cfg-default-ppc64le"),
198+ )
199+
200+ def test_match_path_arch_subarch(self):
201+ method = UEFIAMD64BootMethod()
202+ backend = random.choice(["http", "tftp"])
203+ arch = factory.make_string()
204+ subarch = factory.make_string()
205+ self.assertEqual(
206+ {"arch": arch, "subarch": subarch},
207+ method.match_path(
208+ backend, f"/grub/grub.cfg-default-{arch}-{subarch}".encode()
209+ ),
210+ )
211+
212 def test_link_bootloader_creates_grub_cfg(self):
213 method = UEFIAMD64BootMethod()
214 with tempdir() as tmp:
215diff --git a/src/provisioningserver/boot/uefi_amd64.py b/src/provisioningserver/boot/uefi_amd64.py
216index aed8850..28cd89e 100644
217--- a/src/provisioningserver/boot/uefi_amd64.py
218+++ b/src/provisioningserver/boot/uefi_amd64.py
219@@ -1,4 +1,4 @@
220-# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
221+# Copyright 2014-2021 Canonical Ltd. This software is licensed under the
222 # GNU Affero General Public License version 3 (see the file LICENSE).
223
224 """UEFI AMD64 Boot Method"""
225@@ -25,11 +25,11 @@ CONFIG_FILE = dedent(
226 # MAAS GRUB2 pre-loader configuration file
227
228 # Load based on MAC address first.
229- configfile (pxe)/grub/grub.cfg-${net_default_mac}
230+ configfile /grub/grub.cfg-${net_default_mac}
231
232- # Failed to load based on MAC address.
233- # Load amd64 by default, UEFI only supported by 64-bit
234- configfile (pxe)/grub/grub.cfg-default-amd64
235+ # Failed to load based on MAC address. Load based on the CPU
236+ # architecture.
237+ configfile /grub/grub.cfg-default-${grub_cpu}
238 """
239 )
240
241@@ -37,7 +37,7 @@ CONFIG_FILE = dedent(
242 # format. Required for UEFI as GRUB2 only presents the MAC address
243 # in colon-seperated format.
244 re_mac_address_octet = r"[0-9a-f]{2}"
245-re_mac_address = re.compile(":".join(repeat(re_mac_address_octet, 6)))
246+re_mac_address = re.compile("[:-]".join(repeat(re_mac_address_octet, 6)))
247
248 # Match the grub/grub.cfg-* request for UEFI (aka. GRUB2)
249 re_config_file = r"""
250@@ -49,10 +49,10 @@ re_config_file = r"""
251 (?P<mac>{re_mac_address.pattern}) # Capture UEFI MAC.
252 | # or "default"
253 default
254- (?: # perhaps with specified arch, with a separator of '-'
255+ (?: # perhaps with specified arch, with a separator of '-'
256 [-](?P<arch>\w+) # arch
257 (?:-(?P<subarch>\w+))? # optional subarch
258- )?
259+ )?
260 )
261 $
262 """
263@@ -91,6 +91,14 @@ class UEFIAMD64BootMethod(BootMethod):
264 if mac is not None:
265 params["mac"] = mac.replace(":", "-")
266
267+ # MAAS uses Debian architectures while GRUB uses standard Linux
268+ # architectures.
269+ arch = params.get("arch")
270+ if arch == "x86_64":
271+ params["arch"] = "amd64"
272+ elif arch in {"powerpc", "ppc64", "ppc64le"}:
273+ params["arch"] = "ppc64el"
274+
275 return params
276
277 def get_reader(self, backend, kernel_params, **extra):

Subscribers

People subscribed via source and target branches