Merge lp:~hazmat/pyjuju/unit-with-addresses into lp:pyjuju
- unit-with-addresses
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Gustavo Niemeyer | ||||
Approved revision: | 400 | ||||
Merged at revision: | 373 | ||||
Proposed branch: | lp:~hazmat/pyjuju/unit-with-addresses | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~bcsaller/pyjuju/lxc-omega | ||||
Diff against target: |
482 lines (+333/-7) 9 files modified
juju/agents/tests/test_unit.py (+17/-0) juju/agents/unit.py (+8/-0) juju/environment/config.py (+1/-0) juju/providers/lxc/__init__.py (+7/-0) juju/providers/lxc/tests/test_provider.py (+5/-5) juju/state/service.py (+60/-1) juju/state/tests/test_service.py (+23/-1) juju/unit/address.py (+86/-0) juju/unit/tests/test_address.py (+126/-0) |
||||
To merge this branch: | bzr merge lp:~hazmat/pyjuju/unit-with-addresses | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Review via email: mp+76917@code.launchpad.net |
Commit message
Description of the change
Service units should know their public and private addresses.
This adds a two additional accessor/mutators to units for public/private
addresses, respectively. As well as provider specific implementation of address
detection, and unit agent initialization of those addresses on the unit state.
Kapil Thangavelu (hazmat) wrote : | # |
Excerpts from Gustavo Niemeyer's message of Mon Sep 26 13:15:39 UTC 2011:
> Review: Approve
>
> This looks very nice too, thanks!
>
> Just a couple of details:
>
>
> [1]
>
> + elif provider_type == "lxc":
>
> Please do not forget to rename the provider type to "local"
> before making this public.
sounds good, i'll put another branch in for the rename.
>
> [2]
>
> + output = subprocess.
>
> Why is this different for orchestra? Won't this fail if thethe release.
> machine doesn't have a properly assigned domain name? I suspect
> it will, and suggest using the same implementation of the local
> case there as well.
>
orchestra asserts that hostname is a valid fqdn. after long discussion on irc,
it seems that this is the most appropriate thing, and helps us get the
equivalent lines out of charms.
cheers,
Kapil
- 401. By Kapil Thangavelu
-
remove stray pdb
- 402. By Kapil Thangavelu
-
Merged lxc-omega-merge into unit-with-
addresses. - 403. By Kapil Thangavelu
-
Merged lxc-omega-merge into unit-with-
addresses. - 404. By Kapil Thangavelu
-
merge trunk and resolve conflict
Preview Diff
1 | === modified file 'juju/agents/tests/test_unit.py' |
2 | --- juju/agents/tests/test_unit.py 2011-09-29 00:33:59 +0000 |
3 | +++ juju/agents/tests/test_unit.py 2011-09-30 02:57:25 +0000 |
4 | @@ -11,6 +11,7 @@ |
5 | from juju.charm import get_charm_from_path |
6 | from juju.charm.url import CharmURL |
7 | from juju.errors import JujuError |
8 | +from juju.state.environment import GlobalSettingsStateManager |
9 | from juju.state.errors import ServiceStateNotFound |
10 | from juju.state.service import NO_HOOKS, RETRY_HOOKS |
11 | |
12 | @@ -29,6 +30,8 @@ |
13 | @inlineCallbacks |
14 | def setUp(self): |
15 | yield super(UnitAgentTestBase, self).setUp() |
16 | + settings = GlobalSettingsStateManager(self.client) |
17 | + yield settings.set_provider_type("dummy") |
18 | self.change_environment( |
19 | PATH=get_cli_environ_path(), |
20 | JUJU_UNIT_NAME="mysql/0") |
21 | @@ -148,6 +151,18 @@ |
22 | return self.assertFailure(agent.startService(), ServiceStateNotFound) |
23 | |
24 | @inlineCallbacks |
25 | + def test_agent_records_address_on_startup(self): |
26 | + """On startup the agent will record the unit's addresses. |
27 | + """ |
28 | + yield self.agent.startService() |
29 | + self.assertEqual( |
30 | + (yield self.agent.unit_state.get_public_address()), |
31 | + "localhost") |
32 | + self.assertEqual( |
33 | + (yield self.agent.unit_state.get_private_address()), |
34 | + "localhost") |
35 | + |
36 | + @inlineCallbacks |
37 | def test_agent_agent_executes_install_and_start_hooks_on_startup(self): |
38 | """On initial startup, the unit agent executes install and start hooks. |
39 | """ |
40 | @@ -497,6 +512,8 @@ |
41 | @inlineCallbacks |
42 | def setUp(self): |
43 | yield super(UnitAgentTestBase, self).setUp() |
44 | + settings = GlobalSettingsStateManager(self.client) |
45 | + yield settings.set_provider_type("dummy") |
46 | self.makeDir(path=os.path.join(self.juju_directory, "charms")) |
47 | |
48 | @inlineCallbacks |
49 | |
50 | === modified file 'juju/agents/unit.py' |
51 | --- juju/agents/unit.py 2011-09-27 19:18:08 +0000 |
52 | +++ juju/agents/unit.py 2011-09-30 02:57:25 +0000 |
53 | @@ -10,6 +10,7 @@ |
54 | from juju.hooks.protocol import UnitSettingsFactory |
55 | from juju.hooks.executor import HookExecutor |
56 | |
57 | +from juju.unit.address import get_unit_address |
58 | from juju.unit.lifecycle import UnitLifecycle, HOOK_SOCKET_FILE |
59 | from juju.unit.workflow import UnitWorkflowState |
60 | |
61 | @@ -84,6 +85,13 @@ |
62 | os.path.join(self.unit_directory, HOOK_SOCKET_FILE), |
63 | self.api_factory) |
64 | |
65 | + # Setup the unit state's address |
66 | + address = yield get_unit_address(self.client) |
67 | + yield self.unit_state.set_public_address( |
68 | + (yield address.get_public_address())) |
69 | + yield self.unit_state.set_private_address( |
70 | + (yield address.get_private_address())) |
71 | + |
72 | # Inform the system, we're alive. |
73 | yield self.unit_state.connect_agent() |
74 | |
75 | |
76 | === modified file 'juju/environment/config.py' |
77 | --- juju/environment/config.py 2011-09-27 04:29:30 +0000 |
78 | +++ juju/environment/config.py 2011-09-30 02:57:25 +0000 |
79 | @@ -57,6 +57,7 @@ |
80 | "default-series": String()}, |
81 | optional=["storage-url", "placement", |
82 | "default-series"]), |
83 | + "dummy": KeyDict({}), |
84 | "lxc": KeyDict({"admin-secret": String(), |
85 | "data-dir": String(), |
86 | "placement": Constant("local"), |
87 | |
88 | === modified file 'juju/providers/lxc/__init__.py' |
89 | --- juju/providers/lxc/__init__.py 2011-09-27 04:29:30 +0000 |
90 | +++ juju/providers/lxc/__init__.py 2011-09-30 02:57:25 +0000 |
91 | @@ -46,6 +46,13 @@ |
92 | "Unsupported placement policy for local provider") |
93 | return LOCAL_POLICY |
94 | |
95 | + def get_placement_policy(self): |
96 | + """Local dev supports only one unit placement policy.""" |
97 | + if self.config.get("placement", LOCAL_POLICY) != LOCAL_POLICY: |
98 | + raise ProviderError( |
99 | + "Unsupported placement policy for local provider") |
100 | + return LOCAL_POLICY |
101 | + |
102 | @property |
103 | def provider_type(self): |
104 | return "lxc" |
105 | |
106 | === modified file 'juju/providers/lxc/tests/test_provider.py' |
107 | --- juju/providers/lxc/tests/test_provider.py 2011-09-27 04:29:30 +0000 |
108 | +++ juju/providers/lxc/tests/test_provider.py 2011-09-30 02:57:25 +0000 |
109 | @@ -1,7 +1,6 @@ |
110 | import logging |
111 | import os |
112 | import pwd |
113 | -import subprocess |
114 | from StringIO import StringIO |
115 | import zookeeper |
116 | |
117 | @@ -124,10 +123,11 @@ |
118 | lxc_ls_mock = self.mocker.mock() |
119 | self.patch(lxc_lib, "_cmd", lxc_ls_mock) |
120 | lxc_ls_mock(["lxc-ls"]) |
121 | - self.mocker.result((0, |
122 | - "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n" |
123 | - "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n" |
124 | - "%(name)s-local-test-unit-2\n" % dict(name=user_name))) |
125 | + self.mocker.result( |
126 | + (0, |
127 | + "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n" |
128 | + "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n" |
129 | + "%(name)s-local-test-unit-2\n" % dict(name=user_name))) |
130 | |
131 | mock_container = self.mocker.patch(LXCContainer) |
132 | mock_container.stop() |
133 | |
134 | === modified file 'juju/state/service.py' |
135 | --- juju/state/service.py 2011-09-28 23:54:04 +0000 |
136 | +++ juju/state/service.py 2011-09-30 02:57:25 +0000 |
137 | @@ -15,7 +15,7 @@ |
138 | ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse, |
139 | BadDescriptor, BadServiceStateName, NoUnusedMachines, |
140 | ServiceUnitDebugAlreadyEnabled, ServiceUnitResolvedAlreadyEnabled, |
141 | - ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher) |
142 | + ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, StateError) |
143 | from juju.state.charm import CharmStateManager |
144 | from juju.state.relation import ServiceRelationState, RelationStateManager |
145 | from juju.state.machine import _public_machine_id, MachineState |
146 | @@ -708,6 +708,64 @@ |
147 | return "/units/%s/agent" % self._internal_id |
148 | |
149 | @inlineCallbacks |
150 | + def get_public_address(self): |
151 | + """Get the public address of the unit. |
152 | + |
153 | + If the unit is unassigned, or its unit agent hasn't started this |
154 | + value maybe None. |
155 | + """ |
156 | + unit_data, stat = yield self._client.get( |
157 | + "/units/%s" % self.internal_id) |
158 | + data = yaml.load(unit_data) |
159 | + returnValue(data.get("public-address")) |
160 | + |
161 | + @inlineCallbacks |
162 | + def set_public_address(self, public_address): |
163 | + """A unit's public address can be utilized to access the service. |
164 | + |
165 | + The service must have been exposed for the service to be reachable |
166 | + outside of the environment. |
167 | + """ |
168 | + def update_private_address(content, stat): |
169 | + data = yaml.load(content) |
170 | + data["public-address"] = public_address |
171 | + return yaml.safe_dump(data) |
172 | + |
173 | + yield retry_change( |
174 | + self._client, |
175 | + "/units/%s" % self.internal_id, |
176 | + update_private_address) |
177 | + |
178 | + @inlineCallbacks |
179 | + def get_private_address(self): |
180 | + """Get the private address of the unit. |
181 | + |
182 | + If the unit is unassigned, or its unit agent hasn't started this |
183 | + value maybe None. |
184 | + """ |
185 | + unit_data, stat = yield self._client.get( |
186 | + "/units/%s" % self.internal_id) |
187 | + data = yaml.load(unit_data) |
188 | + returnValue(data.get("private-address")) |
189 | + |
190 | + @inlineCallbacks |
191 | + def set_private_address(self, private_address): |
192 | + """A unit's address private to the environment. |
193 | + |
194 | + Other service will see and utilize this address for relations. |
195 | + """ |
196 | + |
197 | + def update_private_address(content, stat): |
198 | + data = yaml.load(content) |
199 | + data["private-address"] = private_address |
200 | + return yaml.safe_dump(data) |
201 | + |
202 | + yield retry_change( |
203 | + self._client, |
204 | + "/units/%s" % self.internal_id, |
205 | + update_private_address) |
206 | + |
207 | + @inlineCallbacks |
208 | def get_charm_id(self): |
209 | """Get the charm identifier that the unit is currently running.""" |
210 | unit_data, stat = yield self._client.get( |
211 | @@ -1331,6 +1389,7 @@ |
212 | sequence = int(sequence) |
213 | return service_name, sequence |
214 | |
215 | + |
216 | def parse_service_name(unit_name): |
217 | """Return the service name from a given unit name.""" |
218 | try: |
219 | |
220 | === modified file 'juju/state/tests/test_service.py' |
221 | --- juju/state/tests/test_service.py 2011-09-28 22:42:59 +0000 |
222 | +++ juju/state/tests/test_service.py 2011-09-30 02:57:25 +0000 |
223 | @@ -20,7 +20,7 @@ |
224 | ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse, |
225 | BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled, |
226 | MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled, |
227 | - ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher) |
228 | + ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, StateError) |
229 | |
230 | |
231 | from juju.state.tests.common import StateTestBase |
232 | @@ -532,6 +532,28 @@ |
233 | self.fail("Error not raised") |
234 | |
235 | @inlineCallbacks |
236 | + def test_get_set_public_address(self): |
237 | + service_state = yield self.service_state_manager.add_service_state( |
238 | + "wordpress", self.charm_state) |
239 | + unit_state = yield service_state.add_unit_state() |
240 | + self.assertEqual((yield unit_state.get_public_address()), None) |
241 | + yield unit_state.set_public_address("example.foobar.com") |
242 | + yield self.assertEqual( |
243 | + (yield unit_state.get_public_address()), |
244 | + "example.foobar.com") |
245 | + |
246 | + @inlineCallbacks |
247 | + def test_get_set_private_address(self): |
248 | + service_state = yield self.service_state_manager.add_service_state( |
249 | + "wordpress", self.charm_state) |
250 | + unit_state = yield service_state.add_unit_state() |
251 | + self.assertEqual((yield unit_state.get_private_address()), None) |
252 | + yield unit_state.set_private_address("example.local") |
253 | + yield self.assertEqual( |
254 | + (yield unit_state.get_private_address()), |
255 | + "example.local") |
256 | + |
257 | + @inlineCallbacks |
258 | def test_get_service_unit_with_changing_state(self): |
259 | """ |
260 | If a service is removed during operation, get_service_unit() |
261 | |
262 | === added file 'juju/unit/address.py' |
263 | --- juju/unit/address.py 1970-01-01 00:00:00 +0000 |
264 | +++ juju/unit/address.py 2011-09-30 02:57:25 +0000 |
265 | @@ -0,0 +1,86 @@ |
266 | +"""Service units have both a public and private address. |
267 | +""" |
268 | +import subprocess |
269 | + |
270 | +from twisted.internet.defer import inlineCallbacks, returnValue, succeed |
271 | +from twisted.internet.threads import deferToThread |
272 | +from twisted.web import client |
273 | + |
274 | +from juju.errors import JujuError |
275 | +from juju.state.environment import GlobalSettingsStateManager |
276 | + |
277 | + |
278 | +@inlineCallbacks |
279 | +def get_unit_address(client): |
280 | + settings = GlobalSettingsStateManager(client) |
281 | + provider_type = yield settings.get_provider_type() |
282 | + if provider_type == "ec2": |
283 | + returnValue(EC2UnitAddress()) |
284 | + elif provider_type == "lxc": |
285 | + returnValue(LocalUnitAddress()) |
286 | + elif provider_type == "orchestra": |
287 | + returnValue(OrchestraUnitAddress()) |
288 | + elif provider_type == "dummy": |
289 | + returnValue(DummyUnitAddress()) |
290 | + |
291 | + raise JujuError( |
292 | + "Unknown provider type: %r, unit addresses unknown." % provider_type) |
293 | + |
294 | + |
295 | +class UnitAddress(object): |
296 | + |
297 | + def get_private_address(self): |
298 | + raise NotImplemented() |
299 | + |
300 | + def get_public_address(self): |
301 | + raise NotImplemented() |
302 | + |
303 | + |
304 | +class DummyUnitAddress(UnitAddress): |
305 | + |
306 | + def get_private_address(self): |
307 | + return succeed("localhost") |
308 | + |
309 | + def get_public_address(self): |
310 | + return succeed("localhost") |
311 | + |
312 | + |
313 | +class EC2UnitAddress(UnitAddress): |
314 | + |
315 | + @inlineCallbacks |
316 | + def get_private_address(self): |
317 | + content = yield client.getPage( |
318 | + "http://169.254.169.254/latest/meta-data/local-hostname") |
319 | + returnValue(content.strip()) |
320 | + |
321 | + @inlineCallbacks |
322 | + def get_public_address(self): |
323 | + content = yield client.getPage( |
324 | + "http://169.254.169.254/latest/meta-data/public-hostname") |
325 | + returnValue(content.strip()) |
326 | + |
327 | + |
328 | +class LocalUnitAddress(UnitAddress): |
329 | + |
330 | + def get_private_address(self): |
331 | + return deferToThread(self._get_address) |
332 | + |
333 | + def get_public_address(self): |
334 | + return deferToThread(self._get_address) |
335 | + |
336 | + def _get_address(self): |
337 | + output = subprocess.check_output(["hostname", "-I"]) |
338 | + return output.strip().split()[0] |
339 | + |
340 | + |
341 | +class OrchestraUnitAddress(UnitAddress): |
342 | + |
343 | + def get_private_address(self): |
344 | + return deferToThread(self._get_address) |
345 | + |
346 | + def get_public_address(self): |
347 | + return deferToThread(self._get_address) |
348 | + |
349 | + def _get_address(self): |
350 | + output = subprocess.check_output(["hostname", "-f"]) |
351 | + return output.strip() |
352 | |
353 | === added file 'juju/unit/tests/test_address.py' |
354 | --- juju/unit/tests/test_address.py 1970-01-01 00:00:00 +0000 |
355 | +++ juju/unit/tests/test_address.py 2011-09-30 02:57:25 +0000 |
356 | @@ -0,0 +1,126 @@ |
357 | +import subprocess |
358 | +import zookeeper |
359 | + |
360 | +from twisted.internet.defer import inlineCallbacks, succeed, returnValue |
361 | +from twisted.web import client |
362 | + |
363 | +from juju.errors import JujuError |
364 | +from juju.lib.testing import TestCase |
365 | +from juju.unit.address import ( |
366 | + EC2UnitAddress, LocalUnitAddress, OrchestraUnitAddress, DummyUnitAddress, |
367 | + get_unit_address) |
368 | +from juju.state.environment import GlobalSettingsStateManager |
369 | + |
370 | + |
371 | +class AddressTest(TestCase): |
372 | + |
373 | + def setUp(self): |
374 | + zookeeper.set_debug_level(0) |
375 | + self.client = self.get_zookeeper_client() |
376 | + return self.client.connect() |
377 | + |
378 | + @inlineCallbacks |
379 | + def get_address_for(self, provider_type): |
380 | + settings = GlobalSettingsStateManager(self.client) |
381 | + yield settings.set_provider_type(provider_type) |
382 | + address = yield get_unit_address(self.client) |
383 | + returnValue(address) |
384 | + |
385 | + @inlineCallbacks |
386 | + def test_get_ec2_address(self): |
387 | + address = yield self.get_address_for("ec2") |
388 | + self.assertTrue(isinstance(address, EC2UnitAddress)) |
389 | + |
390 | + @inlineCallbacks |
391 | + def test_get_local_address(self): |
392 | + address = yield self.get_address_for("lxc") |
393 | + self.assertTrue(isinstance(address, LocalUnitAddress)) |
394 | + |
395 | + @inlineCallbacks |
396 | + def test_get_orchestra_address(self): |
397 | + address = yield self.get_address_for("orchestra") |
398 | + self.assertTrue(isinstance(address, OrchestraUnitAddress)) |
399 | + |
400 | + @inlineCallbacks |
401 | + def test_get_dummy_address(self): |
402 | + address = yield self.get_address_for("dummy") |
403 | + self.assertTrue(isinstance(address, DummyUnitAddress)) |
404 | + |
405 | + def test_get_unknown_address(self): |
406 | + return self.assertFailure(self.get_address_for("foobar"), JujuError) |
407 | + |
408 | + |
409 | +class DummyAddressTest(TestCase): |
410 | + |
411 | + def setUp(self): |
412 | + self.address = DummyUnitAddress() |
413 | + |
414 | + def test_get_address(self): |
415 | + |
416 | + self.assertEqual( |
417 | + (yield self.address.get_public_address()), |
418 | + "localhost") |
419 | + |
420 | + self.assertEqual( |
421 | + (yield self.address.get_private_address()), |
422 | + "localhost") |
423 | + |
424 | + |
425 | +class EC2AddressTest(TestCase): |
426 | + |
427 | + def setUp(self): |
428 | + self.address = EC2UnitAddress() |
429 | + |
430 | + @inlineCallbacks |
431 | + def test_get_address(self): |
432 | + urls = [ |
433 | + "http://169.254.169.254/latest/meta-data/local-hostname", |
434 | + "http://169.254.169.254/latest/meta-data/public-hostname"] |
435 | + |
436 | + def verify_args(url): |
437 | + self.assertEqual(urls.pop(0), url) |
438 | + return succeed("foobar\n") |
439 | + |
440 | + self.patch(client, "getPage", verify_args) |
441 | + self.assertEqual( |
442 | + (yield self.address.get_private_address()), "foobar") |
443 | + self.assertEqual( |
444 | + (yield self.address.get_public_address()), "foobar") |
445 | + |
446 | + |
447 | +class LocalAddressTest(TestCase): |
448 | + |
449 | + def setUp(self): |
450 | + self.address = LocalUnitAddress() |
451 | + |
452 | + @inlineCallbacks |
453 | + def test_get_address(self): |
454 | + self.patch( |
455 | + subprocess, "check_output", |
456 | + lambda args: "192.168.1.122 127.0.0.1\n") |
457 | + self.assertEqual( |
458 | + (yield self.address.get_public_address()), |
459 | + "192.168.1.122") |
460 | + self.assertEqual( |
461 | + (yield self.address.get_private_address()), |
462 | + "192.168.1.122") |
463 | + |
464 | + |
465 | +class OrchestraAddressTest(TestCase): |
466 | + |
467 | + def setUp(self): |
468 | + self.address = OrchestraUnitAddress() |
469 | + |
470 | + @inlineCallbacks |
471 | + def test_get_address(self): |
472 | + self.patch( |
473 | + subprocess, "check_output", |
474 | + lambda args: "slice.foobar.domain.net\n") |
475 | + |
476 | + self.assertEqual( |
477 | + (yield self.address.get_public_address()), |
478 | + "slice.foobar.domain.net") |
479 | + |
480 | + self.assertEqual( |
481 | + (yield self.address.get_private_address()), |
482 | + "slice.foobar.domain.net") |
This looks very nice too, thanks!
Just a couple of details:
[1]
+ elif provider_type == "lxc":
Please do not forget to rename the provider type to "local"
before making this public.
[2]
+ output = subprocess. check_output( ["hostname" , "-f"])
Why is this different for orchestra? Won't this fail if thethe release.
machine doesn't have a properly assigned domain name? I suspect
it will, and suggest using the same implementation of the local
case there as well.