Merge lp:~allenap/maas/new-tftp-layout into lp:maas/trunk

Proposed by Gavin Panella on 2012-08-17
Status: Merged
Approved by: Gavin Panella on 2012-08-23
Approved revision: 891
Merged at revision: 915
Proposed branch: lp:~allenap/maas/new-tftp-layout
Merge into: lp:maas/trunk
Prerequisite: lp:~allenap/maas/pxe-template-lookup
Diff against target: 449 lines (+58/-129)
11 files modified
scripts/maas-import-pxe-files (+3/-5)
src/provisioningserver/dhcp/config.py (+2/-35)
src/provisioningserver/dhcp/tests/test_config.py (+3/-15)
src/provisioningserver/pxe/install_bootloader.py (+4/-14)
src/provisioningserver/pxe/tests/test_install_bootloader.py (+6/-12)
src/provisioningserver/pxe/tests/test_tftppath.py (+5/-16)
src/provisioningserver/pxe/tftppath.py (+14/-9)
src/provisioningserver/tests/test_maas_import_pxe_files.py (+16/-5)
src/provisioningserver/tests/test_tasks.py (+1/-1)
src/provisioningserver/tests/test_tftp.py (+4/-13)
src/provisioningserver/tftp.py (+0/-4)
To merge this branch: bzr merge lp:~allenap/maas/new-tftp-layout
Reviewer Review Type Date Requested Status
Julian Edwards (community) 2012-08-17 Approve on 2012-08-22
Review via email: mp+120182@code.launchpad.net

Commit Message

Entirely remove support for arch-dependent bootloaders.

Description of the Change

We can't assume that we control DHCP, and we can't reliably
differentiate between 32-bit and 64-bit machines there anyway, so we
must do it in the PXE config.

This branch doesn't quite get all the way there - a follow-up branch
will complete this work - but it lays more ground. In short, it
entirely removes support for arch-dependent bootloaders. It also
changes a couple of test helpers for maas-import-pxe-files to be based
on the functions in tftppath instead of redefining them again.

To post a comment you must log in.
Julian Edwards (julian-edwards) wrote :

Nice branch.

review: Approve
lp:~allenap/maas/new-tftp-layout updated on 2012-08-23
891. By Gavin Panella on 2012-08-23

