Merge lp:~fwereade/pyjuju/maas-name-constraint into lp:~fwereade/pyjuju/shadow-trunk-1204

Proposed by William Reade
Status: Merged
Approved by: William Reade
Approved revision: 514
Merged at revision: 510
Proposed branch: lp:~fwereade/pyjuju/maas-name-constraint
Merge into: lp:~fwereade/pyjuju/shadow-trunk-1204
Prerequisite: lp:~fwereade/pyjuju/constraints-get
Diff against target: 388 lines (+169/-22)
11 files modified
juju/environment/config.py (+3/-1)
juju/environment/tests/test_config.py (+8/-6)
juju/machine/constraints.py (+3/-2)
juju/machine/tests/test_constraints.py (+11/-0)
juju/providers/maas/launch.py (+1/-4)
juju/providers/maas/maas.py (+5/-1)
juju/providers/maas/provider.py (+15/-0)
juju/providers/maas/tests/test_launch.py (+38/-5)
juju/providers/maas/tests/test_maas.py (+38/-1)
juju/providers/maas/tests/test_provider.py (+39/-1)
juju/providers/maas/tests/testing.py (+8/-1)
To merge this branch: bzr merge lp:~fwereade/pyjuju/maas-name-constraint
Reviewer Review Type Date Requested Status
William Reade Pending
Review via email: mp+99746@code.launchpad.net

Description of the change

Implement maas-name constraint

Descended from lp:~julian-edwards/juju/name-constraints; but uses new-style
constraints registration.

Also uses a custom ubuntu-series constraint to forbid use of charms written
for systems we can't yet provision with MAAS.

https://codereview.appspot.com/5938050/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+99746_code.launchpad.net,

Message:
Please take a look.

Description:
Implement maas-name constraint

Descended from lp:~julian-edwards/juju/name-constraints; but uses
new-style
constraints registration.

Also uses a custom ubuntu-series constraint to forbid use of charms
written
for systems we can't yet provision with MAAS.

https://code.launchpad.net/~fwereade/juju/maas-name-constraint/+merge/99746

Requires:
https://code.launchpad.net/~fwereade/juju/constraints-get/+merge/99704

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/5938050/

Affected files:
   A [revision details]
   M juju/environment/config.py
   M juju/environment/tests/test_config.py
   M juju/machine/constraints.py
   M juju/machine/tests/test_constraints.py
   M juju/providers/maas/launch.py
   M juju/providers/maas/maas.py
   M juju/providers/maas/provider.py
   M juju/providers/maas/tests/test_launch.py
   M juju/providers/maas/tests/test_maas.py
   M juju/providers/maas/tests/test_provider.py
   M juju/providers/maas/tests/testing.py

Revision history for this message
Gavin Panella (allenap) wrote :

On 2012/03/28 14:46:46, fwereade wrote:
> Please take a look.

Looks sensible to me.

https://codereview.appspot.com/5938050/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

LGTM

https://codereview.appspot.com/5938050/diff/1/juju/environment/config.py
File juju/environment/config.py (right):

https://codereview.appspot.com/5938050/diff/1/juju/environment/config.py#newcode68
juju/environment/config.py:68: "default-series": Constant("precise")},
deserves a comment regarding maas limitation in this regard.

https://codereview.appspot.com/5938050/diff/1/juju/environment/tests/test_config.py
File juju/environment/tests/test_config.py (right):

https://codereview.appspot.com/5938050/diff/1/juju/environment/tests/test_config.py#newcode681
juju/environment/tests/test_config.py:681: self.assertRaises(
it would be nice to check this error message for its end user usability.
the nicely messaged value error in the provider doesn't really ever get
surfaced to the user, but this one does.

https://codereview.appspot.com/5938050/

514. By William Reade

merge parent

Revision history for this message
William Reade (fwereade) wrote :

https://codereview.appspot.com/5938050/diff/1/juju/environment/config.py
File juju/environment/config.py (right):

https://codereview.appspot.com/5938050/diff/1/juju/environment/config.py#newcode68
juju/environment/config.py:68: "default-series": Constant("precise")},
On 2012/03/28 18:10:47, hazmat wrote:
> deserves a comment regarding maas limitation in this regard.

Done.

https://codereview.appspot.com/5938050/diff/1/juju/environment/tests/test_config.py
File juju/environment/tests/test_config.py (right):

https://codereview.appspot.com/5938050/diff/1/juju/environment/tests/test_config.py#newcode681
juju/environment/tests/test_config.py:681: self.assertRaises(
On 2012/03/28 18:10:47, hazmat wrote:
> it would be nice to check this error message for its end user
usability. the
> nicely messaged value error in the provider doesn't really ever get
surfaced to
> the user, but this one does.

Good catch, thank you. Done.

https://codereview.appspot.com/5938050/

515. By William Reade

address review

516. By William Reade

merge parent

Revision history for this message
William Reade (fwereade) wrote :

*** Submitted:

Implement maas-name constraint

Descended from lp:~julian-edwards/juju/name-constraints; but uses
new-style
constraints registration.

Also uses a custom ubuntu-series constraint to forbid use of charms
written
for systems we can't yet provision with MAAS.

R=allenap, hazmat
CC=
https://codereview.appspot.com/5938050

https://codereview.appspot.com/5938050/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'juju/environment/config.py'
2--- juju/environment/config.py 2012-03-23 11:05:05 +0000
3+++ juju/environment/config.py 2012-03-28 19:04:19 +0000
4@@ -65,7 +65,9 @@
5 "maas-oauth": OAuthString(),
6 "admin-secret": String(),
7 "placement": _EITHER_PLACEMENT,
8- "default-series": String()},
9+ # MAAS currently only provisions precise; any other default-series
10+ # would just lead to errors down the line.
11+ "default-series": Constant("precise")},
12 optional=["placement"]),
13 "local": KeyDict({
14 "admin-secret": String(),
15
16=== modified file 'juju/environment/tests/test_config.py'
17--- juju/environment/tests/test_config.py 2012-03-27 23:22:28 +0000
18+++ juju/environment/tests/test_config.py 2012-03-28 19:04:19 +0000
19@@ -46,7 +46,7 @@
20 maas-server: somewhe.re
21 maas-oauth: foo:bar:baz
22 admin-secret: garden
23- default-series: oneiric
24+ default-series: precise
25 """
26
27 SAMPLE_LOCAL = """
28@@ -674,14 +674,16 @@
29 self.fail("Did not properly require %s when type == maas"
30 % require)
31
32- def test_maas_respects_default_series(self):
33+ def test_maas_default_series(self):
34 config = yaml.load(SAMPLE_MAAS)
35 config["environments"]["sample"]["default-series"] = "magnificent"
36 self.write_config(yaml.dump(config), other_path=True)
37- self.config.load(self.other_path)
38-
39- provider = self.config.get_default().get_machine_provider()
40- self.assertEqual(provider.config["default-series"], "magnificent")
41+ e = self.assertRaises(
42+ EnvironmentsConfigError, self.config.load, self.other_path)
43+ self.assertIn(
44+ "environments.sample.default-series: expected 'precise', got "
45+ "'magnificent'",
46+ str(e))
47
48 def test_maas_verifies_placement(self):
49 config = yaml.load(SAMPLE_MAAS)
50
51=== modified file 'juju/machine/constraints.py'
52--- juju/machine/constraints.py 2012-03-28 17:47:44 +0000
53+++ juju/machine/constraints.py 2012-03-28 19:04:19 +0000
54@@ -12,8 +12,9 @@
55 ALL_NAMES = (
56 "ubuntu-series", "provider-type",
57 "arch", "cpu", "mem", "instance-type",
58+ "ec2-zone",
59+ "maas-name",
60 "orchestra-classes",
61- "ec2-zone",
62 )
63
64
65@@ -237,7 +238,7 @@
66 """Return a Constraints with the "ubuntu-series" set to `series`"""
67 data = dict(self._data)
68 data["ubuntu-series"] = series
69- return Constraints(self._available, data)
70+ return self._available.load(data)
71
72 @property
73 def complete(self):
74
75=== modified file 'juju/machine/tests/test_constraints.py'
76--- juju/machine/tests/test_constraints.py 2012-03-28 08:43:23 +0000
77+++ juju/machine/tests/test_constraints.py 2012-03-28 19:04:19 +0000
78@@ -391,3 +391,14 @@
79 self.assertEquals(c8["cpu"], 1.0)
80 self.assertEquals(c8["mem"], 512.0)
81 self.assertEquals(c8["instance-type"], None)
82+
83+ def test_load_validates(self):
84+ self.allow_names("foo")
85+ cs = ConstraintSet("provider")
86+
87+ def blam(s):
88+ raise ValueError(s)
89+
90+ cs.register("foo", converter=blam)
91+ e = self.assertRaises(ConstraintError, cs.load, {"foo": "bar"})
92+ self.assertEquals(str(e), "Bad 'foo' constraint 'bar': bar")
93
94=== modified file 'juju/providers/maas/launch.py'
95--- juju/providers/maas/launch.py 2012-03-23 08:34:09 +0000
96+++ juju/providers/maas/launch.py 2012-03-28 19:04:19 +0000
97@@ -35,11 +35,8 @@
98 representing the newly-launched machine
99 :rtype: :class:`twisted.internet.defer.Deferred`
100 """
101- log.warning(
102- "The maas provider does not yet support machine constraints; "
103- "machine will be chosen abritrarily.")
104 maas_client = self._provider.maas_client
105- instance_data = yield maas_client.acquire_node()
106+ instance_data = yield maas_client.acquire_node(self._constraints)
107 instance_uri = instance_data["resource_uri"]
108 # If anything goes wrong after the acquire and before the launch
109 # actually happens, we attempt to release the node.
110
111=== modified file 'juju/providers/maas/maas.py'
112--- juju/providers/maas/maas.py 2012-03-27 10:58:36 +0000
113+++ juju/providers/maas/maas.py 2012-03-28 19:04:19 +0000
114@@ -96,13 +96,17 @@
115 for resource_uri in resource_uris)
116 return self.get("api/1.0/nodes/", params)
117
118- def acquire_node(self):
119+ def acquire_node(self, constraints=None):
120 """Ask MAAS to assign a node to us.
121
122 :return: A Deferred whose value is the resource URI to the node
123 that was acquired.
124 """
125 params = {"op": "acquire"}
126+ if constraints is not None:
127+ name = constraints["maas-name"]
128+ if name is not None:
129+ params["name"] = name
130 return self.post("api/1.0/nodes/", params)
131
132 def start_node(self, resource_uri, user_data):
133
134=== modified file 'juju/providers/maas/provider.py'
135--- juju/providers/maas/provider.py 2012-03-28 07:04:10 +0000
136+++ juju/providers/maas/provider.py 2012-03-28 19:04:19 +0000
137@@ -33,6 +33,21 @@
138 """Return a WebDAV-backed FileStorage abstraction."""
139 return self._storage
140
141+ @inlineCallbacks
142+ def get_constraint_set(self):
143+ """Return the set of constraints that are valid for this provider."""
144+ cs = yield super(MachineProvider, self).get_constraint_set()
145+
146+ def require_precise(s):
147+ if s != "precise":
148+ raise ValueError(
149+ "MAAS currently only provisions machines running precise")
150+ return s
151+
152+ cs.register("ubuntu-series", converter=require_precise, visible=False)
153+ cs.register("maas-name")
154+ returnValue(cs)
155+
156 def get_serialization_data(self):
157 """Get provider configuration suitable for serialization.
158
159
160=== modified file 'juju/providers/maas/tests/test_launch.py'
161--- juju/providers/maas/tests/test_launch.py 2012-03-28 07:04:10 +0000
162+++ juju/providers/maas/tests/test_launch.py 2012-03-28 19:04:19 +0000
163@@ -42,7 +42,7 @@
164 provider._storage = FakeStorage()
165 return provider
166
167- def test_bad_data(self):
168+ def test_no_machine_id(self):
169 provider = self._get_provider()
170 d = provider.start_machine({})
171 self.assertFailure(d, ProviderError)
172@@ -54,12 +54,25 @@
173 d.addCallback(verify)
174 return d
175
176+ def test_no_constraints(self):
177+ provider = self._get_provider()
178+ d = provider.start_machine({"machine-id": 99})
179+ self.assertFailure(d, ProviderError)
180+
181+ def verify(error):
182+ self.assertEqual(
183+ str(error),
184+ "Cannot launch a machine without specifying constraints")
185+ d.addCallback(verify)
186+ return d
187+
188 def test_no_available_machines(self):
189 # Requesting a startup of an already-acquired machine should
190 # result in a Fault being returned.
191 provider = self._get_provider(
192 FakeMAASHTTPConnectionWithNoAvailableNodes)
193- machine_data = {"machine-id": "foo", "constraints": {}}
194+ machine_data = {
195+ "machine-id": "foo", "constraints": {"maas-name": None}}
196 d = provider.start_machine(machine_data)
197
198 # These arbitrary fake failure values come from
199@@ -82,7 +95,8 @@
200
201 # Try to start up that node using the fake MAAS.
202 provider = self._get_provider(FakeMAASHTTPConnection)
203- machine_data = {"machine-id": machine_id, "constraints": {}}
204+ machine_data = {
205+ "machine-id": machine_id, "constraints": {"maas-name": None}}
206 machine_list = yield provider.start_machine(machine_data)
207
208 # Test that it returns a list containing a single MAASMachine
209@@ -96,6 +110,25 @@
210 "MAASMachine values of instance_id / dns_name don't match expected"
211 "values")
212
213+ @inlineCallbacks
214+ def test_launch_with_name_constraint(self):
215+ # Try to launch a particular machine by its name using the
216+ # "maas-name" constraint.
217+
218+ # Pick the "moon" node out of the test data.
219+ target_node = NODE_JSON[1]
220+ self.assertEqual("moon", target_node["hostname"])
221+ machine_id = "foo"
222+ provider = self._get_provider(FakeMAASHTTPConnection)
223+ constraints = {"maas-name": "moon"}
224+ machine_data = {"machine-id": machine_id, "constraints": constraints}
225+ machine_list = yield provider.start_machine(machine_data)
226+
227+ # Check that "moon" was started.
228+ [machine] = machine_list
229+ self.assertIsInstance(machine, MAASMachine)
230+ self.assertEqual(machine.dns_name, "moon")
231+
232 def test_failed_launch(self):
233 """If an acquired node fails to start, it is released."""
234 # Throw away logs.
235@@ -116,7 +149,7 @@
236 # The following operations happen in sequence.
237 mocker.order()
238 # First, the node is acquired.
239- mock_client.acquire_node()
240+ mock_client.acquire_node({"maas-name": None})
241 mocker.result({"resource_uri": "/node/123"})
242 # Second, the node is started. We simulate a failure at this stage.
243 mock_client.start_node("/node/123", ANY)
244@@ -127,5 +160,5 @@
245
246 mocker.replay()
247 return self.assertFailure(
248- MAASLaunchMachine(mock_provider, {}).run("fred"),
249+ MAASLaunchMachine(mock_provider, {"maas-name": None}).run("fred"),
250 ZeroDivisionError)
251
252=== modified file 'juju/providers/maas/tests/test_maas.py'
253--- juju/providers/maas/tests/test_maas.py 2012-03-27 11:26:50 +0000
254+++ juju/providers/maas/tests/test_maas.py 2012-03-28 19:04:19 +0000
255@@ -116,13 +116,16 @@
256 return succeed(json.dumps(request_url))
257
258
259-class TestMAASClientWithTwisted(TestCase):
260+class TestMAASClientBase:
261
262 def get_client(self):
263 """Return a MAASClient with a FakeMAASHTTPConnection factory."""
264 log = self.setup_connection(MAASClient, FakeMAASHTTPConnection)
265 return MAASClient(CONFIG), log
266
267+
268+class TestMAASClientWithTwisted(TestCase, TestMAASClientBase):
269+
270 def assertPathEqual(self, expected_path, observed_url):
271 """Assert path of `observed_url` is equal to `expected_path`."""
272 observed_path = urlparse(observed_url).path
273@@ -261,3 +264,37 @@
274 client, log = self.get_client()
275 returned_data = yield client.release_node(resource_uri)
276 self.assertEqual(returned_data, NODE_JSON[0])
277+
278+
279+class TestConstraints(TestCase, TestMAASClientBase):
280+
281+ class fake_post:
282+ def __call__(self, uri, params):
283+ self.params_used = params
284+
285+ def set_up_client_with_fake(self):
286+ fake = self.fake_post()
287+ client, log = self.get_client()
288+ self.patch(client, 'post', fake)
289+ return client
290+
291+ def test_acquire_node_handles_name_constraint(self):
292+ # Ensure that the name constraint is passed through to the post
293+ # method.
294+ client = self.set_up_client_with_fake()
295+ constraints = {"maas-name": "gargleblaster"}
296+ client.acquire_node(constraints)
297+
298+ name = client.post.params_used.get("name")
299+ self.assertEqual("gargleblaster", name)
300+
301+ def test_acquire_node_ignores_unknown_constraints(self):
302+ # If an unknown constraint is passed it should be ignored.
303+ client = self.set_up_client_with_fake()
304+ constraints = {"maas-name": "zaphod", "guinness": "widget"}
305+ client.acquire_node(constraints)
306+
307+ guinness = client.post.params_used.get("guinness")
308+ self.assertIs(None, guinness)
309+ name = client.post.params_used.get("name")
310+ self.assertEqual("zaphod", name)
311
312=== modified file 'juju/providers/maas/tests/test_provider.py'
313--- juju/providers/maas/tests/test_provider.py 2012-03-27 12:47:35 +0000
314+++ juju/providers/maas/tests/test_provider.py 2012-03-28 19:04:19 +0000
315@@ -5,7 +5,7 @@
316
317 from twisted.internet.defer import inlineCallbacks
318
319-from juju.errors import MachinesNotFound, ProviderError
320+from juju.errors import ConstraintError, MachinesNotFound, ProviderError
321 from juju.lib.mocker import ANY
322 from juju.providers.maas import MachineProvider
323 from juju.providers.maas.maas import MAASClient
324@@ -138,3 +138,41 @@
325 self.assertEqual(
326 [machines[0].instance_id],
327 [machine.instance_id for machine in machines_terminated])
328+
329+ @inlineCallbacks
330+ def test_constraints(self):
331+ provider = MachineProvider("blah", CONFIG)
332+ cs = yield provider.get_constraint_set()
333+ self.assertEquals(cs.parse([]), {
334+ "provider-type": "maas",
335+ "ubuntu-series": None,
336+ "maas-name": None})
337+ self.assertEquals(cs.parse(["maas-name=totoro"]), {
338+ "provider-type": "maas",
339+ "ubuntu-series": None,
340+ "maas-name": "totoro"})
341+
342+ bill = cs.parse(["maas-name=bill"]).with_series("precise")
343+ ben = cs.parse(["maas-name=ben"]).with_series("precise")
344+ nil = cs.parse([]).with_series("precise")
345+ self.assertTrue(bill.can_satisfy(bill))
346+ self.assertTrue(bill.can_satisfy(nil))
347+ self.assertTrue(ben.can_satisfy(ben))
348+ self.assertTrue(ben.can_satisfy(nil))
349+ self.assertTrue(nil.can_satisfy(nil))
350+ self.assertFalse(nil.can_satisfy(bill))
351+ self.assertFalse(nil.can_satisfy(ben))
352+ self.assertFalse(ben.can_satisfy(bill))
353+ self.assertFalse(bill.can_satisfy(ben))
354+
355+ @inlineCallbacks
356+ def test_precise_only_constraints(self):
357+ provider = MachineProvider("blah", CONFIG)
358+ cs = yield provider.get_constraint_set()
359+ e = self.assertRaises(
360+ ConstraintError, cs.parse([]).with_series, "not-precise")
361+ self.assertEquals(
362+ str(e),
363+ "Bad 'ubuntu-series' constraint 'not-precise': MAAS currently "
364+ "only provisions machines running precise")
365+
366
367=== modified file 'juju/providers/maas/tests/testing.py'
368--- juju/providers/maas/tests/testing.py 2012-03-27 11:26:50 +0000
369+++ juju/providers/maas/tests/testing.py 2012-03-28 19:04:19 +0000
370@@ -142,10 +142,17 @@
371 raise AssertionError("Unknown API method called")
372
373 def start_node(self):
374- # TODO: Simply returns the first node, ignoring the data. Fix.
375+ # Poor man's node selection.
376+ if NODE_JSON[1]["system_id"] in self.url:
377+ return defer.succeed(json.dumps(NODE_JSON[1]))
378 return defer.succeed(json.dumps(NODE_JSON[0]))
379
380 def acquire_node(self):
381+ # Implement a poor man's name constraints.
382+ if "moon" in self.data:
383+ return defer.succeed(json.dumps(NODE_JSON[1]))
384+ elif "sun" in self.data:
385+ return defer.succeed(json.dumps(NODE_JSON[0]))
386 return defer.succeed(json.dumps(NODE_JSON[0]))
387
388 def stop_node(self):

Subscribers

People subscribed via source and target branches