Merge lp:~blake-rouse/maas/tftp-backend-readers-1.5 into lp:maas/1.5
- tftp-backend-readers-1.5
- Merge into 1.5
Proposed by
Blake Rouse
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2280 |
Proposed branch: | lp:~blake-rouse/maas/tftp-backend-readers-1.5 |
Merge into: | lp:maas/1.5 |
Diff against target: |
633 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-1.5 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+221230@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
To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) : | # |
review:
Approve
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 16:32:07 +0000 |
3 | +++ src/provisioningserver/boot/__init__.py 2014-05-28 13:35:49 +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 16:32:07 +0000 |
70 | +++ src/provisioningserver/boot/powerkvm.py 2014-05-28 13:35:49 +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-28 13:35:49 +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-28 13:35:49 +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 16:32:07 +0000 |
171 | +++ src/provisioningserver/boot/tests/test_powerkvm.py 2014-05-28 13:35:49 +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-03-28 04:31:32 +0000 |
197 | +++ src/provisioningserver/boot/tests/test_pxe.py 2014-05-28 13:35:49 +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,17 +243,18 @@ |
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 | get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name") |
305 | get_ephemeral_name.return_value = factory.make_name("ephemeral") |
306 | options = { |
307 | + "backend": None, |
308 | "kernel_params": make_kernel_parameters( |
309 | testcase=self, subarch="generic", purpose=self.purpose), |
310 | } |
311 | - output = method.render_config(**options) |
312 | + output = method.get_reader(**options).read(10000) |
313 | config = parse_pxe_config(output) |
314 | # The default section is defined. |
315 | default_section_label = config.header["DEFAULT"] |
316 | |
317 | === modified file 'src/provisioningserver/boot/tests/test_uefi.py' |
318 | --- src/provisioningserver/boot/tests/test_uefi.py 2014-05-15 17:39:27 +0000 |
319 | +++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-28 13:35:49 +0000 |
320 | @@ -18,6 +18,7 @@ |
321 | |
322 | from maastesting.factory import factory |
323 | from maastesting.testcase import MAASTestCase |
324 | +from provisioningserver.boot import BytesReader |
325 | from provisioningserver.boot.tftppath import compose_image_path |
326 | from provisioningserver.boot.uefi import ( |
327 | re_config_file, |
328 | @@ -60,14 +61,15 @@ |
329 | class TestRenderUEFIConfig(MAASTestCase): |
330 | """Tests for `provisioningserver.boot.uefi.UEFIBootMethod`.""" |
331 | |
332 | - def test_render(self): |
333 | + def test_get_reader(self): |
334 | # Given the right configuration options, the UEFI configuration is |
335 | # correctly rendered. |
336 | method = UEFIBootMethod() |
337 | params = make_kernel_parameters(purpose="install") |
338 | - output = method.render_config(kernel_params=params) |
339 | - # The output is always a Unicode string. |
340 | - self.assertThat(output, IsInstance(unicode)) |
341 | + output = method.get_reader(backend=None, kernel_params=params) |
342 | + # The output is a BytesReader. |
343 | + self.assertThat(output, IsInstance(BytesReader)) |
344 | + output = output.read(10000) |
345 | # The template has rendered without error. UEFI configurations |
346 | # typically start with a DEFAULT line. |
347 | self.assertThat(output, StartsWith("set default=\"0\"")) |
348 | @@ -85,32 +87,34 @@ |
349 | r'.*^\s+initrd %s/di-initrd$' % re.escape(image_dir), |
350 | re.MULTILINE | re.DOTALL))) |
351 | |
352 | - def test_render_with_extra_arguments_does_not_affect_output(self): |
353 | - # render_config() allows any keyword arguments as a safety valve. |
354 | + def test_get_reader_with_extra_arguments_does_not_affect_output(self): |
355 | + # get_reader() allows any keyword arguments as a safety valve. |
356 | method = UEFIBootMethod() |
357 | options = { |
358 | + "backend": None, |
359 | "kernel_params": make_kernel_parameters(purpose="install"), |
360 | } |
361 | # Capture the output before sprinking in some random options. |
362 | - output_before = method.render_config(**options) |
363 | + output_before = method.get_reader(**options).read(10000) |
364 | # Sprinkle some magic in. |
365 | options.update( |
366 | (factory.make_name("name"), factory.make_name("value")) |
367 | for _ in range(10)) |
368 | # Capture the output after sprinking in some random options. |
369 | - output_after = method.render_config(**options) |
370 | + output_after = method.get_reader(**options).read(10000) |
371 | # The generated template is the same. |
372 | self.assertEqual(output_before, output_after) |
373 | |
374 | - def test_render_config_with_local_purpose(self): |
375 | + def test_get_reader_with_local_purpose(self): |
376 | # If purpose is "local", the config.localboot.template should be |
377 | # used. |
378 | method = UEFIBootMethod() |
379 | options = { |
380 | + "backend": None, |
381 | "kernel_params": make_kernel_parameters( |
382 | purpose="local", arch="amd64"), |
383 | } |
384 | - output = method.render_config(**options) |
385 | + output = method.get_reader(**options).read(10000) |
386 | self.assertIn("configfile /efi/ubuntu/grub.cfg", output) |
387 | |
388 | |
389 | |
390 | === modified file 'src/provisioningserver/boot/uefi.py' |
391 | --- src/provisioningserver/boot/uefi.py 2014-03-28 04:06:27 +0000 |
392 | +++ src/provisioningserver/boot/uefi.py 2014-05-28 13:35:49 +0000 |
393 | @@ -25,6 +25,7 @@ |
394 | from provisioningserver.boot import ( |
395 | BootMethod, |
396 | BootMethodInstallError, |
397 | + BytesReader, |
398 | get_parameters, |
399 | utils, |
400 | ) |
401 | @@ -120,10 +121,11 @@ |
402 | bootloader_path = "bootx64.efi" |
403 | arch_octet = "00:07" # AMD64 EFI |
404 | |
405 | - def match_config_path(self, path): |
406 | + def match_path(self, backend, path): |
407 | """Checks path for the configuration file that needs to be |
408 | generated. |
409 | |
410 | + :param backend: requesting backend |
411 | :param path: requested path |
412 | :returns: dict of match params from path, None if no match |
413 | """ |
414 | @@ -139,19 +141,20 @@ |
415 | |
416 | return params |
417 | |
418 | - def render_config(self, kernel_params, **extra): |
419 | + def get_reader(self, backend, kernel_params, **extra): |
420 | """Render a configuration file as a unicode string. |
421 | |
422 | + :param backend: requesting backend |
423 | :param kernel_params: An instance of `KernelParameters`. |
424 | :param extra: Allow for other arguments. This is a safety valve; |
425 | parameters generated in another component (for example, see |
426 | - `TFTPBackend.get_config_reader`) won't cause this to break. |
427 | + `TFTPBackend.get_boot_method_reader`) won't cause this to break. |
428 | """ |
429 | template = self.get_template( |
430 | kernel_params.purpose, kernel_params.arch, |
431 | kernel_params.subarch) |
432 | namespace = self.compose_template_namespace(kernel_params) |
433 | - return template.substitute(namespace) |
434 | + return BytesReader(template.substitute(namespace).encode("utf-8")) |
435 | |
436 | def install_bootloader(self, destination): |
437 | """Installs the required files for UEFI booting into the |
438 | |
439 | === modified file 'src/provisioningserver/tests/test_tftp.py' |
440 | --- src/provisioningserver/tests/test_tftp.py 2014-03-28 06:51:59 +0000 |
441 | +++ src/provisioningserver/tests/test_tftp.py 2014-05-28 13:35:49 +0000 |
442 | @@ -28,11 +28,11 @@ |
443 | from maastesting.testcase import MAASTestCase |
444 | import mock |
445 | from provisioningserver import tftp as tftp_module |
446 | +from provisioningserver.boot import BytesReader |
447 | from provisioningserver.boot.pxe import PXEBootMethod |
448 | from provisioningserver.boot.tests.test_pxe import compose_config_path |
449 | from provisioningserver.tests.test_kernel_opts import make_kernel_parameters |
450 | from provisioningserver.tftp import ( |
451 | - BytesReader, |
452 | TFTPBackend, |
453 | TFTPService, |
454 | ) |
455 | @@ -127,9 +127,9 @@ |
456 | self.assertEqual(b"", reader.read(1)) |
457 | |
458 | @inlineCallbacks |
459 | - def test_get_reader_config_file(self): |
460 | - # For paths matching re_config_file, TFTPBackend.get_reader() returns |
461 | - # a Deferred that will yield a BytesReader. |
462 | + def test_get_render_file(self): |
463 | + # For paths matching PXEBootMethod.match_path, TFTPBackend.get_reader() |
464 | + # returns a Deferred that will yield a BytesReader. |
465 | cluster_uuid = factory.getRandomUUID() |
466 | self.patch(tftp_module, 'get_cluster_uuid').return_value = ( |
467 | cluster_uuid) |
468 | @@ -147,8 +147,8 @@ |
469 | factory.getRandomPort()), |
470 | } |
471 | |
472 | - @partial(self.patch, backend, "get_config_reader") |
473 | - def get_config_reader(boot_method, params): |
474 | + @partial(self.patch, backend, "get_boot_method_reader") |
475 | + def get_boot_method_reader(boot_method, params): |
476 | params_json = json.dumps(params) |
477 | params_json_reader = BytesReader(params_json) |
478 | return succeed(params_json_reader) |
479 | @@ -168,9 +168,10 @@ |
480 | self.assertEqual(expected_params, observed_params) |
481 | |
482 | @inlineCallbacks |
483 | - def test_get_config_reader_returns_rendered_params(self): |
484 | - # get_config_reader() takes a dict() of parameters and returns an |
485 | - # `IReader` of a PXE configuration, rendered by `render_pxe_config`. |
486 | + def test_get_boot_method_reader_returns_rendered_params(self): |
487 | + # get_boot_method_reader() takes a dict() of parameters and returns an |
488 | + # `IReader` of a PXE configuration, rendered by |
489 | + # `PXEBootMethod.get_reader`. |
490 | backend = TFTPBackend(self.make_dir(), b"http://example.com/") |
491 | # Fake configuration parameters, as discovered from the file path. |
492 | fake_params = {"mac": factory.getRandomMACAddress("-")} |
493 | @@ -182,15 +183,15 @@ |
494 | get_page_patch = self.patch(backend, "get_page") |
495 | get_page_patch.return_value = succeed(fake_get_page_result) |
496 | |
497 | - # Stub render_config to return the render parameters. |
498 | + # Stub get_reader to return the render parameters. |
499 | method = PXEBootMethod() |
500 | - fake_render_result = factory.make_name("render") |
501 | - render_patch = self.patch(method, "render_config") |
502 | - render_patch.return_value = fake_render_result |
503 | + fake_render_result = factory.make_name("render").encode("utf-8") |
504 | + render_patch = self.patch(method, "get_reader") |
505 | + render_patch.return_value = BytesReader(fake_render_result) |
506 | |
507 | # Get the rendered configuration, which will actually be a JSON dump |
508 | # of the render-time parameters. |
509 | - reader = yield backend.get_config_reader(method, fake_params) |
510 | + reader = yield backend.get_boot_method_reader(method, fake_params) |
511 | self.addCleanup(reader.finish) |
512 | self.assertIsInstance(reader, BytesReader) |
513 | output = reader.read(10000) |
514 | @@ -198,13 +199,13 @@ |
515 | # The kernel parameters were fetched using `backend.get_page`. |
516 | self.assertThat(backend.get_page, MockCalledOnceWith(mock.ANY)) |
517 | |
518 | - # The result has been rendered by `backend.render_config`. |
519 | + # The result has been rendered by `method.get_reader`. |
520 | self.assertEqual(fake_render_result.encode("utf-8"), output) |
521 | - self.assertThat(method.render_config, MockCalledOnceWith( |
522 | - kernel_params=fake_kernel_params, **fake_params)) |
523 | + self.assertThat(method.get_reader, MockCalledOnceWith( |
524 | + backend, kernel_params=fake_kernel_params, **fake_params)) |
525 | |
526 | @inlineCallbacks |
527 | - def test_get_config_reader_substitutes_armhf_in_params(self): |
528 | + def test_get_boot_method_render_substitutes_armhf_in_params(self): |
529 | # get_config_reader() should substitute "arm" for "armhf" in the |
530 | # arch field of the parameters (mapping from pxe to maas |
531 | # namespace). |
532 | @@ -224,8 +225,8 @@ |
533 | factory.getRandomPort()), |
534 | } |
535 | |
536 | - @partial(self.patch, backend, "get_config_reader") |
537 | - def get_config_reader(boot_method, params): |
538 | + @partial(self.patch, backend, "get_boot_method_reader") |
539 | + def get_boot_method_reader(boot_method, params): |
540 | params_json = json.dumps(params) |
541 | params_json_reader = BytesReader(params_json) |
542 | return succeed(params_json_reader) |
543 | |
544 | === modified file 'src/provisioningserver/tftp.py' |
545 | --- src/provisioningserver/tftp.py 2014-03-28 15:17:34 +0000 |
546 | +++ src/provisioningserver/tftp.py 2014-05-28 13:35:49 +0000 |
547 | @@ -18,7 +18,6 @@ |
548 | ] |
549 | |
550 | import httplib |
551 | -from io import BytesIO |
552 | import json |
553 | from urllib import urlencode |
554 | from urlparse import ( |
555 | @@ -34,10 +33,7 @@ |
556 | deferred, |
557 | get_all_interface_addresses, |
558 | ) |
559 | -from tftp.backend import ( |
560 | - FilesystemSynchronousBackend, |
561 | - IReader, |
562 | - ) |
563 | +from tftp.backend import FilesystemSynchronousBackend |
564 | from tftp.errors import FileNotFound |
565 | from tftp.protocol import TFTP |
566 | from twisted.application import internet |
567 | @@ -45,22 +41,6 @@ |
568 | from twisted.python.context import get |
569 | from twisted.web.client import getPage |
570 | import twisted.web.error |
571 | -from zope.interface import implementer |
572 | - |
573 | - |
574 | -@implementer(IReader) |
575 | -class BytesReader: |
576 | - |
577 | - def __init__(self, data): |
578 | - super(BytesReader, self).__init__() |
579 | - self.buffer = BytesIO(data) |
580 | - self.size = len(data) |
581 | - |
582 | - def read(self, size): |
583 | - return self.buffer.read(size) |
584 | - |
585 | - def finish(self): |
586 | - self.buffer.close() |
587 | |
588 | |
589 | class TFTPBackend(FilesystemSynchronousBackend): |
590 | @@ -118,7 +98,7 @@ |
591 | def get_boot_method(self, file_name): |
592 | """Finds the correct boot method.""" |
593 | for _, method in BootMethodRegistry: |
594 | - params = method.match_config_path(file_name) |
595 | + params = method.match_path(self, file_name) |
596 | if params is not None: |
597 | return method, params |
598 | return None, None |
599 | @@ -142,21 +122,19 @@ |
600 | return d |
601 | |
602 | @deferred |
603 | - def get_config_reader(self, boot_method, params): |
604 | + def get_boot_method_reader(self, boot_method, params): |
605 | """Return an `IReader` for a boot method. |
606 | |
607 | :param boot_method: Boot method that is generating the config |
608 | :param params: Parameters so far obtained, typically from the file |
609 | path requested. |
610 | """ |
611 | - def generate_config(kernel_params): |
612 | - config = boot_method.render_config( |
613 | - kernel_params=kernel_params, **params) |
614 | - return config.encode("utf-8") |
615 | + def generate(kernel_params): |
616 | + return boot_method.get_reader( |
617 | + self, kernel_params=kernel_params, **params) |
618 | |
619 | d = self.get_kernel_params(params) |
620 | - d.addCallback(generate_config) |
621 | - d.addCallback(BytesReader) |
622 | + d.addCallback(generate) |
623 | return d |
624 | |
625 | @staticmethod |
626 | @@ -203,7 +181,7 @@ |
627 | remote_host, remote_port = get("remote", (None, None)) |
628 | params["remote"] = remote_host |
629 | params["cluster_uuid"] = get_cluster_uuid() |
630 | - d = self.get_config_reader(boot_method, params) |
631 | + d = self.get_boot_method_reader(boot_method, params) |
632 | d.addErrback(self.get_page_errback, file_name) |
633 | return d |
634 |