Merge lp:~blake-rouse/maas/tftp-backend-readers 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: 2378
Proposed branch: lp:~blake-rouse/maas/tftp-backend-readers
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 635 lines (+117/-100)
10 files modified
src/provisioningserver/boot/__init__.py (+26/-6)
src/provisioningserver/boot/powerkvm.py (+3/-3)
src/provisioningserver/boot/pxe.py (+7/-4)
src/provisioningserver/boot/tests/test_boot.py (+4/-3)
src/provisioningserver/boot/tests/test_powerkvm.py (+4/-4)
src/provisioningserver/boot/tests/test_pxe.py (+23/-16)
src/provisioningserver/boot/tests/test_uefi.py (+14/-10)
src/provisioningserver/boot/uefi.py (+7/-4)
src/provisioningserver/tests/test_tftp.py (+21/-20)
src/provisioningserver/tftp.py (+8/-30)
To merge this branch: bzr merge lp:~blake-rouse/maas/tftp-backend-readers
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+221138@code.launchpad.net

Commit message

Change BootMethods to return their own IReader per-request, update method names to reflect new usage.

Description of the change

BootMethods now return an IReader to the tftp backend, instead of a string. This allows the boot methods, to respond with configuration files and binary files. This change is will be used by the WindowsBootMethod and the PowerNVBootMethod.

Re-factored the BootMethod class method's to reflect their new usage.

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

Looks good.

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

Yes, a later branch will use backend.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/boot/__init__.py'
2--- src/provisioningserver/boot/__init__.py 2014-05-23 14:12:26 +0000
3+++ src/provisioningserver/boot/__init__.py 2014-05-27 19:49:46 +0000
4@@ -23,6 +23,7 @@
5 abstractproperty,
6 )
7 from errno import ENOENT
8+from io import BytesIO
9 from os import path
10
11 from provisioningserver.boot.tftppath import compose_image_path
12@@ -30,6 +31,23 @@
13 from provisioningserver.utils import locate_config
14 from provisioningserver.utils.registry import Registry
15 import tempita
16+from tftp.backend import IReader
17+from zope.interface import implementer
18+
19+
20+@implementer(IReader)
21+class BytesReader:
22+
23+ def __init__(self, data):
24+ super(BytesReader, self).__init__()
25+ self.buffer = BytesIO(data)
26+ self.size = len(data)
27+
28+ def read(self, size):
29+ return self.buffer.read(size)
30+
31+ def finish(self):
32+ self.buffer.close()
33
34
35 class BootMethodError(Exception):
36@@ -100,22 +118,24 @@
37 """
38
39 @abstractmethod
40- def match_config_path(self, path):
41- """Checks path for the configuration file that needs to be
42- generated.
43+ def match_path(self, backend, path):
44+ """Checks path for a file the boot method needs to handle.
45
46+ :param backend: requesting backend
47 :param path: requested path
48 :returns: dict of match params from path, None if no match
49 """
50
51 @abstractmethod
52- def render_config(self, kernel_params, **extra):
53- """Render a configuration file as a unicode string.
54+ def get_reader(self, backend, kernel_params, **extra):
55+ """Gets the reader the backend will use for this combination of
56+ boot method, kernel parameters, and extra parameters.
57
58+ :param backend: requesting backend
59 :param kernel_params: An instance of `KernelParameters`.
60 :param extra: Allow for other arguments. This is a safety valve;
61 parameters generated in another component (for example, see
62- `TFTPBackend.get_config_reader`) won't cause this to break.
63+ `TFTPBackend.get_boot_method_reader`) won't cause this to break.
64 """
65
66 @abstractmethod
67
68=== modified file 'src/provisioningserver/boot/powerkvm.py'
69--- src/provisioningserver/boot/powerkvm.py 2014-05-23 14:12:26 +0000
70+++ src/provisioningserver/boot/powerkvm.py 2014-05-27 19:49:46 +0000
71@@ -45,17 +45,17 @@
72 bootloader_path = "bootppc64.bin"
73 arch_octet = "00:0C"
74
75- def match_config_path(self, path):
76+ def match_path(self, backend, path):
77 """Doesn't need to do anything, as the UEFIBootMethod provides
78 the grub implementation needed.
79 """
80 return None
81
82- def render_config(self, kernel_params, **extra):
83+ def get_reader(self, backend, kernel_params, **extra):
84 """Doesn't need to do anything, as the UEFIBootMethod provides
85 the grub implementation needed.
86 """
87- return ""
88+ return None
89
90 def install_bootloader(self, destination):
91 """Installs the required files for PowerKVM booting into the
92
93=== modified file 'src/provisioningserver/boot/pxe.py'
94--- src/provisioningserver/boot/pxe.py 2014-03-28 04:06:27 +0000
95+++ src/provisioningserver/boot/pxe.py 2014-05-27 19:49:46 +0000
96@@ -22,6 +22,7 @@
97
98 from provisioningserver.boot import (
99 BootMethod,
100+ BytesReader,
101 get_parameters,
102 )
103 from provisioningserver.boot.install_bootloader import install_bootloader
104@@ -78,10 +79,11 @@
105 bootloader_path = "pxelinux.0"
106 arch_octet = "00:00"
107
108- def match_config_path(self, path):
109+ def match_path(self, backend, path):
110 """Checks path for the configuration file that needs to be
111 generated.
112
113+ :param backend: requesting backend
114 :param path: requested path
115 :returns: dict of match params from path, None if no match
116 """
117@@ -90,19 +92,20 @@
118 return None
119 return get_parameters(match)
120
121- def render_config(self, kernel_params, **extra):
122+ def get_reader(self, backend, kernel_params, **extra):
123 """Render a configuration file as a unicode string.
124
125+ :param backend: requesting backend
126 :param kernel_params: An instance of `KernelParameters`.
127 :param extra: Allow for other arguments. This is a safety valve;
128 parameters generated in another component (for example, see
129- `TFTPBackend.get_config_reader`) won't cause this to break.
130+ `TFTPBackend.get_boot_method_reader`) won't cause this to break.
131 """
132 template = self.get_template(
133 kernel_params.purpose, kernel_params.arch,
134 kernel_params.subarch)
135 namespace = self.compose_template_namespace(kernel_params)
136- return template.substitute(namespace)
137+ return BytesReader(template.substitute(namespace).encode("utf-8"))
138
139 def install_bootloader(self, destination):
140 """Installs the required files for PXE booting into the
141
142=== modified file 'src/provisioningserver/boot/tests/test_boot.py'
143--- src/provisioningserver/boot/tests/test_boot.py 2014-03-21 19:01:40 +0000
144+++ src/provisioningserver/boot/tests/test_boot.py 2014-05-27 19:49:46 +0000
145@@ -24,6 +24,7 @@
146 from provisioningserver import boot
147 from provisioningserver.boot import (
148 BootMethod,
149+ BytesReader,
150 gen_template_filenames,
151 )
152 import tempita
153@@ -36,11 +37,11 @@
154 bootloader_path = "fake.efi"
155 arch_octet = "00:00"
156
157- def match_config_path(self, path):
158+ def match_path(self, backend, path):
159 return {}
160
161- def render_config(kernel_params, **extra):
162- return ""
163+ def get_reader(backend, kernel_params, **extra):
164+ return BytesReader("")
165
166 def install_bootloader():
167 pass
168
169=== modified file 'src/provisioningserver/boot/tests/test_powerkvm.py'
170--- src/provisioningserver/boot/tests/test_powerkvm.py 2014-05-23 14:12:26 +0000
171+++ src/provisioningserver/boot/tests/test_powerkvm.py 2014-05-27 19:49:46 +0000
172@@ -35,17 +35,17 @@
173 class TestPowerKVMBootMethod(MAASTestCase):
174 """Tests `provisioningserver.boot.powerkvm.PowerKVMBootMethod`."""
175
176- def test_match_config_path_returns_none(self):
177+ def test_match_path_returns_None(self):
178 method = PowerKVMBootMethod()
179 paths = [factory.getRandomString() for _ in range(3)]
180 for path in paths:
181- self.assertEqual(None, method.match_config_path(path))
182+ self.assertEqual(None, method.match_path(None, path))
183
184- def test_render_config_returns_empty_string(self):
185+ def test_get_reader_returns_None(self):
186 method = PowerKVMBootMethod()
187 params = [make_kernel_parameters() for _ in range(3)]
188 for param in params:
189- self.assertEqual("", method.render_config(params))
190+ self.assertEqual(None, method.get_reader(None, params))
191
192 def test_install_bootloader_get_package_raises_error(self):
193 method = PowerKVMBootMethod()
194
195=== modified file 'src/provisioningserver/boot/tests/test_pxe.py'
196--- src/provisioningserver/boot/tests/test_pxe.py 2014-04-24 14:09:58 +0000
197+++ src/provisioningserver/boot/tests/test_pxe.py 2014-05-27 19:49:46 +0000
198@@ -20,6 +20,7 @@
199 from maastesting.factory import factory
200 from maastesting.testcase import MAASTestCase
201 from provisioningserver import kernel_opts
202+from provisioningserver.boot import BytesReader
203 from provisioningserver.boot.pxe import (
204 ARP_HTYPE,
205 PXEBootMethod,
206@@ -150,14 +151,15 @@
207 class TestPXEBootMethodRenderConfig(MAASTestCase):
208 """Tests for `provisioningserver.boot.pxe.PXEBootMethod.render_config`."""
209
210- def test_render_install(self):
211+ def test_get_reader_install(self):
212 # Given the right configuration options, the PXE configuration is
213 # correctly rendered.
214 method = PXEBootMethod()
215 params = make_kernel_parameters(self, purpose="install")
216- output = method.render_config(kernel_params=params)
217- # The output is always a Unicode string.
218- self.assertThat(output, IsInstance(unicode))
219+ output = method.get_reader(backend=None, kernel_params=params)
220+ # The output is a BytesReader.
221+ self.assertThat(output, IsInstance(BytesReader))
222+ output = output.read(10000)
223 # The template has rendered without error. PXELINUX configurations
224 # typically start with a DEFAULT line.
225 self.assertThat(output, StartsWith("DEFAULT "))
226@@ -177,54 +179,58 @@
227 r'.*^\s+APPEND .+?$',
228 re.MULTILINE | re.DOTALL)))
229
230- def test_render_with_extra_arguments_does_not_affect_output(self):
231- # render_config() allows any keyword arguments as a safety valve.
232+ def test_get_reader_with_extra_arguments_does_not_affect_output(self):
233+ # get_reader() allows any keyword arguments as a safety valve.
234 method = PXEBootMethod()
235 options = {
236+ "backend": None,
237 "kernel_params": make_kernel_parameters(self, purpose="install"),
238 }
239 # Capture the output before sprinking in some random options.
240- output_before = method.render_config(**options)
241+ output_before = method.get_reader(**options).read(10000)
242 # Sprinkle some magic in.
243 options.update(
244 (factory.make_name("name"), factory.make_name("value"))
245 for _ in range(10))
246 # Capture the output after sprinking in some random options.
247- output_after = method.render_config(**options)
248+ output_after = method.get_reader(**options).read(10000)
249 # The generated template is the same.
250 self.assertEqual(output_before, output_after)
251
252- def test_render_config_with_local_purpose(self):
253+ def test_get_reader_with_local_purpose(self):
254 # If purpose is "local", the config.localboot.template should be
255 # used.
256 method = PXEBootMethod()
257 options = {
258+ "backend": None,
259 "kernel_params": make_kernel_parameters(purpose="local"),
260 }
261- output = method.render_config(**options)
262+ output = method.get_reader(**options).read(10000)
263 self.assertIn("LOCALBOOT 0", output)
264
265- def test_render_config_with_local_purpose_i386_arch(self):
266+ def test_get_reader_with_local_purpose_i386_arch(self):
267 # Intel i386 is a special case and needs to use the chain.c32
268 # loader as the LOCALBOOT PXE directive is unreliable.
269 method = PXEBootMethod()
270 options = {
271+ "backend": None,
272 "kernel_params": make_kernel_parameters(
273 arch="i386", purpose="local"),
274 }
275- output = method.render_config(**options)
276+ output = method.get_reader(**options).read(10000)
277 self.assertIn("chain.c32", output)
278 self.assertNotIn("LOCALBOOT", output)
279
280- def test_render_config_with_local_purpose_amd64_arch(self):
281+ def test_get_reader_with_local_purpose_amd64_arch(self):
282 # Intel amd64 is a special case and needs to use the chain.c32
283 # loader as the LOCALBOOT PXE directive is unreliable.
284 method = PXEBootMethod()
285 options = {
286+ "backend": None,
287 "kernel_params": make_kernel_parameters(
288 arch="amd64", purpose="local"),
289 }
290- output = method.render_config(**options)
291+ output = method.get_reader(**options).read(10000)
292 self.assertIn("chain.c32", output)
293 self.assertNotIn("LOCALBOOT", output)
294
295@@ -237,7 +243,7 @@
296 ("xinstall", dict(purpose="xinstall")),
297 ]
298
299- def test_render_config_scenarios(self):
300+ def test_get_reader_scenarios(self):
301 # The commissioning config uses an extra PXELINUX module to auto
302 # select between i386 and amd64.
303 method = PXEBootMethod()
304@@ -245,11 +251,12 @@
305 get_ephemeral_name.return_value = factory.make_name("ephemeral")
306 osystem = factory.make_name('osystem')
307 options = {
308+ "backend": None,
309 "kernel_params": make_kernel_parameters(
310 testcase=self, osystem=osystem, subarch="generic",
311 purpose=self.purpose),
312 }
313- output = method.render_config(**options)
314+ output = method.get_reader(**options).read(10000)
315 config = parse_pxe_config(output)
316 # The default section is defined.
317 default_section_label = config.header["DEFAULT"]
318
319=== modified file 'src/provisioningserver/boot/tests/test_uefi.py'
320--- src/provisioningserver/boot/tests/test_uefi.py 2014-05-15 17:27:53 +0000
321+++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-27 19:49:46 +0000
322@@ -18,6 +18,7 @@
323
324 from maastesting.factory import factory
325 from maastesting.testcase import MAASTestCase
326+from provisioningserver.boot import BytesReader
327 from provisioningserver.boot.tftppath import compose_image_path
328 from provisioningserver.boot.uefi import (
329 re_config_file,
330@@ -60,14 +61,15 @@
331 class TestRenderUEFIConfig(MAASTestCase):
332 """Tests for `provisioningserver.boot.uefi.UEFIBootMethod`."""
333
334- def test_render(self):
335+ def test_get_reader(self):
336 # Given the right configuration options, the UEFI configuration is
337 # correctly rendered.
338 method = UEFIBootMethod()
339 params = make_kernel_parameters(purpose="install")
340- output = method.render_config(kernel_params=params)
341- # The output is always a Unicode string.
342- self.assertThat(output, IsInstance(unicode))
343+ output = method.get_reader(backend=None, kernel_params=params)
344+ # The output is a BytesReader.
345+ self.assertThat(output, IsInstance(BytesReader))
346+ output = output.read(10000)
347 # The template has rendered without error. UEFI configurations
348 # typically start with a DEFAULT line.
349 self.assertThat(output, StartsWith("set default=\"0\""))
350@@ -85,32 +87,34 @@
351 r'.*^\s+initrd %s/di-initrd$' % re.escape(image_dir),
352 re.MULTILINE | re.DOTALL)))
353
354- def test_render_with_extra_arguments_does_not_affect_output(self):
355- # render_config() allows any keyword arguments as a safety valve.
356+ def test_get_reader_with_extra_arguments_does_not_affect_output(self):
357+ # get_reader() allows any keyword arguments as a safety valve.
358 method = UEFIBootMethod()
359 options = {
360+ "backend": None,
361 "kernel_params": make_kernel_parameters(purpose="install"),
362 }
363 # Capture the output before sprinking in some random options.
364- output_before = method.render_config(**options)
365+ output_before = method.get_reader(**options).read(10000)
366 # Sprinkle some magic in.
367 options.update(
368 (factory.make_name("name"), factory.make_name("value"))
369 for _ in range(10))
370 # Capture the output after sprinking in some random options.
371- output_after = method.render_config(**options)
372+ output_after = method.get_reader(**options).read(10000)
373 # The generated template is the same.
374 self.assertEqual(output_before, output_after)
375
376- def test_render_config_with_local_purpose(self):
377+ def test_get_reader_with_local_purpose(self):
378 # If purpose is "local", the config.localboot.template should be
379 # used.
380 method = UEFIBootMethod()
381 options = {
382+ "backend": None,
383 "kernel_params": make_kernel_parameters(
384 purpose="local", arch="amd64"),
385 }
386- output = method.render_config(**options)
387+ output = method.get_reader(**options).read(10000)
388 self.assertIn("configfile /efi/ubuntu/grub.cfg", output)
389
390
391
392=== modified file 'src/provisioningserver/boot/uefi.py'
393--- src/provisioningserver/boot/uefi.py 2014-03-28 04:06:27 +0000
394+++ src/provisioningserver/boot/uefi.py 2014-05-27 19:49:46 +0000
395@@ -25,6 +25,7 @@
396 from provisioningserver.boot import (
397 BootMethod,
398 BootMethodInstallError,
399+ BytesReader,
400 get_parameters,
401 utils,
402 )
403@@ -120,10 +121,11 @@
404 bootloader_path = "bootx64.efi"
405 arch_octet = "00:07" # AMD64 EFI
406
407- def match_config_path(self, path):
408+ def match_path(self, backend, path):
409 """Checks path for the configuration file that needs to be
410 generated.
411
412+ :param backend: requesting backend
413 :param path: requested path
414 :returns: dict of match params from path, None if no match
415 """
416@@ -139,19 +141,20 @@
417
418 return params
419
420- def render_config(self, kernel_params, **extra):
421+ def get_reader(self, backend, kernel_params, **extra):
422 """Render a configuration file as a unicode string.
423
424+ :param backend: requesting backend
425 :param kernel_params: An instance of `KernelParameters`.
426 :param extra: Allow for other arguments. This is a safety valve;
427 parameters generated in another component (for example, see
428- `TFTPBackend.get_config_reader`) won't cause this to break.
429+ `TFTPBackend.get_boot_method_reader`) won't cause this to break.
430 """
431 template = self.get_template(
432 kernel_params.purpose, kernel_params.arch,
433 kernel_params.subarch)
434 namespace = self.compose_template_namespace(kernel_params)
435- return template.substitute(namespace)
436+ return BytesReader(template.substitute(namespace).encode("utf-8"))
437
438 def install_bootloader(self, destination):
439 """Installs the required files for UEFI booting into the
440
441=== modified file 'src/provisioningserver/tests/test_tftp.py'
442--- src/provisioningserver/tests/test_tftp.py 2014-03-28 06:51:59 +0000
443+++ src/provisioningserver/tests/test_tftp.py 2014-05-27 19:49:46 +0000
444@@ -28,11 +28,11 @@
445 from maastesting.testcase import MAASTestCase
446 import mock
447 from provisioningserver import tftp as tftp_module
448+from provisioningserver.boot import BytesReader
449 from provisioningserver.boot.pxe import PXEBootMethod
450 from provisioningserver.boot.tests.test_pxe import compose_config_path
451 from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
452 from provisioningserver.tftp import (
453- BytesReader,
454 TFTPBackend,
455 TFTPService,
456 )
457@@ -127,9 +127,9 @@
458 self.assertEqual(b"", reader.read(1))
459
460 @inlineCallbacks
461- def test_get_reader_config_file(self):
462- # For paths matching re_config_file, TFTPBackend.get_reader() returns
463- # a Deferred that will yield a BytesReader.
464+ def test_get_render_file(self):
465+ # For paths matching PXEBootMethod.match_path, TFTPBackend.get_reader()
466+ # returns a Deferred that will yield a BytesReader.
467 cluster_uuid = factory.getRandomUUID()
468 self.patch(tftp_module, 'get_cluster_uuid').return_value = (
469 cluster_uuid)
470@@ -147,8 +147,8 @@
471 factory.getRandomPort()),
472 }
473
474- @partial(self.patch, backend, "get_config_reader")
475- def get_config_reader(boot_method, params):
476+ @partial(self.patch, backend, "get_boot_method_reader")
477+ def get_boot_method_reader(boot_method, params):
478 params_json = json.dumps(params)
479 params_json_reader = BytesReader(params_json)
480 return succeed(params_json_reader)
481@@ -168,9 +168,10 @@
482 self.assertEqual(expected_params, observed_params)
483
484 @inlineCallbacks
485- def test_get_config_reader_returns_rendered_params(self):
486- # get_config_reader() takes a dict() of parameters and returns an
487- # `IReader` of a PXE configuration, rendered by `render_pxe_config`.
488+ def test_get_boot_method_reader_returns_rendered_params(self):
489+ # get_boot_method_reader() takes a dict() of parameters and returns an
490+ # `IReader` of a PXE configuration, rendered by
491+ # `PXEBootMethod.get_reader`.
492 backend = TFTPBackend(self.make_dir(), b"http://example.com/")
493 # Fake configuration parameters, as discovered from the file path.
494 fake_params = {"mac": factory.getRandomMACAddress("-")}
495@@ -182,15 +183,15 @@
496 get_page_patch = self.patch(backend, "get_page")
497 get_page_patch.return_value = succeed(fake_get_page_result)
498
499- # Stub render_config to return the render parameters.
500+ # Stub get_reader to return the render parameters.
501 method = PXEBootMethod()
502- fake_render_result = factory.make_name("render")
503- render_patch = self.patch(method, "render_config")
504- render_patch.return_value = fake_render_result
505+ fake_render_result = factory.make_name("render").encode("utf-8")
506+ render_patch = self.patch(method, "get_reader")
507+ render_patch.return_value = BytesReader(fake_render_result)
508
509 # Get the rendered configuration, which will actually be a JSON dump
510 # of the render-time parameters.
511- reader = yield backend.get_config_reader(method, fake_params)
512+ reader = yield backend.get_boot_method_reader(method, fake_params)
513 self.addCleanup(reader.finish)
514 self.assertIsInstance(reader, BytesReader)
515 output = reader.read(10000)
516@@ -198,13 +199,13 @@
517 # The kernel parameters were fetched using `backend.get_page`.
518 self.assertThat(backend.get_page, MockCalledOnceWith(mock.ANY))
519
520- # The result has been rendered by `backend.render_config`.
521+ # The result has been rendered by `method.get_reader`.
522 self.assertEqual(fake_render_result.encode("utf-8"), output)
523- self.assertThat(method.render_config, MockCalledOnceWith(
524- kernel_params=fake_kernel_params, **fake_params))
525+ self.assertThat(method.get_reader, MockCalledOnceWith(
526+ backend, kernel_params=fake_kernel_params, **fake_params))
527
528 @inlineCallbacks
529- def test_get_config_reader_substitutes_armhf_in_params(self):
530+ def test_get_boot_method_render_substitutes_armhf_in_params(self):
531 # get_config_reader() should substitute "arm" for "armhf" in the
532 # arch field of the parameters (mapping from pxe to maas
533 # namespace).
534@@ -224,8 +225,8 @@
535 factory.getRandomPort()),
536 }
537
538- @partial(self.patch, backend, "get_config_reader")
539- def get_config_reader(boot_method, params):
540+ @partial(self.patch, backend, "get_boot_method_reader")
541+ def get_boot_method_reader(boot_method, params):
542 params_json = json.dumps(params)
543 params_json_reader = BytesReader(params_json)
544 return succeed(params_json_reader)
545
546=== modified file 'src/provisioningserver/tftp.py'
547--- src/provisioningserver/tftp.py 2014-03-28 15:17:34 +0000
548+++ src/provisioningserver/tftp.py 2014-05-27 19:49:46 +0000
549@@ -18,7 +18,6 @@
550 ]
551
552 import httplib
553-from io import BytesIO
554 import json
555 from urllib import urlencode
556 from urlparse import (
557@@ -34,10 +33,7 @@
558 deferred,
559 get_all_interface_addresses,
560 )
561-from tftp.backend import (
562- FilesystemSynchronousBackend,
563- IReader,
564- )
565+from tftp.backend import FilesystemSynchronousBackend
566 from tftp.errors import FileNotFound
567 from tftp.protocol import TFTP
568 from twisted.application import internet
569@@ -45,22 +41,6 @@
570 from twisted.python.context import get
571 from twisted.web.client import getPage
572 import twisted.web.error
573-from zope.interface import implementer
574-
575-
576-@implementer(IReader)
577-class BytesReader:
578-
579- def __init__(self, data):
580- super(BytesReader, self).__init__()
581- self.buffer = BytesIO(data)
582- self.size = len(data)
583-
584- def read(self, size):
585- return self.buffer.read(size)
586-
587- def finish(self):
588- self.buffer.close()
589
590
591 class TFTPBackend(FilesystemSynchronousBackend):
592@@ -118,7 +98,7 @@
593 def get_boot_method(self, file_name):
594 """Finds the correct boot method."""
595 for _, method in BootMethodRegistry:
596- params = method.match_config_path(file_name)
597+ params = method.match_path(self, file_name)
598 if params is not None:
599 return method, params
600 return None, None
601@@ -142,21 +122,19 @@
602 return d
603
604 @deferred
605- def get_config_reader(self, boot_method, params):
606+ def get_boot_method_reader(self, boot_method, params):
607 """Return an `IReader` for a boot method.
608
609 :param boot_method: Boot method that is generating the config
610 :param params: Parameters so far obtained, typically from the file
611 path requested.
612 """
613- def generate_config(kernel_params):
614- config = boot_method.render_config(
615- kernel_params=kernel_params, **params)
616- return config.encode("utf-8")
617+ def generate(kernel_params):
618+ return boot_method.get_reader(
619+ self, kernel_params=kernel_params, **params)
620
621 d = self.get_kernel_params(params)
622- d.addCallback(generate_config)
623- d.addCallback(BytesReader)
624+ d.addCallback(generate)
625 return d
626
627 @staticmethod
628@@ -203,7 +181,7 @@
629 remote_host, remote_port = get("remote", (None, None))
630 params["remote"] = remote_host
631 params["cluster_uuid"] = get_cluster_uuid()
632- d = self.get_config_reader(boot_method, params)
633+ d = self.get_boot_method_reader(boot_method, params)
634 d.addErrback(self.get_page_errback, file_name)
635 return d
636