Merge lp:~jimbaker/pyjuju/zk-test-mgmt into lp:pyjuju

Proposed by Jim Baker
Status: Merged
Merged at revision: 93
Proposed branch: lp:~jimbaker/pyjuju/zk-test-mgmt
Merge into: lp:pyjuju
Diff against target: 576 lines (+206/-30)
16 files modified
ensemble/__init__.py (+1/-0)
ensemble/agents/tests/common.py (+2/-1)
ensemble/agents/tests/test_base.py (+7/-5)
ensemble/agents/tests/test_machine.py (+4/-2)
ensemble/control/initialize.py (+5/-1)
ensemble/formula/tests/test_publisher.py (+2/-1)
ensemble/ftests/test_connection.py (+3/-3)
ensemble/machine/tests/test_unit_deployment.py (+7/-5)
ensemble/providers/dummy.py (+2/-2)
ensemble/providers/ec2/tests/test_connect.py (+2/-1)
ensemble/providers/ec2/tests/test_launch.py (+2/-1)
ensemble/providers/tests/test_dummy.py (+1/-2)
ensemble/state/tests/common.py (+3/-2)
ensemble/state/tests/test_initialize.py (+2/-1)
ensemble/tests/common.py (+152/-0)
test (+11/-3)
To merge this branch: bzr merge lp:~jimbaker/pyjuju/zk-test-mgmt
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
Review via email: mp+40160@code.launchpad.net

Description of the change

Manage the running of a test ZooKeeper instance during Twisted Trial execution:

  - Create temp directory with all required files for ZooKeeper to run
  - Work with either dev build or release versions of ZooKeeper
  - Using subprocess, start ZooKeeper, then terminate and cleanup upon
    the end of a test

Modified the following code:

  - test, via the use of a context manager that encapsulates the actual Trial run
  - ensemble/__init__.py, to add the ZK address information
  - unit tests and classes referring to "2181" in their source code

Fixes #641482

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

Hey Jim,

Here are some preliminary comments:

[1]

+"""This port can be overriden when testing"""
+ZOOKEEPER_PORT = 2181
+
+"""For convenience, this should be kept synchronized with the ZOOKEEPER_PORT"""
+ZOOKEEPER_LOCAL_ADDRESS = "127.0.0.1:2181"

We've managed to avoid global variables thus far, and it'd
be nice if we could manage to maintain things that way. It's
fine to have these defined for tests, though, but this is
sitting outside of the test environment.

Also, this is minor, since we're talking about tests, but this
redundancy feels a bit strange. You can just define this latter
one and very easily obtain the port number out of it. That said,
the port number isn't even used anywhere, so let's just get rid
of it entirely.

+# some standard dependency injection in the Java parlance we need to do
+# to override our normal assumptions so we can test

As a random comment, dependency injection is somewhat like
the opposite of a global variable. :-)

[2]

The branch is missing some pieces. zookeeper_test_context wasn't added, specifically.

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

On Fri, Nov 5, 2010 at 11:51 AM, Gustavo Niemeyer <email address hidden>wrote:

> Review: Needs Fixing
> Hey Jim,
>
> Here are some preliminary comments:
>
> [1]
>
> +"""This port can be overriden when testing"""
> +ZOOKEEPER_PORT = 2181

+
> +"""For convenience, this should be kept synchronized with the
> ZOOKEEPER_PORT"""
> +ZOOKEEPER_LOCAL_ADDRESS = "127.0.0.1:2181"
>

>
> We've managed to avoid global variables thus far, and it'd
> be nice if we could manage to maintain things that way. It's
> fine to have these defined for tests, though, but this is
> sitting outside of the test environment.
>

This is one reason it took a while to do this, but it seems better than the
alternative, which is to have "127.0.0.1:2181" (and variants) scattered
through our code.

>
> Also, this is minor, since we're talking about tests, but this
> redundancy feels a bit strange. You can just define this latter
> one and very easily obtain the port number out of it. That said,
> the port number isn't even used anywhere, so let's just get rid
> of it entirely.
>

Agreed port number is not currently being used. I was of two minds on this
because it seemed potentially useful for the future. Having one derived from
the other would be nicer, but property doesn't work on module-level names,
and spending engineering on it to wrap in a class seemed silly. Probably
best to remove.

