Merge lp:~hazmat/pyjuju/unit-with-addresses into lp:pyjuju

Proposed by Kapil Thangavelu
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
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
Review via email: mp+76917@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

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.

review: Approve
Revision history for this message
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.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.
>

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'juju/agents/tests/test_unit.py'
--- juju/agents/tests/test_unit.py 2011-09-29 00:33:59 +0000
+++ juju/agents/tests/test_unit.py 2011-09-30 02:57:25 +0000
@@ -11,6 +11,7 @@
11from juju.charm import get_charm_from_path11from juju.charm import get_charm_from_path
12from juju.charm.url import CharmURL12from juju.charm.url import CharmURL
13from juju.errors import JujuError13from juju.errors import JujuError
14from juju.state.environment import GlobalSettingsStateManager
14from juju.state.errors import ServiceStateNotFound15from juju.state.errors import ServiceStateNotFound
15from juju.state.service import NO_HOOKS, RETRY_HOOKS16from juju.state.service import NO_HOOKS, RETRY_HOOKS
1617
@@ -29,6 +30,8 @@
29 @inlineCallbacks30 @inlineCallbacks
30 def setUp(self):31 def setUp(self):
31 yield super(UnitAgentTestBase, self).setUp()32 yield super(UnitAgentTestBase, self).setUp()
33 settings = GlobalSettingsStateManager(self.client)
34 yield settings.set_provider_type("dummy")
32 self.change_environment(35 self.change_environment(
33 PATH=get_cli_environ_path(),36 PATH=get_cli_environ_path(),
34 JUJU_UNIT_NAME="mysql/0")37 JUJU_UNIT_NAME="mysql/0")
@@ -148,6 +151,18 @@
148 return self.assertFailure(agent.startService(), ServiceStateNotFound)151 return self.assertFailure(agent.startService(), ServiceStateNotFound)
149152
150 @inlineCallbacks153 @inlineCallbacks
154 def test_agent_records_address_on_startup(self):
155 """On startup the agent will record the unit's addresses.
156 """
157 yield self.agent.startService()
158 self.assertEqual(
159 (yield self.agent.unit_state.get_public_address()),
160 "localhost")
161 self.assertEqual(
162 (yield self.agent.unit_state.get_private_address()),
163 "localhost")
164
165 @inlineCallbacks
151 def test_agent_agent_executes_install_and_start_hooks_on_startup(self):166 def test_agent_agent_executes_install_and_start_hooks_on_startup(self):
152 """On initial startup, the unit agent executes install and start hooks.167 """On initial startup, the unit agent executes install and start hooks.
153 """168 """
@@ -497,6 +512,8 @@
497 @inlineCallbacks512 @inlineCallbacks
498 def setUp(self):513 def setUp(self):
499 yield super(UnitAgentTestBase, self).setUp()514 yield super(UnitAgentTestBase, self).setUp()
515 settings = GlobalSettingsStateManager(self.client)
516 yield settings.set_provider_type("dummy")
500 self.makeDir(path=os.path.join(self.juju_directory, "charms"))517 self.makeDir(path=os.path.join(self.juju_directory, "charms"))
501518
502 @inlineCallbacks519 @inlineCallbacks
503520
=== modified file 'juju/agents/unit.py'
--- juju/agents/unit.py 2011-09-27 19:18:08 +0000
+++ juju/agents/unit.py 2011-09-30 02:57:25 +0000
@@ -10,6 +10,7 @@
10from juju.hooks.protocol import UnitSettingsFactory10from juju.hooks.protocol import UnitSettingsFactory
11from juju.hooks.executor import HookExecutor11from juju.hooks.executor import HookExecutor
1212
13from juju.unit.address import get_unit_address
13from juju.unit.lifecycle import UnitLifecycle, HOOK_SOCKET_FILE14from juju.unit.lifecycle import UnitLifecycle, HOOK_SOCKET_FILE
14from juju.unit.workflow import UnitWorkflowState15from juju.unit.workflow import UnitWorkflowState
1516
@@ -84,6 +85,13 @@
84 os.path.join(self.unit_directory, HOOK_SOCKET_FILE),85 os.path.join(self.unit_directory, HOOK_SOCKET_FILE),
85 self.api_factory)86 self.api_factory)
8687
88 # Setup the unit state's address
89 address = yield get_unit_address(self.client)
90 yield self.unit_state.set_public_address(
91 (yield address.get_public_address()))
92 yield self.unit_state.set_private_address(
93 (yield address.get_private_address()))
94
87 # Inform the system, we're alive.95 # Inform the system, we're alive.
88 yield self.unit_state.connect_agent()96 yield self.unit_state.connect_agent()
8997
9098
=== modified file 'juju/environment/config.py'
--- juju/environment/config.py 2011-09-27 04:29:30 +0000
+++ juju/environment/config.py 2011-09-30 02:57:25 +0000
@@ -57,6 +57,7 @@
57 "default-series": String()},57 "default-series": String()},
58 optional=["storage-url", "placement",58 optional=["storage-url", "placement",
59 "default-series"]),59 "default-series"]),
60 "dummy": KeyDict({}),
60 "lxc": KeyDict({"admin-secret": String(),61 "lxc": KeyDict({"admin-secret": String(),
61 "data-dir": String(),62 "data-dir": String(),
62 "placement": Constant("local"),63 "placement": Constant("local"),
6364
=== modified file 'juju/providers/lxc/__init__.py'
--- juju/providers/lxc/__init__.py 2011-09-27 04:29:30 +0000
+++ juju/providers/lxc/__init__.py 2011-09-30 02:57:25 +0000
@@ -46,6 +46,13 @@
46 "Unsupported placement policy for local provider")46 "Unsupported placement policy for local provider")
47 return LOCAL_POLICY47 return LOCAL_POLICY
4848
49 def get_placement_policy(self):
50 """Local dev supports only one unit placement policy."""
51 if self.config.get("placement", LOCAL_POLICY) != LOCAL_POLICY:
52 raise ProviderError(
53 "Unsupported placement policy for local provider")
54 return LOCAL_POLICY
55
49 @property56 @property
50 def provider_type(self):57 def provider_type(self):
51 return "lxc"58 return "lxc"
5259
=== modified file 'juju/providers/lxc/tests/test_provider.py'
--- juju/providers/lxc/tests/test_provider.py 2011-09-27 04:29:30 +0000
+++ juju/providers/lxc/tests/test_provider.py 2011-09-30 02:57:25 +0000
@@ -1,7 +1,6 @@
1import logging1import logging
2import os2import os
3import pwd3import pwd
4import subprocess
5from StringIO import StringIO4from StringIO import StringIO
6import zookeeper5import zookeeper
76
@@ -124,10 +123,11 @@
124 lxc_ls_mock = self.mocker.mock()123 lxc_ls_mock = self.mocker.mock()
125 self.patch(lxc_lib, "_cmd", lxc_ls_mock)124 self.patch(lxc_lib, "_cmd", lxc_ls_mock)
126 lxc_ls_mock(["lxc-ls"])125 lxc_ls_mock(["lxc-ls"])
127 self.mocker.result((0,126 self.mocker.result(
128 "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n"127 (0,
129 "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n"128 "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n"
130 "%(name)s-local-test-unit-2\n" % dict(name=user_name)))129 "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n"
130 "%(name)s-local-test-unit-2\n" % dict(name=user_name)))
131131
132 mock_container = self.mocker.patch(LXCContainer)132 mock_container = self.mocker.patch(LXCContainer)
133 mock_container.stop()133 mock_container.stop()
134134
=== modified file 'juju/state/service.py'
--- juju/state/service.py 2011-09-28 23:54:04 +0000
+++ juju/state/service.py 2011-09-30 02:57:25 +0000
@@ -15,7 +15,7 @@
15 ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,15 ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,
16 BadDescriptor, BadServiceStateName, NoUnusedMachines,16 BadDescriptor, BadServiceStateName, NoUnusedMachines,
17 ServiceUnitDebugAlreadyEnabled, ServiceUnitResolvedAlreadyEnabled,17 ServiceUnitDebugAlreadyEnabled, ServiceUnitResolvedAlreadyEnabled,
18 ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher)18 ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, StateError)
19from juju.state.charm import CharmStateManager19from juju.state.charm import CharmStateManager
20from juju.state.relation import ServiceRelationState, RelationStateManager20from juju.state.relation import ServiceRelationState, RelationStateManager
21from juju.state.machine import _public_machine_id, MachineState21from juju.state.machine import _public_machine_id, MachineState
@@ -708,6 +708,64 @@
708 return "/units/%s/agent" % self._internal_id708 return "/units/%s/agent" % self._internal_id
709709
710 @inlineCallbacks710 @inlineCallbacks
711 def get_public_address(self):
712 """Get the public address of the unit.
713
714 If the unit is unassigned, or its unit agent hasn't started this
715 value maybe None.
716 """
717 unit_data, stat = yield self._client.get(
718 "/units/%s" % self.internal_id)
719 data = yaml.load(unit_data)
720 returnValue(data.get("public-address"))
721
722 @inlineCallbacks
723 def set_public_address(self, public_address):
724 """A unit's public address can be utilized to access the service.
725
726 The service must have been exposed for the service to be reachable
727 outside of the environment.
728 """
729 def update_private_address(content, stat):
730 data = yaml.load(content)
731 data["public-address"] = public_address
732 return yaml.safe_dump(data)
733
734 yield retry_change(
735 self._client,
736 "/units/%s" % self.internal_id,
737 update_private_address)
738
739 @inlineCallbacks
740 def get_private_address(self):
741 """Get the private address of the unit.
742
743 If the unit is unassigned, or its unit agent hasn't started this
744 value maybe None.
745 """
746 unit_data, stat = yield self._client.get(
747 "/units/%s" % self.internal_id)
748 data = yaml.load(unit_data)
749 returnValue(data.get("private-address"))
750
751 @inlineCallbacks
752 def set_private_address(self, private_address):
753 """A unit's address private to the environment.
754
755 Other service will see and utilize this address for relations.
756 """
757
758 def update_private_address(content, stat):
759 data = yaml.load(content)
760 data["private-address"] = private_address
761 return yaml.safe_dump(data)
762
763 yield retry_change(
764 self._client,
765 "/units/%s" % self.internal_id,
766 update_private_address)
767
768 @inlineCallbacks
711 def get_charm_id(self):769 def get_charm_id(self):
712 """Get the charm identifier that the unit is currently running."""770 """Get the charm identifier that the unit is currently running."""
713 unit_data, stat = yield self._client.get(771 unit_data, stat = yield self._client.get(
@@ -1331,6 +1389,7 @@
1331 sequence = int(sequence)1389 sequence = int(sequence)
1332 return service_name, sequence1390 return service_name, sequence
13331391
1392
1334def parse_service_name(unit_name):1393def parse_service_name(unit_name):
1335 """Return the service name from a given unit name."""1394 """Return the service name from a given unit name."""
1336 try:1395 try:
13371396
=== modified file 'juju/state/tests/test_service.py'
--- juju/state/tests/test_service.py 2011-09-28 22:42:59 +0000
+++ juju/state/tests/test_service.py 2011-09-30 02:57:25 +0000
@@ -20,7 +20,7 @@
20 ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,20 ServiceUnitStateMachineAlreadyAssigned, ServiceStateNameInUse,
21 BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled,21 BadDescriptor, BadServiceStateName, ServiceUnitDebugAlreadyEnabled,
22 MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled,22 MachineStateNotFound, NoUnusedMachines, ServiceUnitResolvedAlreadyEnabled,
23 ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher)23 ServiceUnitRelationResolvedAlreadyEnabled, StopWatcher, StateError)
2424
2525
26from juju.state.tests.common import StateTestBase26from juju.state.tests.common import StateTestBase
@@ -532,6 +532,28 @@
532 self.fail("Error not raised")532 self.fail("Error not raised")
533533
534 @inlineCallbacks534 @inlineCallbacks
535 def test_get_set_public_address(self):
536 service_state = yield self.service_state_manager.add_service_state(
537 "wordpress", self.charm_state)
538 unit_state = yield service_state.add_unit_state()
539 self.assertEqual((yield unit_state.get_public_address()), None)
540 yield unit_state.set_public_address("example.foobar.com")
541 yield self.assertEqual(
542 (yield unit_state.get_public_address()),
543 "example.foobar.com")
544
545 @inlineCallbacks
546 def test_get_set_private_address(self):
547 service_state = yield self.service_state_manager.add_service_state(
548 "wordpress", self.charm_state)
549 unit_state = yield service_state.add_unit_state()
550 self.assertEqual((yield unit_state.get_private_address()), None)
551 yield unit_state.set_private_address("example.local")
552 yield self.assertEqual(
553 (yield unit_state.get_private_address()),
554 "example.local")
555
556 @inlineCallbacks
535 def test_get_service_unit_with_changing_state(self):557 def test_get_service_unit_with_changing_state(self):
536 """558 """
537 If a service is removed during operation, get_service_unit()559 If a service is removed during operation, get_service_unit()
538560
=== added file 'juju/unit/address.py'
--- juju/unit/address.py 1970-01-01 00:00:00 +0000
+++ juju/unit/address.py 2011-09-30 02:57:25 +0000
@@ -0,0 +1,86 @@
1"""Service units have both a public and private address.
2"""
3import subprocess
4
5from twisted.internet.defer import inlineCallbacks, returnValue, succeed
6from twisted.internet.threads import deferToThread
7from twisted.web import client
8
9from juju.errors import JujuError
10from juju.state.environment import GlobalSettingsStateManager
11
12
13@inlineCallbacks
14def get_unit_address(client):
15 settings = GlobalSettingsStateManager(client)
16 provider_type = yield settings.get_provider_type()
17 if provider_type == "ec2":
18 returnValue(EC2UnitAddress())
19 elif provider_type == "lxc":
20 returnValue(LocalUnitAddress())
21 elif provider_type == "orchestra":
22 returnValue(OrchestraUnitAddress())
23 elif provider_type == "dummy":
24 returnValue(DummyUnitAddress())
25
26 raise JujuError(
27 "Unknown provider type: %r, unit addresses unknown." % provider_type)
28
29
30class UnitAddress(object):
31
32 def get_private_address(self):
33 raise NotImplemented()
34
35 def get_public_address(self):
36 raise NotImplemented()
37
38
39class DummyUnitAddress(UnitAddress):
40
41 def get_private_address(self):
42 return succeed("localhost")
43
44 def get_public_address(self):
45 return succeed("localhost")
46
47
48class EC2UnitAddress(UnitAddress):
49
50 @inlineCallbacks
51 def get_private_address(self):
52 content = yield client.getPage(
53 "http://169.254.169.254/latest/meta-data/local-hostname")
54 returnValue(content.strip())
55
56 @inlineCallbacks
57 def get_public_address(self):
58 content = yield client.getPage(
59 "http://169.254.169.254/latest/meta-data/public-hostname")
60 returnValue(content.strip())
61
62
63class LocalUnitAddress(UnitAddress):
64
65 def get_private_address(self):
66 return deferToThread(self._get_address)
67
68 def get_public_address(self):
69 return deferToThread(self._get_address)
70
71 def _get_address(self):
72 output = subprocess.check_output(["hostname", "-I"])
73 return output.strip().split()[0]
74
75
76class OrchestraUnitAddress(UnitAddress):
77
78 def get_private_address(self):
79 return deferToThread(self._get_address)
80
81 def get_public_address(self):
82 return deferToThread(self._get_address)
83
84 def _get_address(self):
85 output = subprocess.check_output(["hostname", "-f"])
86 return output.strip()
087
=== added file 'juju/unit/tests/test_address.py'
--- juju/unit/tests/test_address.py 1970-01-01 00:00:00 +0000
+++ juju/unit/tests/test_address.py 2011-09-30 02:57:25 +0000
@@ -0,0 +1,126 @@
1import subprocess
2import zookeeper
3
4from twisted.internet.defer import inlineCallbacks, succeed, returnValue
5from twisted.web import client
6
7from juju.errors import JujuError
8from juju.lib.testing import TestCase
9from juju.unit.address import (
10 EC2UnitAddress, LocalUnitAddress, OrchestraUnitAddress, DummyUnitAddress,
11 get_unit_address)
12from juju.state.environment import GlobalSettingsStateManager
13
14
15class AddressTest(TestCase):
16
17 def setUp(self):
18 zookeeper.set_debug_level(0)
19 self.client = self.get_zookeeper_client()
20 return self.client.connect()
21
22 @inlineCallbacks
23 def get_address_for(self, provider_type):
24 settings = GlobalSettingsStateManager(self.client)
25 yield settings.set_provider_type(provider_type)
26 address = yield get_unit_address(self.client)
27 returnValue(address)
28
29 @inlineCallbacks
30 def test_get_ec2_address(self):
31 address = yield self.get_address_for("ec2")
32 self.assertTrue(isinstance(address, EC2UnitAddress))
33
34 @inlineCallbacks
35 def test_get_local_address(self):
36 address = yield self.get_address_for("lxc")
37 self.assertTrue(isinstance(address, LocalUnitAddress))
38
39 @inlineCallbacks
40 def test_get_orchestra_address(self):
41 address = yield self.get_address_for("orchestra")
42 self.assertTrue(isinstance(address, OrchestraUnitAddress))
43
44 @inlineCallbacks
45 def test_get_dummy_address(self):
46 address = yield self.get_address_for("dummy")
47 self.assertTrue(isinstance(address, DummyUnitAddress))
48
49 def test_get_unknown_address(self):
50 return self.assertFailure(self.get_address_for("foobar"), JujuError)
51
52
53class DummyAddressTest(TestCase):
54
55 def setUp(self):
56 self.address = DummyUnitAddress()
57
58 def test_get_address(self):
59
60 self.assertEqual(
61 (yield self.address.get_public_address()),
62 "localhost")
63
64 self.assertEqual(
65 (yield self.address.get_private_address()),
66 "localhost")
67
68
69class EC2AddressTest(TestCase):
70
71 def setUp(self):
72 self.address = EC2UnitAddress()
73
74 @inlineCallbacks
75 def test_get_address(self):
76 urls = [
77 "http://169.254.169.254/latest/meta-data/local-hostname",
78 "http://169.254.169.254/latest/meta-data/public-hostname"]
79
80 def verify_args(url):
81 self.assertEqual(urls.pop(0), url)
82 return succeed("foobar\n")
83
84 self.patch(client, "getPage", verify_args)
85 self.assertEqual(
86 (yield self.address.get_private_address()), "foobar")
87 self.assertEqual(
88 (yield self.address.get_public_address()), "foobar")
89
90
91class LocalAddressTest(TestCase):
92
93 def setUp(self):
94 self.address = LocalUnitAddress()
95
96 @inlineCallbacks
97 def test_get_address(self):
98 self.patch(
99 subprocess, "check_output",
100 lambda args: "192.168.1.122 127.0.0.1\n")
101 self.assertEqual(
102 (yield self.address.get_public_address()),
103 "192.168.1.122")
104 self.assertEqual(
105 (yield self.address.get_private_address()),
106 "192.168.1.122")
107
108
109class OrchestraAddressTest(TestCase):
110
111 def setUp(self):
112 self.address = OrchestraUnitAddress()
113
114 @inlineCallbacks
115 def test_get_address(self):
116 self.patch(
117 subprocess, "check_output",
118 lambda args: "slice.foobar.domain.net\n")
119
120 self.assertEqual(
121 (yield self.address.get_public_address()),
122 "slice.foobar.domain.net")
123
124 self.assertEqual(
125 (yield self.address.get_private_address()),
126 "slice.foobar.domain.net")

Subscribers

People subscribed via source and target branches

to status/vote changes: