Merge lp:~jimbaker/pyjuju/expose-provision-machines-reexpose into lp:pyjuju

Proposed by Jim Baker
Status: Merged
Approved by: Kapil Thangavelu
Approved revision: 300
Merged at revision: 308
Proposed branch: lp:~jimbaker/pyjuju/expose-provision-machines-reexpose
Merge into: lp:pyjuju
Prerequisite: lp:~jimbaker/pyjuju/expose-provision-machines
Diff against target: 287 lines (+168/-18)
4 files modified
ensemble/agents/provision.py (+15/-8)
ensemble/agents/tests/test_provision.py (+27/-1)
ensemble/state/base.py (+15/-0)
ensemble/state/tests/test_base.py (+111/-9)
To merge this branch: bzr merge lp:~jimbaker/pyjuju/expose-provision-machines-reexpose
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+68313@code.launchpad.net

Description of the change

Fixing this was essentially a matter of making
cb_watch_service_exposed_flag be symmetric on opening/closing ports on
the corresponding service units; that is, the following code should be
run regardless of whether the service is exposed:

            for unit_state in unit_states:
                yield self.open_close_ports(unit_state)

Tests in test_provision have been changed so that they actually test
re-exposing a service.

In addition, the support for re-exposing brought up the issue that
StateBase._watch_topology has the same issue previously identified in
other watches: that it needs to guard on the connection being
connected upon entry to the watch function. These changes were made in
both _watch_topology and __watch_topology. Tests were also add to
test_base to verify that when the client is disconnected, the watch
properly shuts down. (The necessity for this change to StateBase is
otherwise only seen in repeated looping of tests that in the
provisioning agent.)

Lastly, I removed the unnecessary sleeps in test_base in favor of our
current practice of using poke_zk (but only for these types of tests).

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

Looks good, thanks Jim.

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

looks good, thanks for digging into the additional robustness checks.

review: Approve
Revision history for this message
Jim Baker (jimbaker) wrote :

Thanks. I will be merging this into trunk, along with its upstream,
expose-provision-machines, once expose-provider-ec2 is in place, since it
requires a provider implementation to be present.

On Mon, Jul 25, 2011 at 11:01 AM, Kapil Thangavelu <
<email address hidden>> wrote:

> The proposal to merge
> lp:~jimbaker/ensemble/expose-provision-machines-reexpose into lp:ensemble
> has been updated.
>
> Status: Needs review => Approved
>
> For more details, see:
>
> https://code.launchpad.net/~jimbaker/ensemble/expose-provision-machines-reexpose/+merge/68313<https://code.launchpad.net/%7Ejimbaker/ensemble/expose-provision-machines-reexpose/+merge/68313>
> --
>
> https://code.launchpad.net/~jimbaker/ensemble/expose-provision-machines-reexpose/+merge/68313<https://code.launchpad.net/%7Ejimbaker/ensemble/expose-provision-machines-reexpose/+merge/68313>
> You are the owner of
> lp:~jimbaker/ensemble/expose-provision-machines-reexpose.
>

301. By Jim Baker

Merged upstream branch expose-provision-machines

302. By Jim Baker

Merged upstream expose-provision-machines

303. By Jim Baker

Merged upstream expose-provision-machines

304. By Jim Baker

Merged upstream

305. By Jim Baker