>
> +# some standard dependency injection in the Java parlance we need to do
> +# to override our normal assumptions so we can test
>
> As a random comment, dependency injection is somewhat like
> the opposite of a global variable. :-)
>

Yeah, except in Python, being able to just change it is something like DI.
There's no need to write an accessor when you can just set an attribute, so
it works the same. (Real DI of course allows for more sophisticated sense of
context, but in this case global context seems to be just right.)

>
> [2]
>
> The branch is missing some pieces. zookeeper_test_context wasn't added,
> specifically.
>

This function is in ensemble.tests.common, which I have verified is in the
branch.

> --
> https://code.launchpad.net/~jimbaker/ensemble/zk-test-mgmt/+merge/40160
> You are the owner of lp:~jimbaker/ensemble/zk-test-mgmt.
>

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

> This is one reason it took a while to do this, but it seems better than the
> alternative, which is to have "127.0.0.1:2181" (and variants) scattered
> through our code.

It's better to have an internal default value which is used by the specific class
which connects to zookeeper, than having a public value like that which is used
through out the code and directly modified by clients, for all the reasons why
global variables are bad (common state rather than local, side effects across
the board, etc).

If you want to define that for tests only, then that certainly feels ok, but can
we please have that under the test namespace rather than the main package one?

> Agreed port number is not currently being used. I was of two minds on this
> because it seemed potentially useful for the future. Having one derived from
> the other would be nicer, but property doesn't work on module-level names,
> and spending engineering on it to wrap in a class seemed silly. Probably
> best to remove.

Thanks!

> > As a random comment, dependency injection is somewhat like
> > the opposite of a global variable. :-)
>
> Yeah, except in Python, being able to just change it is something like DI.

This is a quite orthogonal point to the whole review, but having a global
variable like this is pretty much the same thing as having a "public static"
member in an "Ensemble" class, which (other :-) Java advocates won't really
buy as DI by itself.

> There's no need to write an accessor when you can just set an attribute, so
> it works the same. (Real DI of course allows for more sophisticated sense of
> context, but in this case global context seems to be just right.)

Global state is bad in pretty much all cases. Imagine that we want to connect
to two different servers to interconnect them. We want to be able to build
model objects with local state, that do not depend on a unique source of
information for the server address.

Dependency injection might fail in this case as well, in fact, which is one
of the reasons why I'm not a big fan of it either. The context should
generally be provided to the objects on an instance basis, rather than
having objects configured on a class level.

[2]

Yeah, I'm sorry about this. I've screwed it up locally. Will follow up with
the continuation shortly.

review: Needs Fixing
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

That looks awesome, thanks a lot!

In addition to the above, have only one significant comment, and
two minors. +1 one considering these.

[3]

I have two test failures within the branch, but all tests pass with trunk.

I've pasted the errors to: https://pastebin.canonical.com/39455/

[4]

+dataDir=%s
+clientPort=%s
+""" % (os.path.join(self.working_path, "data"), self.port))

It'd probably safer to put the data under a subdirectory of working_path,
as you have done for logs. This will avoid any potential conflicts, and
isolate the various data files in a single place.

[5]

+ with zookeeper_test_context(
+ os.environ["ZOOKEEPER_PATH"],
+ os.environ.get("ZOOKEEPER_TEST_PORT", 28181)):
+ run()

As a silly minor, would you mind to indent the second and third lines in by
another 4 spaces or something similar? It feels more readable to not have
the statement continuation aligned with the actual block content.

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

On Fri, Nov 5, 2010 at 2:29 PM, Gustavo Niemeyer <email address hidden>wrote:

> Review: Approve
> That looks awesome, thanks a lot!
>
> I will commit to trunk momentarily, see my other comments.

> In addition to the above, have only one significant comment, and
> two minors. +1 one considering these.
>
> [3]
>
> I have two test failures within the branch, but all tests pass with trunk.
>
> I've pasted the errors to: https://pastebin.canonical.com/39455/

These errors occasionally happen on trunk checkout too. They should be fixed
of course, but not related specifically to this branch.

>
>
> [4]
>
> +dataDir=%s
> +clientPort=%s
> +""" % (os.path.join(self.working_path, "data"), self.port))
>
> It'd probably safer to put the data under a subdirectory of working_path,
> as you have done for logs. This will avoid any potential conflicts, and
> isolate the various data files in a single place.
>

Actually this is a separate subdirectory, but I have made clearer in the
current code its setup.

