Merge lp:~blake-rouse/maas/ppc64el-grub into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 2323
Proposed branch: lp:~blake-rouse/maas/ppc64el-grub
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 320 lines (+216/-8)
10 files modified
etc/maas/templates/commissioning-user-data/snippets/maas_enlist.sh (+1/-1)
etc/maas/templates/uefi/config.commissioning.template (+2/-4)
etc/maas/templates/uefi/config.install.template (+0/-1)
etc/maas/templates/uefi/config.local.amd64.template (+1/-1)
etc/maas/templates/uefi/config.local.ppc64el.template (+8/-0)
src/provisioningserver/boot/__init__.py (+2/-0)
src/provisioningserver/boot/ppc64el.py (+107/-0)
src/provisioningserver/boot/tests/test_ppc64el.py (+92/-0)
src/provisioningserver/boot/tests/test_uefi.py (+2/-1)
src/provisioningserver/driver/__init__.py (+1/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/ppc64el-grub
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Andres Rodriguez (community) Approve
Review via email: mp+215711@code.launchpad.net

Commit message

Hardware enablement support for PowerKVM

Description of the change

Hardware enablement for PowerKVM. Installs the correct grub boot loader files and modifies the dhcpd config to allow booting of PPC64EL. The UEFIBootMethod provides the rest of the support for grub booting.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Oh and please backport this to 1.5

Revision history for this message
Gavin Panella (allenap) wrote :

Looks good. The first 6 comments are all fairly trivial but [7] is a
blocker.

[1]

+    echo   '{{kernel_params | kernel_command}} BOOTIF=01-'${net_default_mac}

If the kernel command has any single quotes in it this might do funny
things. I see this kind of thing done in many places. I'm provisionally
calling it hope-quoting as of now. Omit this line if there's no
satisfactory way of quoting/escaping the kernel command that'll work
with the boot config's echo command.

[2]

+menuentry 'Local' {
+    echo 'Booting local disk ...'

Minor, trivial, point, but the ellipsis is separated by one space from
the sentence. Compare with:

 menuentry 'Commission' {
     echo   'Booting under MAAS direction...'

Can you make it consistent?

[3]

+GRUB_CONFIG = dedent("""
+    configfile (pxe)/grub/grub.cfg-${net_default_mac}
+    configfile (pxe)/grub/grub.cfg-default-ppc64el
+    """)

Remove the leading blank line with a back-slash:

GRUB_CONFIG = dedent("""\
    configfile (pxe)/grub/grub.cfg-${net_default_mac}
    configfile (pxe)/grub/grub.cfg-default-ppc64el
    """)

[4]

+    def match_config_path(self, path):
+        """Doesn't need to do anything, as the UEFIBootMethod provides
+        the grub implementation needed.
+        """
+        return None
+
+    def render_config(self, kernel_params, **extra):
+        """Doesn't need to do anything, as the UEFIBootMethod provides
+        the grub implementation needed.
+        """
+        return ""

I don't understand the docstrings here. Can you elaborate? If I'm simply
being stupid, just reply with the answer, otherwise please update the
docstring.

[5]

+            for module in glob.glob(os.path.join(module_dir, '*.mod')):
+                modules.append(os.path.splitext(os.path.basename(module))[0])

For clarity, can you do this in a few more lines, e.g.:

            for module_path in glob.glob(os.path.join(module_dir, '*.mod')):
                module_filename = os.path.basename(module_path)
                module_name, _ = os.path.splitext(module_filename)
                modules.append(module_name)

[6]

         options = {
-            "kernel_params": make_kernel_parameters(purpose="local"),
+            "kernel_params": make_kernel_parameters(
+                purpose="local", arch="amd64"),
             }

Why this change?

[7]

There are no tests.

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

[1,2,3] Fixed

[4] The methods actually do nothing as the UEFIBootMethod class provides the implementation.

[5] Fixed

[6] Changed as the wrong config would be used for the test.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

[7] Tests added.

Revision history for this message
Gavin Panella (allenap) wrote :

Looks good! I think [8] needs fixing, but I'll mark this approved
because I don't want to delay you once you've fixed it.

[8]

+        ppc64el_module.tempdir = tempdir

Use self.patch() here too; I think this will leak otherwise.

[9]

+        tmp = tempfile.mkdtemp('', 'maas-test-')
+        dest = tempfile.mkdtemp('', 'maas-test-')
...
+        rmtree(tmp)
+        rmtree(dest)

Fwiw, the call to rmtree should be put into a clean-up as soon as the
directory is created, so that a test failure doesn't leave detritus
around. For example:

        tmp = tempfile.mkdtemp('', 'maas-test-')
        self.addCleanup(rmtree, tmp)

However, look at MAASTestCase.make_dir(). This does what you need.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/templates/commissioning-user-data/snippets/maas_enlist.sh'
2--- etc/maas/templates/commissioning-user-data/snippets/maas_enlist.sh 2014-04-25 18:03:38 +0000
3+++ etc/maas/templates/commissioning-user-data/snippets/maas_enlist.sh 2014-05-15 17:23:24 +0000
4@@ -61,7 +61,7 @@
5 get_host_subarchitecture() {
6 local arch=$1
7 case $arch in
8- i386|amd64)
9+ i386|amd64|ppc64el)
10 # Skip the call to archdetect as that's what
11 # get_host_architecture does
12 echo generic
13
14=== modified file 'etc/maas/templates/uefi/config.commissioning.template'
15--- etc/maas/templates/uefi/config.commissioning.template 2014-03-18 17:41:14 +0000
16+++ etc/maas/templates/uefi/config.commissioning.template 2014-05-15 17:23:24 +0000
17@@ -1,10 +1,8 @@
18 set default="0"
19 set timeout=0
20
21-# Force AMD64 for commissioning as UEFI only supports AMD64 currently.
22 menuentry 'Commission' {
23 echo 'Booting under MAAS direction...'
24- echo '{{kernel_params() | kernel_command}} BOOTIF=01-'${net_default_mac}
25- linux {{kernel_params(arch="amd64") | kernel_path }} {{kernel_params(arch="amd64") | kernel_command}} BOOTIF=01-${net_default_mac}
26- initrd {{kernel_params(arch="amd64") | initrd_path }}
27+ linux {{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac}
28+ initrd {{kernel_params | initrd_path }}
29 }
30
31=== modified file 'etc/maas/templates/uefi/config.install.template'
32--- etc/maas/templates/uefi/config.install.template 2014-03-04 16:59:37 +0000
33+++ etc/maas/templates/uefi/config.install.template 2014-05-15 17:23:24 +0000
34@@ -3,7 +3,6 @@
35
36 menuentry 'Install' {
37 echo 'Booting under MAAS direction...'
38- echo '{{kernel_params | kernel_command}} BOOTIF=01-'${net_default_mac}
39 linux {{kernel_params | kernel_path }} {{kernel_params | kernel_command}} BOOTIF=01-${net_default_mac}
40 initrd {{kernel_params | initrd_path }}
41 }
42
43=== renamed file 'etc/maas/templates/uefi/config.local.template' => 'etc/maas/templates/uefi/config.local.amd64.template'
44--- etc/maas/templates/uefi/config.local.template 2014-03-28 19:03:46 +0000
45+++ etc/maas/templates/uefi/config.local.amd64.template 2014-05-15 17:23:24 +0000
46@@ -2,7 +2,7 @@
47 set timeout=0
48
49 menuentry 'Local' {
50- echo 'Booting local disk ...'
51+ echo 'Booting local disk...'
52 search --set=root --file /efi/ubuntu/grub.cfg
53 configfile /efi/ubuntu/grub.cfg
54 }
55
56=== added file 'etc/maas/templates/uefi/config.local.ppc64el.template'
57--- etc/maas/templates/uefi/config.local.ppc64el.template 1970-01-01 00:00:00 +0000
58+++ etc/maas/templates/uefi/config.local.ppc64el.template 2014-05-15 17:23:24 +0000
59@@ -0,0 +1,8 @@
60+set default="0"
61+set timeout=0
62+
63+menuentry 'Local' {
64+ echo 'Booting local disk...'
65+ search --set=root --file /boot/grub/grub.cfg
66+ configfile /boot/grub/grub.cfg
67+}
68
69=== modified file 'src/provisioningserver/boot/__init__.py'
70--- src/provisioningserver/boot/__init__.py 2014-04-24 14:09:58 +0000
71+++ src/provisioningserver/boot/__init__.py 2014-05-15 17:23:24 +0000
72@@ -202,11 +202,13 @@
73 # Import the supported boot methods after defining BootMethod.
74 from provisioningserver.boot.pxe import PXEBootMethod
75 from provisioningserver.boot.uefi import UEFIBootMethod
76+from provisioningserver.boot.ppc64el import PPC64ELBootMethod
77
78
79 builtin_boot_methods = [
80 PXEBootMethod(),
81 UEFIBootMethod(),
82+ PPC64ELBootMethod(),
83 ]
84 for method in builtin_boot_methods:
85 BootMethodRegistry.register_item(method.name, method)
86
87=== added file 'src/provisioningserver/boot/ppc64el.py'
88--- src/provisioningserver/boot/ppc64el.py 1970-01-01 00:00:00 +0000
89+++ src/provisioningserver/boot/ppc64el.py 2014-05-15 17:23:24 +0000
90@@ -0,0 +1,107 @@
91+# Copyright 2014 Canonical Ltd. This software is licensed under the
92+# GNU Affero General Public License version 3 (see the file LICENSE).
93+
94+"""PPC64EL Boot Method"""
95+
96+from __future__ import (
97+ absolute_import,
98+ print_function,
99+ unicode_literals,
100+ )
101+
102+str = None
103+
104+__metaclass__ = type
105+__all__ = [
106+ 'PPC64ELBootMethod',
107+ ]
108+
109+import glob
110+import os.path
111+from textwrap import dedent
112+
113+from provisioningserver.boot import (
114+ BootMethod,
115+ BootMethodInstallError,
116+ utils,
117+ )
118+from provisioningserver.boot.install_bootloader import install_bootloader
119+from provisioningserver.utils import (
120+ call_and_check,
121+ tempdir,
122+ )
123+
124+
125+GRUB_CONFIG = dedent("""\
126+ configfile (pxe)/grub/grub.cfg-${net_default_mac}
127+ configfile (pxe)/grub/grub.cfg-default-ppc64el
128+ """)
129+
130+
131+class PPC64ELBootMethod(BootMethod):
132+
133+ name = "ppc64el"
134+ template_subdir = "ppc64el"
135+ bootloader_path = "bootppc64.bin"
136+ arch_octet = "00:0C"
137+
138+ def match_config_path(self, path):
139+ """Doesn't need to do anything, as the UEFIBootMethod provides
140+ the grub implementation needed.
141+ """
142+ return None
143+
144+ def render_config(self, kernel_params, **extra):
145+ """Doesn't need to do anything, as the UEFIBootMethod provides
146+ the grub implementation needed.
147+ """
148+ return ""
149+
150+ def install_bootloader(self, destination):
151+ """Installs the required files for PPC64EL booting into the
152+ tftproot.
153+ """
154+ with tempdir() as tmp:
155+ # Download the grub-ieee1275-bin package
156+ data, filename = utils.get_updates_package(
157+ 'grub-ieee1275-bin', 'http://ports.ubuntu.com',
158+ 'main', 'ppc64el')
159+ if data is None:
160+ raise BootMethodInstallError(
161+ 'Failed to download grub-ieee1275-bin package from '
162+ 'the archive.')
163+ grub_output = os.path.join(tmp, filename)
164+ with open(grub_output, 'wb') as stream:
165+ stream.write(data)
166+
167+ # Extract the package with dpkg, and install the shim
168+ call_and_check(["dpkg", "-x", grub_output, tmp])
169+
170+ # Output the embedded config, so grub-mkimage can use it
171+ config_output = os.path.join(tmp, 'grub.cfg')
172+ with open(config_output, 'wb') as stream:
173+ stream.write(GRUB_CONFIG.encode('utf-8'))
174+
175+ # Get list of grub modules
176+ module_dir = os.path.join(
177+ tmp, 'usr', 'lib', 'grub', 'powerpc-ieee1275')
178+ modules = []
179+ for module_path in glob.glob(os.path.join(module_dir, '*.mod')):
180+ module_filename = os.path.basename(module_path)
181+ module_name, _ = os.path.splitext(module_filename)
182+ modules.append(module_name)
183+
184+ # Generate the grub bootloader
185+ mkimage_output = os.path.join(tmp, self.bootloader_path)
186+ args = [
187+ 'grub-mkimage',
188+ '-o', mkimage_output,
189+ '-O', 'powerpc-ieee1275',
190+ '-d', module_dir,
191+ '-c', config_output,
192+ ]
193+ call_and_check(args + modules)
194+
195+ install_bootloader(
196+ mkimage_output,
197+ os.path.join(destination, self.bootloader_path))
198
199=== added file 'src/provisioningserver/boot/tests/test_ppc64el.py'
200--- src/provisioningserver/boot/tests/test_ppc64el.py 1970-01-01 00:00:00 +0000
201+++ src/provisioningserver/boot/tests/test_ppc64el.py 2014-05-15 17:23:24 +0000
202@@ -0,0 +1,92 @@
203+# Copyright 2014 Canonical Ltd. This software is licensed under the
204+# GNU Affero General Public License version 3 (see the file LICENSE).
205+
206+"""Tests for `provisioningserver.boot.ppc64el`."""
207+
208+from __future__ import (
209+ absolute_import,
210+ print_function,
211+ unicode_literals,
212+ )
213+
214+str = None
215+
216+__metaclass__ = type
217+__all__ = []
218+
219+from contextlib import contextmanager
220+import os
221+
222+from maastesting.factory import factory
223+from maastesting.matchers import MockCalledOnceWith
224+from maastesting.testcase import MAASTestCase
225+from provisioningserver.boot import (
226+ BootMethodInstallError,
227+ ppc64el as ppc64el_module,
228+ utils,
229+ )
230+from provisioningserver.boot.ppc64el import (
231+ GRUB_CONFIG,
232+ PPC64ELBootMethod,
233+ )
234+from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
235+
236+
237+class TestPPC64ELBootMethod(MAASTestCase):
238+ """Tests `provisioningserver.boot.ppc64el.PPC64ELBootMethod`."""
239+
240+ def test_match_config_path_returns_none(self):
241+ method = PPC64ELBootMethod()
242+ paths = [factory.getRandomString() for _ in range(3)]
243+ for path in paths:
244+ self.assertEqual(None, method.match_config_path(path))
245+
246+ def test_render_config_returns_empty_string(self):
247+ method = PPC64ELBootMethod()
248+ params = [make_kernel_parameters() for _ in range(3)]
249+ for param in params:
250+ self.assertEqual("", method.render_config(params))
251+
252+ def test_install_bootloader_get_package_raises_error(self):
253+ method = PPC64ELBootMethod()
254+ self.patch(utils, 'get_updates_package').return_value = (None, None)
255+ self.assertRaises(
256+ BootMethodInstallError, method.install_bootloader, None)
257+
258+ def test_install_bootloader(self):
259+ method = PPC64ELBootMethod()
260+ filename = factory.make_name('dpkg')
261+ data = factory.getRandomString()
262+ tmp = self.make_dir()
263+ dest = self.make_dir()
264+
265+ @contextmanager
266+ def tempdir():
267+ try:
268+ yield tmp
269+ finally:
270+ pass
271+
272+ mock_get_updates_package = self.patch(utils, 'get_updates_package')
273+ mock_get_updates_package.return_value = (data, filename)
274+ self.patch(ppc64el_module, 'call_and_check')
275+ self.patch(ppc64el_module, 'tempdir').side_effect = tempdir
276+
277+ mock_install_bootloader = self.patch(
278+ ppc64el_module, 'install_bootloader')
279+
280+ method.install_bootloader(dest)
281+
282+ with open(os.path.join(tmp, filename), 'rb') as stream:
283+ saved_data = stream.read()
284+ self.assertEqual(data, saved_data)
285+
286+ with open(os.path.join(tmp, 'grub.cfg'), 'rb') as stream:
287+ saved_config = stream.read().decode('utf-8')
288+ self.assertEqual(GRUB_CONFIG, saved_config)
289+
290+ mkimage_expected = os.path.join(tmp, method.bootloader_path)
291+ dest_expected = os.path.join(dest, method.bootloader_path)
292+ self.assertThat(
293+ mock_install_bootloader,
294+ MockCalledOnceWith(mkimage_expected, dest_expected))
295
296=== modified file 'src/provisioningserver/boot/tests/test_uefi.py'
297--- src/provisioningserver/boot/tests/test_uefi.py 2014-04-24 14:09:58 +0000
298+++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-15 17:23:24 +0000
299@@ -107,7 +107,8 @@
300 # used.
301 method = UEFIBootMethod()
302 options = {
303- "kernel_params": make_kernel_parameters(purpose="local"),
304+ "kernel_params": make_kernel_parameters(
305+ purpose="local", arch="amd64"),
306 }
307 output = method.render_config(**options)
308 self.assertIn("configfile /efi/ubuntu/grub.cfg", output)
309
310=== modified file 'src/provisioningserver/driver/__init__.py'
311--- src/provisioningserver/driver/__init__.py 2014-05-01 19:25:38 +0000
312+++ src/provisioningserver/driver/__init__.py 2014-05-15 17:23:24 +0000
313@@ -204,6 +204,7 @@
314 Architecture(
315 name="armhf/generic", description="armhf/generic",
316 pxealiases=["arm"], kernel_options=["console=ttyAMA0"]),
317+ Architecture(name="ppc64el/generic", description="ppc64el"),
318 ]
319 for arch in builtin_architectures:
320 ArchitectureRegistry.register_item(arch.name, arch)