Merge lp:~allenap/maas/rpc-get-preseed-data-helper into lp:~maas-committers/maas/trunk
- rpc-get-preseed-data-helper
- Merge into trunk
Proposed by
Gavin Panella
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2597 |
Proposed branch: | lp:~allenap/maas/rpc-get-preseed-data-helper |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
333 lines (+146/-44) 5 files modified
src/apiclient/tests/test_maas_client.py (+2/-9) src/maasserver/clusterrpc/osystems.py (+40/-2) src/maasserver/clusterrpc/tests/test_osystems.py (+57/-14) src/maasserver/tests/test_preseed.py (+2/-10) src/maastesting/factory.py (+45/-9) |
To merge this branch: | bzr merge lp:~allenap/maas/rpc-get-preseed-data-helper |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+228086@code.launchpad.net |
Commit message
Helper function to call GetPreseedData on the cluster from the region.
Also cleans up several implementations of make_url().
Description of the change
To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) : | # |
review:
Approve
Revision history for this message
Gavin Panella (allenap) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/apiclient/tests/test_maas_client.py' | |||
2 | --- src/apiclient/tests/test_maas_client.py 2014-07-16 14:12:13 +0000 | |||
3 | +++ src/apiclient/tests/test_maas_client.py 2014-07-24 15:53:02 +0000 | |||
4 | @@ -138,14 +138,6 @@ | |||
5 | 138 | self.assertEqual(content, read_content) | 138 | self.assertEqual(content, read_content) |
6 | 139 | 139 | ||
7 | 140 | 140 | ||
8 | 141 | def make_url(): | ||
9 | 142 | """Create an arbitrary URL.""" | ||
10 | 143 | return 'http://example.com:%d/%s/' % ( | ||
11 | 144 | factory.pick_port(), | ||
12 | 145 | factory.make_string(), | ||
13 | 146 | ) | ||
14 | 147 | |||
15 | 148 | |||
16 | 149 | def make_path(): | 141 | def make_path(): |
17 | 150 | """Create an arbitrary resource path.""" | 142 | """Create an arbitrary resource path.""" |
18 | 151 | return "/" + '/'.join(factory.make_string() for counter in range(2)) | 143 | return "/" + '/'.join(factory.make_string() for counter in range(2)) |
19 | @@ -172,7 +164,8 @@ | |||
20 | 172 | def make_client(root=None, result=None): | 164 | def make_client(root=None, result=None): |
21 | 173 | """Create a MAASClient.""" | 165 | """Create a MAASClient.""" |
22 | 174 | if root is None: | 166 | if root is None: |
24 | 175 | root = make_url() | 167 | root = factory.make_simple_http_url( |
25 | 168 | path=factory.make_name("path") + "/") | ||
26 | 176 | auth = MAASOAuth( | 169 | auth = MAASOAuth( |
27 | 177 | factory.make_string(), factory.make_string(), | 170 | factory.make_string(), factory.make_string(), |
28 | 178 | factory.make_string()) | 171 | factory.make_string()) |
29 | 179 | 172 | ||
30 | === modified file 'src/maasserver/clusterrpc/osystems.py' | |||
31 | --- src/maasserver/clusterrpc/osystems.py 2014-07-16 22:11:14 +0000 | |||
32 | +++ src/maasserver/clusterrpc/osystems.py 2014-07-24 15:53:02 +0000 | |||
33 | @@ -14,14 +14,23 @@ | |||
34 | 14 | __metaclass__ = type | 14 | __metaclass__ = type |
35 | 15 | __all__ = [ | 15 | __all__ = [ |
36 | 16 | "gen_all_known_operating_systems", | 16 | "gen_all_known_operating_systems", |
37 | 17 | "get_preseed_data", | ||
38 | 17 | ] | 18 | ] |
39 | 18 | 19 | ||
40 | 19 | from collections import defaultdict | 20 | from collections import defaultdict |
41 | 20 | from functools import partial | 21 | from functools import partial |
42 | 22 | from urlparse import urlparse | ||
43 | 21 | 23 | ||
45 | 22 | from maasserver.rpc import getAllClients | 24 | from maasserver.rpc import ( |
46 | 25 | getAllClients, | ||
47 | 26 | getClientFor, | ||
48 | 27 | ) | ||
49 | 23 | from maasserver.utils import async | 28 | from maasserver.utils import async |
51 | 24 | from provisioningserver.rpc.cluster import ListOperatingSystems | 29 | from provisioningserver.rpc.cluster import ( |
52 | 30 | GetPreseedData, | ||
53 | 31 | ListOperatingSystems, | ||
54 | 32 | ) | ||
55 | 33 | from provisioningserver.utils import synchronous | ||
56 | 25 | from twisted.python.failure import Failure | 34 | from twisted.python.failure import Failure |
57 | 26 | 35 | ||
58 | 27 | 36 | ||
59 | @@ -35,6 +44,7 @@ | |||
60 | 35 | yield response | 44 | yield response |
61 | 36 | 45 | ||
62 | 37 | 46 | ||
63 | 47 | @synchronous | ||
64 | 38 | def gen_all_known_operating_systems(): | 48 | def gen_all_known_operating_systems(): |
65 | 39 | """Generator yielding details on OSes supported by any cluster. | 49 | """Generator yielding details on OSes supported by any cluster. |
66 | 40 | 50 | ||
67 | @@ -52,3 +62,31 @@ | |||
68 | 52 | if osystem not in seen[name]: | 62 | if osystem not in seen[name]: |
69 | 53 | seen[name].append(osystem) | 63 | seen[name].append(osystem) |
70 | 54 | yield osystem | 64 | yield osystem |
71 | 65 | |||
72 | 66 | |||
73 | 67 | @synchronous | ||
74 | 68 | def get_preseed_data(preseed_type, node, token, metadata_url): | ||
75 | 69 | """Obtain optional preseed data for this OS, preseed type, and node. | ||
76 | 70 | |||
77 | 71 | :param preseed_type: The type of preseed to compose. | ||
78 | 72 | :param node: The node model instance. | ||
79 | 73 | :param token: The token model instance. | ||
80 | 74 | :param metadata_url: The URL where this node's metadata will be made | ||
81 | 75 | available. | ||
82 | 76 | |||
83 | 77 | :raises NoConnectionsAvailable: When no connections to the node's | ||
84 | 78 | cluster are available for use. | ||
85 | 79 | :raises NoSuchOperatingSystem: When the node's declared operating | ||
86 | 80 | system is not known to its cluster. | ||
87 | 81 | :raises NotImplementedError: When this node's OS does not want to | ||
88 | 82 | define any OS-specific preseed data. | ||
89 | 83 | :raises TimeoutError: If a response has not been received within 30 | ||
90 | 84 | seconds. | ||
91 | 85 | """ | ||
92 | 86 | client = getClientFor(node.nodegroup.uuid).wait(5) | ||
93 | 87 | call = client( | ||
94 | 88 | GetPreseedData, osystem=node.get_osystem(), preseed_type=preseed_type, | ||
95 | 89 | node_system_id=node.system_id, node_hostname=node.hostname, | ||
96 | 90 | consumer_key=token.consumer.key, token_key=token.key, | ||
97 | 91 | token_secret=token.secret, metadata_url=urlparse(metadata_url)) | ||
98 | 92 | return call.wait(30) | ||
99 | 55 | 93 | ||
100 | === modified file 'src/maasserver/clusterrpc/tests/test_osystems.py' | |||
101 | --- src/maasserver/clusterrpc/tests/test_osystems.py 2014-07-21 12:28:45 +0000 | |||
102 | +++ src/maasserver/clusterrpc/tests/test_osystems.py 2014-07-24 15:53:02 +0000 | |||
103 | @@ -20,12 +20,18 @@ | |||
104 | 20 | ) | 20 | ) |
105 | 21 | 21 | ||
106 | 22 | from maasserver import eventloop | 22 | from maasserver import eventloop |
108 | 23 | from maasserver.clusterrpc.osystems import gen_all_known_operating_systems | 23 | from maasserver.clusterrpc.osystems import ( |
109 | 24 | gen_all_known_operating_systems, | ||
110 | 25 | get_preseed_data, | ||
111 | 26 | ) | ||
112 | 27 | from maasserver.enum import PRESEED_TYPE | ||
113 | 24 | from maasserver.rpc import getAllClients | 28 | from maasserver.rpc import getAllClients |
114 | 25 | from maasserver.rpc.testing.fixtures import ClusterRPCFixture | 29 | from maasserver.rpc.testing.fixtures import ClusterRPCFixture |
115 | 26 | from maasserver.testing.eventloop import RegionEventLoopFixture | 30 | from maasserver.testing.eventloop import RegionEventLoopFixture |
116 | 27 | from maasserver.testing.factory import factory | 31 | from maasserver.testing.factory import factory |
117 | 28 | from maasserver.testing.testcase import MAASServerTestCase | 32 | from maasserver.testing.testcase import MAASServerTestCase |
118 | 33 | from metadataserver.models import NodeKey | ||
119 | 34 | from provisioningserver.rpc.exceptions import NoSuchOperatingSystem | ||
120 | 29 | from testtools.matchers import ( | 35 | from testtools.matchers import ( |
121 | 30 | AfterPreprocessing, | 36 | AfterPreprocessing, |
122 | 31 | AllMatch, | 37 | AllMatch, |
123 | @@ -37,21 +43,22 @@ | |||
124 | 37 | from twisted.internet.defer import succeed | 43 | from twisted.internet.defer import succeed |
125 | 38 | 44 | ||
126 | 39 | 45 | ||
127 | 46 | def fake_cluster_rpc(test): | ||
128 | 47 | # Set-up the event-loop with only the RPC service running, then | ||
129 | 48 | # layer on a fake cluster RPC implementation. | ||
130 | 49 | test.useFixture(RegionEventLoopFixture("rpc")) | ||
131 | 50 | test.addCleanup(lambda: eventloop.reset().wait(5)) | ||
132 | 51 | eventloop.start().wait(5) | ||
133 | 52 | test.useFixture(ClusterRPCFixture()) | ||
134 | 53 | |||
135 | 54 | |||
136 | 40 | class TestGenAllKnownOperatingSystems(MAASServerTestCase): | 55 | class TestGenAllKnownOperatingSystems(MAASServerTestCase): |
137 | 41 | """Tests for `gen_all_known_operating_systems`.""" | 56 | """Tests for `gen_all_known_operating_systems`.""" |
138 | 42 | 57 | ||
139 | 43 | def fake_cluster_rpc(self): | ||
140 | 44 | # Set-up the event-loop with only the RPC service running, then | ||
141 | 45 | # layer on a fake cluster RPC implementation. | ||
142 | 46 | self.useFixture(RegionEventLoopFixture("rpc")) | ||
143 | 47 | self.addCleanup(lambda: eventloop.reset().wait(5)) | ||
144 | 48 | eventloop.start().wait(5) | ||
145 | 49 | self.useFixture(ClusterRPCFixture()) | ||
146 | 50 | |||
147 | 51 | def test_yields_oses_known_to_a_cluster(self): | 58 | def test_yields_oses_known_to_a_cluster(self): |
148 | 52 | # The operating systems known to a single node are returned. | 59 | # The operating systems known to a single node are returned. |
149 | 53 | factory.make_node_group().accept() | 60 | factory.make_node_group().accept() |
151 | 54 | self.fake_cluster_rpc() | 61 | fake_cluster_rpc(self) |
152 | 55 | osystems = gen_all_known_operating_systems() | 62 | osystems = gen_all_known_operating_systems() |
153 | 56 | self.assertIsInstance(osystems, Iterator) | 63 | self.assertIsInstance(osystems, Iterator) |
154 | 57 | osystems = list(osystems) | 64 | osystems = list(osystems) |
155 | @@ -61,7 +68,7 @@ | |||
156 | 61 | def test_yields_oses_known_to_multiple_clusters(self): | 68 | def test_yields_oses_known_to_multiple_clusters(self): |
157 | 62 | factory.make_node_group().accept() | 69 | factory.make_node_group().accept() |
158 | 63 | factory.make_node_group().accept() | 70 | factory.make_node_group().accept() |
160 | 64 | self.fake_cluster_rpc() | 71 | fake_cluster_rpc(self) |
161 | 65 | osystems = gen_all_known_operating_systems() | 72 | osystems = gen_all_known_operating_systems() |
162 | 66 | self.assertIsInstance(osystems, Iterator) | 73 | self.assertIsInstance(osystems, Iterator) |
163 | 67 | osystems = list(osystems) | 74 | osystems = list(osystems) |
164 | @@ -73,7 +80,7 @@ | |||
165 | 73 | # every cluster will have several (or all) OSes in common. | 80 | # every cluster will have several (or all) OSes in common. |
166 | 74 | factory.make_node_group().accept() | 81 | factory.make_node_group().accept() |
167 | 75 | factory.make_node_group().accept() | 82 | factory.make_node_group().accept() |
169 | 76 | self.fake_cluster_rpc() | 83 | fake_cluster_rpc(self) |
170 | 77 | counter = Counter( | 84 | counter = Counter( |
171 | 78 | osystem["name"] for osystem in | 85 | osystem["name"] for osystem in |
172 | 79 | gen_all_known_operating_systems()) | 86 | gen_all_known_operating_systems()) |
173 | @@ -88,7 +95,7 @@ | |||
174 | 88 | 95 | ||
175 | 89 | def test_os_data_is_passed_through_unmolested(self): | 96 | def test_os_data_is_passed_through_unmolested(self): |
176 | 90 | factory.make_node_group().accept() | 97 | factory.make_node_group().accept() |
178 | 91 | self.fake_cluster_rpc() | 98 | fake_cluster_rpc(self) |
179 | 92 | example = { | 99 | example = { |
180 | 93 | "osystems": [ | 100 | "osystems": [ |
181 | 94 | { | 101 | { |
182 | @@ -109,7 +116,7 @@ | |||
183 | 109 | factory.make_node_group().accept() | 116 | factory.make_node_group().accept() |
184 | 110 | factory.make_node_group().accept() | 117 | factory.make_node_group().accept() |
185 | 111 | factory.make_node_group().accept() | 118 | factory.make_node_group().accept() |
187 | 112 | self.fake_cluster_rpc() | 119 | fake_cluster_rpc(self) |
188 | 113 | 120 | ||
189 | 114 | clients = getAllClients().wait() | 121 | clients = getAllClients().wait() |
190 | 115 | for index, client in enumerate(clients): | 122 | for index, client in enumerate(clients): |
191 | @@ -129,3 +136,39 @@ | |||
192 | 129 | self.assertItemsEqual( | 136 | self.assertItemsEqual( |
193 | 130 | [{"name": clients[0].ident}], | 137 | [{"name": clients[0].ident}], |
194 | 131 | gen_all_known_operating_systems()) | 138 | gen_all_known_operating_systems()) |
195 | 139 | |||
196 | 140 | |||
197 | 141 | class TestGetPreseedData(MAASServerTestCase): | ||
198 | 142 | """Tests for `get_preseed_data`.""" | ||
199 | 143 | |||
200 | 144 | def test_returns_preseed_data(self): | ||
201 | 145 | # The Windows driver is known to provide custom preseed data. | ||
202 | 146 | node = factory.make_node(osystem="windows") | ||
203 | 147 | node.nodegroup.accept() | ||
204 | 148 | fake_cluster_rpc(self) | ||
205 | 149 | preseed_data = get_preseed_data( | ||
206 | 150 | PRESEED_TYPE.COMMISSIONING, node, | ||
207 | 151 | token=NodeKey.objects.get_token_for_node(node), | ||
208 | 152 | metadata_url=factory.make_url()) | ||
209 | 153 | self.assertThat(preseed_data, IsInstance(dict)) | ||
210 | 154 | self.assertThat(preseed_data, Not(HasLength(0))) | ||
211 | 155 | |||
212 | 156 | def test_propagates_NotImplementedError(self): | ||
213 | 157 | # The Windows driver is known to *not* provide custom preseed | ||
214 | 158 | # data when using Curtin. | ||
215 | 159 | node = factory.make_node(osystem="windows") | ||
216 | 160 | node.nodegroup.accept() | ||
217 | 161 | fake_cluster_rpc(self) | ||
218 | 162 | self.assertRaises( | ||
219 | 163 | NotImplementedError, get_preseed_data, PRESEED_TYPE.CURTIN, | ||
220 | 164 | node, token=NodeKey.objects.get_token_for_node(node), | ||
221 | 165 | metadata_url=factory.make_url()) | ||
222 | 166 | |||
223 | 167 | def test_propagates_NoSuchOperatingSystem(self): | ||
224 | 168 | node = factory.make_node(osystem=factory.make_name("foo")) | ||
225 | 169 | node.nodegroup.accept() | ||
226 | 170 | fake_cluster_rpc(self) | ||
227 | 171 | self.assertRaises( | ||
228 | 172 | NoSuchOperatingSystem, get_preseed_data, PRESEED_TYPE.CURTIN, | ||
229 | 173 | node, token=NodeKey.objects.get_token_for_node(node), | ||
230 | 174 | metadata_url=factory.make_url()) | ||
231 | 132 | 175 | ||
232 | === modified file 'src/maasserver/tests/test_preseed.py' | |||
233 | --- src/maasserver/tests/test_preseed.py 2014-07-18 15:44:55 +0000 | |||
234 | +++ src/maasserver/tests/test_preseed.py 2014-07-24 15:53:02 +0000 | |||
235 | @@ -442,14 +442,6 @@ | |||
236 | 442 | pick_cluster_controller_address(node)) | 442 | pick_cluster_controller_address(node)) |
237 | 443 | 443 | ||
238 | 444 | 444 | ||
239 | 445 | def make_url(name): | ||
240 | 446 | """Create a fake archive URL.""" | ||
241 | 447 | return "http://%s.example.com/%s/" % ( | ||
242 | 448 | factory.make_name(name), | ||
243 | 449 | factory.make_name('path'), | ||
244 | 450 | ) | ||
245 | 451 | |||
246 | 452 | |||
247 | 453 | class TestPreseedContext(MAASServerTestCase): | 445 | class TestPreseedContext(MAASServerTestCase): |
248 | 454 | """Tests for `get_preseed_context`.""" | 446 | """Tests for `get_preseed_context`.""" |
249 | 455 | 447 | ||
250 | @@ -467,8 +459,8 @@ | |||
251 | 467 | def test_get_preseed_context_archive_refs(self): | 459 | def test_get_preseed_context_archive_refs(self): |
252 | 468 | # urlparse lowercases the hostnames. That should not have any | 460 | # urlparse lowercases the hostnames. That should not have any |
253 | 469 | # impact but for testing, create lower-case hostnames. | 461 | # impact but for testing, create lower-case hostnames. |
256 | 470 | main_archive = make_url('main_archive') | 462 | main_archive = factory.make_url(netloc="main-archive.example.com") |
257 | 471 | ports_archive = make_url('ports_archive') | 463 | ports_archive = factory.make_url(netloc="ports-archive.example.com") |
258 | 472 | Config.objects.set_config('main_archive', main_archive) | 464 | Config.objects.set_config('main_archive', main_archive) |
259 | 473 | Config.objects.set_config('ports_archive', ports_archive) | 465 | Config.objects.set_config('ports_archive', ports_archive) |
260 | 474 | nodegroup = factory.make_node_group(maas_url=factory.make_string()) | 466 | nodegroup = factory.make_node_group(maas_url=factory.make_string()) |
261 | 475 | 467 | ||
262 | === modified file 'src/maastesting/factory.py' | |||
263 | --- src/maastesting/factory.py 2014-07-16 14:12:13 +0000 | |||
264 | +++ src/maastesting/factory.py 2014-07-24 15:53:02 +0000 | |||
265 | @@ -322,23 +322,59 @@ | |||
266 | 322 | scheme for scheme in urlparse.uses_params | 322 | scheme for scheme in urlparse.uses_params |
267 | 323 | if scheme != "") | 323 | if scheme != "") |
268 | 324 | 324 | ||
270 | 325 | def make_parsed_url(self): | 325 | def make_parsed_url( |
271 | 326 | self, scheme=None, netloc=None, path=None, params=None, | ||
272 | 327 | query=None, fragment=None): | ||
273 | 326 | """Generate a random parsed URL object. | 328 | """Generate a random parsed URL object. |
274 | 327 | 329 | ||
275 | 330 | Contains randomly generated values for all parts of a URL: scheme, | ||
276 | 331 | location, path, parameters, query, and fragment. However, each part | ||
277 | 332 | can be overridden individually. | ||
278 | 333 | |||
279 | 328 | :return: Instance of :py:class:`urlparse.ParseResult`. | 334 | :return: Instance of :py:class:`urlparse.ParseResult`. |
280 | 329 | """ | 335 | """ |
282 | 330 | return urlparse.ParseResult( | 336 | if scheme is None: |
283 | 331 | # Select a scheme that allows parameters; see above. | 337 | # Select a scheme that allows parameters; see above. |
286 | 332 | random.choice(self._make_parsed_url_schemes), | 338 | scheme = random.choice(self._make_parsed_url_schemes) |
287 | 333 | self.make_name("netloc").lower(), | 339 | if netloc is None: |
288 | 340 | netloc = "%s.example.com" % self.make_name("netloc").lower() | ||
289 | 341 | if path is None: | ||
290 | 334 | # A leading forward-slash will be added in geturl() if we | 342 | # A leading forward-slash will be added in geturl() if we |
291 | 335 | # don't, so ensure it's here now so tests can compare URLs | 343 | # don't, so ensure it's here now so tests can compare URLs |
292 | 336 | # without worrying about it. | 344 | # without worrying about it. |
298 | 337 | self.make_name("/path"), | 345 | path = self.make_name("/path") |
299 | 338 | self.make_name("params"), | 346 | else: |
300 | 339 | self.make_name("query"), | 347 | # Same here with the forward-slash prefix. |
301 | 340 | self.make_name("fragment"), | 348 | if not path.startswith("/"): |
302 | 341 | ) | 349 | path = "/" + path |
303 | 350 | if params is None: | ||
304 | 351 | params = self.make_name("params") | ||
305 | 352 | if query is None: | ||
306 | 353 | query = self.make_name("query") | ||
307 | 354 | if fragment is None: | ||
308 | 355 | fragment = self.make_name("fragment") | ||
309 | 356 | return urlparse.ParseResult( | ||
310 | 357 | scheme, netloc, path, params, query, fragment) | ||
311 | 358 | |||
312 | 359 | def make_url( | ||
313 | 360 | self, scheme=None, netloc=None, path=None, params=None, | ||
314 | 361 | query=None, fragment=None): | ||
315 | 362 | """Generate a random URL. | ||
316 | 363 | |||
317 | 364 | Contains randomly generated values for all parts of a URL: scheme, | ||
318 | 365 | location, path, parameters, query, and fragment. However, each part | ||
319 | 366 | can be overridden individually. | ||
320 | 367 | |||
321 | 368 | :return: string | ||
322 | 369 | """ | ||
323 | 370 | return self.make_parsed_url( | ||
324 | 371 | scheme, netloc, path, params, query, fragment).geturl() | ||
325 | 372 | |||
326 | 373 | def make_simple_http_url(self, netloc=None, path=None): | ||
327 | 374 | """Create an arbitrary HTTP URL with only a location and path.""" | ||
328 | 375 | return self.make_parsed_url( | ||
329 | 376 | scheme="http", netloc=netloc, path=path, params="", query="", | ||
330 | 377 | fragment="").geturl() | ||
331 | 342 | 378 | ||
332 | 343 | def make_names(self, *prefixes): | 379 | def make_names(self, *prefixes): |
333 | 344 | """Generate random names. | 380 | """Generate random names. |
Thanks for the review!