>
> [5]
>
> + with zookeeper_test_context(
> + os.environ["ZOOKEEPER_PATH"],
> + os.environ.get("ZOOKEEPER_TEST_PORT", 28181)):
> + run()
>
> As a silly minor, would you mind to indent the second and third lines in by
> another 4 spaces or something similar? It feels more readable to not have
> the statement continuation aligned with the actual block content.
>

Done.

>
> --
> https://code.launchpad.net/~jimbaker/ensemble/zk-test-mgmt/+merge/40160
> You are the owner of lp:~jimbaker/ensemble/zk-test-mgmt.
>

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

On Fri, Nov 5, 2010 at 2:08 PM, Gustavo Niemeyer <email address hidden>wrote:

> Review: Needs Fixing
> [1]
>
> > This is one reason it took a while to do this, but it seems better than
> the
> > alternative, which is to have "127.0.0.1:2181" (and variants) scattered
> > through our code.
>
> It's better to have an internal default value which is used by the specific
> class
> which connects to zookeeper, than having a public value like that which is
> used
> through out the code and directly modified by clients, for all the reasons
> why
> global variables are bad (common state rather than local, side effects
> across
> the board, etc).
>
> If you want to define that for tests only, then that certainly feels ok,
> but can
> we please have that under the test namespace rather than the main package
> one?
>

I have move the address lookup to
ensemble.tests.common.get_test_zookeeper_address. In non-test code, this is
instead driven by an optional new env var, ZOOKEEPER_ADDRESS. This is set by
the test process, and is used in two places in non-test code:

1. In the construction of the client in ensemble.control.initialize.command,
then passed down as in trunk
2. In ensemble.providers.dummy.MachineProvider.connect (and this is really
just test code too, just put in parallel)

>
> > Agreed port number is not currently being used. I was of two minds on
> this
> > because it seemed potentially useful for the future. Having one derived
> from
> > the other would be nicer, but property doesn't work on module-level
> names,
> > and spending engineering on it to wrap in a class seemed silly. Probably
> > best to remove.
>
> Thanks!
>
>
Done

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ensemble/__init__.py'
2--- ensemble/__init__.py 2010-05-04 14:11:23 +0000
3+++ ensemble/__init__.py 2010-11-08 23:03:47 +0000
4@@ -0,0 +1,1 @@
5+#
6
7=== modified file 'ensemble/agents/tests/common.py'
8--- ensemble/agents/tests/common.py 2010-09-20 19:53:57 +0000
9+++ ensemble/agents/tests/common.py 2010-11-08 23:03:47 +0000
10@@ -7,6 +7,7 @@
11 from ensemble.agents.base import TwistedOptionNamespace
12 from ensemble.environment.config import EnvironmentsConfig
13 from ensemble.state.tests.common import StateTestBase
14+from ensemble.tests.common import get_test_zookeeper_address
15
16
17 SAMPLE_ENV = """\
18@@ -52,7 +53,7 @@
19
20 def get_agent_config(self):
21 options = TwistedOptionNamespace()
22- options["zookeeper_servers"] = "localhost:2181"
23+ options["zookeeper_servers"] = get_test_zookeeper_address()
24 return succeed(options)
25
26 @inlineCallbacks
27
28=== modified file 'ensemble/agents/tests/test_base.py'
29--- ensemble/agents/tests/test_base.py 2010-09-15 19:53:52 +0000
30+++ ensemble/agents/tests/test_base.py 2010-11-08 23:03:47 +0000
31@@ -10,11 +10,13 @@
32 from txzookeeper import ZookeeperClient
33 from ensemble.lib.testing import TestCase
34 from ensemble.lib.mocker import MATCH
35+from ensemble.tests.common import get_test_zookeeper_address
36
37 from ensemble.agents.base import (
38 BaseAgent, TwistedOptionNamespace, AgentRunner)
39 from ensemble.errors import NoConnection
40
41+
42 MATCH_APP = MATCH(lambda x: isinstance(x, Componentized))
43
44
45@@ -105,13 +107,13 @@
46 This will create an agent instance, parse the cli args, passes them to
47 the agent, and starts the agent runner.
48 """
49- self.change_args("es-agent", "--zookeeper-servers", "localhost:2181")
50+ self.change_args("es-agent", "--zookeeper-servers", get_test_zookeeper_address())
51 runner = self.mocker.patch(AgentRunner)
52 runner.run()
53 mock_agent = self.mocker.patch(BaseAgent)
54
55 def match_args(config):
56- self.assertEqual(config["zookeeper_servers"], "localhost:2181")
57+ self.assertEqual(config["zookeeper_servers"], get_test_zookeeper_address())
58 return True
59
60 mock_agent.configure(MATCH(match_args))
61@@ -143,7 +145,7 @@
62
63 pid_file = self.makeFile()
64 self.change_args(
65- "es-agent", "--zookeeper-servers", "localhost:2181",
66+ "es-agent", "--zookeeper-servers", get_test_zookeeper_address(),
67 "--pidfile", pid_file)
68 runner = self.mocker.patch(AgentRunner)
69 logger = self.mocker.patch(AppLogger)
70@@ -218,13 +220,13 @@
71 def test_run_base_raises_error(self):
72 """The base class agent, raises a notimplemented error when started."""
73 client = self.mocker.patch(ZookeeperClient)
74- client.connect("localhost:2181")
75+ client.connect(get_test_zookeeper_address())
76 client_mock = self.mocker.mock()
77 self.mocker.result(succeed(client_mock))
78 self.mocker.replay()
79
80 agent = BaseAgent()
81- agent.configure({"zookeeper_servers": "localhost:2181"})
82+ agent.configure({"zookeeper_servers": get_test_zookeeper_address()})
83 d = agent.startService()
84 self.failUnlessFailure(d, NotImplementedError)
85 return d
86
87=== modified file 'ensemble/agents/tests/test_machine.py'
88--- ensemble/agents/tests/test_machine.py 2010-10-14 19:54:48 +0000
89+++ ensemble/agents/tests/test_machine.py 2010-11-08 23:03:47 +0000
90@@ -17,6 +17,8 @@
91 from ensemble.state.environment import EnvironmentStateManager
92 from ensemble.state.machine import MachineStateManager, MachineState
93 from ensemble.state.service import ServiceStateManager
94+from ensemble.tests.common import get_test_zookeeper_address
95+
96
97 from .common import AgentTestBase
98
99@@ -169,7 +171,7 @@
100 yield self.agent.start()
101
102 mock_deployment = self.mocker.patch(ServiceUnitDeployment)
103- mock_deployment.start("0", "localhost:2181")
104+ mock_deployment.start("0", get_test_zookeeper_address())
105
106 test_deferred = Deferred()
107
108@@ -225,7 +227,7 @@
109 test_deferred = Deferred()
110
111 mock_deployment = self.mocker.patch(ServiceUnitDeployment)
112- mock_deployment.start("0", "localhost:2181")
113+ mock_deployment.start("0", get_test_zookeeper_address())
114 self.mocker.result(succeed(True))
115 mock_deployment.destroy()
116
117
118=== modified file 'ensemble/control/initialize.py'
119--- ensemble/control/initialize.py 2010-09-22 20:28:49 +0000
120+++ ensemble/control/initialize.py 2010-11-08 23:03:47 +0000
121@@ -1,3 +1,5 @@
122+import os
123+
124 from twisted.internet.defer import inlineCallbacks
125 from twisted.internet import reactor
126
127@@ -22,7 +24,9 @@
128 """
129 Initialize Zookeeper hierarchy
130 """
131- client = yield ZookeeperClient("localhost:2181").connect()
132+ zk_address = os.environ.get("ZOOKEEPER_ADDRESS", "127.0.0.1:2181")
133+ print "Using ZK address", zk_address
134+ client = yield ZookeeperClient(zk_address).connect()
135 try:
136 hierarchy = StateHierarchy(
137 client, options.admin_identity, options.instance_id)
138
139=== modified file 'ensemble/formula/tests/test_publisher.py'
140--- ensemble/formula/tests/test_publisher.py 2010-10-14 16:08:48 +0000
141+++ ensemble/formula/tests/test_publisher.py 2010-11-08 23:03:47 +0000
142@@ -14,6 +14,7 @@
143 from ensemble.providers.dummy import FileStorage
144 from ensemble.state.formula import FormulaStateManager
145 from ensemble.state.errors import StateChanged
146+from ensemble.tests.common import get_test_zookeeper_address
147
148 from ensemble.environment.tests.test_config import (
149 EnvironmentsConfigTestBase, SAMPLE_ENV)
150@@ -37,7 +38,7 @@
151 self.formula_storage_key = "%s:%s" % (
152 self.formula_key, self.formula.get_sha256())
153
154- self.client = ZookeeperClient("127.0.0.1:2181")
155+ self.client = ZookeeperClient(get_test_zookeeper_address())
156 self.storage = FileStorage(self.makeDir())
157 self.publisher = FormulaPublisher(self.client, self.storage)
158
159
160=== modified file 'ensemble/ftests/test_connection.py'
161--- ensemble/ftests/test_connection.py 2010-09-29 20:31:59 +0000
162+++ ensemble/ftests/test_connection.py 2010-11-08 23:03:47 +0000
163@@ -12,10 +12,10 @@
164 import pwd
165 import zookeeper
166
167-
168 from ensemble.errors import NoConnection
169 from ensemble.lib.testing import TestCase
170 from ensemble.state.sshclient import SSHClient
171+from ensemble.tests.common import get_test_zookeeper_address
172
173
174 class ConnectionTest(TestCase):
175@@ -39,7 +39,7 @@
176 Forwarding a port spawns an ssh process with port forwarding arguments.
177 """
178 connect_deferred = self.client.connect(
179- "localhost:2181", timeout=20)
180+ get_test_zookeeper_address(), timeout=20)
181
182 def validate_connected(client):
183 self.assertTrue(client.connected)
184@@ -80,7 +80,7 @@
185 """
186 SSHClient.remote_user = "rabbit"
187 connect_deferred = self.client.connect(
188- "localhost:2181", timeout=10)
189+ get_test_zookeeper_address(), timeout=10)
190 self.failUnlessFailure(connect_deferred, NoConnection)
191
192 def validate_log(result):
193
194=== modified file 'ensemble/machine/tests/test_unit_deployment.py'
195--- ensemble/machine/tests/test_unit_deployment.py 2010-11-02 16:27:50 +0000
196+++ ensemble/machine/tests/test_unit_deployment.py 2010-11-08 23:03:47 +0000
197@@ -15,6 +15,8 @@
198 from ensemble.lib.mocker import MATCH
199 from ensemble.machine.unit import ServiceUnitDeployment
200 from ensemble.machine.errors import UnitDeploymentError
201+from ensemble.tests.common import get_test_zookeeper_address
202+
203
204 MATCH_PROTOCOL = MATCH(lambda x: isinstance(x, ProcessProtocol))
205
206@@ -67,7 +69,7 @@
207 if it does not exist and a running service unit agent.
208 """
209 self.deployment.unpack_formula(self.bundle)
210- d = self.deployment.start("0", "localhost:2181")
211+ d = self.deployment.start("0", get_test_zookeeper_address())
212
213 @inlineCallbacks
214 def validate_result(result):
215@@ -87,7 +89,7 @@
216 if it does not exist and a running service unit agent.
217 """
218 self.deployment.unpack_formula(self.bundle)
219- d = self.deployment.start(21, "localhost:2181")
220+ d = self.deployment.start(21, get_test_zookeeper_address())
221
222 @inlineCallbacks
223 def validate_result(result):
224@@ -108,7 +110,7 @@
225 """
226 self.deployment.unit_agent_module = "magichat.xr1"
227 self.deployment.unpack_formula(self.bundle)
228- d = self.deployment.start("0", "localhost:2181")
229+ d = self.deployment.start("0", get_test_zookeeper_address())
230
231 self.failUnlessFailure(d, UnitDeploymentError)
232
233@@ -165,7 +167,7 @@
234 """
235 error = self.assertRaises(
236 UnitDeploymentError,
237- self.deployment.start, "0", "localhost:2181")
238+ self.deployment.start, "0", get_test_zookeeper_address())
239 self.assertIn("Formula must be unpacked first.", str(error))
240
241 @inlineCallbacks
242@@ -175,7 +177,7 @@
243 on the machine, and kills the unit agent process.
244 """
245 self.deployment.unpack_formula(self.bundle)
246- yield self.deployment.start("0", "localhost:2181")
247+ yield self.deployment.start("0", get_test_zookeeper_address())
248 # give the process time to write its pid file
249 yield self.sleep(0.1)
250 pid = int(open(self.deployment.pid_file).read())
251
252=== modified file 'ensemble/providers/dummy.py'
253--- ensemble/providers/dummy.py 2010-09-30 00:31:07 +0000
254+++ ensemble/providers/dummy.py 2010-11-08 23:03:47 +0000
255@@ -5,10 +5,10 @@
256 from txzookeeper import ZookeeperClient
257 from twisted.internet.defer import succeed, fail
258
259+import ensemble
260 from ensemble.errors import FileNotFound, ProviderError
261 from ensemble.machine import ProviderMachine
262
263-
264 class DummyMachine(ProviderMachine):
265 """Provider machine implementation specific to the dummy provider."""
266
267@@ -23,7 +23,7 @@
268
269 def connect(self):
270 """Connect to the zookeeper ensemble running in the machine provider"""
271- return ZookeeperClient("127.0.0.1:2181").connect()
272+ return ZookeeperClient(os.environ.get("ZOOKEEPER_ADDRESS", "127.0.0.1:2181")).connect()
273
274 def list_machines(self):
275 """List all the machine running in the provider."""
276
277=== modified file 'ensemble/providers/ec2/tests/test_connect.py'
278--- ensemble/providers/ec2/tests/test_connect.py 2010-10-14 21:14:05 +0000
279+++ ensemble/providers/ec2/tests/test_connect.py 2010-11-08 23:03:47 +0000
280@@ -20,6 +20,7 @@
281 from ensemble.providers.ec2.tests.common import EC2TestMixin
282 from ensemble.providers.ec2.connect import EC2Connect
283 from ensemble.errors import ProviderInteractionError
284+from ensemble.tests.common import get_test_zookeeper_address
285
286
287 class EC2ConnectTest(EC2TestMixin, TestCase):
288@@ -154,7 +155,7 @@
289 self.mocker.replay()
290
291 zookeeper.set_debug_level(0)
292- yield connected_client.connect("localhost:2181")
293+ yield connected_client.connect(get_test_zookeeper_address())
294
295 client_result = []
296
297
298=== modified file 'ensemble/providers/ec2/tests/test_launch.py'
299--- ensemble/providers/ec2/tests/test_launch.py 2010-10-15 21:30:31 +0000
300+++ ensemble/providers/ec2/tests/test_launch.py 2010-11-08 23:03:47 +0000
301@@ -13,6 +13,7 @@
302 from ensemble.providers.common import (
303 DEFAULT_REPOSITORIES, DEFAULT_PACKAGES, BOOTSTRAP_PACKAGES)
304 from ensemble.state.auth import make_identity
305+from ensemble.tests.common import get_test_zookeeper_address
306
307 from ensemble.lib.testing import TestCase
308 from ensemble.lib.mocker import MATCH
309@@ -167,7 +168,7 @@
310 launch = EC2LaunchMachine(provider)
311 variables = launch.get_machine_variables(
312 machine_data={"machine-id": "machine-2",
313- "ensemble-zookeeper-hosts": "127.0.0.1:2181"})
314+ "ensemble-zookeeper-hosts": get_test_zookeeper_address()})
315 cmd = "cd /usr/lib/ensemble/ensemble && sudo /usr/bin/bzr switch %s"%(
316 custom_branch)
317 self.failUnlessIn(cmd, variables["scripts"])
318
319=== modified file 'ensemble/providers/tests/test_dummy.py'
320--- ensemble/providers/tests/test_dummy.py 2010-09-30 00:31:07 +0000
321+++ ensemble/providers/tests/test_dummy.py 2010-11-08 23:03:47 +0000
322@@ -113,8 +113,7 @@
323
324 def setUp(self):
325 directory = self.makeDir()
326- self.provider = MachineProvider(
327- "foo", {"storage-directory": directory})
328+ self.provider = MachineProvider("foo", {"storage-directory": directory})
329 self.storage = self.provider.get_file_storage()
330
331 def test_get_file_non_existant(self):
332
333=== modified file 'ensemble/state/tests/common.py'
334--- ensemble/state/tests/common.py 2010-10-21 12:12:26 +0000
335+++ ensemble/state/tests/common.py 2010-11-08 23:03:47 +0000
336@@ -10,6 +10,7 @@
337 from ensemble.formula.tests.test_directory import sample_directory
338 from ensemble.formula.directory import FormulaDirectory
339 from ensemble.state.topology import InternalTopology
340+from ensemble.tests.common import get_test_zookeeper_address
341
342
343 class StateTestBase(TestCase):
344@@ -18,7 +19,7 @@
345 def setUp(self):
346 zookeeper.set_debug_level(0)
347 self.formula = FormulaDirectory(sample_directory)
348- self.client = ZookeeperClient("localhost:2181")
349+ self.client = ZookeeperClient(get_test_zookeeper_address())
350
351 yield self.client.connect()
352 yield self.client.create("/formulas")
353@@ -32,7 +33,7 @@
354 # Close and reopen connection, so that watches set during
355 # testing are not affected by the cleaning up.
356 self.client.close()
357- client = ZookeeperClient("localhost:2181")
358+ client = ZookeeperClient(get_test_zookeeper_address())
359 yield client.connect()
360 deleteTree(handle=client.handle)
361 client.close()
362
363=== modified file 'ensemble/state/tests/test_initialize.py'
364--- ensemble/state/tests/test_initialize.py 2010-09-22 18:59:04 +0000
365+++ ensemble/state/tests/test_initialize.py 2010-11-08 23:03:47 +0000
366@@ -9,6 +9,7 @@
367 from ensemble.state.initialize import StateHierarchy
368 from ensemble.state.machine import MachineStateManager
369 from ensemble.lib.testing import TestCase
370+from ensemble.tests.common import get_test_zookeeper_address
371
372
373 class LayoutTest(TestCase):
374@@ -16,7 +17,7 @@
375 def setUp(self):
376 self.log = self.capture_logging("ensemble.state.init")
377 zookeeper.set_debug_level(0)
378- self.client = ZookeeperClient("localhost:2181")
379+ self.client = ZookeeperClient(get_test_zookeeper_address())
380 self.identity = make_identity("admin:genie")
381 self.layout = StateHierarchy(
382 self.client, self.identity, "i-abcdef")
383
384=== added file 'ensemble/tests/common.py'
385--- ensemble/tests/common.py 1970-01-01 00:00:00 +0000
386+++ ensemble/tests/common.py 2010-11-08 23:03:47 +0000
387@@ -0,0 +1,152 @@
388+import logging
389+import os
390+import os.path
391+import shutil
392+import subprocess
393+import sys
394+import tempfile
395+
396+from contextlib import contextmanager
397+from glob import glob
398+
399+
400+__all__ = ("ManagedZooKeeper",
401+ "zookeeper_test_context",
402+ "get_zookeeper_test_address")
403+
404+log = logging.getLogger("ensemble.tests.common")
405+
406+
407+class ManagedZooKeeper(object):
408+ """Class to manage the running of a ZooKeeper instance for testing.
409+
410+ Note: no attempt is made to probe the ZooKeeper instance is
411+ actually available, or that the selected port is free. In the
412+ future, we may want to do that, especially when run in a
413+ Hudson/Buildbot context, to ensure more test robustness."""
414+
415+ def __init__(self, install_path, port=28181):
416+ """Define the ZooKeeper test instance.
417+
418+ @param install_path: The path to the install for ZK
419+ @param port: The port to run the managed ZK instance
420+ """
421+
422+ self.install_path = install_path
423+ self.port = port
424+ self.host = "127.0.0.1"
425+
426+ def run(self):
427+ """Run the ZooKeeper instance under a temporary directory.
428+
429+ Writes ZK log messages to zookeeper.log in the current directory.
430+ """
431+
432+ self.working_path = tempfile.mkdtemp()
433+ log.info("Starting test zookeeper: installed=%s, port=%s",
434+ self.install_path, self.port)
435+
436+ config_path = os.path.join(self.working_path, "zoo.cfg")
437+ log_path = os.path.join(self.working_path, "log")
438+ log4j_path = os.path.join(self.working_path, "log4j.properties")
439+ data_path = os.path.join(self.working_path, "data")
440+
441+ # various setup steps
442+ os.mkdir(log_path)
443+
444+ with open(config_path, "w") as config:
445+ config.write("""
446+tickTime=2000
447+dataDir=%s
448+clientPort=%s
449+""" % (data_path, self.port))
450+
451+ with open(log4j_path, "w") as log4j:
452+ log4j.write("""
453+# DEFAULT: console appender only
454+log4j.rootLogger=INFO, ROLLINGFILE
455+log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
456+log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
457+log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
458+log4j.appender.ROLLINGFILE.Threshold=DEBUG
459+log4j.appender.ROLLINGFILE.File=zookeeper.log
460+""")
461+
462+ self.process = subprocess.Popen(
463+ args=["java",
464+ "-cp", self.classpath,
465+ "-Dzookeeper.log.dir=%s" % log_path,
466+ "-Dzookeeper.root.logger=INFO,CONSOLE",
467+ "-Dlog4j.configuration=file:%s" % log4j_path,
468+ # "-Dlog4j.debug",
469+ "org.apache.zookeeper.server.quorum.QuorumPeerMain",
470+ config_path],
471+ )
472+
473+ @property
474+ def classpath(self):
475+ """Get the classpath necessary to run ZooKeeper."""
476+
477+ # Two possibilities, as seen in zkEnv.sh:
478+ # Check for a release - top-level zookeeper-*.jar?
479+ jars = glob((os.path.join(
480+ self.install_path, 'zookeeper-*.jar')))
481+ if jars:
482+ # Relase build (`ant package`)
483+ jars.extend(glob(os.path.join(
484+ self.install_path,
485+ "lib/*.jar")))
486+ else:
487+ # Development build (plain `ant`)
488+ jars = glob((os.path.join(
489+ self.install_path, 'build/zookeeper-*.jar')))
490+ jars.extend(glob(os.path.join(
491+ self.install_path,
492+ "build/lib/*.jar")))
493+ return ":".join(jars)
494+
495+ @property
496+ def address(self):
497+ """Get the address of the ZooKeeper instance."""
498+ return "%s:%s" % (self.host, self.port)
499+
500+ def stop(self):
501+ """Stop the ZooKeeper instance and cleanup."""
502+ self.process.terminate()
503+ shutil.rmtree(self.working_path)
504+
505+
506+"""Global to manage the ZK test address - only for testing of course!"""
507+_zookeeper_address = "127.0.0.1:2181"
508+
509+
510+def get_test_zookeeper_address():
511+ """Get the current test ZK address, such as '127.0.0.1:2181'"""
512+ return _zookeeper_address
513+
514+
515+@contextmanager
516+def zookeeper_test_context(install_path, port=28181):
517+ """Manage context to run/stop a ZooKeeper for testing and related vars.
518+
519+ @param install_path: The path to the install for ZK
520+ @param port: The port to run the managed ZK instance
521+ """
522+ global _zookeeper_address
523+
524+ saved_zookeeper_address = _zookeeper_address
525+ saved_env = os.environ.get("ZOOKEEPER_ADDRESS")
526+ test_zookeeper = ManagedZooKeeper(install_path, port)
527+ test_zookeeper.run()
528+ os.environ["ZOOKEEPER_ADDRESS"] = _zookeeper_address = test_zookeeper.address
529+ try:
530+ yield test_zookeeper
531+ finally:
532+ test_zookeeper.stop()
533+ _zookeeper_address = saved_zookeeper_address
534+ if saved_env:
535+ os.environ["ZOOKEEPER_ADDRESS"] = saved_env
536+ else:
537+ del os.environ["ZOOKEEPER_ADDRESS"]
538+
539+
540
541=== modified file 'test'
542--- test 2010-07-08 22:47:52 +0000
543+++ test 2010-11-08 23:03:47 +0000
544@@ -1,12 +1,17 @@
545 #!/usr/bin/env python
546+import os
547 import sys
548-import os
549 from twisted.scripts.trial import run
550
551+from ensemble.tests.common import zookeeper_test_context
552
553 FUNCTIONAL = '--functional'
554
555 def main(args):
556+ if not os.environ["ZOOKEEPER_PATH"]:
557+ print "Environment variable ZOOKEEPER_PATH must be defined"
558+ exit()
559+
560 matched = [arg for arg in args if arg.startswith("ensemble")]
561
562 if FUNCTIONAL in sys.argv:
563@@ -20,8 +25,11 @@
564 packages.remove("ftests")
565 sys.argv.extend(
566 ["ensemble.%s"%p for p in packages])
567- run()
568+
569+ with zookeeper_test_context(
570+ os.environ["ZOOKEEPER_PATH"],
571+ os.environ.get("ZOOKEEPER_TEST_PORT", 28181)):
572+ run()
573
574 if __name__ == "__main__":
575 main(sys.argv[1:])
576-

Subscribers

People subscribed via source and target branches

to status/vote changes: