Merge lp:~allenap/maas/rpc-get-preseed-data-helper into lp:~maas-committers/maas/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
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().

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 :

Thanks for the review!

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 self.assertEqual(content, read_content)
6
7
8-def make_url():
9- """Create an arbitrary URL."""
10- return 'http://example.com:%d/%s/' % (
11- factory.pick_port(),
12- factory.make_string(),
13- )
14-
15-
16 def make_path():
17 """Create an arbitrary resource path."""
18 return "/" + '/'.join(factory.make_string() for counter in range(2))
19@@ -172,7 +164,8 @@
20 def make_client(root=None, result=None):
21 """Create a MAASClient."""
22 if root is None:
23- root = make_url()
24+ root = factory.make_simple_http_url(
25+ path=factory.make_name("path") + "/")
26 auth = MAASOAuth(
27 factory.make_string(), factory.make_string(),
28 factory.make_string())
29
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 __metaclass__ = type
35 __all__ = [
36 "gen_all_known_operating_systems",
37+ "get_preseed_data",
38 ]
39
40 from collections import defaultdict
41 from functools import partial
42+from urlparse import urlparse
43
44-from maasserver.rpc import getAllClients
45+from maasserver.rpc import (
46+ getAllClients,
47+ getClientFor,
48+ )
49 from maasserver.utils import async
50-from provisioningserver.rpc.cluster import ListOperatingSystems
51+from provisioningserver.rpc.cluster import (
52+ GetPreseedData,
53+ ListOperatingSystems,
54+ )
55+from provisioningserver.utils import synchronous
56 from twisted.python.failure import Failure
57
58
59@@ -35,6 +44,7 @@
60 yield response
61
62
63+@synchronous
64 def gen_all_known_operating_systems():
65 """Generator yielding details on OSes supported by any cluster.
66
67@@ -52,3 +62,31 @@
68 if osystem not in seen[name]:
69 seen[name].append(osystem)
70 yield osystem
71+
72+
73+@synchronous
74+def get_preseed_data(preseed_type, node, token, metadata_url):
75+ """Obtain optional preseed data for this OS, preseed type, and node.
76+
77+ :param preseed_type: The type of preseed to compose.
78+ :param node: The node model instance.
79+ :param token: The token model instance.
80+ :param metadata_url: The URL where this node's metadata will be made
81+ available.
82+
83+ :raises NoConnectionsAvailable: When no connections to the node's
84+ cluster are available for use.
85+ :raises NoSuchOperatingSystem: When the node's declared operating
86+ system is not known to its cluster.
87+ :raises NotImplementedError: When this node's OS does not want to
88+ define any OS-specific preseed data.
89+ :raises TimeoutError: If a response has not been received within 30
90+ seconds.
91+ """
92+ client = getClientFor(node.nodegroup.uuid).wait(5)
93+ call = client(
94+ GetPreseedData, osystem=node.get_osystem(), preseed_type=preseed_type,
95+ node_system_id=node.system_id, node_hostname=node.hostname,
96+ consumer_key=token.consumer.key, token_key=token.key,
97+ token_secret=token.secret, metadata_url=urlparse(metadata_url))
98+ return call.wait(30)
99
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 )
105
106 from maasserver import eventloop
107-from maasserver.clusterrpc.osystems import gen_all_known_operating_systems
108+from maasserver.clusterrpc.osystems import (
109+ gen_all_known_operating_systems,
110+ get_preseed_data,
111+ )
112+from maasserver.enum import PRESEED_TYPE
113 from maasserver.rpc import getAllClients
114 from maasserver.rpc.testing.fixtures import ClusterRPCFixture
115 from maasserver.testing.eventloop import RegionEventLoopFixture
116 from maasserver.testing.factory import factory
117 from maasserver.testing.testcase import MAASServerTestCase
118+from metadataserver.models import NodeKey
119+from provisioningserver.rpc.exceptions import NoSuchOperatingSystem
120 from testtools.matchers import (
121 AfterPreprocessing,
122 AllMatch,
123@@ -37,21 +43,22 @@
124 from twisted.internet.defer import succeed
125
126
127+def fake_cluster_rpc(test):
128+ # Set-up the event-loop with only the RPC service running, then
129+ # layer on a fake cluster RPC implementation.
130+ test.useFixture(RegionEventLoopFixture("rpc"))
131+ test.addCleanup(lambda: eventloop.reset().wait(5))
132+ eventloop.start().wait(5)
133+ test.useFixture(ClusterRPCFixture())
134+
135+
136 class TestGenAllKnownOperatingSystems(MAASServerTestCase):
137 """Tests for `gen_all_known_operating_systems`."""
138
139- def fake_cluster_rpc(self):
140- # Set-up the event-loop with only the RPC service running, then
141- # layer on a fake cluster RPC implementation.
142- self.useFixture(RegionEventLoopFixture("rpc"))
143- self.addCleanup(lambda: eventloop.reset().wait(5))
144- eventloop.start().wait(5)
145- self.useFixture(ClusterRPCFixture())
146-
147 def test_yields_oses_known_to_a_cluster(self):
148 # The operating systems known to a single node are returned.
149 factory.make_node_group().accept()
150- self.fake_cluster_rpc()
151+ fake_cluster_rpc(self)
152 osystems = gen_all_known_operating_systems()
153 self.assertIsInstance(osystems, Iterator)
154 osystems = list(osystems)
155@@ -61,7 +68,7 @@
156 def test_yields_oses_known_to_multiple_clusters(self):
157 factory.make_node_group().accept()
158 factory.make_node_group().accept()
159- self.fake_cluster_rpc()
160+ fake_cluster_rpc(self)
161 osystems = gen_all_known_operating_systems()
162 self.assertIsInstance(osystems, Iterator)
163 osystems = list(osystems)
164@@ -73,7 +80,7 @@
165 # every cluster will have several (or all) OSes in common.
166 factory.make_node_group().accept()
167 factory.make_node_group().accept()
168- self.fake_cluster_rpc()
169+ fake_cluster_rpc(self)
170 counter = Counter(
171 osystem["name"] for osystem in
172 gen_all_known_operating_systems())
173@@ -88,7 +95,7 @@
174
175 def test_os_data_is_passed_through_unmolested(self):
176 factory.make_node_group().accept()
177- self.fake_cluster_rpc()
178+ fake_cluster_rpc(self)
179 example = {
180 "osystems": [
181 {
182@@ -109,7 +116,7 @@
183 factory.make_node_group().accept()
184 factory.make_node_group().accept()
185 factory.make_node_group().accept()
186- self.fake_cluster_rpc()
187+ fake_cluster_rpc(self)
188
189 clients = getAllClients().wait()
190 for index, client in enumerate(clients):
191@@ -129,3 +136,39 @@
192 self.assertItemsEqual(
193 [{"name": clients[0].ident}],
194 gen_all_known_operating_systems())
195+
196+
197+class TestGetPreseedData(MAASServerTestCase):
198+ """Tests for `get_preseed_data`."""
199+
200+ def test_returns_preseed_data(self):
201+ # The Windows driver is known to provide custom preseed data.
202+ node = factory.make_node(osystem="windows")
203+ node.nodegroup.accept()
204+ fake_cluster_rpc(self)
205+ preseed_data = get_preseed_data(
206+ PRESEED_TYPE.COMMISSIONING, node,
207+ token=NodeKey.objects.get_token_for_node(node),
208+ metadata_url=factory.make_url())
209+ self.assertThat(preseed_data, IsInstance(dict))
210+ self.assertThat(preseed_data, Not(HasLength(0)))
211+
212+ def test_propagates_NotImplementedError(self):
213+ # The Windows driver is known to *not* provide custom preseed
214+ # data when using Curtin.
215+ node = factory.make_node(osystem="windows")
216+ node.nodegroup.accept()
217+ fake_cluster_rpc(self)
218+ self.assertRaises(
219+ NotImplementedError, get_preseed_data, PRESEED_TYPE.CURTIN,
220+ node, token=NodeKey.objects.get_token_for_node(node),
221+ metadata_url=factory.make_url())
222+
223+ def test_propagates_NoSuchOperatingSystem(self):
224+ node = factory.make_node(osystem=factory.make_name("foo"))
225+ node.nodegroup.accept()
226+ fake_cluster_rpc(self)
227+ self.assertRaises(
228+ NoSuchOperatingSystem, get_preseed_data, PRESEED_TYPE.CURTIN,
229+ node, token=NodeKey.objects.get_token_for_node(node),
230+ metadata_url=factory.make_url())
231
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 pick_cluster_controller_address(node))
237
238
239-def make_url(name):
240- """Create a fake archive URL."""
241- return "http://%s.example.com/%s/" % (
242- factory.make_name(name),
243- factory.make_name('path'),
244- )
245-
246-
247 class TestPreseedContext(MAASServerTestCase):
248 """Tests for `get_preseed_context`."""
249
250@@ -467,8 +459,8 @@
251 def test_get_preseed_context_archive_refs(self):
252 # urlparse lowercases the hostnames. That should not have any
253 # impact but for testing, create lower-case hostnames.
254- main_archive = make_url('main_archive')
255- ports_archive = make_url('ports_archive')
256+ main_archive = factory.make_url(netloc="main-archive.example.com")
257+ ports_archive = factory.make_url(netloc="ports-archive.example.com")
258 Config.objects.set_config('main_archive', main_archive)
259 Config.objects.set_config('ports_archive', ports_archive)
260 nodegroup = factory.make_node_group(maas_url=factory.make_string())
261
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 scheme for scheme in urlparse.uses_params
267 if scheme != "")
268
269- def make_parsed_url(self):
270+ def make_parsed_url(
271+ self, scheme=None, netloc=None, path=None, params=None,
272+ query=None, fragment=None):
273 """Generate a random parsed URL object.
274
275+ Contains randomly generated values for all parts of a URL: scheme,
276+ location, path, parameters, query, and fragment. However, each part
277+ can be overridden individually.
278+
279 :return: Instance of :py:class:`urlparse.ParseResult`.
280 """
281- return urlparse.ParseResult(
282+ if scheme is None:
283 # Select a scheme that allows parameters; see above.
284- random.choice(self._make_parsed_url_schemes),
285- self.make_name("netloc").lower(),
286+ scheme = random.choice(self._make_parsed_url_schemes)
287+ if netloc is None:
288+ netloc = "%s.example.com" % self.make_name("netloc").lower()
289+ if path is None:
290 # A leading forward-slash will be added in geturl() if we
291 # don't, so ensure it's here now so tests can compare URLs
292 # without worrying about it.
293- self.make_name("/path"),
294- self.make_name("params"),
295- self.make_name("query"),
296- self.make_name("fragment"),
297- )
298+ path = self.make_name("/path")
299+ else:
300+ # Same here with the forward-slash prefix.
301+ if not path.startswith("/"):
302+ path = "/" + path
303+ if params is None:
304+ params = self.make_name("params")
305+ if query is None:
306+ query = self.make_name("query")
307+ if fragment is None:
308+ fragment = self.make_name("fragment")
309+ return urlparse.ParseResult(
310+ scheme, netloc, path, params, query, fragment)
311+
312+ def make_url(
313+ self, scheme=None, netloc=None, path=None, params=None,
314+ query=None, fragment=None):
315+ """Generate a random URL.
316+
317+ Contains randomly generated values for all parts of a URL: scheme,
318+ location, path, parameters, query, and fragment. However, each part
319+ can be overridden individually.
320+
321+ :return: string
322+ """
323+ return self.make_parsed_url(
324+ scheme, netloc, path, params, query, fragment).geturl()
325+
326+ def make_simple_http_url(self, netloc=None, path=None):
327+ """Create an arbitrary HTTP URL with only a location and path."""
328+ return self.make_parsed_url(
329+ scheme="http", netloc=netloc, path=path, params="", query="",
330+ fragment="").geturl()
331
332 def make_names(self, *prefixes):
333 """Generate random names.