Not using classes in DHCP any more.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'scripts/maas-import-pxe-files'
2--- scripts/maas-import-pxe-files 2012-08-15 12:54:49 +0000
3+++ scripts/maas-import-pxe-files 2012-08-23 08:19:22 +0000
4@@ -72,13 +72,11 @@
5 # version). Otherwise, leave the filesystem alone.
6 if [ -f pxelinux.0 ]
7 then
8- # TODO: Pass sub-architecture once we support those.
9 # TODO: pxelinux.0 is downloaded but there's no reason why it can't
10 # come from the syslinux package like chain.c32 is.
11- maas-provision install-pxe-bootloader \
12- --arch=$arch --loader='pxelinux.0'
13- maas-provision install-pxe-bootloader \
14- --arch=$arch --loader='/usr/lib/syslinux/chain.c32'
15+ maas-provision install-pxe-bootloader --loader='pxelinux.0'
16+ maas-provision install-pxe-bootloader \
17+ --loader='/usr/lib/syslinux/chain.c32'
18 fi
19 }
20
21
22=== modified file 'src/provisioningserver/dhcp/config.py'
23--- src/provisioningserver/dhcp/config.py 2012-08-17 04:28:56 +0000
24+++ src/provisioningserver/dhcp/config.py 2012-08-23 08:19:22 +0000
25@@ -26,28 +26,10 @@
26 """Exception raised for errors processing the DHCP config."""
27
28
29-# Architectures that we support netbooting for.
30-# Actually we don't really do amd64; those systems get to use the i386
31-# bootloader instead.
32-bootloader_architectures = [
33- ('amd64', 'generic'),
34- ('arm', 'highbank'),
35- ('i386', 'generic'),
36- ]
37-
38 template_content = dedent("""\
39- class "pxe" {
40- match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
41- filename "{{bootloaders['i386', 'generic']}}";
42- }
43- class "uboot-highbank" {
44- match if substring (option vendor-class-identifier, 0, 21) = \
45- "U-boot.armv7.highbank";
46- filename "{{bootloaders['arm', 'highbank']}}";
47- }
48-
49 subnet {{subnet}} netmask {{subnet_mask}} {
50 next-server {{next_server}};
51+ filename "{{bootloader}}";
52 option subnet-mask {{subnet_mask}};
53 option broadcast-address {{broadcast_ip}};
54 option domain-name-servers {{dns_servers}};
55@@ -66,20 +48,6 @@
56 template_content, name="%s.template" % __name__)
57
58
59-def compose_bootloaders():
60- """Compose "bootloaders" dict for substitution in the DHCP template.
61-
62- The bootloaders dict maps tuples of (architecture, subarchitecture) to
63- their respective TFTP bootloader paths.
64-
65- Thus, bootloaders['i386', 'generic'] yields the TFTP path for the i386
66- bootloader.
67- """
68- return {
69- (arch, subarch): compose_bootloader_path(arch, subarch)
70- for arch, subarch in bootloader_architectures}
71-
72-
73 def get_config(**params):
74 """Return a DHCP config file based on the supplied parameters.
75
76@@ -96,8 +64,7 @@
77 :param high_range: The last IP address in the range of IP addresses to
78 allocate
79 """
80- params['bootloaders'] = compose_bootloaders()
81-
82+ params['bootloader'] = compose_bootloader_path()
83 # This is a really simple substitution for now but it's encapsulated
84 # here so that its implementation can be changed later if required.
85 try:
86
87=== modified file 'src/provisioningserver/dhcp/tests/test_config.py'
88--- src/provisioningserver/dhcp/tests/test_config.py 2012-08-17 04:28:56 +0000
89+++ src/provisioningserver/dhcp/tests/test_config.py 2012-08-23 08:19:22 +0000
90@@ -14,7 +14,7 @@
91
92 from textwrap import dedent
93
94-from maastesting.matchers import ContainsAll
95+from maastesting.matchers import Contains
96 from provisioningserver.dhcp import config
97 from provisioningserver.pxe.tftppath import compose_bootloader_path
98 import tempita
99@@ -83,19 +83,7 @@
100 "name 'subnet' is not defined at line \d+ column \d+ "
101 "in file %s" % template.name))
102
103- def test_config_refers_to_PXE_for_supported_architectures(self):
104+ def test_config_refers_to_bootloader(self):
105 params = make_sample_params()
106- bootloaders = config.compose_bootloaders()
107- archs = [
108- ('i386', 'generic'),
109- ('arm', 'highbank'),
110- ]
111- paths = [bootloaders[arch] for arch in archs]
112 output = config.get_config(**params)
113- self.assertThat(output, ContainsAll(paths))
114-
115- def test_compose_bootloaders_lists_tftp_paths(self):
116- sample_arch = ('i386', 'generic')
117- self.assertEqual(
118- compose_bootloader_path(*sample_arch),
119- config.compose_bootloaders()[sample_arch])
120+ self.assertThat(output, Contains(compose_bootloader_path()))
121
122=== modified file 'src/provisioningserver/pxe/install_bootloader.py'
123--- src/provisioningserver/pxe/install_bootloader.py 2012-08-14 01:19:41 +0000
124+++ src/provisioningserver/pxe/install_bootloader.py 2012-08-23 08:19:22 +0000
125@@ -26,20 +26,16 @@
126 )
127
128
129-def make_destination(tftproot, arch, subarch):
130+def make_destination(tftproot):
131 """Locate a loader's destination, creating the directory if needed.
132
133 :param tftproot: The root directory served up by the TFTP server,
134 e.g. /var/lib/tftpboot/.
135- :param arch: Main architecture to locate the destination for.
136- :param subarch: Sub-architecture of the main architecture.
137 :return: Full path describing the directory that the installed loader
138- should end up having. For example, the loader for i386 (with
139- sub-architecture "generic") should install at
140- /maas/i386/generic/
141+ should end up having.
142 """
143 path = locate_tftp_path(
144- compose_bootloader_path(arch, subarch),
145+ compose_bootloader_path(),
146 tftproot=tftproot)
147 directory = os.path.dirname(path)
148 if not os.path.isdir(directory):
149@@ -89,12 +85,6 @@
150
151 def add_arguments(parser):
152 parser.add_argument(
153- '--arch', dest='arch', default=None,
154- help="Main system architecture that the bootloader is for.")
155- parser.add_argument(
156- '--subarch', dest='subarch', default='generic',
157- help="Sub-architecture of the main architecture [%(default)s].")
158- parser.add_argument(
159 '--loader', dest='loader', default=None,
160 help="PXE pre-boot loader to install.")
161
162@@ -106,6 +96,6 @@
163 """
164 config = Config.load(args.config_file)
165 tftproot = config["tftp"]["root"]
166- destination_path = make_destination(tftproot, args.arch, args.subarch)
167+ destination_path = make_destination(tftproot)
168 destination = os.path.join(destination_path, os.path.basename(args.loader))
169 install_bootloader(args.loader, destination)
170
171=== modified file 'src/provisioningserver/pxe/tests/test_install_bootloader.py'
172--- src/provisioningserver/pxe/tests/test_install_bootloader.py 2012-08-13 08:01:31 +0000
173+++ src/provisioningserver/pxe/tests/test_install_bootloader.py 2012-08-23 08:19:22 +0000
174@@ -47,18 +47,16 @@
175 self.useFixture(config_fixture)
176
177 loader = self.make_file()
178- arch = factory.make_name('arch')
179- subarch = factory.make_name('subarch')
180
181 action = factory.make_name("action")
182 script = MainScript(action)
183 script.register(action, provisioningserver.pxe.install_bootloader)
184 script.execute(
185- ("--config-file", config_fixture.filename, action, "--arch", arch,
186- "--subarch", subarch, "--loader", loader))
187+ ("--config-file", config_fixture.filename, action,
188+ "--loader", loader))
189
190 bootloader_filename = os.path.join(
191- os.path.dirname(compose_bootloader_path(arch, subarch)),
192+ os.path.dirname(compose_bootloader_path()),
193 os.path.basename(loader))
194 self.assertThat(
195 locate_tftp_path(
196@@ -67,17 +65,13 @@
197
198 def test_make_destination_creates_directory_if_not_present(self):
199 tftproot = self.make_dir()
200- arch = factory.make_name('arch')
201- subarch = factory.make_name('subarch')
202- dest = make_destination(tftproot, arch, subarch)
203+ dest = make_destination(tftproot)
204 self.assertThat(dest, DirExists())
205
206 def test_make_destination_returns_existing_directory(self):
207 tftproot = self.make_dir()
208- arch = factory.make_name('arch')
209- subarch = factory.make_name('subarch')
210- make_destination(tftproot, arch, subarch)
211- dest = make_destination(tftproot, arch, subarch)
212+ make_destination(tftproot)
213+ dest = make_destination(tftproot)
214 self.assertThat(dest, DirExists())
215
216 def test_install_bootloader_installs_new_bootloader(self):
217
218=== modified file 'src/provisioningserver/pxe/tests/test_tftppath.py'
219--- src/provisioningserver/pxe/tests/test_tftppath.py 2012-08-01 16:16:09 +0000
220+++ src/provisioningserver/pxe/tests/test_tftppath.py 2012-08-23 08:19:22 +0000
221@@ -39,20 +39,15 @@
222 self.useFixture(ConfigFixture(self.config))
223
224 def test_compose_config_path_follows_maas_pxe_directory_layout(self):
225- arch = factory.make_name('arch')
226- subarch = factory.make_name('subarch')
227 name = factory.make_name('config')
228 self.assertEqual(
229- 'maas/%s/%s/pxelinux.cfg/%02x-%s' % (
230- arch, subarch, ARP_HTYPE.ETHERNET, name),
231- compose_config_path(arch, subarch, name))
232+ 'maas/pxelinux.cfg/%02x-%s' % (ARP_HTYPE.ETHERNET, name),
233+ compose_config_path(name))
234
235 def test_compose_config_path_does_not_include_tftp_root(self):
236- arch = factory.make_name('arch')
237- subarch = factory.make_name('subarch')
238 name = factory.make_name('config')
239 self.assertThat(
240- compose_config_path(arch, subarch, name),
241+ compose_config_path(name),
242 Not(StartsWith(self.tftproot)))
243
244 def test_compose_image_path_follows_maas_pxe_directory_layout(self):
245@@ -74,17 +69,11 @@
246 Not(StartsWith(self.tftproot)))
247
248 def test_compose_bootloader_path_follows_maas_pxe_directory_layout(self):
249- arch = factory.make_name('arch')
250- subarch = factory.make_name('subarch')
251- self.assertEqual(
252- 'maas/%s/%s/pxelinux.0' % (arch, subarch),
253- compose_bootloader_path(arch, subarch))
254+ self.assertEqual('maas/pxelinux.0', compose_bootloader_path())
255
256 def test_compose_bootloader_path_does_not_include_tftp_root(self):
257- arch = factory.make_name('arch')
258- subarch = factory.make_name('subarch')
259 self.assertThat(
260- compose_bootloader_path(arch, subarch),
261+ compose_bootloader_path(),
262 Not(StartsWith(self.tftproot)))
263
264 def test_locate_tftp_path_prefixes_tftp_root(self):
265
266=== modified file 'src/provisioningserver/pxe/tftppath.py'
267--- src/provisioningserver/pxe/tftppath.py 2012-08-01 16:16:09 +0000
268+++ src/provisioningserver/pxe/tftppath.py 2012-08-23 08:19:22 +0000
269@@ -22,29 +22,34 @@
270 from provisioningserver.enum import ARP_HTYPE
271
272
273-def compose_bootloader_path(arch, subarch):
274- """Compose the TFTP path for a PXE pre-boot loader."""
275- return '/'.join(['maas', arch, subarch, 'pxelinux.0'])
276+def compose_bootloader_path():
277+ """Compose the TFTP path for a PXE pre-boot loader.
278+
279+ All Intel-like architectures will use `pxelinux.0`. Other architectures
280+ simulate PXELINUX and don't actually load `pxelinux.0`, but use its path
281+ to figure out where configuration files are located.
282+ """
283+ return "maas/pxelinux.0"
284
285
286 # TODO: move this; it is now only used for testing.
287-def compose_config_path(arch, subarch, name):
288+def compose_config_path(mac):
289 """Compose the TFTP path for a PXE configuration file.
290
291 The path returned is relative to the TFTP root, as it would be
292 identified by clients on the network.
293
294- :param arch: Main machine architecture.
295- :param subarch: Sub-architecture, or "generic" if there is none.
296- :param name: Configuration file's name.
297+ :param mac: A MAC address, in IEEE 802 hyphen-separated form,
298+ corresponding to the machine for which this configuration is
299+ relevant. This relates to PXELINUX's lookup protocol.
300 :return: Path for the corresponding PXE config file as exposed over
301 TFTP.
302 """
303 # Not using os.path.join: this is a TFTP path, not a native path. Yes, in
304 # practice for us they're the same. We always assume that the ARP HTYPE
305 # (hardware type) that PXELINUX sends is Ethernet.
306- return "maas/{arch}/{subarch}/pxelinux.cfg/{htype:02x}-{name}".format(
307- arch=arch, subarch=subarch, htype=ARP_HTYPE.ETHERNET, name=name)
308+ return "maas/pxelinux.cfg/{htype:02x}-{mac}".format(
309+ htype=ARP_HTYPE.ETHERNET, mac=mac)
310
311
312 def compose_image_path(arch, subarch, release, purpose):
313
314=== modified file 'src/provisioningserver/tests/test_maas_import_pxe_files.py'
315--- src/provisioningserver/tests/test_maas_import_pxe_files.py 2012-08-03 12:35:52 +0000
316+++ src/provisioningserver/tests/test_maas_import_pxe_files.py 2012-08-23 08:19:22 +0000
317@@ -21,6 +21,7 @@
318 age_file,
319 get_write_time,
320 )
321+from provisioningserver.pxe import tftppath
322 from provisioningserver.testing.config import ConfigFixture
323 from testtools.matchers import (
324 FileContains,
325@@ -55,14 +56,24 @@
326 'images', 'netboot', 'ubuntu-installer', arch)
327
328
329-def compose_tftp_path(tftproot, arch, *path):
330+def compose_tftp_bootloader_path(tftproot):
331+ """Compose path for MAAS TFTP bootloader."""
332+ return tftppath.locate_tftp_path(
333+ tftppath.compose_bootloader_path(), tftproot)
334+
335+
336+def compose_tftp_path(tftproot, arch, release, purpose, *path):
337 """Compose path for MAAS TFTP files for given architecture.
338
339 After the TFTP root directory and the architecture, just append any path
340 components you want to drill deeper, e.g. the release name to get to the
341 files for a specific release.
342 """
343- return os.path.join(tftproot, 'maas', arch, 'generic', *path)
344+ return os.path.join(
345+ tftppath.locate_tftp_path(
346+ tftppath.compose_image_path(arch, "generic", release, purpose),
347+ tftproot),
348+ *path)
349
350
351 class TestImportPXEFiles(TestCase):
352@@ -129,7 +140,7 @@
353 release = 'precise'
354 archive = self.make_downloads(arch=arch, release=release)
355 self.call_script(archive, self.tftproot, arch=arch, release=release)
356- tftp_path = compose_tftp_path(self.tftproot, arch, 'pxelinux.0')
357+ tftp_path = compose_tftp_bootloader_path(self.tftproot)
358 download_path = compose_download_dir(archive, arch, release)
359 expected_contents = read_file(download_path, 'pxelinux.0')
360 self.assertThat(tftp_path, FileContains(expected_contents))
361@@ -141,13 +152,13 @@
362 download_path = compose_download_dir(archive, arch, release)
363 os.remove(os.path.join(download_path, 'pxelinux.0'))
364 self.call_script(archive, self.tftproot, arch=arch, release=release)
365- tftp_path = compose_tftp_path(self.tftproot, arch, 'pxelinux.0')
366+ tftp_path = compose_tftp_bootloader_path(self.tftproot)
367 self.assertThat(tftp_path, Not(FileExists()))
368
369 def test_updates_pre_boot_loader(self):
370 arch = factory.make_name('arch')
371 release = 'precise'
372- tftp_path = compose_tftp_path(self.tftproot, arch, 'pxelinux.0')
373+ tftp_path = compose_tftp_bootloader_path(self.tftproot)
374 os.makedirs(os.path.dirname(tftp_path))
375 with open(tftp_path, 'w') as existing_file:
376 existing_file.write(factory.getRandomString())
377
378=== modified file 'src/provisioningserver/tests/test_tasks.py'
379--- src/provisioningserver/tests/test_tasks.py 2012-08-20 11:15:44 +0000
380+++ src/provisioningserver/tests/test_tasks.py 2012-08-23 08:19:22 +0000
381@@ -215,7 +215,7 @@
382 FileContains(
383 matcher=ContainsAll(
384 [
385- 'class "pxe"',
386+ "next-server ",
387 "option subnet-mask",
388 ])))
389 self.assertEqual(
390
391=== modified file 'src/provisioningserver/tests/test_tftp.py'
392--- src/provisioningserver/tests/test_tftp.py 2012-08-02 16:15:06 +0000
393+++ src/provisioningserver/tests/test_tftp.py 2012-08-23 08:19:22 +0000
394@@ -70,15 +70,10 @@
395 the expected groups from a match.
396 """
397 components = {
398- "arch": factory.make_name("arch"),
399- "subarch": factory.make_name("subarch"),
400+ "bootpath": b"maas", # Static.
401 "mac": factory.getRandomMACAddress(b"-"),
402 }
403- # The bootpath component is a superset of arch and subarch.
404- components["bootpath"] = "maas/{arch}/{subarch}".format(**components)
405- config_path = compose_config_path(
406- arch=components["arch"], subarch=components["subarch"],
407- name=components["mac"])
408+ config_path = compose_config_path(components["mac"])
409 return config_path, components
410
411 def test_re_config_file(self):
412@@ -168,10 +163,8 @@
413 def test_get_reader_config_file(self):
414 # For paths matching re_config_file, TFTPBackend.get_reader() returns
415 # a Deferred that will yield a BytesReader.
416- arch = factory.make_name("arch").encode("ascii")
417- subarch = factory.make_name("subarch").encode("ascii")
418 mac = factory.getRandomMACAddress(b"-")
419- config_path = compose_config_path(arch, subarch, mac)
420+ config_path = compose_config_path(mac)
421 backend = TFTPBackend(self.make_dir(), b"http://example.com/")
422
423 @partial(self.patch, backend, "get_config_reader")
424@@ -184,9 +177,7 @@
425 output = reader.read(10000)
426 # The expected parameters include bootpath; this is extracted from the
427 # file path by re_config_file.
428- expected_params = dict(
429- arch=arch, subarch=subarch, mac=mac,
430- bootpath="maas/%s/%s" % (arch, subarch))
431+ expected_params = dict(mac=mac, bootpath="maas")
432 observed_params = json.loads(output)
433 self.assertEqual(expected_params, observed_params)
434
435
436=== modified file 'src/provisioningserver/tftp.py'
437--- src/provisioningserver/tftp.py 2012-08-06 12:23:00 +0000
438+++ src/provisioningserver/tftp.py 2012-08-23 08:19:22 +0000
439@@ -87,10 +87,6 @@
440 ^/?
441 (?P<bootpath>
442 maas # Static namespacing.
443- /
444- (?P<arch>[^/]+) # Capture arch.
445- /
446- (?P<subarch>[^/]+) # Capture subarch.
447 ) # Capture boot directory.
448 /
449 pxelinux[.]cfg # PXELINUX expects this.