Merge lp:~jimbaker/pyjuju/robust-zk-connect into lp:pyjuju

Proposed by Jim Baker
Status: Merged
Approved by: Kapil Thangavelu
Approved revision: 423
Merged at revision: 432
Proposed branch: lp:~jimbaker/pyjuju/robust-zk-connect
Merge into: lp:pyjuju
Diff against target: 715 lines (+231/-335)
7 files modified
juju/lib/tests/test_twistutils.py (+12/-1)
juju/lib/twistutils.py (+15/-1)
juju/providers/common/base.py (+0/-2)
juju/providers/common/connect.py (+50/-29)
juju/providers/common/tests/test_connect.py (+154/-48)
juju/providers/ec2/tests/test_connect.py (+0/-143)
juju/providers/orchestra/tests/test_connect.py (+0/-111)
To merge this branch: bzr merge lp:~jimbaker/pyjuju/robust-zk-connect
Reviewer Review Type Date Requested Status
Kapil Thangavelu (community) Approve
William Reade (community) Approve
Review via email: mp+82768@code.launchpad.net

Description of the change

This branch implements the change in the API, per this mailing list discussion:
https://lists.ubuntu.com/archives/juju/2011-November/000990.html

Some things not covered in this branch:

Documentation of the rationale, as requested by https://lists.ubuntu.com/archives/juju/2011-November/001010.html since However, there's not anything documenting this in general, so it's a bit larger in scope. Instead this should be covered in a separate doc branch.

Support for timeouts. However, no one discussed this on the mailing list, so this issue may be moot.

Running with --verbose is *very* verbose indeed with these retries, due to ZooKeeper client logging. This could be addressed by providing for more logging levels (maybe --verbose=DEBUG_ZK).

Bug 892254 was added to take in account that during retries, spurious messages are printed to stderr. This has no impact however on the robustness of the retry process, however.

Lastly, I removed what appears to be redundant test code in the orchestra and ec2 providers. From what I can tell, this was simply combining some aspects of testing the findzookeepers code, which is necessarily different for each provider, with the same connection tests in juju.providers.common.tests.test_connect, which is in fact common. So updating these tests to take in account that EnvironmentPending indicates that a retry should be attempted, or address a couple of gaps in testing, didn't make sense.

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

Can't see anything to complain about at all. +1.

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

It works :-) I'm a little concerned by the content and verbosity of
the output both when run normally or in verbose mode.

verbose mode -> http://paste.ubuntu.com/762451/
normal mode -> http://paste.ubuntu.com/762452/

[0]

The retry is a bit aggressive, i see 3-4 calls per second to try and
resolve the host ip address, over the course of avg ~30s. Some backoff
might be beneficial here, like 1s between retries on a machine without
an address.

[1]
2011-11-29 21:06:27,053:13891(0xb6500b70):ZOO_ERROR@handle_socket_error_msg@1579: Socket [127.0.0.1:35354] zk retcode=-4, errno=111(Connection refused): server refused to accept the client
Unhandled error in Deferred:
Unhandled error in Deferred:
Unhandled Error
Traceback (most recent call last):
Failure: txzookeeper.client.ConnectionTimeoutException: could not connect before timeout

it would be nice if this could be resolved as well in this branch, i
tracked it down to the called_aware_deferred_chain returning
failures if the parent deferred is called.

The more i look at this branch, i find the sshclient/forward code more
mystifying. Its not germane to this branch, but i've got a refactoring
branch that will followup this branch to make this code a bit cleaner.
The gist of the cleanup http://pastebin.ubuntu.com/761938/

[2] The zookeeper c library doesn't have a notion of timeouts, its an
internal thing the txzk library manages using a twisted
reactor.callLater. Effectively it means everytime we attempt a
connect, there will be a background attempt continously running in the
zk io thread.

An effective usage change would be to just increase the timeout on connect, the tunnel is already active, so its just a matter of waiting. I just commited r45 on txzookeeper to handle this issue there, namely tackle closing the conn should terminate the background connect activity, but i don't see why we wouldn't just go for the longer delay since its a continual reconnection attempt.

[3]

2011-12-05 17:54:46,490 ERROR Connection refused

Normal stdout log for the command has a few of these error
messages. Since this is normal activity it would be good to ellide
them unless in verbose mode.

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

i've fixed the verbosity/output issues from [1-3] in a follow up branch (lp:~hazmat/juju/sshclient-refactor) that also cleanups the ssh client connect code.. so outside of [0] this branch should be fine.

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

For [0], I added a 1 second deferred sleep so as to implement the desired backoff. This also marks the addition of a sleep function finally to twistutils, after a number of thoughts of doing so.

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

LGTM, let me know when your planning on merging, i'd like to get the followup branch in with it to avoid getting builds with this branch alone. Also this functionality could definitely use an email out to the list once landed.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'juju/lib/tests/test_twistutils.py'
2--- juju/lib/tests/test_twistutils.py 2011-09-15 18:50:23 +0000
3+++ juju/lib/tests/test_twistutils.py 2011-12-08 20:20:29 +0000
4@@ -1,4 +1,5 @@
5 import os
6+import time
7
8 from twisted.internet.defer import (
9 succeed, fail, Deferred, DeferredList, inlineCallbacks, returnValue)
10@@ -8,7 +9,7 @@
11
12 from juju.lib.testing import TestCase
13 from juju.lib.twistutils import (
14- concurrent_execution_guard, gather_results, get_module_directory)
15+ concurrent_execution_guard, gather_results, get_module_directory, sleep)
16
17
18 class Bar(object):
19@@ -144,3 +145,13 @@
20 self.assertIn("juju", directory)
21 self.assertNotIn("_trial_temp", directory)
22 self.assertTrue(os.path.isdir(directory))
23+
24+
25+class SleepTest(TestCase):
26+
27+ @inlineCallbacks
28+ def test_sleep(self):
29+ """Directly test deferred sleep."""
30+ start = time.time()
31+ yield sleep(0.1)
32+ self.assertGreaterEqual(time.time() - start, 0.1)
33
34=== modified file 'juju/lib/twistutils.py'
35--- juju/lib/twistutils.py 2011-02-15 15:15:16 +0000
36+++ juju/lib/twistutils.py 2011-12-08 20:20:29 +0000
37@@ -1,7 +1,9 @@
38 import inspect
39 import os
40
41-from twisted.internet.defer import maybeDeferred, succeed, DeferredList
42+from twisted.internet import reactor
43+from twisted.internet.defer import (
44+ Deferred, maybeDeferred, succeed, DeferredList)
45 from twisted.python.util import mergeFunctionMetadata
46
47
48@@ -53,3 +55,15 @@
49 """
50 return os.path.abspath(os.path.dirname(inspect.getabsfile(module)).replace(
51 "/_trial_temp", ""))
52+
53+
54+def sleep(delay):
55+ """Non-blocking sleep.
56+
57+ :param int delay: time in seconds to sleep.
58+ :return: a Deferred that fires after the desired delay.
59+ :rtype: :class:`twisted.internet.defer.Deferred`
60+ """
61+ deferred = Deferred()
62+ reactor.callLater(delay, deferred.callback, None)
63+ return deferred
64
65=== modified file 'juju/providers/common/base.py'
66--- juju/providers/common/base.py 2011-09-28 09:48:30 +0000
67+++ juju/providers/common/base.py 2011-12-08 20:20:29 +0000
68@@ -148,8 +148,6 @@
69
70 :raises: :exc:`juju.errors.EnvironmentNotFound` when no zookeepers
71 exist
72- :raises: :exc:`juju.errors.EnvironmentPending` when zookeepers
73- exist but connection attempt fails
74 """
75 return ZookeeperConnect(self).run(share=share)
76
77
78=== modified file 'juju/providers/common/connect.py'
79--- juju/providers/common/connect.py 2011-09-16 19:37:15 +0000
80+++ juju/providers/common/connect.py 2011-12-08 20:20:29 +0000
81@@ -1,8 +1,11 @@
82+import random
83+
84 from twisted.internet.defer import inlineCallbacks, returnValue
85
86 from txzookeeper.client import ConnectionTimeoutException
87
88-from juju.errors import EnvironmentPending, NoConnection
89+from juju.errors import EnvironmentNotFound, EnvironmentPending, NoConnection
90+from juju.lib.twistutils import sleep
91 from juju.state.sshclient import SSHClient
92
93 from .utils import log
94@@ -15,51 +18,69 @@
95
96 @inlineCallbacks
97 def run(self, share=False):
98- """Attempt to connect to a running zookeeper node.
99+ """Attempt to connect to a running zookeeper node, retrying as needed.
100
101 :param bool share: where feasible, attempt to share a connection with
102- other clients
103+ other clients.
104
105 :return: an open :class:`txzookeeper.client.ZookeeperClient`
106 :rtype: :class:`twisted.internet.defer.Deferred`
107
108 :raises: :exc:`juju.errors.EnvironmentNotFound` when no zookeepers
109 exist
110- :raises: :exc:`juju.errors.EnvironmentPending` when zookeepers
111- exist but connection attempt fails
112+
113+ Internally this method catches all
114+ :exc:`juju.errors.EnvironmentPending`, since
115+ this exception explicitly means that a retry is feasible.
116+
117+ TODO consider supporting a timeout for this method, instead of
118+ any such timeouts being done externally.
119 """
120+ log.info("Connecting to environment...")
121+ while True:
122+ try:
123+ client = yield self._internal_connect(share)
124+ log.info("Connected to environment.")
125+ returnValue(client)
126+ except EnvironmentPending as e:
127+ log.debug("Retrying connection: %s", e)
128+ except EnvironmentNotFound:
129+ # Expected if not bootstrapped, simply raise up
130+ raise
131+ except Exception as e:
132+ # Otherwise this is unexpected, log with some details
133+ log.exception("Cannot connect to environment: %s", e)
134+ raise
135+
136+ @inlineCallbacks
137+ def _internal_connect(self, share):
138+ """Attempt connection to one of the ZK nodes."""
139 candidates = yield self._provider.get_zookeeper_machines()
140- chosen = yield self._pick_machine(candidates)
141- client = yield self._connect_to_machine(chosen, share)
142+ assigned = [machine for machine in candidates if machine.dns_name]
143+ if not assigned:
144+ yield sleep(1) # Momentarily backoff
145+ raise EnvironmentPending("No machines have assigned addresses")
146+
147+ chosen = random.choice(assigned)
148+ log.debug("Connecting to environment using %s...", chosen.dns_name)
149+ try:
150+ client = yield SSHClient().connect(
151+ chosen.dns_name + ":2181", timeout=30, share=share)
152+ except (NoConnection, ConnectionTimeoutException) as e:
153+ raise EnvironmentPending(
154+ "Cannot connect to environment using %s "
155+ "(perhaps still initializing): %s" % (
156+ chosen.dns_name, str(e)))
157+
158 yield self.wait_for_initialization(client)
159 returnValue(client)
160
161- def _pick_machine(self, machines):
162- # TODO Should we pick a random entry from the nodes list?
163- for machine in machines:
164- if machine.dns_name:
165- return machine
166- raise EnvironmentPending("No machines have addresses assigned yet")
167-
168- def _connect_to_machine(self, machine, share):
169- log.info("Connecting to environment.")
170- result = SSHClient().connect(
171- machine.dns_name + ":2181", timeout=30, share=share)
172- result.addErrback(self._cannot_connect, machine)
173- return result
174-
175- def _cannot_connect(self, failure, machine):
176- failure.trap(NoConnection, ConnectionTimeoutException)
177- raise EnvironmentPending(
178- "Cannot connect to machine %s (perhaps still initializing): %s"
179- % (machine.instance_id, str(failure.value)))
180-
181 @inlineCallbacks
182 def wait_for_initialization(self, client):
183 exists_d, watch_d = client.exists_and_watch("/initialized")
184 exists = yield exists_d
185 if not exists:
186- log.info("Environment still initializing. Will wait.")
187+ log.debug("Environment still initializing. Will wait.")
188 yield watch_d
189 else:
190- log.debug("Environment already initialized.")
191+ log.debug("Environment is initialized.")
192
193=== modified file 'juju/providers/common/tests/test_connect.py'
194--- juju/providers/common/tests/test_connect.py 2011-09-15 18:50:23 +0000
195+++ juju/providers/common/tests/test_connect.py 2011-12-08 20:20:29 +0000
196@@ -1,3 +1,6 @@
197+import logging
198+import random
199+
200 from twisted.internet.defer import fail, inlineCallbacks, succeed
201
202 import zookeeper
203@@ -6,7 +9,7 @@
204 from txzookeeper.client import ConnectionTimeoutException
205 from txzookeeper.tests.utils import deleteTree
206
207-from juju.errors import EnvironmentPending, NoConnection
208+from juju.errors import EnvironmentNotFound, NoConnection
209 from juju.lib.testing import TestCase
210 from juju.machine import ProviderMachine
211 from juju.providers.common.base import MachineProviderBase
212@@ -16,16 +19,26 @@
213
214 class DummyProvider(MachineProviderBase):
215
216- def __init__(self, second_zookeeeper):
217- self._second_zookeeper = second_zookeeeper
218+ def __init__(self, *zookeepers):
219+ self._zookeepers = zookeepers
220
221 def get_zookeeper_machines(self):
222 """
223 Return a pair of possible zookeepers, the first of which is invalid
224 """
225- return succeed([
226- ProviderMachine("i-havenodns"),
227- self._second_zookeeper])
228+ machines = [ProviderMachine("i-havenodns")]
229+ machines.extend(self._zookeepers)
230+ return succeed(machines)
231+
232+
233+class NotBootstrappedProvider(MachineProviderBase):
234+ """Pretend to be an environment that has not been bootstrapped."""
235+
236+ def __init__(self):
237+ pass
238+
239+ def get_zookeeper_machines(self):
240+ return fail(EnvironmentNotFound("is the environment bootstrapped?"))
241
242
243 class ConnectTest(TestCase):
244@@ -35,33 +48,32 @@
245 client.connect("foo.example.com:2181", timeout=30, share=share)
246 self.mocker.result(result)
247
248- def assert_connect_error(self, error, expect_type, expect_message):
249- self.mock_connect(False, fail(error))
250+ @inlineCallbacks
251+ def test_none_have_dns_at_first(self):
252+ """Verify retry of ZK machine that at first doesn't have a DNS name."""
253+ log = self.capture_logging(level=logging.DEBUG)
254+ mocked_machine = self.mocker.proxy(ProviderMachine(
255+ "i-have-no-dns-at-first"))
256+ mocked_machine.dns_name
257+ self.mocker.result(None)
258+ mocked_machine.dns_name
259+ self.mocker.count(0, None)
260+ self.mocker.result("foo.example.com") # name assumed by mock_connect
261+
262+ # Provide for a valid mocked client once connected
263+ client = self.mocker.mock(type=SSHClient)
264+ self.mock_connect(True, succeed(client))
265+ client.exists_and_watch("/initialized")
266+ self.mocker.result((succeed(True), None))
267+
268 self.mocker.replay()
269
270- provider = DummyProvider(ProviderMachine("i-exist", "foo.example.com"))
271- d = provider.connect()
272-
273- def check_error(error):
274- self.assertEqual(str(error), expect_message)
275- self.assertFailure(d, expect_type)
276- d.addCallback(check_error)
277- return d
278-
279- def test_none_have_dns(self):
280- """
281- `EnvironmentPending` should be raised if no zookeeper nodes have dns
282- names
283- """
284- provider = DummyProvider(ProviderMachine("i-havenodnseither"))
285- d = provider.connect()
286- self.assertFailure(d, EnvironmentPending)
287-
288- def check(error):
289- self.assertEquals(
290- str(error), "No machines have addresses assigned yet")
291- d.addCallback(check)
292- return d
293+ provider = DummyProvider(mocked_machine)
294+ client = yield provider.connect(share=True)
295+
296+ self.assertIn(
297+ "Retrying connection: No machines have assigned addresses",
298+ log.getvalue())
299
300 def test_share_kwarg(self):
301 """The `share` kwarg should be passed through to `SSHClient.connect`"""
302@@ -79,26 +91,80 @@
303 d.addCallback(verify)
304 return d
305
306+ @inlineCallbacks
307 def test_no_connection(self):
308- """`NoConnection` errors should become `EnvironmentPending`s"""
309- return self.assert_connect_error(
310- NoConnection("KABOOM!"), EnvironmentPending,
311- "Cannot connect to machine i-exist (perhaps still initializing): "
312- "KABOOM!")
313-
314+ """Verify retry of `NoConnection` errors."""
315+ log = self.capture_logging(level=logging.DEBUG)
316+
317+ # First connection attempts fails
318+ mock_client = self.mocker.patch(SSHClient)
319+ mock_client.connect("foo.example.com:2181", timeout=30, share=False)
320+ self.mocker.result(fail(NoConnection("KABOOM!")))
321+
322+ # Subsequent connection attempt then succeeds with a valid
323+ # (mocked) client that can be waited on.
324+ mock_client.connect("foo.example.com:2181", timeout=30, share=False)
325+ self.mocker.result(succeed(mock_client))
326+ mock_client.exists_and_watch("/initialized")
327+ self.mocker.result((succeed(True), None))
328+
329+ self.mocker.replay()
330+
331+ provider = DummyProvider(ProviderMachine("i-exist", "foo.example.com"))
332+ yield provider.connect()
333+ self.assertIn(
334+ "Retrying connection: Cannot connect to environment "
335+ "using foo.example.com (perhaps still initializing): KABOOM!",
336+ log.getvalue())
337+
338+ @inlineCallbacks
339+ def test_not_bootstrapped(self):
340+ """Verify failure when the provider has not been bootstrapped."""
341+ provider = NotBootstrappedProvider()
342+ e = yield self.assertFailure(provider.connect(), EnvironmentNotFound)
343+ self.assertEqual(
344+ str(e),
345+ "juju environment not found: is the environment bootstrapped?")
346+
347+ @inlineCallbacks
348 def test_txzookeeper_error(self):
349- """
350- `ConnectionTimeoutException` errors should become `EnvironmentPending`s
351- """
352- return self.assert_connect_error(
353- ConnectionTimeoutException("SPLAT!"), EnvironmentPending,
354- "Cannot connect to machine i-exist (perhaps still initializing): "
355- "SPLAT!")
356-
357+ """Verify retry of `ConnectionTimeoutException` errors."""
358+ log = self.capture_logging(level=logging.DEBUG)
359+
360+ # First connection attempts fails
361+ mock_client = self.mocker.patch(SSHClient)
362+ mock_client.connect("foo.example.com:2181", timeout=30, share=False)
363+ self.mocker.result(fail(ConnectionTimeoutException("SPLAT!")))
364+
365+ # Subsequent connection attempt then succeeds with a valid
366+ # (mocked) client that can be waited on.
367+ mock_client.connect("foo.example.com:2181", timeout=30, share=False)
368+ self.mocker.result(succeed(mock_client))
369+ mock_client.exists_and_watch("/initialized")
370+ self.mocker.result((succeed(True), None))
371+
372+ self.mocker.replay()
373+
374+ provider = DummyProvider(ProviderMachine("i-exist", "foo.example.com"))
375+ yield provider.connect()
376+ self.assertIn(
377+ "Retrying connection: Cannot connect to environment "
378+ "using foo.example.com (perhaps still initializing): SPLAT!",
379+ log.getvalue())
380+
381+ @inlineCallbacks
382 def test_other_error(self):
383 """Other errors should propagate"""
384- return self.assert_connect_error(
385- TypeError("THUD!"), TypeError, "THUD!")
386+ log = self.capture_logging()
387+ self.mock_connect(False, fail(TypeError("THUD!")))
388+ self.mocker.replay()
389+
390+ provider = DummyProvider(ProviderMachine("i-exist", "foo.example.com"))
391+ ex = yield self.assertFailure(provider.connect(), TypeError)
392+ self.assertEqual(str(ex), "THUD!")
393+ self.assertIn(
394+ "Cannot connect to environment: THUD!",
395+ log.getvalue())
396
397 @inlineCallbacks
398 def test_wait_for_initialize(self):
399@@ -107,7 +173,7 @@
400 is not ready, should wait until that state is ready.
401 """
402 client = ZookeeperClient()
403- self.client = client # for poke_zk
404+ self.client = client # for poke_zk
405 self.mock_connect(False, succeed(client))
406 self.mocker.replay()
407
408@@ -131,3 +197,43 @@
409 finally:
410 deleteTree("/", client.handle)
411 client.close()
412+
413+ @inlineCallbacks
414+ def test_fast_connection(self):
415+ """Verify connection when requirements are available at time of conn.
416+
417+ This includes /initialized is already set. In addition, also
418+ verifies that if multiple ZKs are available, one is selected
419+ via random.choice.
420+ """
421+ log = self.capture_logging(level=logging.DEBUG)
422+ client = ZookeeperClient()
423+ self.mock_connect(False, succeed(client))
424+
425+ def get_choice(lst):
426+ for item in lst:
427+ if item.dns_name == "foo.example.com":
428+ return item
429+ raise AssertionError("expected choice not seen")
430+
431+ self.patch(random, "choice", get_choice)
432+ self.mocker.replay()
433+
434+ provider = DummyProvider(
435+ ProviderMachine("i-am-picked", "foo.example.com"),
436+ ProviderMachine("i-was-not", "bar.example.com"))
437+
438+ zookeeper.set_debug_level(0)
439+ yield client.connect(get_test_zookeeper_address())
440+ try:
441+ yield client.create("/initialized")
442+ yield provider.connect()
443+ self.assertEqual(
444+ "Connecting to environment...\n"
445+ "Connecting to environment using foo.example.com...\n"
446+ "Environment is initialized.\n"
447+ "Connected to environment.\n",
448+ log.getvalue())
449+ finally:
450+ deleteTree("/", client.handle)
451+ client.close()
452
453=== removed file 'juju/providers/ec2/tests/test_connect.py'
454--- juju/providers/ec2/tests/test_connect.py 2011-09-15 18:50:23 +0000
455+++ juju/providers/ec2/tests/test_connect.py 1970-01-01 00:00:00 +0000
456@@ -1,143 +0,0 @@
457-from yaml import dump
458-
459-from twisted.internet.defer import inlineCallbacks, succeed, fail
460-
461-import zookeeper
462-
463-from txzookeeper import ZookeeperClient
464-from txzookeeper.client import ConnectionTimeoutException
465-from txzookeeper.tests.utils import deleteTree
466-
467-from juju.lib.testing import TestCase
468-
469-from juju.errors import EnvironmentPending, NoConnection
470-from juju.state.sshclient import SSHClient
471-from juju.providers.ec2.tests.common import EC2TestMixin
472-from juju.tests.common import get_test_zookeeper_address
473-
474-
475-class EC2ConnectTest(EC2TestMixin, TestCase):
476-
477- def mock_find_zookeepers(self, instance):
478- self.s3.get_object(self.env_name, "provider-state")
479- self.mocker.result(succeed(dump(
480- {"zookeeper-instances": ["i-foobar"]})))
481- self.ec2.describe_instances("i-foobar")
482- self.mocker.result(succeed([instance]))
483-
484- def mock_connect(self, share, instance, result):
485- self.mock_find_zookeepers(instance)
486- client = self.mocker.patch(SSHClient)
487- client.connect("foo.example.com:2181", timeout=30, share=share)
488- self.mocker.result(result)
489-
490- def assert_connect_error(self, error, expect_type, expect_message):
491- instance = self.get_instance("i-foobar", dns_name="foo.example.com")
492- self.mock_connect(False, instance, fail(error))
493- self.mocker.replay()
494-
495- provider = self.get_provider()
496- d = provider.connect()
497-
498- def check_error(error):
499- self.assertEqual(str(error), expect_message)
500- self.assertFailure(d, expect_type)
501- d.addCallback(check_error)
502- return d
503-
504- def test_no_dns_name(self):
505- """
506- `EnvironmentPending` should be raised if no zookeeper nodes have dns
507- names
508- """
509- instance = self.get_instance("i-foobar")
510- self.mock_find_zookeepers(instance)
511- self.mocker.replay()
512-
513- provider = self.get_provider()
514- d = provider.connect()
515-
516- def check_error(error):
517- self.assertEqual(
518- str(error), "No machines have addresses assigned yet")
519-
520- self.assertFailure(d, NoConnection)
521- d.addCallback(check_error)
522- return d
523-
524- def test_provider_connect_forwards_share_option(self):
525- """The `share` kwarg should be passed through to `SSHClient.connect`"""
526- instance = self.get_instance("i-foobar", dns_name="foo.example.com")
527- connected_client = self.mocker.mock(type=SSHClient)
528- self.mock_connect(True, instance, succeed(connected_client))
529- # We'll test the wait on initialization separately.
530- connected_client.exists_and_watch("/initialized")
531- self.mocker.result((succeed(True), None))
532- self.mocker.replay()
533-
534- provider = self.get_provider()
535- d = provider.connect(share=True)
536-
537- def verify_result(result):
538- self.assertIdentical(connected_client, result)
539- d.addCallback(verify_result)
540- return d
541-
542- def test_no_connection(self):
543- """`NoConnection` errors should become `EnvironmentPending`s"""
544- return self.assert_connect_error(
545- NoConnection("KABOOM!"), EnvironmentPending,
546- "Cannot connect to machine i-foobar (perhaps still initializing): "
547- "KABOOM!")
548-
549- def test_txzookeeper_error(self):
550- """
551- `ConnectionTimeoutException` errors should become `EnvironmentPending`s
552- """
553- return self.assert_connect_error(
554- ConnectionTimeoutException("SPLAT!"), EnvironmentPending,
555- "Cannot connect to machine i-foobar (perhaps still initializing): "
556- "SPLAT!")
557-
558- def test_other_error(self):
559- """Other errors should propagate"""
560- return self.assert_connect_error(
561- TypeError("THUD!"), TypeError, "THUD!")
562-
563- @inlineCallbacks
564- def test_provider_connect_waits_on_initialization(self):
565- """
566- A connection to a zookeeper that is running, but whose juju state
567- is not ready, should wait until that state is ready.
568- """
569- # Hand back a real connected client to test the wait on initialization.
570- instance = self.get_instance("i-foobar", dns_name="foo.example.com")
571- connected_client = ZookeeperClient()
572- self.client = connected_client # for poke_zk
573- self.mock_connect(False, instance, succeed(connected_client))
574-
575- self.mocker.replay()
576-
577- zookeeper.set_debug_level(0)
578- yield connected_client.connect(get_test_zookeeper_address())
579-
580- client_result = []
581-
582- provider = self.get_provider()
583- client_deferred = provider.connect()
584- client_deferred.addCallback(client_result.append)
585-
586- # Give it a chance to do it incorrectly.
587- yield self.poke_zk()
588-
589- try:
590- self.assertEquals(client_result, [])
591-
592- yield connected_client.create("/initialized")
593-
594- yield client_deferred
595- self.assertTrue(client_result, client_result)
596- self.assertIdentical(client_result[0], connected_client)
597- finally:
598- deleteTree("/", connected_client.handle)
599- connected_client.close()
600
601=== removed file 'juju/providers/orchestra/tests/test_connect.py'
602--- juju/providers/orchestra/tests/test_connect.py 2011-10-17 13:49:47 +0000
603+++ juju/providers/orchestra/tests/test_connect.py 1970-01-01 00:00:00 +0000
604@@ -1,111 +0,0 @@
605-from yaml import dump
606-
607-from twisted.internet.defer import fail, inlineCallbacks, succeed
608-
609-import zookeeper
610-
611-from txzookeeper import ZookeeperClient
612-from txzookeeper.client import ConnectionTimeoutException
613-from txzookeeper.tests.utils import deleteTree
614-
615-from juju.errors import EnvironmentPending, NoConnection
616-from juju.lib.testing import TestCase
617-from juju.state.sshclient import SSHClient
618-from juju.tests.common import get_test_zookeeper_address
619-
620-from .common import OrchestraTestMixin
621-
622-
623-class ConnectTest(TestCase, OrchestraTestMixin):
624-
625- def mock_connect(self, share, result):
626- self.setup_mocks()
627- self.mock_fs_get(
628- "http://somewhe.re/webdav/provider-state", 200, dump(
629- {"zookeeper-instances": ["foo"]}))
630- self.proxy_m.callRemote("get_systems")
631- self.mocker.result(succeed([{
632- "uid": "foo", "name": "foo.example.com",
633- "mgmt_classes": ["acquired"], "netboot_enabled": True}]))
634- client = self.mocker.patch(SSHClient)
635- client.connect("foo.example.com:2181", timeout=30, share=share)
636- self.mocker.result(result)
637-
638- def assert_connect_error(self, error, expect_type, expect_message):
639- self.mock_connect(False, fail(error))
640- self.mocker.replay()
641-
642- d = self.get_provider().connect()
643-
644- def check_error(error):
645- self.assertEqual(str(error), expect_message)
646- self.assertFailure(d, expect_type)
647- d.addCallback(check_error)
648- return d
649-
650- def test_share_kwarg(self):
651- """The `share` kwarg should be passed through to `SSHClient.connect`"""
652- client = self.mocker.mock(type=SSHClient)
653- self.mock_connect(True, succeed(client))
654- client.exists_and_watch("/initialized")
655- self.mocker.result((succeed(True), None))
656- self.mocker.replay()
657-
658- d = self.get_provider().connect(share=True)
659-
660- def verify(result):
661- self.assertIdentical(result, client)
662- d.addCallback(verify)
663- return d
664-
665- def test_no_connection(self):
666- """`NoConnection` errors should become `EnvironmentPending`s"""
667- return self.assert_connect_error(
668- NoConnection("KABOOM!"), EnvironmentPending,
669- "Cannot connect to machine foo (perhaps still initializing): "
670- "KABOOM!")
671-
672- def test_txzookeeper_error(self):
673- """
674- `ConnectionTimeoutException` errors should become `EnvironmentPending`s
675- """
676- return self.assert_connect_error(
677- ConnectionTimeoutException("SPLAT!"), EnvironmentPending,
678- "Cannot connect to machine foo (perhaps still initializing): "
679- "SPLAT!")
680-
681- def test_other_error(self):
682- """Other errors should propagate"""
683- return self.assert_connect_error(
684- TypeError("THUD!"), TypeError, "THUD!")
685-
686- @inlineCallbacks
687- def test_wait_for_initialize(self):
688- """
689- A connection to a zookeeper that is running, but whose juju state
690- is not ready, should wait until that state is ready.
691- """
692- client = ZookeeperClient()
693- self.client = client # for poke_zk
694- self.mock_connect(False, succeed(client))
695- self.mocker.replay()
696-
697- zookeeper.set_debug_level(0)
698- yield client.connect(get_test_zookeeper_address())
699-
700- d = self.get_provider().connect()
701- client_result = []
702- d.addCallback(client_result.append)
703-
704- # Give it an opportunity to do it incorrectly.
705- yield self.poke_zk()
706-
707- try:
708- self.assertEquals(client_result, [])
709- yield client.create("/initialized")
710- yield d
711- self.assertTrue(client_result, client_result)
712- self.assertIdentical(client_result[0], client)
713- finally:
714- deleteTree("/", client.handle)
715- client.close()

Subscribers

People subscribed via source and target branches

to status/vote changes: