Merge lp:~blake-rouse/maas/tftp-backend-readers into lp:~maas-committers/maas/trunk
- tftp-backend-readers
- Merge into 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 |
Related bugs: |
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
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 |
Looks good.