Merge lp:~jtv/maas/bug-1295479 into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 2213
Proposed branch: lp:~jtv/maas/bug-1295479
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 398 lines (+0/-372)
3 files modified
src/provisioningserver/tasks.py (+0/-6)
src/provisioningserver/tests/test_maas_import_pxe_files.py (+0/-347)
src/provisioningserver/tests/test_tasks.py (+0/-19)
To merge this branch: bzr merge lp:~jtv/maas/bug-1295479
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+213595@code.launchpad.net

Commit message

Remove remains of the old import script: tests, and the passing of archive URLs (not used by Simplestreams) through environment variables.

Description of the change

In a future change, I think it would also make sense to stop running maas-import-pxe-files as a script, and call its Python function from the task instead.

Jeroen

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This is so good.

> In a future change, I think it would also make sense to stop running
> maas-import-pxe-files as a script, and call its Python function from
> the task instead.

Yes, please.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 01 Apr 2014 10:58:00 you wrote:
> Review: Approve
>
> This is so good.
>
> > In a future change, I think it would also make sense to stop running
> > maas-import-pxe-files as a script, and call its Python function from
> > the task instead.
>
> Yes, please.

Yes yes yes! This was always the intention.

We need to make sure that the task is bulletproof and meets all the same use
cases. Also I'd like to do that work at the same time as we push all the settings
out of the yaml file and into the UI.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/tasks.py'
2--- src/provisioningserver/tasks.py 2014-03-26 13:26:23 +0000
3+++ src/provisioningserver/tasks.py 2014-04-01 06:39:20 +0000
4@@ -452,12 +452,6 @@
5 if http_proxy is not None:
6 env['http_proxy'] = http_proxy
7 env['https_proxy'] = http_proxy
8- if main_archive is not None:
9- env['MAIN_ARCHIVE'] = main_archive
10- if ports_archive is not None:
11- env['PORTS_ARCHIVE'] = ports_archive
12- if cloud_images_archive is not None:
13- env['CLOUD_IMAGES_ARCHIVE'] = cloud_images_archive
14 call_and_check(['sudo', '-n', '-E', 'maas-import-pxe-files'], env=env)
15 if callback is not None:
16 callback.delay()
17
18=== removed file 'src/provisioningserver/tests/test_maas_import_pxe_files.py'
19--- src/provisioningserver/tests/test_maas_import_pxe_files.py 2014-03-28 05:48:14 +0000
20+++ src/provisioningserver/tests/test_maas_import_pxe_files.py 1970-01-01 00:00:00 +0000
21@@ -1,347 +0,0 @@
22-# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
23-# GNU Affero General Public License version 3 (see the file LICENSE).
24-
25-"""Tests for the maas-import-pxe-files script."""
26-
27-from __future__ import (
28- absolute_import,
29- print_function,
30- unicode_literals,
31- )
32-
33-str = None
34-
35-__metaclass__ = type
36-__all__ = []
37-
38-import os
39-from subprocess import check_call
40-import unittest
41-
42-from maastesting import root
43-from maastesting.factory import factory
44-from maastesting.testcase import MAASTestCase
45-from maastesting.utils import (
46- age_file,
47- get_write_time,
48- )
49-from provisioningserver.boot import tftppath
50-from provisioningserver.boot.pxe import PXEBootMethod
51-from provisioningserver.boot.uefi import UEFIBootMethod
52-from provisioningserver.testing.config import set_tftp_root
53-from testtools.content import text_content
54-from testtools.matchers import (
55- DirExists,
56- FileContains,
57- Not,
58- )
59-
60-
61-def read_file(path, name):
62- """Return the contents of the file at `path/name`."""
63- with open(os.path.join(path, name)) as infile:
64- return infile.read()
65-
66-
67-def backdate(path):
68- """Set the last modification time for the file at `path` to the past."""
69- age_file(path, 9999999)
70-
71-
72-def compose_download_dir(archive, arch, release):
73- """Locate a bootloader, initrd, and kernel in an archive.
74-
75- :param archive: Archive directory (corresponding to the script's ARCHIVE
76- setting, except here it's a filesystem path not a URL).
77- :param arch: Architecture.
78- :param release: Ubuntu release name.
79- :return: A 2-tuple of:
80- * Base path of the images/ dir.
81- * Full absolute path to the directory holding the requisite files
82- for this archive, arch, and release.
83- """
84- if arch[0] in {'amd64', 'i386'}:
85- basepath = os.path.join(
86- archive, 'dists', release, 'main', 'installer-%s' % arch[0],
87- 'current', 'images')
88- fullpath = os.path.join(
89- basepath, 'netboot', 'ubuntu-installer', arch[0])
90- return basepath, fullpath
91- elif arch[0] == 'armhf':
92- basepath = os.path.join(
93- archive, 'dists', '%s-updates' % release, 'main',
94- 'installer-%s' % arch[0], 'current', 'images')
95- fullpath = os.path.join(basepath, arch[1], 'netboot')
96- return basepath, fullpath
97- else:
98- raise NotImplementedError('Unknown architecture: %r' % (arch, ))
99-
100-
101-def compose_download_kernel_name(arch, release):
102- """Name the kernel file expected to be found in a netboot archive.
103-
104- :param archive: Archive directory (corresponding to the script's ARCHIVE
105- setting, except here it's a filesystem path not a URL).
106- :param arch: Architecture.
107- :return: the kernel name string, eg. "vmlinuz" or "linux" as appropriate
108- """
109- if arch[0] in {'amd64', 'i386'}:
110- return 'linux'
111- elif arch[0] == 'armhf':
112- return 'vmlinuz'
113- else:
114- raise NotImplementedError('Unknown architecture: %r' % arch)
115-
116-
117-def compose_download_filenames(arch, release):
118- """Name files expected to be found in the directory of a netboot archive.
119-
120- :param archive: Archive directory (corresponding to the script's ARCHIVE
121- setting, except here it's a filesystem path not a URL).
122- :param arch: Architecture.
123- :return: list of names to be found in the netboot directory for this
124- particular arch and release. Eg: ['vmlinuz', 'initrd.gz'] or
125- ['linux', 'initrd.gz'] as appropriate.
126- """
127- return [compose_download_kernel_name(arch, release), 'initrd.gz']
128-
129-
130-def compose_uefi_dir(archive, release):
131- """File locations expected to be found in the archive for UEFI netboot.
132-
133- :param archive: Archive directory (corresponding to the script's ARCHIVE
134- setting, except here it's a filesystem path not a URL).
135- :param release: distro release
136- :return: A 2-tuple of:
137- * Base path of the current/ dir.
138- * Requisite files for this archive.
139- """
140- basepath = os.path.join(
141- archive, 'dists', release, 'main', 'uefi', 'grub2-amd64', 'current')
142- return basepath, ['grubnetx64.efi.signed']
143-
144-
145-def generate_md5sums(basepath):
146- """Write MD5SUMS file at basepath."""
147- script = [
148- "bash", "-c",
149- "cd %s;find . -type f | xargs md5sum > MD5SUMS" % basepath]
150-
151- with open(os.devnull, 'wb') as dev_null:
152- check_call(script, stdout=dev_null)
153-
154-
155-def compose_tftp_bootloader_path(tftproot):
156- """Compose path for MAAS TFTP bootloader."""
157- pxe_method = PXEBootMethod()
158- return tftppath.locate_tftp_path(
159- pxe_method.bootloader_path, tftproot)
160-
161-
162-def compose_tftp_uefi_bootloader_path(tftproot):
163- """Compose path for MAAS UEFI TFTP bootloader."""
164- uefi_method = UEFIBootMethod()
165- return tftppath.locate_tftp_path(
166- uefi_method.bootloader_path, tftproot)
167-
168-
169-def compose_tftp_path(tftproot, arch, release, label, purpose, *path):
170- """Compose path for MAAS TFTP files for given architecture.
171-
172- After the TFTP root directory and the architecture, just append any path
173- components you want to drill deeper, e.g. the release name to get to the
174- files for a specific release.
175- """
176- return os.path.join(
177- tftppath.locate_tftp_path(
178- tftppath.compose_image_path(
179- arch[0], arch[1], release, label, purpose),
180- tftproot),
181- *path)
182-
183-
184-class TestImportPXEFiles(MAASTestCase):
185-
186- # There are some differences between architectures, so run these tests for
187- # all supported architectures.
188- scenarios = (
189- ('i386/generic', {'arch': ('i386', 'generic')}),
190- ('amd64/generic', {'arch': ('amd64', 'generic')}),
191- ('armhf/highbank', {'arch': ('armhf', 'highbank')}),
192- )
193-
194- def setUp(self):
195- super(TestImportPXEFiles, self).setUp()
196- raise unittest.SkipTest(
197- "XXX rvb 2014-03-21 bug=1295479: Disabled. The "
198- "maas-import-pxe-files script has been replaced with a "
199- "new version to use simplestreams v2's data. These tests need "
200- "to be completely refactored.")
201- self.tftproot = self.make_dir()
202- self.config_fixture = self.useFixture(set_tftp_root(self.tftproot))
203-
204- def get_arch(self):
205- """Return an existing, supported architecture/subarchitecture pair."""
206- # Use the architecture which the current test scenario targets.
207- return self.arch
208-
209- def make_downloads(self, release=None, arch=None):
210- """Set up a directory with an image for "download" by the script.
211-
212- Returns an "ARCHIVE" URL for the download.
213- """
214- if release is None:
215- release = factory.make_name('release')
216- archive = self.make_dir()
217- basepath, download = compose_download_dir(archive, arch, release)
218- os.makedirs(download)
219- for filename in compose_download_filenames(arch, release):
220- factory.make_file(download, filename)
221- generate_md5sums(basepath)
222- uefi_archive, uefi_filenames = compose_uefi_dir(archive, release)
223- os.makedirs(uefi_archive)
224- for uefi in uefi_filenames:
225- factory.make_file(uefi_archive, uefi)
226- return archive
227-
228- def call_script(self, archive_dir, tftproot, arch=None, release=None):
229- """Call the maas-import-pxe-files script with given settings.
230-
231- The ARCHIVE URL and TFTPROOT path must be set, or the script will try
232- to download from the Ubuntu server and store into the system's real
233- TFTP root directory, respectively. Both bad ideas.
234- """
235- # TODO: Use path.py <http://pypi.python.org/pypi/path.py> instead, or
236- # something similar; this is tedious stuff.
237- script = os.path.join(root, "scripts", "maas-import-pxe-files")
238- path = [os.path.join(root, "bin"), os.path.join(root, "scripts")]
239- path.extend(os.environ.get("PATH", "").split(os.pathsep))
240- env = {
241- 'MAIN_ARCHIVE': 'file://%s' % archive_dir,
242- 'PORTS_ARCHIVE': 'file://%s' % archive_dir,
243- 'STABLE_RELEASE': release,
244- # Substitute curl for wget; it accepts file:// URLs.
245- 'DOWNLOAD': 'curl -O --silent',
246- 'PATH': os.pathsep.join(path),
247- # Suppress running of maas-import-ephemerals. It gets too
248- # intimate with the system to test here.
249- 'IMPORT_EPHEMERALS': '0',
250- # Suppress GPG checks as we can't sign the file from
251- # here.
252- 'IGNORE_GPG': '1',
253- # Don't check for broken efinet, so arch will be
254- # correct for the download.
255- 'CHECK_BROKEN_EFINET': '0',
256- # Skip shim.efi.sifned, as we don't want it to really
257- # download the package
258- 'SKIP_SHIM_SIGNED': '1',
259- }
260- env.update(self.config_fixture.environ)
261- if arch is not None:
262- env['ARCHES'] = '/'.join(arch)
263- if release is not None:
264- env['RELEASES'] = release
265- errfile = self.make_file()
266-
267- try:
268- with open(os.devnull, 'wb') as out, open(errfile, 'wb') as err:
269- check_call(script, env=env, stdout=out, stderr=err)
270- finally:
271- with open(errfile, 'r') as error_output:
272- self.addDetail('stderr', text_content(error_output.read()))
273-
274- def test_procures_pre_boot_loader(self):
275- arch = self.get_arch()
276- release = 'precise'
277- archive = self.make_downloads(arch=arch, release=release)
278- self.call_script(archive, self.tftproot, arch=arch, release=release)
279- tftp_path = compose_tftp_bootloader_path(self.tftproot)
280- expected_contents = read_file('/usr/lib/syslinux', 'pxelinux.0')
281- self.assertThat(tftp_path, FileContains(expected_contents))
282-
283- def test_updates_pre_boot_loader(self):
284- arch = self.get_arch()
285- release = 'precise'
286- tftp_path = compose_tftp_bootloader_path(self.tftproot)
287- with open(tftp_path, 'w') as existing_file:
288- existing_file.write(factory.getRandomString())
289- archive = self.make_downloads(arch=arch, release=release)
290- self.call_script(archive, self.tftproot, arch=arch, release=release)
291- expected_contents = read_file('/usr/lib/syslinux', 'pxelinux.0')
292- self.assertThat(tftp_path, FileContains(expected_contents))
293-
294- def test_procures_uefi_grubnet(self):
295- arch = self.get_arch()
296- release = 'precise'
297- archive = self.make_downloads(arch=arch, release=release)
298- self.call_script(archive, self.tftproot, arch=arch, release=release)
299- tftp_path = os.path.join(self.tftproot, 'grubx64.efi')
300- uefi_archive, _ = compose_uefi_dir(archive, release)
301- expected_contents = read_file(uefi_archive, 'grubnetx64.efi.signed')
302- self.assertThat(tftp_path, FileContains(expected_contents))
303-
304- def test_updates_uefi_grubnet(self):
305- arch = self.get_arch()
306- release = 'precise'
307- archive = self.make_downloads(arch=arch, release=release)
308- tftp_path = os.path.join(self.tftproot, 'grubx64.efi')
309- with open(tftp_path, 'w') as existing_file:
310- existing_file.write(factory.getRandomString())
311- self.call_script(archive, self.tftproot, arch=arch, release=release)
312- uefi_archive, _ = compose_uefi_dir(archive, release)
313- expected_contents = read_file(uefi_archive, 'grubnetx64.efi.signed')
314- self.assertThat(tftp_path, FileContains(expected_contents))
315-
316- def test_procures_install_image(self):
317- arch = self.get_arch()
318- release = 'precise'
319- archive = self.make_downloads(arch=arch, release=release)
320- self.call_script(archive, self.tftproot, arch=arch, release=release)
321- tftp_path = compose_tftp_path(
322- self.tftproot, arch, release, 'release', 'install', 'linux')
323- _, download_path = compose_download_dir(archive, arch, release)
324- expected_contents = read_file(
325- download_path, compose_download_kernel_name(arch, release))
326- self.assertThat(tftp_path, FileContains(expected_contents))
327-
328- def test_updates_install_image(self):
329- arch = self.get_arch()
330- release = 'precise'
331- tftp_path = compose_tftp_path(
332- self.tftproot, arch, release, 'release', 'install', 'linux')
333- os.makedirs(os.path.dirname(tftp_path))
334- with open(tftp_path, 'w') as existing_file:
335- existing_file.write(factory.getRandomString())
336- archive = self.make_downloads(arch=arch, release=release)
337- self.call_script(archive, self.tftproot, arch=arch, release=release)
338- _, download_path = compose_download_dir(archive, arch, release)
339- expected_contents = read_file(
340- download_path, compose_download_kernel_name(arch, release))
341- self.assertThat(tftp_path, FileContains(expected_contents))
342-
343- def test_leaves_install_image_untouched_if_unchanged(self):
344- arch = self.get_arch()
345- release = 'precise'
346- archive = self.make_downloads(arch=arch, release=release)
347- self.call_script(archive, self.tftproot, arch=arch, release=release)
348- tftp_path = compose_tftp_path(
349- self.tftproot, arch, release, 'release', 'install', 'linux')
350- backdate(tftp_path)
351- original_timestamp = get_write_time(tftp_path)
352- self.call_script(archive, self.tftproot, arch=arch, release=release)
353- self.assertEqual(original_timestamp, get_write_time(tftp_path))
354-
355- def test_survives_failed_download(self):
356- arch = self.get_arch()
357- release = 'precise'
358- archive = self.make_downloads(arch=arch, release=release)
359- # Initrd can't be found. This breaks the download.
360- _, download = compose_download_dir(archive, arch, release)
361- os.remove(os.path.join(download, 'initrd.gz'))
362-
363- self.call_script(archive, self.tftproot, arch=arch, release=release)
364-
365- # The script does not install the broken image, and does not fail.
366- installed_kernel = compose_tftp_path(
367- self.tftproot, arch, release, 'release', 'initrd.gz')
368- self.assertThat(installed_kernel, Not(DirExists()))
369
370=== modified file 'src/provisioningserver/tests/test_tasks.py'
371--- src/provisioningserver/tests/test_tasks.py 2014-03-28 04:31:32 +0000
372+++ src/provisioningserver/tests/test_tasks.py 2014-04-01 06:39:20 +0000
373@@ -641,25 +641,6 @@
374 self.assertThat(recorder, MockCalledOnceWith(
375 ['sudo', '-n', '-E', 'maas-import-pxe-files'], env=expected_env))
376
377- def test_import_boot_images_sets_archive_locations(self):
378- self.patch(tasks, 'call_and_check')
379- archives = {
380- 'main_archive': self.make_archive_url('main'),
381- 'ports_archive': self.make_archive_url('ports'),
382- 'cloud_images_archive': self.make_archive_url('cloud-images'),
383- }
384- expected_settings = {
385- parameter.upper(): value
386- for parameter, value in archives.items()}
387- import_boot_images(**archives)
388- env = tasks.call_and_check.call_args[1]['env']
389- archive_settings = {
390- variable: value
391- for variable, value in env.items()
392- if variable.endswith('_ARCHIVE')
393- }
394- self.assertEqual(expected_settings, archive_settings)
395-
396 def test_import_boot_images_calls_callback(self):
397 self.patch(tasks, 'call_and_check')
398 mock_callback = Mock()