Merge lp:~allenap/maas/rpc-get-preseed-data into lp:~maas-committers/maas/trunk
- rpc-get-preseed-data
- Merge into trunk
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2553 |
Proposed branch: | lp:~allenap/maas/rpc-get-preseed-data |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
438 lines (+236/-22) 10 files modified
src/maasserver/compose_preseed.py (+11/-2) src/maasserver/tests/test_compose_preseed.py (+5/-2) src/provisioningserver/drivers/osystem/__init__.py (+19/-4) src/provisioningserver/drivers/osystem/tests/test_windows.py (+13/-9) src/provisioningserver/drivers/osystem/windows.py (+3/-3) src/provisioningserver/rpc/cluster.py (+31/-1) src/provisioningserver/rpc/clusterservice.py (+16/-0) src/provisioningserver/rpc/osystems.py (+28/-1) src/provisioningserver/rpc/tests/test_clusterservice.py (+70/-0) src/provisioningserver/rpc/tests/test_osystems.py (+40/-0) |
To merge this branch: | bzr merge lp:~allenap/maas/rpc-get-preseed-data |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+226746@code.launchpad.net |
Commit message
New RPC call GetPreseedData
Description of the change
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote : | # |
Thanks for the review! Good catches on the docstrings.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/compose_preseed.py' |
2 | --- src/maasserver/compose_preseed.py 2014-06-20 13:38:39 +0000 |
3 | +++ src/maasserver/compose_preseed.py 2014-07-14 21:39:26 +0000 |
4 | @@ -20,7 +20,11 @@ |
5 | |
6 | from maasserver.enum import PRESEED_TYPE |
7 | from maasserver.utils import absolute_reverse |
8 | -from provisioningserver.drivers.osystem import OperatingSystemRegistry |
9 | +from provisioningserver.drivers.osystem import ( |
10 | + Node as OSystemNode, |
11 | + OperatingSystemRegistry, |
12 | + Token as OSystemToken, |
13 | + ) |
14 | import yaml |
15 | |
16 | |
17 | @@ -108,9 +112,14 @@ |
18 | if preseed_type == PRESEED_TYPE.CURTIN: |
19 | metadata_url = absolute_reverse( |
20 | 'curtin-metadata', base_url=base_url) |
21 | + node_for_osystem = OSystemNode( |
22 | + node.system_id, node.hostname) |
23 | + token_for_osystem = OSystemToken( |
24 | + token.consumer.key, token.key, token.secret) |
25 | try: |
26 | return osystem.compose_preseed( |
27 | - preseed_type, node, token, metadata_url) |
28 | + preseed_type, node_for_osystem, token_for_osystem, |
29 | + metadata_url) |
30 | except NotImplementedError: |
31 | pass |
32 | |
33 | |
34 | === modified file 'src/maasserver/tests/test_compose_preseed.py' |
35 | --- src/maasserver/tests/test_compose_preseed.py 2014-06-11 15:26:45 +0000 |
36 | +++ src/maasserver/tests/test_compose_preseed.py 2014-07-14 21:39:26 +0000 |
37 | @@ -126,10 +126,13 @@ |
38 | mock_compose = self.patch(osystem, 'compose_preseed') |
39 | node = factory.make_node( |
40 | osystem=osystem.name, status=NODE_STATUS.READY) |
41 | - |
42 | token = NodeKey.objects.get_token_for_node(node) |
43 | url = absolute_reverse('curtin-metadata') |
44 | compose_preseed(PRESEED_TYPE.CURTIN, node) |
45 | self.assertThat( |
46 | mock_compose, |
47 | - MockCalledOnceWith(PRESEED_TYPE.CURTIN, node, token, url)) |
48 | + MockCalledOnceWith( |
49 | + PRESEED_TYPE.CURTIN, |
50 | + (node.system_id, node.hostname), |
51 | + (token.consumer.key, token.key, token.secret), |
52 | + url)) |
53 | |
54 | === modified file 'src/provisioningserver/drivers/osystem/__init__.py' |
55 | --- src/provisioningserver/drivers/osystem/__init__.py 2014-07-11 14:07:08 +0000 |
56 | +++ src/provisioningserver/drivers/osystem/__init__.py 2014-07-14 21:39:26 +0000 |
57 | @@ -13,8 +13,10 @@ |
58 | |
59 | __metaclass__ = type |
60 | __all__ = [ |
61 | + "Node", |
62 | "OperatingSystem", |
63 | "OperatingSystemRegistry", |
64 | + "Token", |
65 | ] |
66 | |
67 | from abc import ( |
68 | @@ -22,6 +24,7 @@ |
69 | abstractmethod, |
70 | abstractproperty, |
71 | ) |
72 | +from collections import namedtuple |
73 | |
74 | from provisioningserver.utils.registry import Registry |
75 | |
76 | @@ -36,6 +39,16 @@ |
77 | XINSTALL = 'xinstall' |
78 | |
79 | |
80 | +# A cluster-side representation of a Node, relevant to the osystem code, |
81 | +# with only minimal fields. |
82 | +Node = namedtuple("Node", ("system_id", "hostname")) |
83 | + |
84 | + |
85 | +# A cluster-side representation of a Token, relevant to the osystem code, |
86 | +# with only minimal fields. |
87 | +Token = namedtuple("Token", ("consumer_key", "token_key", "token_secret")) |
88 | + |
89 | + |
90 | class OperatingSystem: |
91 | """Skeleton for an operating system.""" |
92 | |
93 | @@ -139,10 +152,12 @@ |
94 | """Composes the preseed for the given node. |
95 | |
96 | :param preseed_type: Preseed type to compose. |
97 | - :param node: Node preseed needs generating. |
98 | - :param token: OAuth token for url. |
99 | - :param metadata_url: Metdata url for node. |
100 | - :returns: Preseed for node. |
101 | + :param node: Node preseed needs generating for. |
102 | + :type node: :py:class:`Node` |
103 | + :param token: OAuth token for URL. |
104 | + :type token: :py:class:`Token` |
105 | + :param metadata_url: Metdata URL for node. |
106 | + :returns: Preseed data for node. |
107 | :raise: |
108 | NotImplementedError: doesn't implement a custom preseed |
109 | """ |
110 | |
111 | === modified file 'src/provisioningserver/drivers/osystem/tests/test_windows.py' |
112 | --- src/provisioningserver/drivers/osystem/tests/test_windows.py 2014-07-10 17:08:58 +0000 |
113 | +++ src/provisioningserver/drivers/osystem/tests/test_windows.py 2014-07-14 21:39:26 +0000 |
114 | @@ -18,7 +18,10 @@ |
115 | |
116 | from maastesting.factory import factory |
117 | from maastesting.testcase import MAASTestCase |
118 | -from mock import MagicMock |
119 | +from provisioningserver.drivers.osystem import ( |
120 | + Node, |
121 | + Token, |
122 | + ) |
123 | from provisioningserver.drivers.osystem.windows import ( |
124 | BOOT_IMAGE_PURPOSE, |
125 | Config, |
126 | @@ -135,9 +138,10 @@ |
127 | machine = factory.make_name('hostname') |
128 | dns = factory.make_name('dns') |
129 | hostname = '%s.%s' % (machine, dns) |
130 | - node = MagicMock() |
131 | - node.hostname = hostname |
132 | - return node |
133 | + return Node( |
134 | + system_id=factory.make_name("system_id"), |
135 | + hostname=hostname, |
136 | + ) |
137 | |
138 | def make_token(self, consumer_key=None, token_key=None, token_secret=None): |
139 | if consumer_key is None: |
140 | @@ -146,11 +150,11 @@ |
141 | token_key = factory.make_name('token_key') |
142 | if token_secret is None: |
143 | token_secret = factory.make_name('secret_key') |
144 | - token = MagicMock() |
145 | - token.consumer.key = consumer_key |
146 | - token.key = token_key |
147 | - token.secret = token_secret |
148 | - return token |
149 | + return Token( |
150 | + consumer_key=consumer_key, |
151 | + token_key=token_key, |
152 | + token_secret=token_secret, |
153 | + ) |
154 | |
155 | def test_compose_pressed_not_implemented_for_curtin(self): |
156 | osystem = WindowsOS() |
157 | |
158 | === modified file 'src/provisioningserver/drivers/osystem/windows.py' |
159 | --- src/provisioningserver/drivers/osystem/windows.py 2014-07-10 17:08:58 +0000 |
160 | +++ src/provisioningserver/drivers/osystem/windows.py 2014-07-14 21:39:26 +0000 |
161 | @@ -98,9 +98,9 @@ |
162 | credentials = { |
163 | 'maas_metadata_url': metadata_url, |
164 | 'maas_oauth_consumer_secret': '', |
165 | - 'maas_oauth_consumer_key': token.consumer.key, |
166 | - 'maas_oauth_token_key': token.key, |
167 | - 'maas_oauth_token_secret': token.secret, |
168 | + 'maas_oauth_consumer_key': token.consumer_key, |
169 | + 'maas_oauth_token_key': token.token_key, |
170 | + 'maas_oauth_token_secret': token.token_secret, |
171 | 'hostname': hostname, |
172 | } |
173 | return credentials |
174 | |
175 | === modified file 'src/provisioningserver/rpc/cluster.py' |
176 | --- src/provisioningserver/rpc/cluster.py 2014-07-09 21:45:26 +0000 |
177 | +++ src/provisioningserver/rpc/cluster.py 2014-07-14 21:39:26 +0000 |
178 | @@ -24,7 +24,10 @@ |
179 | ] |
180 | |
181 | from provisioningserver.rpc import exceptions |
182 | -from provisioningserver.rpc.arguments import StructureAsJSON |
183 | +from provisioningserver.rpc.arguments import ( |
184 | + ParsedURL, |
185 | + StructureAsJSON, |
186 | + ) |
187 | from provisioningserver.rpc.common import Identify |
188 | from twisted.protocols import amp |
189 | |
190 | @@ -119,3 +122,30 @@ |
191 | exceptions.NoSuchOperatingSystem: ( |
192 | b"NoSuchOperatingSystem"), |
193 | } |
194 | + |
195 | + |
196 | +class GetPreseedData(amp.Command): |
197 | + """Get OS-specific preseed data. |
198 | + |
199 | + :since: 1.7 |
200 | + """ |
201 | + |
202 | + arguments = [ |
203 | + (b"osystem", amp.Unicode()), |
204 | + (b"preseed_type", amp.Unicode()), |
205 | + (b"node_system_id", amp.Unicode()), |
206 | + (b"node_hostname", amp.Unicode()), |
207 | + (b"consumer_key", amp.Unicode()), |
208 | + (b"token_key", amp.Unicode()), |
209 | + (b"token_secret", amp.Unicode()), |
210 | + (b"metadata_url", ParsedURL()), |
211 | + ] |
212 | + response = [ |
213 | + (b"data", StructureAsJSON()), |
214 | + ] |
215 | + errors = { |
216 | + exceptions.NoSuchOperatingSystem: ( |
217 | + b"NoSuchOperatingSystem"), |
218 | + NotImplementedError: ( |
219 | + b"NotImplementedError"), |
220 | + } |
221 | |
222 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
223 | --- src/provisioningserver/rpc/clusterservice.py 2014-07-09 20:59:05 +0000 |
224 | +++ src/provisioningserver/rpc/clusterservice.py 2014-07-14 21:39:26 +0000 |
225 | @@ -41,6 +41,7 @@ |
226 | from provisioningserver.rpc.interfaces import IConnection |
227 | from provisioningserver.rpc.osystems import ( |
228 | gen_operating_systems, |
229 | + get_preseed_data, |
230 | validate_license_key, |
231 | ) |
232 | from twisted.application.internet import ( |
233 | @@ -130,6 +131,21 @@ |
234 | """ |
235 | return {"is_valid": validate_license_key(osystem, release, key)} |
236 | |
237 | + @cluster.GetPreseedData.responder |
238 | + def get_preseed_data( |
239 | + self, osystem, preseed_type, node_system_id, node_hostname, |
240 | + consumer_key, token_key, token_secret, metadata_url): |
241 | + """get_preseed_data() |
242 | + |
243 | + Implementation of |
244 | + :py:class:`~provisioningserver.rpc.cluster.GetPreseedData`. |
245 | + """ |
246 | + return { |
247 | + "data": get_preseed_data( |
248 | + osystem, preseed_type, node_system_id, node_hostname, |
249 | + consumer_key, token_key, token_secret, metadata_url), |
250 | + } |
251 | + |
252 | @amp.StartTLS.responder |
253 | def get_tls_parameters(self): |
254 | """get_tls_parameters() |
255 | |
256 | === modified file 'src/provisioningserver/rpc/osystems.py' |
257 | --- src/provisioningserver/rpc/osystems.py 2014-07-11 16:57:48 +0000 |
258 | +++ src/provisioningserver/rpc/osystems.py 2014-07-14 21:39:26 +0000 |
259 | @@ -17,7 +17,11 @@ |
260 | "validate_license_key", |
261 | ] |
262 | |
263 | -from provisioningserver.drivers.osystem import OperatingSystemRegistry |
264 | +from provisioningserver.drivers.osystem import ( |
265 | + Node, |
266 | + OperatingSystemRegistry, |
267 | + Token, |
268 | + ) |
269 | from provisioningserver.rpc import exceptions |
270 | |
271 | |
272 | @@ -70,3 +74,26 @@ |
273 | raise exceptions.NoSuchOperatingSystem(osystem) |
274 | else: |
275 | return osystem.validate_license_key(release, key) |
276 | + |
277 | + |
278 | +def get_preseed_data( |
279 | + osystem, preseed_type, node_system_id, node_hostname, |
280 | + consumer_key, token_key, token_secret, metadata_url): |
281 | + """Composes preseed data for the given node. |
282 | + |
283 | + :param preseed_type: The preseed type being composed. |
284 | + :param node: The node for which a preseed is being composed. |
285 | + :param token: OAuth token for the metadata URL. |
286 | + :param metadata_url: The metdata URL for the node. |
287 | + :returns: Preseed data for the given node. |
288 | + :raise NotImplementedError: when the specified operating system does |
289 | + not require custom preseed data. |
290 | + """ |
291 | + try: |
292 | + osystem = OperatingSystemRegistry[osystem] |
293 | + except KeyError: |
294 | + raise exceptions.NoSuchOperatingSystem(osystem) |
295 | + else: |
296 | + return osystem.compose_preseed( |
297 | + preseed_type, Node(node_system_id, node_hostname), |
298 | + Token(consumer_key, token_key, token_secret), metadata_url) |
299 | |
300 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
301 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2014-07-11 16:57:48 +0000 |
302 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2014-07-14 21:39:26 +0000 |
303 | @@ -18,6 +18,7 @@ |
304 | import json |
305 | import os.path |
306 | from random import randint |
307 | +from urlparse import urlparse |
308 | |
309 | from fixtures import EnvironmentVariable |
310 | from maastesting.factory import factory |
311 | @@ -36,6 +37,10 @@ |
312 | ) |
313 | from provisioningserver.boot import tftppath |
314 | from provisioningserver.boot.tests.test_tftppath import make_osystem |
315 | +from provisioningserver.drivers.osystem import ( |
316 | + OperatingSystem, |
317 | + OperatingSystemRegistry, |
318 | + ) |
319 | from provisioningserver.power_schema import JSON_POWER_TYPE_PARAMETERS |
320 | from provisioningserver.rpc import ( |
321 | cluster, |
322 | @@ -761,3 +766,68 @@ |
323 | with ExpectedException(exceptions.NoSuchOperatingSystem): |
324 | yield call_responder( |
325 | Cluster(), cluster.ValidateLicenseKey, arguments) |
326 | + |
327 | + |
328 | +class TestClusterProtocol_GetPreseedData(MAASTestCase): |
329 | + |
330 | + run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5) |
331 | + |
332 | + def make_arguments(self): |
333 | + return { |
334 | + "osystem": factory.make_name("osystem"), |
335 | + "preseed_type": factory.make_name("preseed_type"), |
336 | + "node_system_id": factory.make_name("system_id"), |
337 | + "node_hostname": factory.make_name("hostname"), |
338 | + "consumer_key": factory.make_name("consumer_key"), |
339 | + "token_key": factory.make_name("token_key"), |
340 | + "token_secret": factory.make_name("token_secret"), |
341 | + "metadata_url": urlparse( |
342 | + "https://%s/path/to/metadata" % factory.make_hostname()), |
343 | + } |
344 | + |
345 | + def test_is_registered(self): |
346 | + protocol = Cluster() |
347 | + responder = protocol.locateResponder( |
348 | + cluster.GetPreseedData.commandName) |
349 | + self.assertIsNot(responder, None) |
350 | + |
351 | + @inlineCallbacks |
352 | + def test_calls_get_preseed_data(self): |
353 | + get_preseed_data = self.patch(clusterservice, "get_preseed_data") |
354 | + get_preseed_data.return_value = factory.make_name("data") |
355 | + arguments = self.make_arguments() |
356 | + observed = yield call_responder( |
357 | + Cluster(), cluster.GetPreseedData, arguments) |
358 | + expected = {"data": get_preseed_data.return_value} |
359 | + self.assertEqual(expected, observed) |
360 | + # The arguments are passed to the responder positionally. |
361 | + self.assertThat(get_preseed_data, MockCalledOnceWith( |
362 | + arguments["osystem"], arguments["preseed_type"], |
363 | + arguments["node_system_id"], arguments["node_hostname"], |
364 | + arguments["consumer_key"], arguments["token_key"], |
365 | + arguments["token_secret"], arguments["metadata_url"])) |
366 | + |
367 | + @inlineCallbacks |
368 | + def test_exception_when_os_does_not_exist(self): |
369 | + # A remote NoSuchOperatingSystem exception is re-raised locally. |
370 | + get_preseed_data = self.patch( |
371 | + clusterservice, "get_preseed_data") |
372 | + get_preseed_data.side_effect = exceptions.NoSuchOperatingSystem() |
373 | + arguments = self.make_arguments() |
374 | + with ExpectedException(exceptions.NoSuchOperatingSystem): |
375 | + yield call_responder( |
376 | + Cluster(), cluster.GetPreseedData, arguments) |
377 | + |
378 | + @inlineCallbacks |
379 | + def test_exception_when_preseed_not_implemented(self): |
380 | + # A remote NotImplementedError exception is re-raised locally. |
381 | + # Choose an operating system which has not overridden the |
382 | + # default compose_preseed. |
383 | + osystem_name = next( |
384 | + osystem_name for osystem_name, osystem in OperatingSystemRegistry |
385 | + if osystem.compose_preseed == OperatingSystem.compose_preseed) |
386 | + arguments = self.make_arguments() |
387 | + arguments["osystem"] = osystem_name |
388 | + with ExpectedException(exceptions.NoSuchOperatingSystem): |
389 | + yield call_responder( |
390 | + Cluster(), cluster.GetPreseedData, arguments) |
391 | |
392 | === modified file 'src/provisioningserver/rpc/tests/test_osystems.py' |
393 | --- src/provisioningserver/rpc/tests/test_osystems.py 2014-07-11 20:16:44 +0000 |
394 | +++ src/provisioningserver/rpc/tests/test_osystems.py 2014-07-14 21:39:26 +0000 |
395 | @@ -128,3 +128,43 @@ |
396 | self.assertThat( |
397 | os_specific_validate_license_key, |
398 | MockCalledOnceWith(self.release, sentinel.key)) |
399 | + |
400 | + |
401 | +class TestGetPreseedDataErrors(MAASTestCase): |
402 | + |
403 | + def test_throws_exception_when_os_does_not_exist(self): |
404 | + self.assertRaises( |
405 | + exceptions.NoSuchOperatingSystem, |
406 | + osystems.get_preseed_data, factory.make_name("no-such-os"), |
407 | + sentinel.preseed_type, sentinel.node_system_id, |
408 | + sentinel.node_hostname, sentinel.consumer_key, |
409 | + sentinel.token_key, sentinel.token_secret, |
410 | + sentinel.metadata_url) |
411 | + |
412 | + |
413 | +class TestGetPreseedData(MAASTestCase): |
414 | + |
415 | + # Check for every OS. |
416 | + scenarios = [ |
417 | + (osystem.name, {"osystem": osystem}) |
418 | + for _, osystem in OperatingSystemRegistry |
419 | + ] |
420 | + |
421 | + def test_get_preseed_data_calls_compose_preseed(self): |
422 | + # get_preseed_data() calls compose_preseed() on the |
423 | + # OperatingSystem instances. |
424 | + os_specific_compose_preseed = self.patch( |
425 | + self.osystem, "compose_preseed") |
426 | + osystems.get_preseed_data( |
427 | + self.osystem.name, sentinel.preseed_type, |
428 | + sentinel.node_system_id, sentinel.node_hostname, |
429 | + sentinel.consumer_key, sentinel.token_key, |
430 | + sentinel.token_secret, sentinel.metadata_url) |
431 | + self.assertThat( |
432 | + os_specific_compose_preseed, |
433 | + MockCalledOnceWith( |
434 | + sentinel.preseed_type, |
435 | + (sentinel.node_system_id, sentinel.node_hostname), |
436 | + (sentinel.consumer_key, sentinel.token_key, |
437 | + sentinel.token_secret), |
438 | + sentinel.metadata_url)) |
Looks good. One comment inline.
You did not update the docstring for OperatingSystem for compose_preseed. Would be good to update it for Node and Token objects are now passed to that method.