Merge lp:~blake-rouse/maas/tftp-backend-readers-1.5 into lp:maas/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
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.

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
=== modified file 'src/provisioningserver/boot/__init__.py'
--- src/provisioningserver/boot/__init__.py 2014-05-23 16:32:07 +0000
+++ src/provisioningserver/boot/__init__.py 2014-05-28 13:35:49 +0000
@@ -23,6 +23,7 @@
23 abstractproperty,23 abstractproperty,
24 )24 )
25from errno import ENOENT25from errno import ENOENT
26from io import BytesIO
26from os import path27from os import path
2728
28from provisioningserver.boot.tftppath import compose_image_path29from provisioningserver.boot.tftppath import compose_image_path
@@ -30,6 +31,23 @@
30from provisioningserver.utils import locate_config31from provisioningserver.utils import locate_config
31from provisioningserver.utils.registry import Registry32from provisioningserver.utils.registry import Registry
32import tempita33import tempita
34from tftp.backend import IReader
35from zope.interface import implementer
36
37
38@implementer(IReader)
39class BytesReader:
40
41 def __init__(self, data):
42 super(BytesReader, self).__init__()
43 self.buffer = BytesIO(data)
44 self.size = len(data)
45
46 def read(self, size):
47 return self.buffer.read(size)
48
49 def finish(self):
50 self.buffer.close()
3351
3452
35class BootMethodError(Exception):53class BootMethodError(Exception):
@@ -100,22 +118,24 @@
100 """118 """
101119
102 @abstractmethod120 @abstractmethod
103 def match_config_path(self, path):121 def match_path(self, backend, path):
104 """Checks path for the configuration file that needs to be122 """Checks path for a file the boot method needs to handle.
105 generated.
106123
124 :param backend: requesting backend
107 :param path: requested path125 :param path: requested path
108 :returns: dict of match params from path, None if no match126 :returns: dict of match params from path, None if no match
109 """127 """
110128
111 @abstractmethod129 @abstractmethod
112 def render_config(self, kernel_params, **extra):130 def get_reader(self, backend, kernel_params, **extra):
113 """Render a configuration file as a unicode string.131 """Gets the reader the backend will use for this combination of
132 boot method, kernel parameters, and extra parameters.
114133
134 :param backend: requesting backend
115 :param kernel_params: An instance of `KernelParameters`.135 :param kernel_params: An instance of `KernelParameters`.
116 :param extra: Allow for other arguments. This is a safety valve;136 :param extra: Allow for other arguments. This is a safety valve;
117 parameters generated in another component (for example, see137 parameters generated in another component (for example, see
118 `TFTPBackend.get_config_reader`) won't cause this to break.138 `TFTPBackend.get_boot_method_reader`) won't cause this to break.
119 """139 """
120140
121 @abstractmethod141 @abstractmethod
122142
=== modified file 'src/provisioningserver/boot/powerkvm.py'
--- src/provisioningserver/boot/powerkvm.py 2014-05-23 16:32:07 +0000
+++ src/provisioningserver/boot/powerkvm.py 2014-05-28 13:35:49 +0000
@@ -45,17 +45,17 @@
45 bootloader_path = "bootppc64.bin"45 bootloader_path = "bootppc64.bin"
46 arch_octet = "00:0C"46 arch_octet = "00:0C"
4747
48 def match_config_path(self, path):48 def match_path(self, backend, path):
49 """Doesn't need to do anything, as the UEFIBootMethod provides49 """Doesn't need to do anything, as the UEFIBootMethod provides
50 the grub implementation needed.50 the grub implementation needed.
51 """51 """
52 return None52 return None
5353
54 def render_config(self, kernel_params, **extra):54 def get_reader(self, backend, kernel_params, **extra):
55 """Doesn't need to do anything, as the UEFIBootMethod provides55 """Doesn't need to do anything, as the UEFIBootMethod provides
56 the grub implementation needed.56 the grub implementation needed.
57 """57 """
58 return ""58 return None
5959
60 def install_bootloader(self, destination):60 def install_bootloader(self, destination):
61 """Installs the required files for PowerKVM booting into the61 """Installs the required files for PowerKVM booting into the
6262
=== modified file 'src/provisioningserver/boot/pxe.py'
--- src/provisioningserver/boot/pxe.py 2014-03-28 04:06:27 +0000
+++ src/provisioningserver/boot/pxe.py 2014-05-28 13:35:49 +0000
@@ -22,6 +22,7 @@
2222
23from provisioningserver.boot import (23from provisioningserver.boot import (
24 BootMethod,24 BootMethod,
25 BytesReader,
25 get_parameters,26 get_parameters,
26 )27 )
27from provisioningserver.boot.install_bootloader import install_bootloader28from provisioningserver.boot.install_bootloader import install_bootloader
@@ -78,10 +79,11 @@
78 bootloader_path = "pxelinux.0"79 bootloader_path = "pxelinux.0"
79 arch_octet = "00:00"80 arch_octet = "00:00"
8081
81 def match_config_path(self, path):82 def match_path(self, backend, path):
82 """Checks path for the configuration file that needs to be83 """Checks path for the configuration file that needs to be
83 generated.84 generated.
8485
86 :param backend: requesting backend
85 :param path: requested path87 :param path: requested path
86 :returns: dict of match params from path, None if no match88 :returns: dict of match params from path, None if no match
87 """89 """
@@ -90,19 +92,20 @@
90 return None92 return None
91 return get_parameters(match)93 return get_parameters(match)
9294
93 def render_config(self, kernel_params, **extra):95 def get_reader(self, backend, kernel_params, **extra):
94 """Render a configuration file as a unicode string.96 """Render a configuration file as a unicode string.
9597
98 :param backend: requesting backend
96 :param kernel_params: An instance of `KernelParameters`.99 :param kernel_params: An instance of `KernelParameters`.
97 :param extra: Allow for other arguments. This is a safety valve;100 :param extra: Allow for other arguments. This is a safety valve;
98 parameters generated in another component (for example, see101 parameters generated in another component (for example, see
99 `TFTPBackend.get_config_reader`) won't cause this to break.102 `TFTPBackend.get_boot_method_reader`) won't cause this to break.
100 """103 """
101 template = self.get_template(104 template = self.get_template(
102 kernel_params.purpose, kernel_params.arch,105 kernel_params.purpose, kernel_params.arch,
103 kernel_params.subarch)106 kernel_params.subarch)
104 namespace = self.compose_template_namespace(kernel_params)107 namespace = self.compose_template_namespace(kernel_params)
105 return template.substitute(namespace)108 return BytesReader(template.substitute(namespace).encode("utf-8"))
106109
107 def install_bootloader(self, destination):110 def install_bootloader(self, destination):
108 """Installs the required files for PXE booting into the111 """Installs the required files for PXE booting into the
109112
=== modified file 'src/provisioningserver/boot/tests/test_boot.py'
--- src/provisioningserver/boot/tests/test_boot.py 2014-03-21 19:01:40 +0000
+++ src/provisioningserver/boot/tests/test_boot.py 2014-05-28 13:35:49 +0000
@@ -24,6 +24,7 @@
24from provisioningserver import boot24from provisioningserver import boot
25from provisioningserver.boot import (25from provisioningserver.boot import (
26 BootMethod,26 BootMethod,
27 BytesReader,
27 gen_template_filenames,28 gen_template_filenames,
28 )29 )
29import tempita30import tempita
@@ -36,11 +37,11 @@
36 bootloader_path = "fake.efi"37 bootloader_path = "fake.efi"
37 arch_octet = "00:00"38 arch_octet = "00:00"
3839
39 def match_config_path(self, path):40 def match_path(self, backend, path):
40 return {}41 return {}
4142
42 def render_config(kernel_params, **extra):43 def get_reader(backend, kernel_params, **extra):
43 return ""44 return BytesReader("")
4445
45 def install_bootloader():46 def install_bootloader():
46 pass47 pass
4748
=== modified file 'src/provisioningserver/boot/tests/test_powerkvm.py'
--- src/provisioningserver/boot/tests/test_powerkvm.py 2014-05-23 16:32:07 +0000
+++ src/provisioningserver/boot/tests/test_powerkvm.py 2014-05-28 13:35:49 +0000
@@ -35,17 +35,17 @@
35class TestPowerKVMBootMethod(MAASTestCase):35class TestPowerKVMBootMethod(MAASTestCase):
36 """Tests `provisioningserver.boot.powerkvm.PowerKVMBootMethod`."""36 """Tests `provisioningserver.boot.powerkvm.PowerKVMBootMethod`."""
3737
38 def test_match_config_path_returns_none(self):38 def test_match_path_returns_None(self):
39 method = PowerKVMBootMethod()39 method = PowerKVMBootMethod()
40 paths = [factory.getRandomString() for _ in range(3)]40 paths = [factory.getRandomString() for _ in range(3)]
41 for path in paths:41 for path in paths:
42 self.assertEqual(None, method.match_config_path(path))42 self.assertEqual(None, method.match_path(None, path))
4343
44 def test_render_config_returns_empty_string(self):44 def test_get_reader_returns_None(self):
45 method = PowerKVMBootMethod()45 method = PowerKVMBootMethod()
46 params = [make_kernel_parameters() for _ in range(3)]46 params = [make_kernel_parameters() for _ in range(3)]
47 for param in params:47 for param in params:
48 self.assertEqual("", method.render_config(params))48 self.assertEqual(None, method.get_reader(None, params))
4949
50 def test_install_bootloader_get_package_raises_error(self):50 def test_install_bootloader_get_package_raises_error(self):
51 method = PowerKVMBootMethod()51 method = PowerKVMBootMethod()
5252
=== modified file 'src/provisioningserver/boot/tests/test_pxe.py'
--- src/provisioningserver/boot/tests/test_pxe.py 2014-03-28 04:31:32 +0000
+++ src/provisioningserver/boot/tests/test_pxe.py 2014-05-28 13:35:49 +0000
@@ -20,6 +20,7 @@
20from maastesting.factory import factory20from maastesting.factory import factory
21from maastesting.testcase import MAASTestCase21from maastesting.testcase import MAASTestCase
22from provisioningserver import kernel_opts22from provisioningserver import kernel_opts
23from provisioningserver.boot import BytesReader
23from provisioningserver.boot.pxe import (24from provisioningserver.boot.pxe import (
24 ARP_HTYPE,25 ARP_HTYPE,
25 PXEBootMethod,26 PXEBootMethod,
@@ -150,14 +151,15 @@
150class TestPXEBootMethodRenderConfig(MAASTestCase):151class TestPXEBootMethodRenderConfig(MAASTestCase):
151 """Tests for `provisioningserver.boot.pxe.PXEBootMethod.render_config`."""152 """Tests for `provisioningserver.boot.pxe.PXEBootMethod.render_config`."""
152153
153 def test_render_install(self):154 def test_get_reader_install(self):
154 # Given the right configuration options, the PXE configuration is155 # Given the right configuration options, the PXE configuration is
155 # correctly rendered.156 # correctly rendered.
156 method = PXEBootMethod()157 method = PXEBootMethod()
157 params = make_kernel_parameters(self, purpose="install")158 params = make_kernel_parameters(self, purpose="install")
158 output = method.render_config(kernel_params=params)159 output = method.get_reader(backend=None, kernel_params=params)
159 # The output is always a Unicode string.160 # The output is a BytesReader.
160 self.assertThat(output, IsInstance(unicode))161 self.assertThat(output, IsInstance(BytesReader))
162 output = output.read(10000)
161 # The template has rendered without error. PXELINUX configurations163 # The template has rendered without error. PXELINUX configurations
162 # typically start with a DEFAULT line.164 # typically start with a DEFAULT line.
163 self.assertThat(output, StartsWith("DEFAULT "))165 self.assertThat(output, StartsWith("DEFAULT "))
@@ -177,54 +179,58 @@
177 r'.*^\s+APPEND .+?$',179 r'.*^\s+APPEND .+?$',
178 re.MULTILINE | re.DOTALL)))180 re.MULTILINE | re.DOTALL)))
179181
180 def test_render_with_extra_arguments_does_not_affect_output(self):182 def test_get_reader_with_extra_arguments_does_not_affect_output(self):
181 # render_config() allows any keyword arguments as a safety valve.183 # get_reader() allows any keyword arguments as a safety valve.
182 method = PXEBootMethod()184 method = PXEBootMethod()
183 options = {185 options = {
186 "backend": None,
184 "kernel_params": make_kernel_parameters(self, purpose="install"),187 "kernel_params": make_kernel_parameters(self, purpose="install"),
185 }188 }
186 # Capture the output before sprinking in some random options.189 # Capture the output before sprinking in some random options.
187 output_before = method.render_config(**options)190 output_before = method.get_reader(**options).read(10000)
188 # Sprinkle some magic in.191 # Sprinkle some magic in.
189 options.update(192 options.update(
190 (factory.make_name("name"), factory.make_name("value"))193 (factory.make_name("name"), factory.make_name("value"))
191 for _ in range(10))194 for _ in range(10))
192 # Capture the output after sprinking in some random options.195 # Capture the output after sprinking in some random options.
193 output_after = method.render_config(**options)196 output_after = method.get_reader(**options).read(10000)
194 # The generated template is the same.197 # The generated template is the same.
195 self.assertEqual(output_before, output_after)198 self.assertEqual(output_before, output_after)
196199
197 def test_render_config_with_local_purpose(self):200 def test_get_reader_with_local_purpose(self):
198 # If purpose is "local", the config.localboot.template should be201 # If purpose is "local", the config.localboot.template should be
199 # used.202 # used.
200 method = PXEBootMethod()203 method = PXEBootMethod()
201 options = {204 options = {
205 "backend": None,
202 "kernel_params": make_kernel_parameters(purpose="local"),206 "kernel_params": make_kernel_parameters(purpose="local"),
203 }207 }
204 output = method.render_config(**options)208 output = method.get_reader(**options).read(10000)
205 self.assertIn("LOCALBOOT 0", output)209 self.assertIn("LOCALBOOT 0", output)
206210
207 def test_render_config_with_local_purpose_i386_arch(self):211 def test_get_reader_with_local_purpose_i386_arch(self):
208 # Intel i386 is a special case and needs to use the chain.c32212 # Intel i386 is a special case and needs to use the chain.c32
209 # loader as the LOCALBOOT PXE directive is unreliable.213 # loader as the LOCALBOOT PXE directive is unreliable.
210 method = PXEBootMethod()214 method = PXEBootMethod()
211 options = {215 options = {
216 "backend": None,
212 "kernel_params": make_kernel_parameters(217 "kernel_params": make_kernel_parameters(
213 arch="i386", purpose="local"),218 arch="i386", purpose="local"),
214 }219 }
215 output = method.render_config(**options)220 output = method.get_reader(**options).read(10000)
216 self.assertIn("chain.c32", output)221 self.assertIn("chain.c32", output)
217 self.assertNotIn("LOCALBOOT", output)222 self.assertNotIn("LOCALBOOT", output)
218223
219 def test_render_config_with_local_purpose_amd64_arch(self):224 def test_get_reader_with_local_purpose_amd64_arch(self):
220 # Intel amd64 is a special case and needs to use the chain.c32225 # Intel amd64 is a special case and needs to use the chain.c32
221 # loader as the LOCALBOOT PXE directive is unreliable.226 # loader as the LOCALBOOT PXE directive is unreliable.
222 method = PXEBootMethod()227 method = PXEBootMethod()
223 options = {228 options = {
229 "backend": None,
224 "kernel_params": make_kernel_parameters(230 "kernel_params": make_kernel_parameters(
225 arch="amd64", purpose="local"),231 arch="amd64", purpose="local"),
226 }232 }
227 output = method.render_config(**options)233 output = method.get_reader(**options).read(10000)
228 self.assertIn("chain.c32", output)234 self.assertIn("chain.c32", output)
229 self.assertNotIn("LOCALBOOT", output)235 self.assertNotIn("LOCALBOOT", output)
230236
@@ -237,17 +243,18 @@
237 ("xinstall", dict(purpose="xinstall")),243 ("xinstall", dict(purpose="xinstall")),
238 ]244 ]
239245
240 def test_render_config_scenarios(self):246 def test_get_reader_scenarios(self):
241 # The commissioning config uses an extra PXELINUX module to auto247 # The commissioning config uses an extra PXELINUX module to auto
242 # select between i386 and amd64.248 # select between i386 and amd64.
243 method = PXEBootMethod()249 method = PXEBootMethod()
244 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")250 get_ephemeral_name = self.patch(kernel_opts, "get_ephemeral_name")
245 get_ephemeral_name.return_value = factory.make_name("ephemeral")251 get_ephemeral_name.return_value = factory.make_name("ephemeral")
246 options = {252 options = {
253 "backend": None,
247 "kernel_params": make_kernel_parameters(254 "kernel_params": make_kernel_parameters(
248 testcase=self, subarch="generic", purpose=self.purpose),255 testcase=self, subarch="generic", purpose=self.purpose),
249 }256 }
250 output = method.render_config(**options)257 output = method.get_reader(**options).read(10000)
251 config = parse_pxe_config(output)258 config = parse_pxe_config(output)
252 # The default section is defined.259 # The default section is defined.
253 default_section_label = config.header["DEFAULT"]260 default_section_label = config.header["DEFAULT"]
254261
=== modified file 'src/provisioningserver/boot/tests/test_uefi.py'
--- src/provisioningserver/boot/tests/test_uefi.py 2014-05-15 17:39:27 +0000
+++ src/provisioningserver/boot/tests/test_uefi.py 2014-05-28 13:35:49 +0000
@@ -18,6 +18,7 @@
1818
19from maastesting.factory import factory19from maastesting.factory import factory
20from maastesting.testcase import MAASTestCase20from maastesting.testcase import MAASTestCase
21from provisioningserver.boot import BytesReader
21from provisioningserver.boot.tftppath import compose_image_path22from provisioningserver.boot.tftppath import compose_image_path
22from provisioningserver.boot.uefi import (23from provisioningserver.boot.uefi import (
23 re_config_file,24 re_config_file,
@@ -60,14 +61,15 @@
60class TestRenderUEFIConfig(MAASTestCase):61class TestRenderUEFIConfig(MAASTestCase):
61 """Tests for `provisioningserver.boot.uefi.UEFIBootMethod`."""62 """Tests for `provisioningserver.boot.uefi.UEFIBootMethod`."""
6263
63 def test_render(self):64 def test_get_reader(self):
64 # Given the right configuration options, the UEFI configuration is65 # Given the right configuration options, the UEFI configuration is
65 # correctly rendered.66 # correctly rendered.
66 method = UEFIBootMethod()67 method = UEFIBootMethod()
67 params = make_kernel_parameters(purpose="install")68 params = make_kernel_parameters(purpose="install")
68 output = method.render_config(kernel_params=params)69 output = method.get_reader(backend=None, kernel_params=params)
69 # The output is always a Unicode string.70 # The output is a BytesReader.
70 self.assertThat(output, IsInstance(unicode))71 self.assertThat(output, IsInstance(BytesReader))
72 output = output.read(10000)
71 # The template has rendered without error. UEFI configurations73 # The template has rendered without error. UEFI configurations
72 # typically start with a DEFAULT line.74 # typically start with a DEFAULT line.
73 self.assertThat(output, StartsWith("set default=\"0\""))75 self.assertThat(output, StartsWith("set default=\"0\""))
@@ -85,32 +87,34 @@
85 r'.*^\s+initrd %s/di-initrd$' % re.escape(image_dir),87 r'.*^\s+initrd %s/di-initrd$' % re.escape(image_dir),
86 re.MULTILINE | re.DOTALL)))88 re.MULTILINE | re.DOTALL)))
8789
88 def test_render_with_extra_arguments_does_not_affect_output(self):90 def test_get_reader_with_extra_arguments_does_not_affect_output(self):
89 # render_config() allows any keyword arguments as a safety valve.91 # get_reader() allows any keyword arguments as a safety valve.
90 method = UEFIBootMethod()92 method = UEFIBootMethod()
91 options = {93 options = {
94 "backend": None,
92 "kernel_params": make_kernel_parameters(purpose="install"),95 "kernel_params": make_kernel_parameters(purpose="install"),
93 }96 }
94 # Capture the output before sprinking in some random options.97 # Capture the output before sprinking in some random options.
95 output_before = method.render_config(**options)98 output_before = method.get_reader(**options).read(10000)
96 # Sprinkle some magic in.99 # Sprinkle some magic in.
97 options.update(100 options.update(
98 (factory.make_name("name"), factory.make_name("value"))101 (factory.make_name("name"), factory.make_name("value"))
99 for _ in range(10))102 for _ in range(10))
100 # Capture the output after sprinking in some random options.103 # Capture the output after sprinking in some random options.
101 output_after = method.render_config(**options)104 output_after = method.get_reader(**options).read(10000)
102 # The generated template is the same.105 # The generated template is the same.
103 self.assertEqual(output_before, output_after)106 self.assertEqual(output_before, output_after)
104107
105 def test_render_config_with_local_purpose(self):108 def test_get_reader_with_local_purpose(self):
106 # If purpose is "local", the config.localboot.template should be109 # If purpose is "local", the config.localboot.template should be
107 # used.110 # used.
108 method = UEFIBootMethod()111 method = UEFIBootMethod()
109 options = {112 options = {
113 "backend": None,
110 "kernel_params": make_kernel_parameters(114 "kernel_params": make_kernel_parameters(
111 purpose="local", arch="amd64"),115 purpose="local", arch="amd64"),
112 }116 }
113 output = method.render_config(**options)117 output = method.get_reader(**options).read(10000)
114 self.assertIn("configfile /efi/ubuntu/grub.cfg", output)118 self.assertIn("configfile /efi/ubuntu/grub.cfg", output)
115119
116120
117121
=== modified file 'src/provisioningserver/boot/uefi.py'
--- src/provisioningserver/boot/uefi.py 2014-03-28 04:06:27 +0000
+++ src/provisioningserver/boot/uefi.py 2014-05-28 13:35:49 +0000
@@ -25,6 +25,7 @@
25from provisioningserver.boot import (25from provisioningserver.boot import (
26 BootMethod,26 BootMethod,
27 BootMethodInstallError,27 BootMethodInstallError,
28 BytesReader,
28 get_parameters,29 get_parameters,
29 utils,30 utils,
30 )31 )
@@ -120,10 +121,11 @@
120 bootloader_path = "bootx64.efi"121 bootloader_path = "bootx64.efi"
121 arch_octet = "00:07" # AMD64 EFI122 arch_octet = "00:07" # AMD64 EFI
122123
123 def match_config_path(self, path):124 def match_path(self, backend, path):
124 """Checks path for the configuration file that needs to be125 """Checks path for the configuration file that needs to be
125 generated.126 generated.
126127
128 :param backend: requesting backend
127 :param path: requested path129 :param path: requested path
128 :returns: dict of match params from path, None if no match130 :returns: dict of match params from path, None if no match
129 """131 """
@@ -139,19 +141,20 @@
139141
140 return params142 return params
141143
142 def render_config(self, kernel_params, **extra):144 def get_reader(self, backend, kernel_params, **extra):
143 """Render a configuration file as a unicode string.145 """Render a configuration file as a unicode string.
144146
147 :param backend: requesting backend
145 :param kernel_params: An instance of `KernelParameters`.148 :param kernel_params: An instance of `KernelParameters`.
146 :param extra: Allow for other arguments. This is a safety valve;149 :param extra: Allow for other arguments. This is a safety valve;
147 parameters generated in another component (for example, see150 parameters generated in another component (for example, see
148 `TFTPBackend.get_config_reader`) won't cause this to break.151 `TFTPBackend.get_boot_method_reader`) won't cause this to break.
149 """152 """
150 template = self.get_template(153 template = self.get_template(
151 kernel_params.purpose, kernel_params.arch,154 kernel_params.purpose, kernel_params.arch,
152 kernel_params.subarch)155 kernel_params.subarch)
153 namespace = self.compose_template_namespace(kernel_params)156 namespace = self.compose_template_namespace(kernel_params)
154 return template.substitute(namespace)157 return BytesReader(template.substitute(namespace).encode("utf-8"))
155158
156 def install_bootloader(self, destination):159 def install_bootloader(self, destination):
157 """Installs the required files for UEFI booting into the160 """Installs the required files for UEFI booting into the
158161
=== modified file 'src/provisioningserver/tests/test_tftp.py'
--- src/provisioningserver/tests/test_tftp.py 2014-03-28 06:51:59 +0000
+++ src/provisioningserver/tests/test_tftp.py 2014-05-28 13:35:49 +0000
@@ -28,11 +28,11 @@
28from maastesting.testcase import MAASTestCase28from maastesting.testcase import MAASTestCase
29import mock29import mock
30from provisioningserver import tftp as tftp_module30from provisioningserver import tftp as tftp_module
31from provisioningserver.boot import BytesReader
31from provisioningserver.boot.pxe import PXEBootMethod32from provisioningserver.boot.pxe import PXEBootMethod
32from provisioningserver.boot.tests.test_pxe import compose_config_path33from provisioningserver.boot.tests.test_pxe import compose_config_path
33from provisioningserver.tests.test_kernel_opts import make_kernel_parameters34from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
34from provisioningserver.tftp import (35from provisioningserver.tftp import (
35 BytesReader,
36 TFTPBackend,36 TFTPBackend,
37 TFTPService,37 TFTPService,
38 )38 )
@@ -127,9 +127,9 @@
127 self.assertEqual(b"", reader.read(1))127 self.assertEqual(b"", reader.read(1))
128128
129 @inlineCallbacks129 @inlineCallbacks
130 def test_get_reader_config_file(self):130 def test_get_render_file(self):
131 # For paths matching re_config_file, TFTPBackend.get_reader() returns131 # For paths matching PXEBootMethod.match_path, TFTPBackend.get_reader()
132 # a Deferred that will yield a BytesReader.132 # returns a Deferred that will yield a BytesReader.
133 cluster_uuid = factory.getRandomUUID()133 cluster_uuid = factory.getRandomUUID()
134 self.patch(tftp_module, 'get_cluster_uuid').return_value = (134 self.patch(tftp_module, 'get_cluster_uuid').return_value = (
135 cluster_uuid)135 cluster_uuid)
@@ -147,8 +147,8 @@
147 factory.getRandomPort()),147 factory.getRandomPort()),
148 }148 }
149149
150 @partial(self.patch, backend, "get_config_reader")150 @partial(self.patch, backend, "get_boot_method_reader")
151 def get_config_reader(boot_method, params):151 def get_boot_method_reader(boot_method, params):
152 params_json = json.dumps(params)152 params_json = json.dumps(params)
153 params_json_reader = BytesReader(params_json)153 params_json_reader = BytesReader(params_json)
154 return succeed(params_json_reader)154 return succeed(params_json_reader)
@@ -168,9 +168,10 @@
168 self.assertEqual(expected_params, observed_params)168 self.assertEqual(expected_params, observed_params)
169169
170 @inlineCallbacks170 @inlineCallbacks
171 def test_get_config_reader_returns_rendered_params(self):171 def test_get_boot_method_reader_returns_rendered_params(self):
172 # get_config_reader() takes a dict() of parameters and returns an172 # get_boot_method_reader() takes a dict() of parameters and returns an
173 # `IReader` of a PXE configuration, rendered by `render_pxe_config`.173 # `IReader` of a PXE configuration, rendered by
174 # `PXEBootMethod.get_reader`.
174 backend = TFTPBackend(self.make_dir(), b"http://example.com/")175 backend = TFTPBackend(self.make_dir(), b"http://example.com/")
175 # Fake configuration parameters, as discovered from the file path.176 # Fake configuration parameters, as discovered from the file path.
176 fake_params = {"mac": factory.getRandomMACAddress("-")}177 fake_params = {"mac": factory.getRandomMACAddress("-")}
@@ -182,15 +183,15 @@
182 get_page_patch = self.patch(backend, "get_page")183 get_page_patch = self.patch(backend, "get_page")
183 get_page_patch.return_value = succeed(fake_get_page_result)184 get_page_patch.return_value = succeed(fake_get_page_result)
184185
185 # Stub render_config to return the render parameters.186 # Stub get_reader to return the render parameters.
186 method = PXEBootMethod()187 method = PXEBootMethod()
187 fake_render_result = factory.make_name("render")188 fake_render_result = factory.make_name("render").encode("utf-8")
188 render_patch = self.patch(method, "render_config")189 render_patch = self.patch(method, "get_reader")
189 render_patch.return_value = fake_render_result190 render_patch.return_value = BytesReader(fake_render_result)
190191
191 # Get the rendered configuration, which will actually be a JSON dump192 # Get the rendered configuration, which will actually be a JSON dump
192 # of the render-time parameters.193 # of the render-time parameters.
193 reader = yield backend.get_config_reader(method, fake_params)194 reader = yield backend.get_boot_method_reader(method, fake_params)
194 self.addCleanup(reader.finish)195 self.addCleanup(reader.finish)
195 self.assertIsInstance(reader, BytesReader)196 self.assertIsInstance(reader, BytesReader)
196 output = reader.read(10000)197 output = reader.read(10000)
@@ -198,13 +199,13 @@
198 # The kernel parameters were fetched using `backend.get_page`.199 # The kernel parameters were fetched using `backend.get_page`.
199 self.assertThat(backend.get_page, MockCalledOnceWith(mock.ANY))200 self.assertThat(backend.get_page, MockCalledOnceWith(mock.ANY))
200201
201 # The result has been rendered by `backend.render_config`.202 # The result has been rendered by `method.get_reader`.
202 self.assertEqual(fake_render_result.encode("utf-8"), output)203 self.assertEqual(fake_render_result.encode("utf-8"), output)
203 self.assertThat(method.render_config, MockCalledOnceWith(204 self.assertThat(method.get_reader, MockCalledOnceWith(
204 kernel_params=fake_kernel_params, **fake_params))205 backend, kernel_params=fake_kernel_params, **fake_params))
205206
206 @inlineCallbacks207 @inlineCallbacks
207 def test_get_config_reader_substitutes_armhf_in_params(self):208 def test_get_boot_method_render_substitutes_armhf_in_params(self):
208 # get_config_reader() should substitute "arm" for "armhf" in the209 # get_config_reader() should substitute "arm" for "armhf" in the
209 # arch field of the parameters (mapping from pxe to maas210 # arch field of the parameters (mapping from pxe to maas
210 # namespace).211 # namespace).
@@ -224,8 +225,8 @@
224 factory.getRandomPort()),225 factory.getRandomPort()),
225 }226 }
226227
227 @partial(self.patch, backend, "get_config_reader")228 @partial(self.patch, backend, "get_boot_method_reader")
228 def get_config_reader(boot_method, params):229 def get_boot_method_reader(boot_method, params):
229 params_json = json.dumps(params)230 params_json = json.dumps(params)
230 params_json_reader = BytesReader(params_json)231 params_json_reader = BytesReader(params_json)
231 return succeed(params_json_reader)232 return succeed(params_json_reader)
232233
=== modified file 'src/provisioningserver/tftp.py'
--- src/provisioningserver/tftp.py 2014-03-28 15:17:34 +0000
+++ src/provisioningserver/tftp.py 2014-05-28 13:35:49 +0000
@@ -18,7 +18,6 @@
18 ]18 ]
1919
20import httplib20import httplib
21from io import BytesIO
22import json21import json
23from urllib import urlencode22from urllib import urlencode
24from urlparse import (23from urlparse import (
@@ -34,10 +33,7 @@
34 deferred,33 deferred,
35 get_all_interface_addresses,34 get_all_interface_addresses,
36 )35 )
37from tftp.backend import (36from tftp.backend import FilesystemSynchronousBackend
38 FilesystemSynchronousBackend,
39 IReader,
40 )
41from tftp.errors import FileNotFound37from tftp.errors import FileNotFound
42from tftp.protocol import TFTP38from tftp.protocol import TFTP
43from twisted.application import internet39from twisted.application import internet
@@ -45,22 +41,6 @@
45from twisted.python.context import get41from twisted.python.context import get
46from twisted.web.client import getPage42from twisted.web.client import getPage
47import twisted.web.error43import twisted.web.error
48from zope.interface import implementer
49
50
51@implementer(IReader)
52class BytesReader:
53
54 def __init__(self, data):
55 super(BytesReader, self).__init__()
56 self.buffer = BytesIO(data)
57 self.size = len(data)
58
59 def read(self, size):
60 return self.buffer.read(size)
61
62 def finish(self):
63 self.buffer.close()
6444
6545
66class TFTPBackend(FilesystemSynchronousBackend):46class TFTPBackend(FilesystemSynchronousBackend):
@@ -118,7 +98,7 @@
118 def get_boot_method(self, file_name):98 def get_boot_method(self, file_name):
119 """Finds the correct boot method."""99 """Finds the correct boot method."""
120 for _, method in BootMethodRegistry:100 for _, method in BootMethodRegistry:
121 params = method.match_config_path(file_name)101 params = method.match_path(self, file_name)
122 if params is not None:102 if params is not None:
123 return method, params103 return method, params
124 return None, None104 return None, None
@@ -142,21 +122,19 @@
142 return d122 return d
143123
144 @deferred124 @deferred
145 def get_config_reader(self, boot_method, params):125 def get_boot_method_reader(self, boot_method, params):
146 """Return an `IReader` for a boot method.126 """Return an `IReader` for a boot method.
147127
148 :param boot_method: Boot method that is generating the config128 :param boot_method: Boot method that is generating the config
149 :param params: Parameters so far obtained, typically from the file129 :param params: Parameters so far obtained, typically from the file
150 path requested.130 path requested.
151 """131 """
152 def generate_config(kernel_params):132 def generate(kernel_params):
153 config = boot_method.render_config(133 return boot_method.get_reader(
154 kernel_params=kernel_params, **params)134 self, kernel_params=kernel_params, **params)
155 return config.encode("utf-8")
156135
157 d = self.get_kernel_params(params)136 d = self.get_kernel_params(params)
158 d.addCallback(generate_config)137 d.addCallback(generate)
159 d.addCallback(BytesReader)
160 return d138 return d
161139
162 @staticmethod140 @staticmethod
@@ -203,7 +181,7 @@
203 remote_host, remote_port = get("remote", (None, None))181 remote_host, remote_port = get("remote", (None, None))
204 params["remote"] = remote_host182 params["remote"] = remote_host
205 params["cluster_uuid"] = get_cluster_uuid()183 params["cluster_uuid"] = get_cluster_uuid()
206 d = self.get_config_reader(boot_method, params)184 d = self.get_boot_method_reader(boot_method, params)
207 d.addErrback(self.get_page_errback, file_name)185 d.addErrback(self.get_page_errback, file_name)
208 return d186 return d
209187

Subscribers

People subscribed via source and target branches

to all changes: