Merge lp:~blake-rouse/maas/fix-1511437-and-1510120 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: 4433
Proposed branch: lp:~blake-rouse/maas/fix-1511437-and-1510120
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 266 lines (+106/-50)
5 files modified
etc/maas/templates/uefi/config.local.amd64.template (+2/-2)
src/provisioningserver/boot/powerkvm.py (+5/-1)
src/provisioningserver/boot/tests/test_powerkvm.py (+4/-0)
src/provisioningserver/boot/tests/test_uefi.py (+75/-2)
src/provisioningserver/boot/uefi.py (+20/-45)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1511437-and-1510120
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Mike Pontillo (community) Approve
Review via email: mp+276151@code.launchpad.net

Commit message

Fix UEFI to download grub from archive. Drive-by fix for powerkvm not user the user set ports archive. Fix chainloading grub from disk instead of requiring grubnet to have lvm modules.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good in general; I noticed some minor things you should look at.

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

Thanks for the quick review.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1003.7 KiB)

The attempt to merge lp:~blake-rouse/maas/fix-1511437-and-1510120 into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com trusty-security InRelease [64.4 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [98.0 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [242 kB]
Get:5 http://security.ubuntu.com trusty-security/universe Sources [31.0 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [357 kB]
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:8 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [638 kB]
Get:9 http://security.ubuntu.com trusty-security/universe amd64 Packages [117 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Get:11 http://security.ubuntu.com trusty-security/main Translation-en [194 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:12 http://security.ubuntu.com trusty-security/universe Translation-en [68.4 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 2,344 kB in 4s (576 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testres...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/maas/templates/uefi/config.local.amd64.template'
2--- etc/maas/templates/uefi/config.local.amd64.template 2014-05-01 13:43:14 +0000
3+++ etc/maas/templates/uefi/config.local.amd64.template 2015-10-29 17:57:28 +0000
4@@ -3,6 +3,6 @@
5
6 menuentry 'Local' {
7 echo 'Booting local disk...'
8- search --set=root --file /efi/ubuntu/grub.cfg
9- configfile /efi/ubuntu/grub.cfg
10+ search --set=root --file /efi/ubuntu/shimx64.efi
11+ chainloader /efi/ubuntu/shimx64.efi
12 }
13
14=== modified file 'src/provisioningserver/boot/powerkvm.py'
15--- src/provisioningserver/boot/powerkvm.py 2015-08-14 13:48:59 +0000
16+++ src/provisioningserver/boot/powerkvm.py 2015-10-29 17:57:28 +0000
17@@ -19,10 +19,12 @@
18 import glob
19 import os.path
20 from textwrap import dedent
21+from urlparse import urlparse
22
23 from provisioningserver.boot import (
24 BootMethod,
25 BootMethodInstallError,
26+ get_ports_archive_url,
27 utils,
28 )
29 from provisioningserver.boot.install_bootloader import install_bootloader
30@@ -61,10 +63,12 @@
31 """Installs the required files for PowerKVM/PowerVM booting into the
32 tftproot.
33 """
34+ ports_archive_url = get_ports_archive_url()
35+ archive_url = ports_archive_url.strip(urlparse(ports_archive_url).path)
36 with tempdir() as tmp:
37 # Download the grub-ieee1275-bin package
38 data, filename = utils.get_updates_package(
39- 'grub-ieee1275-bin', 'http://ports.ubuntu.com',
40+ 'grub-ieee1275-bin', archive_url,
41 'main', 'ppc64el')
42 if data is None:
43 raise BootMethodInstallError(
44
45=== modified file 'src/provisioningserver/boot/tests/test_powerkvm.py'
46--- src/provisioningserver/boot/tests/test_powerkvm.py 2015-05-07 18:14:38 +0000
47+++ src/provisioningserver/boot/tests/test_powerkvm.py 2015-10-29 17:57:28 +0000
48@@ -49,6 +49,7 @@
49
50 def test_install_bootloader_get_package_raises_error(self):
51 method = PowerKVMBootMethod()
52+ self.patch(powerkvm_module, 'get_ports_archive_url')
53 self.patch(utils, 'get_updates_package').return_value = (None, None)
54 self.assertRaises(
55 BootMethodInstallError, method.install_bootloader, None)
56@@ -67,6 +68,9 @@
57 finally:
58 pass
59
60+ mock_get_ports_archive_url = self.patch(
61+ powerkvm_module, 'get_ports_archive_url')
62+ mock_get_ports_archive_url.return_value = 'http://ports.ubuntu.com'
63 mock_get_updates_package = self.patch(utils, 'get_updates_package')
64 mock_get_updates_package.return_value = (data, filename)
65 self.patch(powerkvm_module, 'call_and_check')
66
67=== modified file 'src/provisioningserver/boot/tests/test_uefi.py'
68--- src/provisioningserver/boot/tests/test_uefi.py 2015-06-11 21:21:22 +0000
69+++ src/provisioningserver/boot/tests/test_uefi.py 2015-10-29 17:57:28 +0000
70@@ -14,11 +14,20 @@
71 __metaclass__ = type
72 __all__ = []
73
74+from contextlib import contextmanager
75+import os
76 import re
77
78 from maastesting.factory import factory
79+from maastesting.matchers import MockCallsMatch
80 from maastesting.testcase import MAASTestCase
81-from provisioningserver.boot import BytesReader
82+from mock import call
83+from provisioningserver.boot import (
84+ BootMethodInstallError,
85+ BytesReader,
86+ uefi as uefi_module,
87+ utils,
88+)
89 from provisioningserver.boot.tftppath import compose_image_path
90 from provisioningserver.boot.uefi import (
91 re_config_file,
92@@ -116,7 +125,7 @@
93 purpose="local", arch="amd64"),
94 }
95 output = method.get_reader(**options).read(10000)
96- self.assertIn("configfile /efi/ubuntu/grub.cfg", output)
97+ self.assertIn("chainloader /efi/ubuntu/shimx64.efi", output)
98
99 def test_get_reader_with_enlist_purpose(self):
100 # If purpose is "enlist", the config.enlist.template should be
101@@ -246,3 +255,67 @@
102 self.assertEqual(
103 {'mac': None, 'arch': arch, 'subarch': subarch},
104 match.groupdict())
105+
106+
107+class TestUEFIBootMethod(MAASTestCase):
108+ """Tests `provisioningserver.boot.uefi.UEFIBootMethod`."""
109+
110+ def test_install_bootloader_get_package_raises_error(self):
111+ method = UEFIBootMethod()
112+ self.patch(uefi_module, 'get_main_archive_url')
113+ self.patch(utils, 'get_updates_package').return_value = (None, None)
114+ self.assertRaises(
115+ BootMethodInstallError, method.install_bootloader, None)
116+
117+ def test_install_bootloader(self):
118+ method = UEFIBootMethod()
119+ shim_filename = factory.make_name('shim-signed')
120+ shim_data = factory.make_string()
121+ grub_filename = factory.make_name('grub-efi-amd64-signed')
122+ grub_data = factory.make_string()
123+ tmp = self.make_dir()
124+ dest = self.make_dir()
125+
126+ @contextmanager
127+ def tempdir():
128+ try:
129+ yield tmp
130+ finally:
131+ pass
132+
133+ mock_get_main_archive_url = self.patch(
134+ uefi_module, 'get_main_archive_url')
135+ mock_get_main_archive_url.return_value = 'http://archive.ubuntu.com'
136+ mock_get_updates_package = self.patch(utils, 'get_updates_package')
137+ mock_get_updates_package.side_effect = [
138+ (shim_data, shim_filename),
139+ (grub_data, grub_filename),
140+ ]
141+ self.patch(uefi_module, 'call_and_check')
142+ self.patch(uefi_module, 'tempdir').side_effect = tempdir
143+
144+ mock_install_bootloader = self.patch(
145+ uefi_module, 'install_bootloader')
146+
147+ method.install_bootloader(dest)
148+
149+ with open(os.path.join(tmp, shim_filename), 'rb') as stream:
150+ saved_shim_data = stream.read()
151+ self.assertEqual(shim_data, saved_shim_data)
152+
153+ with open(os.path.join(tmp, grub_filename), 'rb') as stream:
154+ saved_grub_data = stream.read()
155+ self.assertEqual(grub_data, saved_grub_data)
156+
157+ shim_expected = os.path.join(
158+ tmp, "usr", "lib", "shim", "shim.efi.signed")
159+ shim_dest_expected = os.path.join(dest, method.bootloader_path)
160+ grub_expected = os.path.join(
161+ tmp, "usr", "lib", "grub", "x86_64-efi-signed",
162+ "grubnetx64.efi.signed")
163+ grub_dest_expected = os.path.join(dest, "grubx64.efi")
164+ self.assertThat(
165+ mock_install_bootloader,
166+ MockCallsMatch(
167+ call(shim_expected, shim_dest_expected),
168+ call(grub_expected, grub_dest_expected)))
169
170=== modified file 'src/provisioningserver/boot/uefi.py'
171--- src/provisioningserver/boot/uefi.py 2015-08-14 13:48:59 +0000
172+++ src/provisioningserver/boot/uefi.py 2015-10-29 17:57:28 +0000
173@@ -20,8 +20,6 @@
174 import os.path
175 import re
176 from textwrap import dedent
177-import urllib2
178-from urlparse import urljoin
179
180 from provisioningserver.boot import (
181 BootMethod,
182@@ -39,8 +37,6 @@
183 from provisioningserver.utils.shell import call_and_check
184
185
186-ARCHIVE_PATH = "/main/uefi/grub2-amd64/current/grubnetx64.efi.signed"
187-
188 CONFIG_FILE = dedent("""
189 # MAAS GRUB2 pre-loader configuration file
190
191@@ -82,40 +78,6 @@
192 re_config_file = re.compile(re_config_file, re.VERBOSE)
193
194
195-def archive_grubnet_urls(main_url):
196- """Paths to try to download grubnetx64.efi.signed."""
197- release = utils.get_distro_release()
198- # grubnetx64 will not work below version trusty, as efinet is broken
199- # when loading kernel, force trusty. Note: This is only the grub version
200- # this should not block any of the previous release from running.
201- if release in ['lucid', 'precise', 'quantal', 'saucy']:
202- release = 'trusty'
203- if not main_url.endswith('/'):
204- main_url = main_url + '/'
205- dists_url = urljoin(main_url, 'dists')
206- for dist in ['%s-updates' % release, release]:
207- yield "%s/%s/%s" % (
208- dists_url.rstrip("/"),
209- dist,
210- ARCHIVE_PATH.rstrip("/"))
211-
212-
213-def download_grubnet(main_url, destination):
214- """Downloads grubnetx64.efi.signed from the archive."""
215- for url in archive_grubnet_urls(main_url):
216- try:
217- response = urllib2.urlopen(url)
218- # Okay, if it fails as the updates area might not hold
219- # that file.
220- except urllib2.URLError:
221- continue
222-
223- with open(destination, 'wb') as stream:
224- stream.write(response.read())
225- return True
226- return False
227-
228-
229 class UEFIBootMethod(BootMethod):
230
231 name = "uefi"
232@@ -184,14 +146,27 @@
233 os.path.join(tmp, 'usr', 'lib', 'shim', 'shim.efi.signed'),
234 os.path.join(destination, self.bootloader_path))
235
236- # Download grubnetx64 from the archive and install
237- grub_tmp = os.path.join(tmp, 'grubnetx64.efi.signed')
238- if download_grubnet(archive_url, grub_tmp) is False:
239+ # Download the grub-efi-amd64-signed package.
240+ data, filename = utils.get_updates_package(
241+ 'grub-efi-amd64-signed', archive_url, 'main', 'amd64')
242+ if data is None:
243 raise BootMethodInstallError(
244- 'Failed to download grubnetx64.efi.signed '
245- 'from the archive.')
246- grub_dst = os.path.join(destination, 'grubx64.efi')
247- install_bootloader(grub_tmp, grub_dst)
248+ 'Failed to download grub-efi-amd64-signed package from '
249+ 'the archive.')
250+ grub_output = os.path.join(tmp, filename)
251+ with open(grub_output, 'wb') as stream:
252+ stream.write(data)
253+
254+ # Extract the package with dpkg.
255+ call_and_check(["dpkg", "-x", grub_output, tmp])
256+
257+ # Install the grub boot loader.
258+ grub_signed = os.path.join(
259+ tmp, 'usr', 'lib', 'grub', 'x86_64-efi-signed',
260+ 'grubnetx64.efi.signed')
261+ install_bootloader(
262+ grub_signed,
263+ os.path.join(destination, 'grubx64.efi'))
264
265 config_path = os.path.join(destination, 'grub')
266 config_dst = os.path.join(config_path, 'grub.cfg')