Merged upstream

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ensemble/agents/provision.py'
2--- ensemble/agents/provision.py 2011-08-09 14:27:56 +0000
3+++ ensemble/agents/provision.py 2011-08-09 14:27:56 +0000
4@@ -301,16 +301,23 @@
5 def cb_watch_service_exposed_flag(exposed):
6 if not self._running:
7 raise StopWatcher()
8+
9+ if exposed:
10+ log.debug("Service %r is exposed", service_name)
11+ else:
12+ log.debug("Service %r is unexposed", service_name)
13+
14+ try:
15+ unit_states = yield service_state.get_all_unit_states()
16+ except StateChanged:
17+ log.debug("Stopping watch on %r, no longer in topology",
18+ service_name)
19+ raise StopWatcher()
20+ for unit_state in unit_states:
21+ yield self.open_close_ports(unit_state)
22+
23 if not exposed:
24 self._watched_services[service_name] = NotExposed
25- try:
26- unit_states = yield service_state.get_all_unit_states()
27- except StateChanged:
28- log.debug("Stopping watch on %r, no longer in topology",
29- service_name)
30- raise StopWatcher()
31- for unit_state in unit_states:
32- yield self.open_close_ports(unit_state)
33 else:
34 self._watched_services[service_name] = set()
35 yield self._setup_service_unit_watch(service_state)
36
37=== modified file 'ensemble/agents/tests/test_provision.py'
38--- ensemble/agents/tests/test_provision.py 2011-08-09 14:27:56 +0000
39+++ ensemble/agents/tests/test_provision.py 2011-08-09 14:27:56 +0000
40@@ -507,6 +507,14 @@
41 yield wordpress.clear_exposed_flag()
42 self.assertTrue((yield expected_units))
43
44+ # Re-expose wordpress: set the flag again, verify that it
45+ # triggers on the expected units
46+ expected_units = self.wait_on_expected_units(
47+ set(["wordpress/0"]))
48+ yield wordpress.set_exposed_flag()
49+ self.assertTrue((yield expected_units))
50+ yield self.agent.stop()
51+
52 @inlineCallbacks
53 def test_add_remove_service_units_for_exposed_service(self):
54 """Verify that adding/removing service units for an exposed
55@@ -799,8 +807,8 @@
56 yield drupal_0.open_port(443, "tcp")
57 yield wordpress_0.open_port(80, "tcp")
58 yield wordpress_1.open_port(80, "tcp")
59+ self.assertTrue((yield expected_units))
60 self.assertTrue((yield expected_machines))
61- self.assertTrue((yield expected_units))
62 self.assertEqual((yield self.get_provider_ports(machine_0)),
63 set([(80, "tcp"), (443, "tcp"), (8080, "tcp")]))
64 self.assertEqual((yield self.get_provider_ports(machine_1)),
65@@ -869,4 +877,22 @@
66 self.assertTrue((yield expected_units))
67 self.assertEqual((yield self.get_provider_ports(machine_0)),
68 set([(80, "tcp"), (443, "tcp"), (8080, "tcp")]))
69+
70+ # Unexpose drupal service, verify only wordpress ports are now opened
71+ expected_machines = self.wait_on_expected_machines(set([0]))
72+ expected_units = self.wait_on_expected_units(set(["drupal/0"]))
73+ yield drupal.clear_exposed_flag()
74+ self.assertTrue((yield expected_machines))
75+ self.assertTrue((yield expected_units))
76+ self.assertEqual((yield self.get_provider_ports(machine_0)),
77+ set([(80, "tcp")]))
78+
79+ # Re-expose drupal service, verify ports are once again opened
80+ expected_machines = self.wait_on_expected_machines(set([0]))
81+ expected_units = self.wait_on_expected_units(set(["drupal/0"]))
82+ yield drupal.set_exposed_flag()
83+ self.assertTrue((yield expected_machines))
84+ self.assertTrue((yield expected_units))
85+ self.assertEqual((yield self.get_provider_ports(machine_0)),
86+ set([(80, "tcp"), (443, "tcp"), (8080, "tcp")]))
87 yield self.agent.stop()
88
89=== modified file 'ensemble/state/base.py'
90--- ensemble/state/base.py 2011-05-06 04:40:33 +0000
91+++ ensemble/state/base.py 2011-08-09 14:27:56 +0000
92@@ -94,6 +94,13 @@
93 "private", since the only purpose of this method is to be used by
94 subclasses.
95 """
96+ # Need to guard on the client being connected in the case
97+ # 1) a watch is waiting to run (in the reactor);
98+ # 2) and the connection is closed.
99+ # Because _watch_topology always chains to __watch_topology,
100+ # the other guarding seen with `StopWatcher` is done there.
101+ if not self._client.connected:
102+ return
103 exists, watch = self._client.exists_and_watch("/topology")
104 stat = yield exists
105
106@@ -106,6 +113,14 @@
107 @inlineCallbacks
108 def __topology_changed(self, ignored, watch_topology_function):
109 """Internal callback used by _watch_topology()."""
110+ # Need to guard on the client being connected in the case
111+ # 1) a watch is waiting to run (in the reactor);
112+ # 2) and the connection is closed.
113+ #
114+ # It remains the reponsibility of `watch_topology_function` to
115+ # raise `StopWatcher`, per the doc of `_topology_changed`.
116+ if not self._client.connected:
117+ return
118 try:
119 get, watch = self._client.get_and_watch("/topology")
120 content, stat = yield get
121
122=== modified file 'ensemble/state/tests/test_base.py'
123--- ensemble/state/tests/test_base.py 2011-02-10 20:13:55 +0000
124+++ ensemble/state/tests/test_base.py 2011-08-09 14:27:56 +0000
125@@ -1,11 +1,12 @@
126 from twisted.internet.defer import inlineCallbacks, Deferred
127-from twisted.internet import reactor
128+from txzookeeper import ZookeeperClient
129
130 from ensemble.state.tests.common import StateTestBase
131
132 from ensemble.state.base import StateBase
133 from ensemble.state.errors import StopWatcher
134 from ensemble.state.topology import InternalTopology
135+from ensemble.tests.common import get_test_zookeeper_address
136
137
138 class StateBaseTest(StateTestBase):
139@@ -176,9 +177,7 @@
140 yield wait_callback[1]
141
142 # Give a chance for something bad to happen.
143- go_reactor = Deferred()
144- reactor.callLater(0.5, go_reactor.callback, None)
145- yield go_reactor
146+ yield self.poke_zk()
147
148 # Now the watch callback must have been fired with two
149 # different topologies. The old one, and the new one.
150@@ -273,7 +272,7 @@
151 yield self.set_topology(topology)
152
153 # Give a chance for something bad to happen.
154- yield self.sleep(0.1)
155+ yield self.poke_zk()
156
157 # Ensure we still have a single call.
158 self.assertEquals(len(calls), 1)
159@@ -295,8 +294,8 @@
160 @inlineCallbacks
161 def test_stop_watch(self):
162 """
163- A watch which fires an StopWatcher excepiton, will
164- end the watch."""
165+ A watch that fires a `StopWatcher` exception will end the
166+ watch."""
167 wait_callback = [Deferred() for i in range(5)]
168 calls = []
169
170@@ -324,12 +323,115 @@
171 yield wait_callback[1]
172 self.assertEqual(len(calls), 2)
173
174- # Change the topology again, we shouldnt' see this.
175+ # Change the topology again, we shouldn't see this.
176 topology.add_machine("m-2")
177 yield self.set_topology(topology)
178
179 # Give a chance for something bad to happen.
180- yield self.sleep(0.1)
181+ yield self.poke_zk()
182
183 # Ensure we still have a single call.
184 self.assertEquals(len(calls), 2)
185+
186+ @inlineCallbacks
187+ def test_watch_stops_on_closed_connection(self):
188+ """Verify watches stops when the connection is closed."""
189+
190+ # Use a separate client connection for watching so it can be
191+ # disconnected.
192+ watch_client = ZookeeperClient(get_test_zookeeper_address())
193+ yield watch_client.connect()
194+ watch_base = StateBase(watch_client)
195+
196+ wait_callback = Deferred()
197+ finish_callback = Deferred()
198+ calls = []
199+
200+ def watcher(old_topology, new_topology):
201+ calls.append((old_topology, new_topology))
202+ wait_callback.callback(True)
203+ return finish_callback
204+
205+ # Start watching.
206+ yield watch_base._watch_topology(watcher)
207+
208+ # Create the topology.
209+ topology = InternalTopology()
210+ topology.add_machine("m-0")
211+ yield self.set_topology(topology)
212+
213+ # Hold off until callback is started.
214+ yield wait_callback
215+
216+ # Change the topology.
217+ topology.add_machine("m-1")
218+ yield self.set_topology(topology)
219+
220+ # Ensure that the watch has been called just once so far
221+ # (although still pending due to the finish_callback).
222+ self.assertEquals(len(calls), 1)
223+
224+ # Now disconnect the client.
225+ watch_client.close()
226+ self.assertFalse(watch_client.connected)
227+ self.assertTrue(self.client.connected)
228+
229+ # Change the topology again.
230+ topology.add_machine("m-2")
231+ yield self.set_topology(topology)
232+
233+ # Allow the first call to be completed, starting a process of
234+ # watching for the next change. At this point, the watch will
235+ # encounter that the client is disconnected.
236+ finish_callback.callback(True)
237+
238+ # Give a chance for something bad to happen.
239+ yield self.poke_zk()
240+
241+ # Ensure the watch was still not called.
242+ self.assertEquals(len(calls), 1)
243+
244+ @inlineCallbacks
245+ def test_watch_stops_on_early_closed_connection(self):
246+ """Verify watches stops when the connection is closed early.
247+
248+ _watch_topology chains from an exists_and_watch to a
249+ get_and_watch. This test ensures that this chaining will fail
250+ gracefully if the connection is closed before this chaining
251+ can occur.
252+ """
253+ # Use a separate client connection for watching so it can be
254+ # disconnected.
255+ watch_client = ZookeeperClient(get_test_zookeeper_address())
256+ yield watch_client.connect()
257+ watch_base = StateBase(watch_client)
258+
259+ calls = []
260+
261+ @inlineCallbacks
262+ def watcher(old_topology, new_topology):
263+ calls.append((old_topology, new_topology))
264+
265+ # Create the topology.
266+ topology = InternalTopology()
267+ topology.add_machine("m-0")
268+ yield self.set_topology(topology)
269+
270+ # Now disconnect the client.
271+ watch_client.close()
272+ self.assertFalse(watch_client.connected)
273+ self.assertTrue(self.client.connected)
274+
275+ # Start watching.
276+ yield watch_base._watch_topology(watcher)
277+
278+ # Change the topology, this will trigger the watch.
279+ topology.add_machine("m-1")
280+ yield self.set_topology(topology)
281+
282+ # Give a chance for something bad to happen.
283+ yield self.poke_zk()
284+
285+ # Ensure the watcher was never called, because its client was
286+ # disconnected.
287+ self.assertEquals(len(calls), 0)

Subscribers

People subscribed via source and target branches

to status/vote changes: