Merge lp:~fwereade/pyjuju/cobbler-zk-connect into lp:pyjuju
- cobbler-zk-connect
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jim Baker | ||||
Approved revision: | 331 | ||||
Merged at revision: | 331 | ||||
Proposed branch: | lp:~fwereade/pyjuju/cobbler-zk-connect | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~fwereade/pyjuju/provider-base | ||||
Diff against target: |
1884 lines (+551/-353) 50 files modified
ensemble/agents/provision.py (+4/-4) ensemble/agents/tests/test_provision.py (+3/-3) ensemble/agents/tests/test_unit.py (+4/-4) ensemble/control/status.py (+3/-3) ensemble/control/tests/test_status.py (+0/-2) ensemble/errors.py (+7/-19) ensemble/formula/bundle.py (+3/-3) ensemble/formula/provider.py (+1/-1) ensemble/formula/tests/test_bundle.py (+8/-4) ensemble/formula/tests/test_config.py (+6/-4) ensemble/formula/tests/test_repository.py (+4/-4) ensemble/hooks/tests/test_arguments.py (+2/-2) ensemble/hooks/tests/test_cli.py (+5/-2) ensemble/hooks/tests/test_executor.py (+1/-1) ensemble/hooks/tests/test_invoker.py (+1/-1) ensemble/lib/tests/test_schema.py (+1/-1) ensemble/machine/__init__.py (+1/-3) ensemble/machine/tests/test_unit_deployment.py (+6/-4) ensemble/machine/unit.py (+1/-1) ensemble/providers/common/base.py (+25/-21) ensemble/providers/common/connect.py (+35/-26) ensemble/providers/common/findzookeepers.py (+1/-1) ensemble/providers/common/tests/test_connect.py (+133/-0) ensemble/providers/common/tests/test_findzookeepers.py (+27/-2) ensemble/providers/common/tests/test_utils.py (+2/-5) ensemble/providers/common/utils.py (+3/-1) ensemble/providers/ec2/__init__.py (+7/-25) ensemble/providers/ec2/launch.py (+2/-3) ensemble/providers/ec2/machine.py (+1/-6) ensemble/providers/ec2/securitygroup.py (+6/-7) ensemble/providers/ec2/tests/common.py (+0/-10) ensemble/providers/ec2/tests/test_connect.py (+48/-90) ensemble/providers/ec2/tests/test_getmachines.py (+2/-3) ensemble/providers/ec2/tests/test_launch.py (+3/-4) ensemble/providers/ec2/tests/test_machine.py (+7/-26) ensemble/providers/ec2/tests/test_provider.py (+4/-1) ensemble/providers/ec2/tests/test_securitygroup.py (+6/-6) ensemble/providers/orchestra/__init__.py (+4/-4) ensemble/providers/orchestra/cobbler.py (+5/-5) ensemble/providers/orchestra/machine.py (+4/-0) ensemble/providers/orchestra/tests/test_cobbler.py (+14/-9) ensemble/providers/orchestra/tests/test_connect.py (+111/-0) ensemble/providers/orchestra/tests/test_launch.py (+14/-6) ensemble/providers/orchestra/tests/test_provider.py (+4/-2) ensemble/state/service.py (+2/-1) ensemble/state/sshclient.py (+2/-0) ensemble/state/tests/test_security.py (+1/-1) ensemble/state/tests/test_service.py (+9/-5) ensemble/tests/test_errors.py (+6/-16) ensemble/unit/tests/test_formula.py (+2/-1) |
||||
To merge this branch: | bzr merge lp:~fwereade/pyjuju/cobbler-zk-connect | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jim Baker (community) | Approve | ||
Gustavo Niemeyer | Approve | ||
Review via email: mp+71734@code.launchpad.net |
Commit message
Description of the change
This branch moves providers.
- 328. By William Reade
-
unused import
- 329. By William Reade
-
merge parent
- 330. By William Reade
-
ProviderMachine no longer has a .launch_time
- 331. By William Reade
-
merge trunk
Jim Baker (jimbaker) wrote : | # |
+1, looks great once these minor problems are fixed in addition to what Gustavo mentioned.
[1]
+ def connect(self, share=False):
+ """Connect to the zookeeper ensemble running in the machine provider.
+
+ @param share: Requests sharing of the connection with other clients
+ attempting to connect to the same provider, if that's feasible.
+
+ returns an open C{txzookeeper.
+ C{ensemble.
+ """
+ return ZookeeperConnec
+
Please write new docstrings using RST format.
[2]
def run(self, share=False):
- client_deferred = self._provider.
- client_
- client_
- return client_deferred
+ d = self._provider.
+ d.addCallback(
+ d.addCallback(
+ d.addCallback(
+ return d
It's not difficult to follow the chaining here, but I think it's much easier when written using inlineCallbacks. Also, no docstring here.
[3]
+ def get_zookeeper_
+ return succeed([
+ ProviderMachine
+ self._second_
Docstring
[4]
+ # Give it some time to do it incorrectly.
+ yield self.sleep(0.1)
Is this sleep necessary, vs roundtripping with a poke?
[5]
def machine_
- launch_time = None
- if instance.
- launch_time = datetime.
- instance.
return EC2ProviderMachine(
- instance.
- launch_time)
+ instance.
Docstring
[6]
- # XXX Replace the logic above with self.sleep(0.1) when it's merged.
+ yield self.sleep(0.1)
Again, another necessary sleep?
[7]
There are a number of new tests. None of them have docstrings.
William Reade (fwereade) wrote : | # |
> [4]
>
> + # Give it some time to do it incorrectly.
> + yield self.sleep(0.1)
>
> Is this sleep necessary, vs roundtripping with a poke?
> [6]
>
> - # XXX Replace the logic above with self.sleep(0.1) when it's merged.
> + yield self.sleep(0.1)
>
> Again, another necessary sleep?
I don't understand your proposed solution: if we yield d, we'll block forever, but if we don't yield d and don't sleep we won't be able to confirm that d actually does wait for /initialized's existence. Am I missing something?
- 332. By William Reade
-
fixes per jimbaker's review; mostly dosctrings
- 333. By William Reade
-
fix thinko
William Reade (fwereade) wrote : | # |
Just waiting on clarification for jimbaker's [4], [6], and on https:/
William Reade (fwereade) wrote : | # |
> Just waiting on clarification for jimbaker's [4], [6]
Explained on IRC: .poke_zk() is deterministic, and faster, and does exactly what we need.
- 334. By William Reade
-
remaining fixes per review
- 335. By William Reade
-
merge trunk
- 336. By William Reade
-
change dosctrings per review
- 337. By William Reade
-
merge trunk
- 338. By William Reade
-
merge trunk
- 339. By William Reade
-
merge lp:~fwereade/ensemble/cobbler-zk-connect-error-messages (error messages are more rigorously tested, and in some cases better) [f=831883]
[r=niemeyer, jimbaker]
Preview Diff
1 | === modified file 'ensemble/agents/provision.py' |
2 | --- ensemble/agents/provision.py 2011-08-12 19:28:37 +0000 |
3 | +++ ensemble/agents/provision.py 2011-08-26 13:16:39 +0000 |
4 | @@ -4,7 +4,7 @@ |
5 | from zookeeper import NoNodeException |
6 | |
7 | from ensemble.environment.config import EnvironmentsConfig |
8 | -from ensemble.errors import MachinesNotFound, ProviderInteractionError |
9 | +from ensemble.errors import MachinesNotFound, ProviderError |
10 | from ensemble.lib.twistutils import concurrent_execution_guard |
11 | from ensemble.state.errors import ( |
12 | MachineStateNotFound, ServiceStateNotFound, ServiceUnitStateNotFound, |
13 | @@ -178,7 +178,7 @@ |
14 | # map of instance_id -> machine |
15 | try: |
16 | provider_machines = yield self.provider.get_machines() |
17 | - except ProviderInteractionError: |
18 | + except ProviderError: |
19 | log.exception("Cannot get machine list") |
20 | return |
21 | |
22 | @@ -192,7 +192,7 @@ |
23 | machine_state_id, provider_machines) |
24 | except (StateChanged, |
25 | MachineStateNotFound, |
26 | - ProviderInteractionError): |
27 | + ProviderError): |
28 | log.exception("Cannot process machine %s", machine_state_id) |
29 | continue |
30 | instance_ids.append(instance_id) |
31 | @@ -204,7 +204,7 @@ |
32 | machine = provider_machines[instance_id] |
33 | try: |
34 | yield self.provider.shutdown_machine(machine) |
35 | - except ProviderInteractionError: |
36 | + except ProviderError: |
37 | log.exception("Cannot shutdown machine %s", instance_id) |
38 | continue |
39 | |
40 | |
41 | === modified file 'ensemble/agents/tests/test_provision.py' |
42 | --- ensemble/agents/tests/test_provision.py 2011-08-11 05:59:30 +0000 |
43 | +++ ensemble/agents/tests/test_provision.py 2011-08-26 13:16:39 +0000 |
44 | @@ -313,7 +313,7 @@ |
45 | |
46 | mock_provider = self.mocker.patch(self.agent.provider) |
47 | mock_provider.start_machine({"machine-id": 0}) |
48 | - self.mocker.result(fail(ProviderInteractionError(OSError("Bad")))) |
49 | + self.mocker.result(fail(ProviderInteractionError())) |
50 | |
51 | mock_provider.start_machine({"machine-id": 1}) |
52 | self.mocker.passthrough() |
53 | @@ -339,7 +339,7 @@ |
54 | mock_provider = self.mocker.patch(self.agent.provider) |
55 | |
56 | mock_provider.shutdown_machine(MATCH_MACHINE) |
57 | - self.mocker.result(fail(ProviderInteractionError("Bad"))) |
58 | + self.mocker.result(fail(ProviderInteractionError())) |
59 | |
60 | mock_provider.shutdown_machine(MATCH_MACHINE) |
61 | self.mocker.passthrough() |
62 | @@ -368,7 +368,7 @@ |
63 | |
64 | mock_provider = self.mocker.patch(self.agent.provider) |
65 | mock_provider.get_machines() |
66 | - self.mocker.result(fail(ProviderInteractionError("Bad"))) |
67 | + self.mocker.result(fail(ProviderInteractionError())) |
68 | |
69 | mock_provider.get_machines() |
70 | self.mocker.passthrough() |
71 | |
72 | === modified file 'ensemble/agents/tests/test_unit.py' |
73 | --- ensemble/agents/tests/test_unit.py 2011-07-29 14:42:04 +0000 |
74 | +++ ensemble/agents/tests/test_unit.py 2011-08-26 13:16:39 +0000 |
75 | @@ -118,10 +118,10 @@ |
76 | e = self.assertRaises(EnsembleError, |
77 | self.agent.configure, |
78 | options) |
79 | - self.assertIn( |
80 | - ("--unit-name must be provided in the command line," |
81 | - " or $ENSEMBLE_UNIT_NAME in the environment"), |
82 | - str(e)) |
83 | + self.assertEquals( |
84 | + str(e), |
85 | + "--unit-name must be provided in the command line, or " |
86 | + "$ENSEMBLE_UNIT_NAME in the environment") |
87 | |
88 | def test_agent_unit_name_cli_extraction(self): |
89 | """The unit agent can parse its unit-name from the cli. |
90 | |
91 | === modified file 'ensemble/control/status.py' |
92 | --- ensemble/control/status.py 2011-07-20 23:35:48 +0000 |
93 | +++ ensemble/control/status.py 2011-08-26 13:16:39 +0000 |
94 | @@ -7,7 +7,7 @@ |
95 | from twisted.internet.defer import inlineCallbacks, returnValue |
96 | import yaml |
97 | |
98 | -from ensemble.errors import ProviderInteractionError |
99 | +from ensemble.errors import ProviderError |
100 | from ensemble.environment.errors import EnvironmentsConfigError |
101 | from ensemble.state.errors import UnitRelationStateNotFound |
102 | from ensemble.state.formula import FormulaStateManager |
103 | @@ -264,10 +264,10 @@ |
104 | try: |
105 | pm = yield machine_provider.get_machine(instance_id) |
106 | m["dns-name"] = pm.dns_name |
107 | - except ProviderInteractionError: |
108 | + except ProviderError: |
109 | # The provider doesn't have machine information |
110 | log.error("Machine provider information missing: machine %s" % ( |
111 | - machine_state.id)) |
112 | + machine_state.id)) |
113 | |
114 | machine_data[machine_state.id] = m |
115 | |
116 | |
117 | === modified file 'ensemble/control/tests/test_status.py' |
118 | --- ensemble/control/tests/test_status.py 2011-08-08 21:51:54 +0000 |
119 | +++ ensemble/control/tests/test_status.py 2011-08-26 13:16:39 +0000 |
120 | @@ -458,7 +458,6 @@ |
121 | self.assertEqual(state["machines"][7]["instance-id"], |
122 | "pending") |
123 | |
124 | - |
125 | @inlineCallbacks |
126 | def test_render_yaml(self): |
127 | yield self.build_topology() |
128 | @@ -610,7 +609,6 @@ |
129 | |
130 | self.assertIn("namespace:dummy-1", result) |
131 | |
132 | - |
133 | def test_render_dot_bad_clustering(self): |
134 | """Test around Bug #792448. |
135 | |
136 | |
137 | === modified file 'ensemble/errors.py' |
138 | --- ensemble/errors.py 2011-08-17 17:26:19 +0000 |
139 | +++ ensemble/errors.py 2011-08-26 13:16:39 +0000 |
140 | @@ -48,7 +48,7 @@ |
141 | self.path = path |
142 | |
143 | def __str__(self): |
144 | - return "File was not found: %s" % (self.path,) |
145 | + return "File was not found: %r" % (self.path,) |
146 | |
147 | |
148 | class FormulaError(EnsembleError): |
149 | @@ -59,7 +59,7 @@ |
150 | self.message = message |
151 | |
152 | def __str__(self): |
153 | - return "Error processing '%s': %s." % (self.path, self.message) |
154 | + return "Error processing %r: %s" % (self.path, self.message) |
155 | |
156 | |
157 | class FormulaInvocationError(EnsembleError): |
158 | @@ -70,8 +70,8 @@ |
159 | self.exit_code = exit_code |
160 | |
161 | def __str__(self): |
162 | - return "Error processing '%s': exit code %s." % (self.path, |
163 | - self.exit_code) |
164 | + return "Error processing %r: exit code %s." % ( |
165 | + self.path, self.exit_code) |
166 | |
167 | |
168 | class FileAlreadyExists(EnsembleError): |
169 | @@ -84,7 +84,7 @@ |
170 | self.path = path |
171 | |
172 | def __str__(self): |
173 | - return "File already exists, won't overwrite: %s" % (self.path,) |
174 | + return "File already exists, won't overwrite: %r" % (self.path,) |
175 | |
176 | |
177 | class InvalidEnsembleHeaderValue(EnsembleError): |
178 | @@ -101,7 +101,7 @@ |
179 | |
180 | def __str__(self): |
181 | return ("Expected a YAML file with an 'ensemble: %s' " |
182 | - "header, found 'ensemble: %s': %s" |
183 | + "header, found 'ensemble: %s': %r" |
184 | % (self.expected, self.found, self.path)) |
185 | |
186 | |
187 | @@ -130,12 +130,6 @@ |
188 | class EnvironmentPending(NoConnection): |
189 | """Raised when the ensemble environment is not accessible.""" |
190 | |
191 | - def __init__(self, info="no details available"): |
192 | - self._info = info |
193 | - |
194 | - def __str__(self): |
195 | - return "Ensemble environment is not accessible: %s" % self._info |
196 | - |
197 | |
198 | class ProviderError(EnsembleError): |
199 | """Raised when an exception occurs in a provider.""" |
200 | @@ -154,13 +148,7 @@ |
201 | |
202 | |
203 | class ProviderInteractionError(ProviderError): |
204 | - |
205 | - def __init__(self, error): |
206 | - self.error = error |
207 | - |
208 | - def __str__(self): |
209 | - return "ProviderError: Interaction with machine provider failed: %r" \ |
210 | - % self.error |
211 | + """Raised when an unexpected error occurs interacting with a provider""" |
212 | |
213 | |
214 | class CannotTerminateMachine(EnsembleError): |
215 | |
216 | === modified file 'ensemble/formula/bundle.py' |
217 | --- ensemble/formula/bundle.py 2011-06-05 20:19:18 +0000 |
218 | +++ ensemble/formula/bundle.py 2011-08-26 13:16:39 +0000 |
219 | @@ -18,13 +18,13 @@ |
220 | try: |
221 | zf = ZipFile(path, 'r') |
222 | except BadZipfile, exc: |
223 | - raise FormulaError(path, "Must be a ZIP-file (%s)." % str(exc)) |
224 | + raise FormulaError(path, "must be a zip file (%s)" % exc) |
225 | |
226 | metadata = MetaData() |
227 | if "metadata.yaml" not in zf.namelist(): |
228 | raise FormulaError(path, |
229 | - "Archive does not contain required file: " |
230 | - "\"metadata.yaml\".") |
231 | + "archive does not contain required file " |
232 | + "\"metadata.yaml\"") |
233 | |
234 | content = zf.read("metadata.yaml") |
235 | metadata.parse(content) |
236 | |
237 | === modified file 'ensemble/formula/provider.py' |
238 | --- ensemble/formula/provider.py 2010-11-02 16:15:23 +0000 |
239 | +++ ensemble/formula/provider.py 2011-08-26 13:16:39 +0000 |
240 | @@ -25,5 +25,5 @@ |
241 | from .directory import FormulaDirectory |
242 | return FormulaDirectory(specification) |
243 | |
244 | - raise FormulaError(specification, "Unable to process %s into a formula" % ( |
245 | + raise FormulaError(specification, "unable to process %s into a formula" % ( |
246 | specification)) |
247 | |
248 | === modified file 'ensemble/formula/tests/test_bundle.py' |
249 | --- ensemble/formula/tests/test_bundle.py 2011-05-06 04:45:17 +0000 |
250 | +++ ensemble/formula/tests/test_bundle.py 2011-08-26 13:16:39 +0000 |
251 | @@ -38,8 +38,10 @@ |
252 | try: |
253 | FormulaBundle(filename) |
254 | except FormulaError, exc: |
255 | - self.assertIn(filename, str(exc)) |
256 | - self.assertIn("zip", str(exc).lower()) |
257 | + self.assertEquals( |
258 | + str(exc), |
259 | + ("Error processing %r: " % filename) + |
260 | + "must be a zip file (File is not a zip file)") |
261 | return |
262 | |
263 | self.fail("Expected formula error.") |
264 | @@ -53,8 +55,10 @@ |
265 | try: |
266 | FormulaBundle(filename) |
267 | except FormulaError, exc: |
268 | - self.assertIn(filename, str(exc)) |
269 | - self.assertIn("metadata.yaml", str(exc).lower()) |
270 | + self.assertEquals( |
271 | + str(exc), |
272 | + ("Error processing %r: " % filename) + |
273 | + "archive does not contain required file \"metadata.yaml\"") |
274 | return |
275 | |
276 | self.fail("Expected formula error.") |
277 | |
278 | === modified file 'ensemble/formula/tests/test_config.py' |
279 | --- ensemble/formula/tests/test_config.py 2011-08-24 00:18:27 +0000 |
280 | +++ ensemble/formula/tests/test_config.py 2011-08-26 13:16:39 +0000 |
281 | @@ -102,7 +102,7 @@ |
282 | |
283 | error = self.assertRaises(ServiceConfigError, |
284 | self.config.validate, dict(username="1234")) |
285 | - self.assertIn(str(error), "Invalid value for username: 1234") |
286 | + self.assertEquals(str(error), "Invalid value for username: 1234") |
287 | |
288 | data = self.config.validate(dict(username="12345678")) |
289 | self.assertEqual(data, {"username": "12345678", "title": "My Title"}) |
290 | @@ -114,15 +114,17 @@ |
291 | error = self.assertRaises(ServiceConfigError, |
292 | self.config.parse, |
293 | local_configuration) |
294 | - self.assertIn("options.fails.validator: required value not found", |
295 | - str(error)) |
296 | + self.assertEquals( |
297 | + str(error), |
298 | + "Invalid options specification: options.fails.validator: " |
299 | + "required value not found") |
300 | |
301 | def test_validate_types(self): |
302 | self.config.parse(sample_configuration) |
303 | |
304 | error = self.assertRaises(ServiceConfigError, |
305 | self.config.validate, {"skill-level": "NaN"}) |
306 | - self.assertIn(str(error), "Invalid value for skill-level: NaN") |
307 | + self.assertEquals(str(error), "Invalid value for skill-level: NaN") |
308 | |
309 | data = self.config.validate({"skill-level": "9001"}) |
310 | # its over 9000! |
311 | |
312 | === modified file 'ensemble/formula/tests/test_repository.py' |
313 | --- ensemble/formula/tests/test_repository.py 2011-07-15 12:41:01 +0000 |
314 | +++ ensemble/formula/tests/test_repository.py 2011-08-26 13:16:39 +0000 |
315 | @@ -85,11 +85,11 @@ |
316 | self.assertTrue(formula.metadata) |
317 | |
318 | def test_find_formula_by_name_fails(self): |
319 | - exc = self.assertRaises( |
320 | + error = self.assertRaises( |
321 | FormulaNotFound, self.repository1.find, "missing") |
322 | - self.assertIn("missing", str(exc)) |
323 | - self.assertIn(self.unbundled_repo_path, str(exc), |
324 | - "Repository path not found in the error message.") |
325 | + self.assertEquals(str(error), |
326 | + "Formula 'missing' not found in repository %s" |
327 | + % self.unbundled_repo_path) |
328 | |
329 | def test_find_formula_with_multiple_versions_returns_latest(self): |
330 | # Copy the repository out of the codebase, so that we can hack it. |
331 | |
332 | === modified file 'ensemble/hooks/tests/test_arguments.py' |
333 | --- ensemble/hooks/tests/test_arguments.py 2011-06-11 02:37:35 +0000 |
334 | +++ ensemble/hooks/tests/test_arguments.py 2011-08-26 13:16:39 +0000 |
335 | @@ -52,8 +52,8 @@ |
336 | err = self.capture_stream("stderr") |
337 | os.environ.pop("ENSEMBLE_AGENT_SOCKET", None) |
338 | error = self.failUnlessRaises(SystemExit, cli.parse_args) |
339 | - self.assertEquals("No ENSEMBLE_AGENT_SOCKET/-s option found", |
340 | - str(error)) |
341 | + self.assertEquals(str(error), |
342 | + "No ENSEMBLE_AGENT_SOCKET/-s option found") |
343 | self.assertIn("No ENSEMBLE_AGENT_SOCKET/-s option found", |
344 | err.getvalue()) |
345 | |
346 | |
347 | === modified file 'ensemble/hooks/tests/test_cli.py' |
348 | --- ensemble/hooks/tests/test_cli.py 2011-06-29 09:02:33 +0000 |
349 | +++ ensemble/hooks/tests/test_cli.py 2011-08-26 13:16:39 +0000 |
350 | @@ -335,12 +335,15 @@ |
351 | # and check an error condition |
352 | options = ["content=@missing"] |
353 | error = self.assertRaises(EnsembleError, parse_keyvalue_pairs, options) |
354 | - self.assertIn("No such file", str(error)) |
355 | + self.assertEquals( |
356 | + str(error), |
357 | + "No such file or directory: missing (argument:content)") |
358 | |
359 | # and check when fed non-kvpairs the error makes sense |
360 | options = ["foobar"] |
361 | error = self.assertRaises(EnsembleError, parse_keyvalue_pairs, options) |
362 | - self.assertIn("Expected `option=value`. Found `foobar`", str(error)) |
363 | + self.assertEquals( |
364 | + str(error), "Expected `option=value`. Found `foobar`") |
365 | |
366 | def test_parse_port_protocol(self): |
367 | self.assertEqual(parse_port_protocol("80"), (80, "tcp")) |
368 | |
369 | === modified file 'ensemble/hooks/tests/test_executor.py' |
370 | --- ensemble/hooks/tests/test_executor.py 2011-07-06 18:00:07 +0000 |
371 | +++ ensemble/hooks/tests/test_executor.py 2011-08-26 13:16:39 +0000 |
372 | @@ -326,7 +326,7 @@ |
373 | error = yield self.assertFailure( |
374 | self._executor.run_priority_hook(invoke, "foobar"), |
375 | AssertionError) |
376 | - self.assertIn("Executor must not be running", str(error)) |
377 | + self.assertEquals(str(error), "Executor must not be running") |
378 | |
379 | @inlineCallbacks |
380 | def test_prioritize_with_queued(self): |
381 | |
382 | === modified file 'ensemble/hooks/tests/test_invoker.py' |
383 | --- ensemble/hooks/tests/test_invoker.py 2011-08-16 17:26:09 +0000 |
384 | +++ ensemble/hooks/tests/test_invoker.py 2011-08-26 13:16:39 +0000 |
385 | @@ -480,7 +480,7 @@ |
386 | error = self.failUnlessRaises(errors.FormulaError, exe, hook) |
387 | |
388 | self.assertEqual(error.path, hook) |
389 | - self.assertIn(error.message, "hook is not executable") |
390 | + self.assertEqual(error.message, "hook is not executable") |
391 | |
392 | @defer.inlineCallbacks |
393 | def test_spawn_success(self): |
394 | |
395 | === modified file 'ensemble/lib/tests/test_schema.py' |
396 | --- ensemble/lib/tests/test_schema.py 2011-07-27 08:54:46 +0000 |
397 | +++ ensemble/lib/tests/test_schema.py 2011-08-26 13:16:39 +0000 |
398 | @@ -183,7 +183,7 @@ |
399 | exp = "([a-" |
400 | error = self.assertRaises( |
401 | SchemaError, Regex().coerce, exp, PATH) |
402 | - self.assertIn("expected regex", str(error)) |
403 | + self.assertEquals(str(error), "<path>: expected regex, got '([a-'") |
404 | |
405 | |
406 | class ListTest(TestCase): |
407 | |
408 | === modified file 'ensemble/machine/__init__.py' |
409 | --- ensemble/machine/__init__.py 2011-08-08 17:37:46 +0000 |
410 | +++ ensemble/machine/__init__.py 2011-08-26 13:16:39 +0000 |
411 | @@ -10,10 +10,8 @@ |
412 | :class:`MachineProvider` api. |
413 | """ |
414 | |
415 | - def __init__(self, instance_id, dns_name=None, |
416 | - private_dns_name=None, launch_time=None): |
417 | + def __init__(self, instance_id, dns_name=None, private_dns_name=None): |
418 | self.instance_id = instance_id |
419 | # ideally this would be ip_address, but txaws doesn't expose it. |
420 | self.dns_name = dns_name |
421 | self.private_dns_name = private_dns_name |
422 | - self.launch_time = launch_time |
423 | |
424 | === modified file 'ensemble/machine/tests/test_unit_deployment.py' |
425 | --- ensemble/machine/tests/test_unit_deployment.py 2011-02-15 15:15:16 +0000 |
426 | +++ ensemble/machine/tests/test_unit_deployment.py 2011-08-26 13:16:39 +0000 |
427 | @@ -115,8 +115,8 @@ |
428 | |
429 | self.failUnlessFailure(d, UnitDeploymentError) |
430 | |
431 | - def validate_result(result): |
432 | - self.assertIn("No module named magichat", str(result)) |
433 | + def validate_result(error): |
434 | + self.assertIn("No module named magichat", str(error)) |
435 | |
436 | d.addCallback(validate_result) |
437 | return d |
438 | @@ -170,7 +170,7 @@ |
439 | error = self.assertRaises( |
440 | UnitDeploymentError, |
441 | self.deployment.start, "0", get_test_zookeeper_address()) |
442 | - self.assertIn("Formula must be unpacked first.", str(error)) |
443 | + self.assertEquals(str(error), "Formula must be unpacked first.") |
444 | |
445 | @inlineCallbacks |
446 | def test_service_unit_destroy(self): |
447 | @@ -269,7 +269,9 @@ |
448 | error = self.assertRaises( |
449 | UnitDeploymentError, |
450 | self.deployment.unpack_formula, self.formula) |
451 | - self.assertIn("Invalid formula for deployment:", str(error)) |
452 | + self.assertEquals( |
453 | + str(error), |
454 | + "Invalid formula for deployment: %s" % self.formula.path) |
455 | |
456 | def test_is_running_no_pid_file(self): |
457 | """ |
458 | |
459 | === modified file 'ensemble/machine/unit.py' |
460 | --- ensemble/machine/unit.py 2011-02-15 15:15:16 +0000 |
461 | +++ ensemble/machine/unit.py 2011-08-26 13:16:39 +0000 |
462 | @@ -123,6 +123,6 @@ |
463 | """Unpack a formula to the service units directory.""" |
464 | if not isinstance(formula, FormulaBundle): |
465 | raise UnitDeploymentError( |
466 | - "Invalid formula for deployment: %r" % formula) |
467 | + "Invalid formula for deployment: %s" % formula.path) |
468 | |
469 | formula.extract_to(os.path.join(self.directory, "formula")) |
470 | |
471 | === modified file 'ensemble/providers/common/base.py' |
472 | --- ensemble/providers/common/base.py 2011-08-23 18:47:42 +0000 |
473 | +++ ensemble/providers/common/base.py 2011-08-26 13:16:39 +0000 |
474 | @@ -4,7 +4,9 @@ |
475 | from twisted.internet.defer import inlineCallbacks, returnValue |
476 | |
477 | from ensemble.environment.errors import EnvironmentsConfigError |
478 | + |
479 | from .bootstrap import Bootstrap |
480 | +from .connect import ZookeeperConnect |
481 | from .findzookeepers import find_zookeepers |
482 | from .state import SaveState, LoadState |
483 | from .utils import get_user_authorized_keys |
484 | @@ -16,7 +18,6 @@ |
485 | To write a working subclass, you will need to override the following |
486 | methods: |
487 | |
488 | - * connect |
489 | * get_file_storage |
490 | * start_machine |
491 | * get_machines |
492 | @@ -57,19 +58,8 @@ |
493 | # Subclasses need to implement their own versions of everything |
494 | # in the following block |
495 | |
496 | - def connect(self, share=False): |
497 | - """Connect to the zookeeper ensemble running in the machine provider. |
498 | - |
499 | - @param share: Requests sharing of the connection with other clients |
500 | - attempting to connect to the same provider, if that's feasible. |
501 | - |
502 | - returns an open C{txzookeeper.client.ZookeeperClient} and a |
503 | - C{ensemble.storage.connection.TunnelProtocol} |
504 | - """ |
505 | - raise NotImplementedError() |
506 | - |
507 | def get_file_storage(self): |
508 | - """Retrieve the provider C{FileStorage} abstraction.""" |
509 | + """Retrieve the provider FileStorage abstraction.""" |
510 | raise NotImplementedError() |
511 | |
512 | def start_machine(self, machine_data, master=False): |
513 | @@ -123,10 +113,23 @@ |
514 | |
515 | @return: the first valid instance found as a single element list. |
516 | |
517 | - @raise: EnvironmentNotFound |
518 | + @raise: `EnvironmentNotFound` |
519 | """ |
520 | return find_zookeepers(self) |
521 | |
522 | + def connect(self, share=False): |
523 | + """Attempt to connect to a running zookeeper node. |
524 | + |
525 | + @param share: where feasible, attempt to share a connection with other |
526 | + clients |
527 | + |
528 | + @return: an open `txzookeeper.client.ZookeeperClient` |
529 | + |
530 | + @raise: `EnvironmentNotFound` when no zookeepers exist |
531 | + @raise: `EnvironmentPending` when zookeepers exist but connect fails |
532 | + """ |
533 | + return ZookeeperConnect(self).run(share=share) |
534 | + |
535 | def bootstrap(self): |
536 | """Bootstrap an ensemble server in the provider.""" |
537 | return Bootstrap(self).run() |
538 | @@ -134,9 +137,11 @@ |
539 | def get_machine(self, instance_id): |
540 | """Retrieve a provider machine by instance id. |
541 | |
542 | - @param instance_id: instance_id of the Machine you want to get. |
543 | - |
544 | - @raise: MachinesNotFound |
545 | + @param instance_id: instance_id of the `ProviderMachine` you want. |
546 | + |
547 | + @return: a single `ProviderMachine` |
548 | + |
549 | + @raise: `MachinesNotFound` |
550 | """ |
551 | d = self.get_machines([instance_id]) |
552 | d.addCallback(itemgetter(0)) |
553 | @@ -145,7 +150,7 @@ |
554 | def shutdown_machine(self, machine): |
555 | """Terminate one machine associated with this provider. |
556 | |
557 | - @param machine: machine to shut down. |
558 | + @param machine: ProviderMachine to shut down. |
559 | |
560 | @raise: MachinesNotFound |
561 | """ |
562 | @@ -166,14 +171,13 @@ |
563 | def save_state(self, state): |
564 | """Save state to the provider. |
565 | |
566 | - @param state |
567 | - @type dict |
568 | + @param state: a dictionary to persist |
569 | """ |
570 | return SaveState(self).run(state) |
571 | |
572 | def load_state(self): |
573 | """Load state from the provider. |
574 | |
575 | - @return: a dictionary. |
576 | + @return: a previously-persisted dictionary, or False |
577 | """ |
578 | return LoadState(self).run() |
579 | |
580 | === renamed file 'ensemble/providers/ec2/connect.py' => 'ensemble/providers/common/connect.py' |
581 | --- ensemble/providers/ec2/connect.py 2011-08-17 08:58:51 +0000 |
582 | +++ ensemble/providers/common/connect.py 2011-08-26 13:16:39 +0000 |
583 | @@ -1,45 +1,55 @@ |
584 | -import datetime |
585 | -from datetime import timedelta |
586 | - |
587 | from twisted.internet.defer import inlineCallbacks, returnValue |
588 | |
589 | +from txzookeeper.client import ConnectionTimeoutException |
590 | + |
591 | from ensemble.errors import EnvironmentPending, NoConnection |
592 | from ensemble.state.sshclient import SSHClient |
593 | |
594 | from .utils import log |
595 | |
596 | |
597 | -class EC2Connect(object): |
598 | +class ZookeeperConnect(object): |
599 | |
600 | def __init__(self, provider): |
601 | self._provider = provider |
602 | |
603 | + @inlineCallbacks |
604 | def run(self, share=False): |
605 | - client_deferred = self._provider.get_zookeeper_machines() |
606 | - client_deferred.addCallback(self._connect_to_machine, share) |
607 | - client_deferred.addCallback(self._wait_for_initialization) |
608 | - return client_deferred |
609 | - |
610 | - def _connect_to_machine(self, machines, share): |
611 | + """Attempt to connect to a running zookeeper node. |
612 | + |
613 | + @param share: where feasible, attempt to share a connection with other |
614 | + clients |
615 | + |
616 | + @return: an open txzookeeper.client.ZookeeperClient |
617 | + |
618 | + @raise: EnvironmentNotFound when no zookeepers exist |
619 | + @raise: EnvironmentPending when zookeepers exist but connect fails |
620 | + """ |
621 | + candidates = yield self._provider.get_zookeeper_machines() |
622 | + chosen = yield self._pick_machine(candidates) |
623 | + client = yield self._connect_to_machine(chosen, share) |
624 | + yield self._wait_for_initialization(client) |
625 | + returnValue(client) |
626 | + |
627 | + def _pick_machine(self, machines): |
628 | # TODO Should we pick a random entry from the nodes list? |
629 | + for machine in machines: |
630 | + if machine.dns_name: |
631 | + return machine |
632 | + raise EnvironmentPending("No machines have addresses assigned yet") |
633 | + |
634 | + def _connect_to_machine(self, machine, share): |
635 | log.info("Connecting to environment.") |
636 | - machine = machines[0] |
637 | - if not machine.dns_name: |
638 | - raise EnvironmentPending("Machine %s has no address assigned yet" |
639 | - % machine.instance_id) |
640 | - client = SSHClient() |
641 | - result = client.connect(machine.dns_name + ":2181", |
642 | - timeout=30, share=share) |
643 | - result.addErrback(self._check_machine_age, machine) |
644 | + result = SSHClient().connect( |
645 | + machine.dns_name + ":2181", timeout=30, share=share) |
646 | + result.addErrback(self._cannot_connect, machine) |
647 | return result |
648 | |
649 | - def _check_machine_age(self, failure, machine): |
650 | - failure.trap(NoConnection) |
651 | - five_minutes_ago = datetime.datetime.now() - timedelta(minutes=5) |
652 | - if machine.launch_time > five_minutes_ago: |
653 | - raise NoConnection("Can't yet connect to started machine: %s" % |
654 | - str(failure.value)) |
655 | - return failure |
656 | + def _cannot_connect(self, failure, machine): |
657 | + failure.trap(NoConnection, ConnectionTimeoutException) |
658 | + raise EnvironmentPending( |
659 | + "Cannot connect to machine %s (perhaps still initializing): %s" |
660 | + % (machine.instance_id, str(failure.value))) |
661 | |
662 | @inlineCallbacks |
663 | def _wait_for_initialization(self, client): |
664 | @@ -50,4 +60,3 @@ |
665 | yield watch_d |
666 | else: |
667 | log.debug("Environment already initialized.") |
668 | - returnValue(client) |
669 | |
670 | === modified file 'ensemble/providers/common/findzookeepers.py' |
671 | --- ensemble/providers/common/findzookeepers.py 2011-08-16 13:24:41 +0000 |
672 | +++ ensemble/providers/common/findzookeepers.py 2011-08-26 13:16:39 +0000 |
673 | @@ -26,5 +26,5 @@ |
674 | |
675 | if machines: |
676 | returnValue(machines) |
677 | - raise EnvironmentNotFound("machines are not running (%s)." |
678 | + raise EnvironmentNotFound("machines are not running (%s)" |
679 | % ", ".join(missing_instance_ids)) |
680 | |
681 | === added file 'ensemble/providers/common/tests/test_connect.py' |
682 | --- ensemble/providers/common/tests/test_connect.py 1970-01-01 00:00:00 +0000 |
683 | +++ ensemble/providers/common/tests/test_connect.py 2011-08-26 13:16:39 +0000 |
684 | @@ -0,0 +1,133 @@ |
685 | +from twisted.internet.defer import fail, inlineCallbacks, succeed |
686 | + |
687 | +import zookeeper |
688 | + |
689 | +from txzookeeper import ZookeeperClient |
690 | +from txzookeeper.client import ConnectionTimeoutException |
691 | +from txzookeeper.tests.utils import deleteTree |
692 | + |
693 | +from ensemble.errors import EnvironmentPending, NoConnection |
694 | +from ensemble.lib.testing import TestCase |
695 | +from ensemble.machine import ProviderMachine |
696 | +from ensemble.providers.common.base import MachineProviderBase |
697 | +from ensemble.state.sshclient import SSHClient |
698 | +from ensemble.tests.common import get_test_zookeeper_address |
699 | + |
700 | + |
701 | +class DummyProvider(MachineProviderBase): |
702 | + |
703 | + def __init__(self, second_zookeeeper): |
704 | + self._second_zookeeper = second_zookeeeper |
705 | + |
706 | + def get_zookeeper_machines(self): |
707 | + """ |
708 | + Return a pair of possible zookeepers, the first of which is invalid |
709 | + """ |
710 | + return succeed([ |
711 | + ProviderMachine("i-havenodns"), |
712 | + self._second_zookeeper]) |
713 | + |
714 | + |
715 | +class ConnectTest(TestCase): |
716 | + |
717 | + def mock_connect(self, share, result): |
718 | + client = self.mocker.patch(SSHClient) |
719 | + client.connect("foo.example.com:2181", timeout=30, share=share) |
720 | + self.mocker.result(result) |
721 | + |
722 | + def assert_connect_error(self, error, expect_type, expect_message): |
723 | + self.mock_connect(False, fail(error)) |
724 | + self.mocker.replay() |
725 | + |
726 | + provider = DummyProvider(ProviderMachine("i-exist", "foo.example.com")) |
727 | + d = provider.connect() |
728 | + |
729 | + def check_error(error): |
730 | + self.assertEqual(str(error), expect_message) |
731 | + self.assertFailure(d, expect_type) |
732 | + d.addCallback(check_error) |
733 | + return d |
734 | + |
735 | + def test_none_have_dns(self): |
736 | + """ |
737 | + `EnvironmentPending` should be raised if no zookeeper nodes have dns |
738 | + names |
739 | + """ |
740 | + provider = DummyProvider(ProviderMachine("i-havenodnseither")) |
741 | + d = provider.connect() |
742 | + self.assertFailure(d, EnvironmentPending) |
743 | + |
744 | + def check(error): |
745 | + self.assertEquals( |
746 | + str(error), "No machines have addresses assigned yet") |
747 | + d.addCallback(check) |
748 | + return d |
749 | + |
750 | + def test_share_kwarg(self): |
751 | + """The `share` kwarg should be passed through to `SSHClient.connect`""" |
752 | + client = self.mocker.mock(type=SSHClient) |
753 | + self.mock_connect(True, succeed(client)) |
754 | + client.exists_and_watch("/initialized") |
755 | + self.mocker.result((succeed(True), None)) |
756 | + self.mocker.replay() |
757 | + |
758 | + provider = DummyProvider(ProviderMachine("i-amok", "foo.example.com")) |
759 | + d = provider.connect(share=True) |
760 | + |
761 | + def verify(result): |
762 | + self.assertIdentical(result, client) |
763 | + d.addCallback(verify) |
764 | + return d |
765 | + |
766 | + def test_no_connection(self): |
767 | + """`NoConnection` errors should become `EnvironmentPending`s""" |
768 | + return self.assert_connect_error( |
769 | + NoConnection("KABOOM!"), EnvironmentPending, |
770 | + "Cannot connect to machine i-exist (perhaps still initializing): " |
771 | + "KABOOM!") |
772 | + |
773 | + def test_txzookeeper_error(self): |
774 | + """ |
775 | + `ConnectionTimeoutException` errors should become `EnvironmentPending`s |
776 | + """ |
777 | + return self.assert_connect_error( |
778 | + ConnectionTimeoutException("SPLAT!"), EnvironmentPending, |
779 | + "Cannot connect to machine i-exist (perhaps still initializing): " |
780 | + "SPLAT!") |
781 | + |
782 | + def test_other_error(self): |
783 | + """Other errors should propagate""" |
784 | + return self.assert_connect_error( |
785 | + TypeError("THUD!"), TypeError, "THUD!") |
786 | + |
787 | + @inlineCallbacks |
788 | + def test_wait_for_initialize(self): |
789 | + """ |
790 | + A connection to a zookeeper that is running, but whose ensemble state |
791 | + is not ready, should wait until that state is ready. |
792 | + """ |
793 | + client = ZookeeperClient() |
794 | + self.client = client # for poke_zk |
795 | + self.mock_connect(False, succeed(client)) |
796 | + self.mocker.replay() |
797 | + |
798 | + zookeeper.set_debug_level(0) |
799 | + yield client.connect(get_test_zookeeper_address()) |
800 | + |
801 | + provider = DummyProvider(ProviderMachine("i-amok", "foo.example.com")) |
802 | + d = provider.connect() |
803 | + client_result = [] |
804 | + d.addCallback(client_result.append) |
805 | + |
806 | + # Give it a chance to do it incorrectly. |
807 | + yield self.poke_zk() |
808 | + |
809 | + try: |
810 | + self.assertEquals(client_result, []) |
811 | + yield client.create("/initialized") |
812 | + yield d |
813 | + self.assertTrue(client_result, client_result) |
814 | + self.assertIdentical(client_result[0], client) |
815 | + finally: |
816 | + deleteTree("/", client.handle) |
817 | + client.close() |
818 | |
819 | === modified file 'ensemble/providers/common/tests/test_findzookeepers.py' |
820 | --- ensemble/providers/common/tests/test_findzookeepers.py 2011-08-16 13:44:01 +0000 |
821 | +++ ensemble/providers/common/tests/test_findzookeepers.py 2011-08-26 13:16:39 +0000 |
822 | @@ -37,12 +37,27 @@ |
823 | provider = self.get_provider(False) |
824 | d = provider.get_zookeeper_machines() |
825 | self.assertFailure(d, EnvironmentNotFound) |
826 | + |
827 | + def check(error): |
828 | + self.assertEquals( |
829 | + str(error), |
830 | + "Ensemble environment not found: is the environment " |
831 | + "bootstrapped?") |
832 | + d.addCallback(check) |
833 | return d |
834 | |
835 | def test_empty_state(self): |
836 | provider = self.get_provider({}) |
837 | d = provider.get_zookeeper_machines() |
838 | self.assertFailure(d, EnvironmentNotFound) |
839 | + |
840 | + def check(error): |
841 | + self.assertEquals( |
842 | + str(error), |
843 | + "Ensemble environment not found: is the environment " |
844 | + "bootstrapped?") |
845 | + d.addCallback(check) |
846 | + return d |
847 | return d |
848 | |
849 | def test_get_machine_error_aborts(self): |
850 | @@ -56,14 +71,24 @@ |
851 | self.assertFailure(d, SomeError) |
852 | return d |
853 | |
854 | - def test_bad_machine(self): |
855 | - provider = self.get_provider({"zookeeper-instances": ["porter"]}) |
856 | + def test_bad_machines(self): |
857 | + provider = self.get_provider({ |
858 | + "zookeeper-instances": ["porter", "carter"]}) |
859 | provider.get_machines(["porter"]) |
860 | self.mocker.result(fail(MachinesNotFound(["porter"]))) |
861 | + provider.get_machines(["carter"]) |
862 | + self.mocker.result(fail(MachinesNotFound(["carter"]))) |
863 | self.mocker.replay() |
864 | |
865 | d = provider.get_zookeeper_machines() |
866 | self.assertFailure(d, EnvironmentNotFound) |
867 | + |
868 | + def check(error): |
869 | + self.assertEquals( |
870 | + str(error), |
871 | + "Ensemble environment not found: machines are not running " |
872 | + "(porter, carter)") |
873 | + d.addCallback(check) |
874 | return d |
875 | |
876 | def test_good_machine(self): |
877 | |
878 | === modified file 'ensemble/providers/common/tests/test_utils.py' |
879 | --- ensemble/providers/common/tests/test_utils.py 2011-08-11 22:04:38 +0000 |
880 | +++ ensemble/providers/common/tests/test_utils.py 2011-08-26 13:16:39 +0000 |
881 | @@ -63,9 +63,7 @@ |
882 | OSError("Bad")) |
883 | self.assertInstance(error, EnsembleError) |
884 | self.assertEqual( |
885 | - str(error), |
886 | - "ProviderError: Interaction with machine provider failed: " |
887 | - "OSError('Bad',)") |
888 | + str(error), "Unexpected OSError interacting with provider: Bad") |
889 | |
890 | def test_convert_unknown_error_ignores_ensemble_error(self): |
891 | error = self.assertRaises( |
892 | @@ -87,8 +85,7 @@ |
893 | self.assertInstance(failure.value, ProviderInteractionError) |
894 | self.assertEqual( |
895 | str(failure.value), |
896 | - "ProviderError: Interaction with machine provider failed: " |
897 | - "OSError('Bad',)") |
898 | + "Unexpected OSError interacting with provider: Bad") |
899 | |
900 | |
901 | class FormatCloudInitTest(TestCase): |
902 | |
903 | === modified file 'ensemble/providers/common/utils.py' |
904 | --- ensemble/providers/common/utils.py 2011-08-03 15:53:38 +0000 |
905 | +++ ensemble/providers/common/utils.py 2011-08-26 13:16:39 +0000 |
906 | @@ -31,7 +31,9 @@ |
907 | error = failure |
908 | |
909 | if not isinstance(error, EnsembleError): |
910 | - error = ProviderInteractionError(error) |
911 | + message = ("Unexpected %s interacting with provider: %s" |
912 | + % (type(error).__name__, str(error))) |
913 | + error = ProviderInteractionError(message) |
914 | |
915 | if isinstance(failure, Failure): |
916 | return Failure(error) |
917 | |
918 | === modified file 'ensemble/providers/ec2/__init__.py' |
919 | --- ensemble/providers/ec2/__init__.py 2011-08-23 18:47:42 +0000 |
920 | +++ ensemble/providers/ec2/__init__.py 2011-08-26 13:16:39 +0000 |
921 | @@ -9,9 +9,7 @@ |
922 | from ensemble.errors import ( |
923 | MachinesNotFound, ProviderError, ProviderInteractionError) |
924 | from ensemble.providers.common.base import MachineProviderBase |
925 | -from ensemble.providers.common.utils import convert_unknown_error |
926 | |
927 | -from .connect import EC2Connect |
928 | from .files import FileStorage |
929 | from .launch import EC2LaunchMachine |
930 | from .machine import EC2ProviderMachine, machine_from_instance |
931 | @@ -48,25 +46,8 @@ |
932 | data.setdefault("secret-key", os.environ.get("AWS_SECRET_ACCESS_KEY")) |
933 | return data |
934 | |
935 | - def _run_operation(self, operation, *args, **kw): |
936 | - d = operation.run(*args, **kw) |
937 | - d.addErrback(convert_unknown_error) |
938 | - return d |
939 | - |
940 | - def connect(self, share=False): |
941 | - """Connect to the zookeeper ensemble running in the machine provider. |
942 | - |
943 | - @param share: Requests sharing of the connection with other clients |
944 | - attempting to connect to the same provider, if that's feasible. |
945 | - |
946 | - returns an open C{txzookeeper.client.ZookeeperClient} and a |
947 | - C{ensemble.storage.connection.TunnelProtocol} |
948 | - """ |
949 | - connect = EC2Connect(self) |
950 | - return self._run_operation(connect, share=share) |
951 | - |
952 | def get_file_storage(self): |
953 | - """Retrieve an S3-backed C{FileStorage}.""" |
954 | + """Retrieve an S3-backed `FileStorage`.""" |
955 | return FileStorage(self.s3, self.config["control-bucket"]) |
956 | |
957 | def start_machine(self, machine_data, master=False): |
958 | @@ -75,7 +56,7 @@ |
959 | @param machine_data: a dictionary of data to pass along to the newly |
960 | launched machine. |
961 | |
962 | - @param master: if True, machine will initialize the ensemble admin |
963 | + @param master if True, machine will initialize the ensemble admin |
964 | and run a provisioning agent. |
965 | """ |
966 | return EC2LaunchMachine(self, master).run(machine_data) |
967 | @@ -84,8 +65,8 @@ |
968 | def get_machines(self, instance_ids=()): |
969 | """List machines running in the provider. |
970 | |
971 | - @param instance_ids: ids of instances you want to get. Leave blank |
972 | - to list all machines owned by ensemble. |
973 | + @param instance_ids: ids of instances you want to get. Leave empty |
974 | + to list all machines owned by this provider. |
975 | |
976 | @return: a list of EC2ProviderMachines. |
977 | |
978 | @@ -96,13 +77,14 @@ |
979 | instances = yield self.ec2.describe_instances(*instance_ids) |
980 | except EC2Error as error: |
981 | code = error.get_error_codes() |
982 | + message = error.get_error_messages() |
983 | if code == "InvalidInstanceID.NotFound": |
984 | message = error.get_error_messages() |
985 | raise MachinesNotFound( |
986 | re.findall(r"\bi-[0-9a-f]{3,15}\b", message)) |
987 | raise ProviderInteractionError( |
988 | - "EC2 error when looking up instances %s: %s" |
989 | - % (", ".join(instance_ids), error.get_error_messages())) |
990 | + "Unexpected EC2Error getting machines %s: %s" |
991 | + % (", ".join(instance_ids), message)) |
992 | |
993 | machines = [] |
994 | for instance in instances: |
995 | |
996 | === modified file 'ensemble/providers/ec2/launch.py' |
997 | --- ensemble/providers/ec2/launch.py 2011-08-10 17:23:06 +0000 |
998 | +++ ensemble/providers/ec2/launch.py 2011-08-26 13:16:39 +0000 |
999 | @@ -123,9 +123,8 @@ |
1000 | log.debug("Cannot delete security group %s: %s", |
1001 | ensemble_machine_group, e) |
1002 | raise ProviderInteractionError( |
1003 | - "EC2 error when attempting to delete " |
1004 | - "security group %s: %s" % ( |
1005 | - ensemble_machine_group, e)) |
1006 | + "Unexpected EC2Error deleting security group %s: %s" |
1007 | + % (ensemble_machine_group, e.get_error_messages())) |
1008 | log.debug("Creating ensemble machine security group %s", |
1009 | ensemble_machine_group) |
1010 | yield self._provider.ec2.create_security_group( |
1011 | |
1012 | === modified file 'ensemble/providers/ec2/machine.py' |
1013 | --- ensemble/providers/ec2/machine.py 2011-08-11 05:59:30 +0000 |
1014 | +++ ensemble/providers/ec2/machine.py 2011-08-26 13:16:39 +0000 |
1015 | @@ -11,12 +11,7 @@ |
1016 | |
1017 | |
1018 | def machine_from_instance(instance): |
1019 | - launch_time = None |
1020 | - if instance.launch_time: |
1021 | - launch_time = datetime.datetime.strptime( |
1022 | - instance.launch_time[:19], "%Y-%m-%dT%H:%M:%S") |
1023 | return EC2ProviderMachine( |
1024 | instance.instance_id, |
1025 | instance.dns_name, |
1026 | - instance.private_dns_name, |
1027 | - launch_time) |
1028 | + instance.private_dns_name) |
1029 | |
1030 | === modified file 'ensemble/providers/ec2/securitygroup.py' |
1031 | --- ensemble/providers/ec2/securitygroup.py 2011-08-10 17:23:06 +0000 |
1032 | +++ ensemble/providers/ec2/securitygroup.py 2011-08-26 13:16:39 +0000 |
1033 | @@ -38,8 +38,8 @@ |
1034 | port, protocol, machine.instance_id) |
1035 | except EC2Error, e: |
1036 | raise ProviderInteractionError( |
1037 | - "EC2 error when attempting to open %s/%s on machine %s: %s" % ( |
1038 | - port, protocol, machine.instance_id, e)) |
1039 | + "Unexpected EC2Error opening %s/%s on machine %s: %s" |
1040 | + % (port, protocol, machine.instance_id, e.get_error_messages())) |
1041 | |
1042 | |
1043 | @inlineCallbacks |
1044 | @@ -55,8 +55,8 @@ |
1045 | port, protocol, machine.instance_id) |
1046 | except EC2Error, e: |
1047 | raise ProviderInteractionError( |
1048 | - "EC2 error when attempting to close %s/%s on machine %s: %s" % ( |
1049 | - port, protocol, machine.instance_id, e)) |
1050 | + "Unexpected EC2Error closing %s/%s on machine %s: %s" |
1051 | + % (port, protocol, machine.instance_id, e.get_error_messages())) |
1052 | |
1053 | |
1054 | @inlineCallbacks |
1055 | @@ -72,9 +72,8 @@ |
1056 | _get_machine_group_name(provider, machine_id)) |
1057 | except EC2Error, e: |
1058 | raise ProviderInteractionError( |
1059 | - "EC2 error when attempting to get opened ports " |
1060 | - "on machine %s: %s" % ( |
1061 | - machine.instance_id, e)) |
1062 | + "Unexpected EC2Error getting open ports on machine %s: %s" |
1063 | + % (machine.instance_id, e.get_error_messages())) |
1064 | |
1065 | opened_ports = set() # made up of (port, protocol) pairs |
1066 | for ip_permission in security_groups[0].allowed_ips: |
1067 | |
1068 | === modified file 'ensemble/providers/ec2/tests/common.py' |
1069 | --- ensemble/providers/ec2/tests/common.py 2011-08-22 23:03:03 +0000 |
1070 | +++ ensemble/providers/ec2/tests/common.py 2011-08-26 13:16:39 +0000 |
1071 | @@ -59,16 +59,6 @@ |
1072 | "<error><Code>1</Code><Message>%s</Message></error>" % message, |
1073 | code) |
1074 | |
1075 | - def get_wrapped_ec2_error_text(self, entity_id, reason, |
1076 | - format="The instance ID %r does not exist"): |
1077 | - """By convention, `EC2Error` is wrapped as a `ProviderError`""" |
1078 | - message = format % entity_id |
1079 | - return ( |
1080 | - "ProviderError: Interaction with machine provider failed: " |
1081 | - "\"EC2 error when attempting to %s %s: " |
1082 | - "Error Message: %s\"" % ( |
1083 | - reason, entity_id, message)) |
1084 | - |
1085 | def setUp(self): |
1086 | # mock out the aws services |
1087 | service_factory = self.mocker.replace( |
1088 | |
1089 | === modified file 'ensemble/providers/ec2/tests/test_connect.py' |
1090 | --- ensemble/providers/ec2/tests/test_connect.py 2011-08-17 08:58:51 +0000 |
1091 | +++ ensemble/providers/ec2/tests/test_connect.py 2011-08-26 13:16:39 +0000 |
1092 | @@ -1,6 +1,3 @@ |
1093 | -import datetime |
1094 | -from datetime import timedelta |
1095 | - |
1096 | from yaml import dump |
1097 | |
1098 | from twisted.internet.defer import inlineCallbacks, succeed, fail |
1099 | @@ -8,24 +5,17 @@ |
1100 | import zookeeper |
1101 | |
1102 | from txzookeeper import ZookeeperClient |
1103 | +from txzookeeper.client import ConnectionTimeoutException |
1104 | from txzookeeper.tests.utils import deleteTree |
1105 | |
1106 | from ensemble.lib.testing import TestCase |
1107 | |
1108 | -from ensemble.errors import NoConnection |
1109 | +from ensemble.errors import EnvironmentPending, NoConnection |
1110 | from ensemble.state.sshclient import SSHClient |
1111 | from ensemble.providers.ec2.tests.common import EC2TestMixin |
1112 | -from ensemble.errors import ProviderInteractionError |
1113 | from ensemble.tests.common import get_test_zookeeper_address |
1114 | |
1115 | |
1116 | -def _aws_minutes_ago(minutes_ago): |
1117 | - now = datetime.datetime.now() |
1118 | - delta = timedelta(minutes=minutes_ago, seconds=1) |
1119 | - then = now - delta |
1120 | - return then.strftime("%Y-%m-%dT%H:%M:%S.000Z") |
1121 | - |
1122 | - |
1123 | class EC2ConnectTest(EC2TestMixin, TestCase): |
1124 | |
1125 | def mock_find_zookeepers(self, instance): |
1126 | @@ -41,7 +31,25 @@ |
1127 | client.connect("foo.example.com:2181", timeout=30, share=share) |
1128 | self.mocker.result(result) |
1129 | |
1130 | + def assert_connect_error(self, error, expect_type, expect_message): |
1131 | + instance = self.get_instance("i-foobar", dns_name="foo.example.com") |
1132 | + self.mock_connect(False, instance, fail(error)) |
1133 | + self.mocker.replay() |
1134 | + |
1135 | + provider = self.get_provider() |
1136 | + d = provider.connect() |
1137 | + |
1138 | + def check_error(error): |
1139 | + self.assertEqual(str(error), expect_message) |
1140 | + self.assertFailure(d, expect_type) |
1141 | + d.addCallback(check_error) |
1142 | + return d |
1143 | + |
1144 | def test_no_dns_name(self): |
1145 | + """ |
1146 | + `EnvironmentPending` should be raised if no zookeeper nodes have dns |
1147 | + names |
1148 | + """ |
1149 | instance = self.get_instance("i-foobar") |
1150 | self.mock_find_zookeepers(instance) |
1151 | self.mocker.replay() |
1152 | @@ -51,17 +59,14 @@ |
1153 | |
1154 | def check_error(error): |
1155 | self.assertEqual( |
1156 | - str(error), |
1157 | - "Ensemble environment is not accessible: Machine i-foobar has " |
1158 | - "no address assigned yet") |
1159 | + str(error), "No machines have addresses assigned yet") |
1160 | |
1161 | self.assertFailure(d, NoConnection) |
1162 | d.addCallback(check_error) |
1163 | return d |
1164 | |
1165 | def test_provider_connect_forwards_share_option(self): |
1166 | - """Test connecting to a running ensemble zookeeper server within ec2. |
1167 | - """ |
1168 | + """The `share` kwarg should be passed through to `SSHClient.connect`""" |
1169 | instance = self.get_instance("i-foobar", dns_name="foo.example.com") |
1170 | connected_client = self.mocker.mock(type=SSHClient) |
1171 | self.mock_connect(True, instance, succeed(connected_client)) |
1172 | @@ -78,14 +83,37 @@ |
1173 | d.addCallback(verify_result) |
1174 | return d |
1175 | |
1176 | + def test_no_connection(self): |
1177 | + """`NoConnection` errors should become `EnvironmentPending`s""" |
1178 | + return self.assert_connect_error( |
1179 | + NoConnection("KABOOM!"), EnvironmentPending, |
1180 | + "Cannot connect to machine i-foobar (perhaps still initializing): " |
1181 | + "KABOOM!") |
1182 | + |
1183 | + def test_txzookeeper_error(self): |
1184 | + """ |
1185 | + `ConnectionTimeoutException` errors should become `EnvironmentPending`s |
1186 | + """ |
1187 | + return self.assert_connect_error( |
1188 | + ConnectionTimeoutException("SPLAT!"), EnvironmentPending, |
1189 | + "Cannot connect to machine i-foobar (perhaps still initializing): " |
1190 | + "SPLAT!") |
1191 | + |
1192 | + def test_other_error(self): |
1193 | + """Other errors should propagate""" |
1194 | + return self.assert_connect_error( |
1195 | + TypeError("THUD!"), TypeError, "THUD!") |
1196 | + |
1197 | @inlineCallbacks |
1198 | def test_provider_connect_waits_on_initialization(self): |
1199 | """ |
1200 | - Test connecting to a running ensemble zookeeper server within ec2. |
1201 | + A connection to a zookeeper that is running, but whose ensemble state |
1202 | + is not ready, should wait until that state is ready. |
1203 | """ |
1204 | # Hand back a real connected client to test the wait on initialization. |
1205 | instance = self.get_instance("i-foobar", dns_name="foo.example.com") |
1206 | connected_client = ZookeeperClient() |
1207 | + self.client = connected_client # for poke_zk |
1208 | self.mock_connect(False, instance, succeed(connected_client)) |
1209 | |
1210 | self.mocker.replay() |
1211 | @@ -99,13 +127,8 @@ |
1212 | client_deferred = provider.connect() |
1213 | client_deferred.addCallback(client_result.append) |
1214 | |
1215 | - # Give it some time to do it incorrectly. |
1216 | - from twisted.internet.defer import Deferred |
1217 | - from twisted.internet import reactor |
1218 | - sleep_deferred = Deferred() |
1219 | - reactor.callLater(0.1, sleep_deferred.callback, None) |
1220 | - yield sleep_deferred |
1221 | - # XXX Replace the logic above with self.sleep(0.1) when it's merged. |
1222 | + # Give it a chance to do it incorrectly. |
1223 | + yield self.poke_zk() |
1224 | |
1225 | try: |
1226 | self.assertEquals(client_result, []) |
1227 | @@ -118,68 +141,3 @@ |
1228 | finally: |
1229 | deleteTree("/", connected_client.handle) |
1230 | connected_client.close() |
1231 | - |
1232 | - def test_provider_connection_fails_with_recent_machine(self): |
1233 | - """ |
1234 | - If we can't connect to a recently bootstrapped machine, |
1235 | - we show an error indicating the machine might still be |
1236 | - initializing. |
1237 | - """ |
1238 | - instance = self.get_instance( |
1239 | - "i-foobar", dns_name="foo.example.com", |
1240 | - launch_time=_aws_minutes_ago(1)) |
1241 | - self.mock_connect(False, instance, fail(NoConnection("BADABOOM!"))) |
1242 | - self.mocker.replay() |
1243 | - |
1244 | - provider = self.get_provider() |
1245 | - d = provider.connect() |
1246 | - |
1247 | - def check_error(error): |
1248 | - self.assertEqual( |
1249 | - str(error), "Can't yet connect to started machine: BADABOOM!") |
1250 | - |
1251 | - self.assertFailure(d, NoConnection) |
1252 | - d.addCallback(check_error) |
1253 | - return d |
1254 | - |
1255 | - def test_provider_connection_fails_with_old_machine(self): |
1256 | - """ |
1257 | - If the machine has started for a while, though, we won't |
1258 | - tweak the error message in any way. |
1259 | - """ |
1260 | - instance = self.get_instance( |
1261 | - "i-foobar", dns_name="foo.example.com", |
1262 | - launch_time=_aws_minutes_ago(5)) |
1263 | - self.mock_connect(False, instance, fail(NoConnection("BADABOOM!"))) |
1264 | - self.mocker.replay() |
1265 | - |
1266 | - provider = self.get_provider() |
1267 | - d = provider.connect() |
1268 | - |
1269 | - def check_error(error): |
1270 | - self.assertEqual(str(error), "BADABOOM!") |
1271 | - |
1272 | - self.assertFailure(d, NoConnection) |
1273 | - d.addCallback(check_error) |
1274 | - return d |
1275 | - |
1276 | - def test_recent_machine_error_tweaking_works_with_no_conn_only(self): |
1277 | - """ |
1278 | - If the error raised is not NoConnection, then it should not |
1279 | - be tweaked even if it is a recent machine. |
1280 | - """ |
1281 | - instance = self.get_instance( |
1282 | - "i-foobar", dns_name="foo.example.com", |
1283 | - launch_time=_aws_minutes_ago(1)) |
1284 | - self.mock_connect(False, instance, fail(ValueError("BADAPOFT!"))) |
1285 | - self.mocker.replay() |
1286 | - |
1287 | - provider = self.get_provider() |
1288 | - d = provider.connect() |
1289 | - |
1290 | - def check_error(error): |
1291 | - self.assertEqual(str(error.error), "BADAPOFT!") |
1292 | - |
1293 | - self.assertFailure(d, ProviderInteractionError) |
1294 | - d.addCallback(check_error) |
1295 | - return d |
1296 | |
1297 | === modified file 'ensemble/providers/ec2/tests/test_getmachines.py' |
1298 | --- ensemble/providers/ec2/tests/test_getmachines.py 2011-08-17 08:58:51 +0000 |
1299 | +++ ensemble/providers/ec2/tests/test_getmachines.py 2011-08-26 13:16:39 +0000 |
1300 | @@ -132,9 +132,8 @@ |
1301 | def verify(error): |
1302 | self.assertEquals( |
1303 | str(error), |
1304 | - "ProviderError: Interaction with machine provider failed: " |
1305 | - "\"EC2 error when looking up instances i-brokeit, i-msorry: " |
1306 | - "unhelpful noises ('splat! kerpow!')\"") |
1307 | + "Unexpected EC2Error getting machines i-brokeit, i-msorry: " |
1308 | + "unhelpful noises ('splat! kerpow!')") |
1309 | d.addCallback(verify) |
1310 | return d |
1311 | |
1312 | |
1313 | === modified file 'ensemble/providers/ec2/tests/test_launch.py' |
1314 | --- ensemble/providers/ec2/tests/test_launch.py 2011-08-16 13:55:11 +0000 |
1315 | +++ ensemble/providers/ec2/tests/test_launch.py 2011-08-26 13:16:39 +0000 |
1316 | @@ -141,10 +141,9 @@ |
1317 | ProviderInteractionError) |
1318 | self.assertEqual( |
1319 | str(ex), |
1320 | - self.get_wrapped_ec2_error_text( |
1321 | - "ensemble-moon-machine-1", "delete security group", |
1322 | - "There are active instances using security group %r" |
1323 | - )) |
1324 | + "Unexpected EC2Error deleting security group " |
1325 | + "ensemble-moon-machine-1: There are active instances using " |
1326 | + "security group 'ensemble-moon-machine-1'") |
1327 | |
1328 | def test_provider_type_machine_variable(self): |
1329 | """The provider type is available via cloud-init.""" |
1330 | |
1331 | === modified file 'ensemble/providers/ec2/tests/test_machine.py' |
1332 | --- ensemble/providers/ec2/tests/test_machine.py 2011-07-20 07:10:30 +0000 |
1333 | +++ ensemble/providers/ec2/tests/test_machine.py 2011-08-26 13:16:39 +0000 |
1334 | @@ -1,5 +1,3 @@ |
1335 | -import datetime |
1336 | - |
1337 | from txaws.ec2.model import Instance |
1338 | |
1339 | from ensemble.lib.testing import TestCase |
1340 | @@ -13,27 +11,10 @@ |
1341 | instance = Instance( |
1342 | "i-foobar", "ignored", |
1343 | dns_name="public", |
1344 | - private_dns_name="private", |
1345 | - launch_time="2009-04-27T02:23:18.000Z") |
1346 | - |
1347 | - machine = machine_from_instance(instance) |
1348 | - self.assertTrue(isinstance(machine, EC2ProviderMachine)) |
1349 | - self.assertEquals(machine.instance_id, instance.instance_id) |
1350 | - self.assertEquals(machine.dns_name, instance.dns_name) |
1351 | - self.assertEquals(machine.private_dns_name, instance.private_dns_name) |
1352 | - self.assertEquals(machine.launch_time, |
1353 | - datetime.datetime(2009, 4, 27, 2, 23, 18)) |
1354 | - |
1355 | - def test_machine_from_instance_no_launch_time(self): |
1356 | - instance = Instance( |
1357 | - "i-foobar", "ignored", |
1358 | - dns_name="public", |
1359 | - private_dns_name="private", |
1360 | - launch_time="") |
1361 | - |
1362 | - machine = machine_from_instance(instance) |
1363 | - self.assertTrue(isinstance(machine, EC2ProviderMachine)) |
1364 | - self.assertEquals(machine.instance_id, instance.instance_id) |
1365 | - self.assertEquals(machine.dns_name, instance.dns_name) |
1366 | - self.assertEquals(machine.private_dns_name, instance.private_dns_name) |
1367 | - self.assertEquals(machine.launch_time, None) |
1368 | + private_dns_name="private") |
1369 | + |
1370 | + machine = machine_from_instance(instance) |
1371 | + self.assertTrue(isinstance(machine, EC2ProviderMachine)) |
1372 | + self.assertEquals(machine.instance_id, instance.instance_id) |
1373 | + self.assertEquals(machine.dns_name, instance.dns_name) |
1374 | + self.assertEquals(machine.private_dns_name, instance.private_dns_name) |
1375 | |
1376 | === modified file 'ensemble/providers/ec2/tests/test_provider.py' |
1377 | --- ensemble/providers/ec2/tests/test_provider.py 2011-08-11 19:49:32 +0000 |
1378 | +++ ensemble/providers/ec2/tests/test_provider.py 2011-08-26 13:16:39 +0000 |
1379 | @@ -127,4 +127,7 @@ |
1380 | config["authorized-keys-path"] = "File path" |
1381 | error = self.assertRaises(EnvironmentsConfigError, |
1382 | MachineProvider, "some-env-name", config) |
1383 | - self.assertIn("authorized-keys", str(error)) |
1384 | + self.assertEquals( |
1385 | + str(error), |
1386 | + "Environment config cannot define both authorized-keys and " |
1387 | + "authorized-keys-path. Pick one!") |
1388 | |
1389 | === modified file 'ensemble/providers/ec2/tests/test_securitygroup.py' |
1390 | --- ensemble/providers/ec2/tests/test_securitygroup.py 2011-08-10 17:19:09 +0000 |
1391 | +++ ensemble/providers/ec2/tests/test_securitygroup.py 2011-08-26 13:16:39 +0000 |
1392 | @@ -89,8 +89,8 @@ |
1393 | ProviderInteractionError) |
1394 | self.assertEqual( |
1395 | str(ex), |
1396 | - self.get_wrapped_ec2_error_text( |
1397 | - "i-foobar", "open 80/tcp on machine")) |
1398 | + "Unexpected EC2Error opening 80/tcp on machine i-foobar: " |
1399 | + "The instance ID 'i-foobar' does not exist") |
1400 | |
1401 | @inlineCallbacks |
1402 | def test_close_provider_port_unknown_instance(self): |
1403 | @@ -108,8 +108,8 @@ |
1404 | ProviderInteractionError) |
1405 | self.assertEqual( |
1406 | str(ex), |
1407 | - self.get_wrapped_ec2_error_text( |
1408 | - "i-foobar", "close 80/tcp on machine")) |
1409 | + "Unexpected EC2Error closing 80/tcp on machine i-foobar: " |
1410 | + "The instance ID 'i-foobar' does not exist") |
1411 | |
1412 | @inlineCallbacks |
1413 | def test_get_provider_opened_ports_unknown_instance(self): |
1414 | @@ -125,5 +125,5 @@ |
1415 | ProviderInteractionError) |
1416 | self.assertEqual( |
1417 | str(ex), |
1418 | - self.get_wrapped_ec2_error_text( |
1419 | - "i-foobar", "get opened ports on machine")) |
1420 | + "Unexpected EC2Error getting open ports on machine i-foobar: " |
1421 | + "The instance ID 'i-foobar' does not exist") |
1422 | |
1423 | === modified file 'ensemble/providers/orchestra/__init__.py' |
1424 | --- ensemble/providers/orchestra/__init__.py 2011-08-25 16:11:49 +0000 |
1425 | +++ ensemble/providers/orchestra/__init__.py 2011-08-26 13:16:39 +0000 |
1426 | @@ -20,7 +20,7 @@ |
1427 | self.cobbler = CobblerClient(config) |
1428 | |
1429 | def get_file_storage(self): |
1430 | - """Retrieve the provider C{FileStorage} abstraction.""" |
1431 | + """Return a WebDAV-backed FileStorage abstraction.""" |
1432 | if "storage-url" not in self.config: |
1433 | return FileStorage( |
1434 | "http://%(orchestra-server)s/webdav" % self.config) |
1435 | @@ -41,10 +41,10 @@ |
1436 | def get_machines(self, instance_ids=()): |
1437 | """List machines running in the provider. |
1438 | |
1439 | - @param instance_ids: ids of instances you want to get. Leave blank |
1440 | - to list all machines owned by ensemble. |
1441 | + @param instance_ids: ids of instances you want to get. Leave empty |
1442 | + to list all machines owned by this provider. |
1443 | |
1444 | - @return: a list of EC2ProviderMachines. |
1445 | + @return: a list of OrchestraMachines. |
1446 | |
1447 | @raise: MachinesNotFound |
1448 | """ |
1449 | |
1450 | === modified file 'ensemble/providers/orchestra/cobbler.py' |
1451 | --- ensemble/providers/orchestra/cobbler.py 2011-08-22 13:01:02 +0000 |
1452 | +++ ensemble/providers/orchestra/cobbler.py 2011-08-26 13:16:39 +0000 |
1453 | @@ -3,8 +3,7 @@ |
1454 | from twisted.internet.defer import inlineCallbacks, returnValue |
1455 | from twisted.web.xmlrpc import Proxy |
1456 | |
1457 | -from ensemble.errors import ( |
1458 | - MachinesNotFound, ProviderError, ProviderInteractionError) |
1459 | +from ensemble.errors import MachinesNotFound, ProviderError |
1460 | |
1461 | |
1462 | class CobblerCaller(object): |
1463 | @@ -62,7 +61,7 @@ |
1464 | def check(actual): |
1465 | if actual != expect: |
1466 | raise ProviderError( |
1467 | - "Bad result from call to %s with %s (got %r, expected %r)" |
1468 | + "Bad result from call to %s with %s: got %r, expected %r" |
1469 | % (name, args, actual, expect)) |
1470 | return actual |
1471 | |
1472 | @@ -84,8 +83,9 @@ |
1473 | |
1474 | def extract_name(systems): |
1475 | if len(systems) > 1: |
1476 | - raise ProviderInteractionError( |
1477 | - "Got multiple instances with id %s" % instance_id) |
1478 | + raise ProviderError( |
1479 | + "Got multiple names for machine %s: %s" |
1480 | + % (instance_id, ", ".join(systems))) |
1481 | if not systems: |
1482 | raise MachinesNotFound([instance_id]) |
1483 | return systems[0] |
1484 | |
1485 | === modified file 'ensemble/providers/orchestra/machine.py' |
1486 | --- ensemble/providers/orchestra/machine.py 2011-08-11 04:30:55 +0000 |
1487 | +++ ensemble/providers/orchestra/machine.py 2011-08-26 13:16:39 +0000 |
1488 | @@ -6,4 +6,8 @@ |
1489 | |
1490 | |
1491 | def machine_from_dict(d): |
1492 | + """Convert a `dict` into an `OrchestraMachine`. |
1493 | + |
1494 | + Expects dicts as returned by `CobblerClient.get_system[s]` |
1495 | + """ |
1496 | return OrchestraMachine(d["uid"], d["name"], d["name"]) |
1497 | |
1498 | === modified file 'ensemble/providers/orchestra/tests/test_cobbler.py' |
1499 | --- ensemble/providers/orchestra/tests/test_cobbler.py 2011-08-12 21:35:34 +0000 |
1500 | +++ ensemble/providers/orchestra/tests/test_cobbler.py 2011-08-26 13:16:39 +0000 |
1501 | @@ -3,8 +3,7 @@ |
1502 | from twisted.internet.defer import fail, succeed |
1503 | from twisted.web.xmlrpc import Proxy |
1504 | |
1505 | -from ensemble.errors import ( |
1506 | - MachinesNotFound, ProviderError, ProviderInteractionError) |
1507 | +from ensemble.errors import MachinesNotFound, ProviderError |
1508 | from ensemble.lib.testing import TestCase |
1509 | from ensemble.providers.orchestra.cobbler import CobblerCaller, CobblerClient |
1510 | |
1511 | @@ -243,11 +242,14 @@ |
1512 | d.addCallback(verify) |
1513 | return d |
1514 | |
1515 | - def check_bad_call(self, d, method): |
1516 | + def check_bad_call(self, d, method, args): |
1517 | self.assertFailure(d, ProviderError) |
1518 | |
1519 | def verify(error): |
1520 | - self.assertIn("Bad result from call to %s" % method, str(error)) |
1521 | + self.assertEquals( |
1522 | + str(error), |
1523 | + "Bad result from call to %s with %s: got False, expected True" |
1524 | + % (method, args)) |
1525 | d.addCallback(verify) |
1526 | return d |
1527 | |
1528 | @@ -261,11 +263,13 @@ |
1529 | self.mock_get_name(succeed(["some-name", "another-name"])) |
1530 | self.mocker.replay() |
1531 | d = self.call_cobbler_method(method_name, *args) |
1532 | - self.assertFailure(d, ProviderInteractionError) |
1533 | + self.assertFailure(d, ProviderError) |
1534 | |
1535 | def check_error(error): |
1536 | - self.assertIn("Got multiple instances with id some-uid", |
1537 | - str(error)) |
1538 | + self.assertEquals( |
1539 | + str(error), |
1540 | + "Got multiple names for machine some-uid: some-name, " |
1541 | + "another-name") |
1542 | d.addCallback(check_error) |
1543 | return d |
1544 | |
1545 | @@ -301,7 +305,8 @@ |
1546 | self.mock_modify_system(succeed(False), key, value) |
1547 | self.mocker.replay() |
1548 | d = self.call_cobbler_method(method_name, *args) |
1549 | - return self.check_bad_call(d, "modify_system") |
1550 | + return self.check_bad_call( |
1551 | + d, "modify_system", ("some-handle", key, value)) |
1552 | |
1553 | def check_modify_system_error(self, key, value, method_name, *args): |
1554 | self.mock_modify_system(fail(SomeError()), key, value) |
1555 | @@ -314,7 +319,7 @@ |
1556 | self.mock_save_system(succeed(False)) |
1557 | self.mocker.replay() |
1558 | d = self.call_cobbler_method(method_name, *args) |
1559 | - return self.check_bad_call(d, "save_system") |
1560 | + return self.check_bad_call(d, "save_system", ("some-handle",)) |
1561 | |
1562 | def check_save_system_error(self, method_name, *args): |
1563 | self.mock_save_system(fail(SomeError())) |
1564 | |
1565 | === added file 'ensemble/providers/orchestra/tests/test_connect.py' |
1566 | --- ensemble/providers/orchestra/tests/test_connect.py 1970-01-01 00:00:00 +0000 |
1567 | +++ ensemble/providers/orchestra/tests/test_connect.py 2011-08-26 13:16:39 +0000 |
1568 | @@ -0,0 +1,111 @@ |
1569 | +from yaml import dump |
1570 | + |
1571 | +from twisted.internet.defer import fail, inlineCallbacks, succeed |
1572 | + |
1573 | +import zookeeper |
1574 | + |
1575 | +from txzookeeper import ZookeeperClient |
1576 | +from txzookeeper.client import ConnectionTimeoutException |
1577 | +from txzookeeper.tests.utils import deleteTree |
1578 | + |
1579 | +from ensemble.errors import EnvironmentPending, NoConnection |
1580 | +from ensemble.lib.testing import TestCase |
1581 | +from ensemble.state.sshclient import SSHClient |
1582 | +from ensemble.tests.common import get_test_zookeeper_address |
1583 | + |
1584 | +from .common import OrchestraTestMixin |
1585 | + |
1586 | + |
1587 | +class ConnectTest(TestCase, OrchestraTestMixin): |
1588 | + |
1589 | + def mock_connect(self, share, result): |
1590 | + self.setup_mocks() |
1591 | + self.getPage("http://somewhe.re/webdav/provider-state") |
1592 | + self.mocker.result(succeed(dump( |
1593 | + {"zookeeper-instances": ["foo"]}))) |
1594 | + self.proxy_m.callRemote("get_systems") |
1595 | + self.mocker.result(succeed([{"uid": "foo", |
1596 | + "name": "foo.example.com", |
1597 | + "mgmt_classes": ["acquired"]}])) |
1598 | + client = self.mocker.patch(SSHClient) |
1599 | + client.connect("foo.example.com:2181", timeout=30, share=share) |
1600 | + self.mocker.result(result) |
1601 | + |
1602 | + def assert_connect_error(self, error, expect_type, expect_message): |
1603 | + self.mock_connect(False, fail(error)) |
1604 | + self.mocker.replay() |
1605 | + |
1606 | + d = self.get_provider().connect() |
1607 | + |
1608 | + def check_error(error): |
1609 | + self.assertEqual(str(error), expect_message) |
1610 | + self.assertFailure(d, expect_type) |
1611 | + d.addCallback(check_error) |
1612 | + return d |
1613 | + |
1614 | + def test_share_kwarg(self): |
1615 | + """The `share` kwarg should be passed through to `SSHClient.connect`""" |
1616 | + client = self.mocker.mock(type=SSHClient) |
1617 | + self.mock_connect(True, succeed(client)) |
1618 | + client.exists_and_watch("/initialized") |
1619 | + self.mocker.result((succeed(True), None)) |
1620 | + self.mocker.replay() |
1621 | + |
1622 | + d = self.get_provider().connect(share=True) |
1623 | + |
1624 | + def verify(result): |
1625 | + self.assertIdentical(result, client) |
1626 | + d.addCallback(verify) |
1627 | + return d |
1628 | + |
1629 | + def test_no_connection(self): |
1630 | + """`NoConnection` errors should become `EnvironmentPending`s""" |
1631 | + return self.assert_connect_error( |
1632 | + NoConnection("KABOOM!"), EnvironmentPending, |
1633 | + "Cannot connect to machine foo (perhaps still initializing): " |
1634 | + "KABOOM!") |
1635 | + |
1636 | + def test_txzookeeper_error(self): |
1637 | + """ |
1638 | + `ConnectionTimeoutException` errors should become `EnvironmentPending`s |
1639 | + """ |
1640 | + return self.assert_connect_error( |
1641 | + ConnectionTimeoutException("SPLAT!"), EnvironmentPending, |
1642 | + "Cannot connect to machine foo (perhaps still initializing): " |
1643 | + "SPLAT!") |
1644 | + |
1645 | + def test_other_error(self): |
1646 | + """Other errors should propagate""" |
1647 | + return self.assert_connect_error( |
1648 | + TypeError("THUD!"), TypeError, "THUD!") |
1649 | + |
1650 | + @inlineCallbacks |
1651 | + def test_wait_for_initialize(self): |
1652 | + """ |
1653 | + A connection to a zookeeper that is running, but whose ensemble state |
1654 | + is not ready, should wait until that state is ready. |
1655 | + """ |
1656 | + client = ZookeeperClient() |
1657 | + self.client = client # for poke_zk |
1658 | + self.mock_connect(False, succeed(client)) |
1659 | + self.mocker.replay() |
1660 | + |
1661 | + zookeeper.set_debug_level(0) |
1662 | + yield client.connect(get_test_zookeeper_address()) |
1663 | + |
1664 | + d = self.get_provider().connect() |
1665 | + client_result = [] |
1666 | + d.addCallback(client_result.append) |
1667 | + |
1668 | + # Give it an opportunity to do it incorrectly. |
1669 | + yield self.poke_zk() |
1670 | + |
1671 | + try: |
1672 | + self.assertEquals(client_result, []) |
1673 | + yield client.create("/initialized") |
1674 | + yield d |
1675 | + self.assertTrue(client_result, client_result) |
1676 | + self.assertIdentical(client_result[0], client) |
1677 | + finally: |
1678 | + deleteTree("/", client.handle) |
1679 | + client.close() |
1680 | |
1681 | === modified file 'ensemble/providers/orchestra/tests/test_launch.py' |
1682 | --- ensemble/providers/orchestra/tests/test_launch.py 2011-08-11 23:27:45 +0000 |
1683 | +++ ensemble/providers/orchestra/tests/test_launch.py 2011-08-26 13:16:39 +0000 |
1684 | @@ -30,9 +30,13 @@ |
1685 | |
1686 | d = self.get_provider().start_machine({"machine-id": "32767"}) |
1687 | self.assertFailure(d, ProviderError) |
1688 | - self.assertIn("Could not find any Cobbler systems marked as available " |
1689 | - "and configured for network boot.", |
1690 | - str(d)) |
1691 | + |
1692 | + def verify(error): |
1693 | + self.assertEquals( |
1694 | + str(error), |
1695 | + "Could not find any Cobbler systems marked as available and " |
1696 | + "configured for network boot.") |
1697 | + d.addCallback(verify) |
1698 | return d |
1699 | |
1700 | def test_no_acceptable_machines(self): |
1701 | @@ -43,9 +47,13 @@ |
1702 | |
1703 | d = self.get_provider().start_machine({"machine-id": "32767"}) |
1704 | self.assertFailure(d, ProviderError) |
1705 | - self.assertIn("All available Cobbler systems were also marked as " |
1706 | - "acquired (instances: bad-system-uid).", |
1707 | - str(d)) |
1708 | + |
1709 | + def verify(error): |
1710 | + self.assertEquals( |
1711 | + str(error), |
1712 | + "All available Cobbler systems were also marked as acquired " |
1713 | + "(instances: bad-system-uid).") |
1714 | + d.addCallback(verify) |
1715 | return d |
1716 | |
1717 | def test_actually_launch(self): |
1718 | |
1719 | === modified file 'ensemble/providers/orchestra/tests/test_provider.py' |
1720 | --- ensemble/providers/orchestra/tests/test_provider.py 2011-08-23 16:07:09 +0000 |
1721 | +++ ensemble/providers/orchestra/tests/test_provider.py 2011-08-26 13:16:39 +0000 |
1722 | @@ -53,7 +53,10 @@ |
1723 | config["authorized-keys-path"] = "File path" |
1724 | error = self.assertRaises(EnvironmentsConfigError, |
1725 | MachineProvider, "some-env-name", config) |
1726 | - self.assertIn("authorized-keys", str(error)) |
1727 | + self.assertEquals( |
1728 | + str(error), |
1729 | + "Environment config cannot define both authorized-keys and " |
1730 | + "authorized-keys-path. Pick one!") |
1731 | |
1732 | @inlineCallbacks |
1733 | def test_open_port(self): |
1734 | @@ -75,4 +78,3 @@ |
1735 | yield MachineProvider("blah", CONFIG).get_opened_ports(None, None) |
1736 | self.assertIn( |
1737 | "Firewalling is not yet implemented in Orchestra", log.getvalue()) |
1738 | - |
1739 | |
1740 | === modified file 'ensemble/state/service.py' |
1741 | --- ensemble/state/service.py 2011-08-08 03:22:15 +0000 |
1742 | +++ ensemble/state/service.py 2011-08-26 13:16:39 +0000 |
1743 | @@ -855,7 +855,8 @@ |
1744 | cleared. |
1745 | """ |
1746 | if not isinstance(hook_names, (list, tuple)): |
1747 | - raise AssertionError("hook names must be a list %r" % hook_names) |
1748 | + raise AssertionError("Hook names must be a list: got %r" |
1749 | + % hook_names) |
1750 | |
1751 | if "*" in hook_names and len(hook_names) > 1: |
1752 | msg = "Ambigious to debug all hooks and named hooks %r" % ( |
1753 | |
1754 | === modified file 'ensemble/state/sshclient.py' |
1755 | --- ensemble/state/sshclient.py 2011-08-15 00:41:54 +0000 |
1756 | +++ ensemble/state/sshclient.py 2011-08-26 13:16:39 +0000 |
1757 | @@ -193,6 +193,8 @@ |
1758 | else: |
1759 | returnValue(self) |
1760 | self.close() |
1761 | + # we raise ConnectionTimeoutException (rather than one of our own, with |
1762 | + # the same meaning) to maintain ZookeeperClient interface |
1763 | raise ConnectionTimeoutException( |
1764 | "could not connect before timeout after %d retries" % num_retries) |
1765 | |
1766 | |
1767 | === modified file 'ensemble/state/tests/test_security.py' |
1768 | --- ensemble/state/tests/test_security.py 2011-08-05 22:55:52 +0000 |
1769 | +++ ensemble/state/tests/test_security.py 2011-08-26 13:16:39 +0000 |
1770 | @@ -316,7 +316,7 @@ |
1771 | principal = Principal("zebra", "zoo") |
1772 | error = yield self.assertFailure(self.db.get(principal.name), |
1773 | PrincipalNotFound) |
1774 | - self.assertIn("zebra", str(error)) |
1775 | + self.assertEquals(str(error), "Principal 'zebra' not found") |
1776 | |
1777 | |
1778 | class PolicyTest(TestCase): |
1779 | |
1780 | === modified file 'ensemble/state/tests/test_service.py' |
1781 | --- ensemble/state/tests/test_service.py 2011-08-22 23:02:38 +0000 |
1782 | +++ ensemble/state/tests/test_service.py 2011-08-26 13:16:39 +0000 |
1783 | @@ -1111,8 +1111,10 @@ |
1784 | error = yield self.assertFailure( |
1785 | unit_state.enable_hook_debug(["*", "db-relation-changed"]), |
1786 | ValueError) |
1787 | - self.assertIn("Ambigious to debug all hooks and named hooks", |
1788 | - str(error)) |
1789 | + self.assertEquals( |
1790 | + str(error), |
1791 | + "Ambigious to debug all hooks and named hooks " |
1792 | + "['*', 'db-relation-changed']") |
1793 | |
1794 | @inlineCallbacks |
1795 | def test_enable_debug_requires_sequence(self): |
1796 | @@ -1123,7 +1125,7 @@ |
1797 | error = yield self.assertFailure( |
1798 | unit_state.enable_hook_debug(None), |
1799 | AssertionError) |
1800 | - self.assertIn("hook names must be a list", str(error)) |
1801 | + self.assertEquals(str(error), "Hook names must be a list: got None") |
1802 | |
1803 | @inlineCallbacks |
1804 | def test_enable_named_debug_hook(self): |
1805 | @@ -1144,8 +1146,10 @@ |
1806 | """ |
1807 | unit_state = yield self.get_unit_state() |
1808 | yield unit_state.enable_hook_debug(["*"]) |
1809 | - yield self.assertFailure(unit_state.enable_hook_debug(["*"]), |
1810 | - ServiceUnitDebugAlreadyEnabled) |
1811 | + error = yield self.assertFailure(unit_state.enable_hook_debug(["*"]), |
1812 | + ServiceUnitDebugAlreadyEnabled) |
1813 | + self.assertEquals( |
1814 | + str(error), "Service unit 'wordpress/0' is already in debug mode.") |
1815 | |
1816 | @inlineCallbacks |
1817 | def test_enable_debug_hook_lifetime(self): |
1818 | |
1819 | === modified file 'ensemble/tests/test_errors.py' |
1820 | --- ensemble/tests/test_errors.py 2011-08-17 17:26:19 +0000 |
1821 | +++ ensemble/tests/test_errors.py 2011-08-26 13:16:39 +0000 |
1822 | @@ -23,20 +23,20 @@ |
1823 | |
1824 | def test_FileNotFound(self): |
1825 | error = FileNotFound("/path") |
1826 | - self.assertEquals(str(error), "File was not found: /path") |
1827 | + self.assertEquals(str(error), "File was not found: '/path'") |
1828 | self.assertIsEnsembleError(error) |
1829 | |
1830 | def test_FileAlreadyExists(self): |
1831 | error = FileAlreadyExists("/path") |
1832 | self.assertEquals(str(error), |
1833 | - "File already exists, won't overwrite: /path") |
1834 | + "File already exists, won't overwrite: '/path'") |
1835 | self.assertIsEnsembleError(error) |
1836 | |
1837 | def test_InvalidEnsembleHeaderValue(self): |
1838 | error = InvalidEnsembleHeaderValue("/path", "xpctd", "fnd") |
1839 | self.assertEquals(str(error), |
1840 | "Expected a YAML file with an 'ensemble: xpctd' " |
1841 | - "header, found 'ensemble: fnd': /path") |
1842 | + "header, found 'ensemble: fnd': '/path'") |
1843 | self.assertIsEnsembleError(error) |
1844 | |
1845 | def test_NoConnection(self): |
1846 | @@ -62,12 +62,9 @@ |
1847 | self.assertIsEnsembleError(error) |
1848 | |
1849 | def test_ProviderInteractionError(self): |
1850 | - error = ProviderInteractionError(OSError("Bad Stuff")) |
1851 | + error = ProviderInteractionError("Bad Stuff") |
1852 | self.assertIsEnsembleError(error) |
1853 | - self.assertEquals( |
1854 | - str(error), |
1855 | - "ProviderError: Interaction with machine provider failed: " |
1856 | - "OSError('Bad Stuff',)") |
1857 | + self.assertEquals(str(error), "Bad Stuff") |
1858 | |
1859 | def test_CannotTerminateMachine(self): |
1860 | error = CannotTerminateMachine(0, "environment would be destroyed") |
1861 | @@ -101,11 +98,4 @@ |
1862 | |
1863 | def testEnvironmentPendingWithInfo(self): |
1864 | error = EnvironmentPending("problem") |
1865 | - self.assertEquals(str(error), |
1866 | - "Ensemble environment is not accessible: problem") |
1867 | - |
1868 | - def testEnvironmentPendingNoInfo(self): |
1869 | - error = EnvironmentPending() |
1870 | - self.assertEquals(str(error), |
1871 | - "Ensemble environment is not accessible: no " |
1872 | - "details available") |
1873 | + self.assertEquals(str(error), "problem") |
1874 | |
1875 | === modified file 'ensemble/unit/tests/test_formula.py' |
1876 | --- ensemble/unit/tests/test_formula.py 2011-08-18 21:33:55 +0000 |
1877 | +++ ensemble/unit/tests/test_formula.py 2011-08-26 13:16:39 +0000 |
1878 | @@ -191,4 +191,5 @@ |
1879 | self.client, "local:mickey-21", formula_directory), |
1880 | FormulaStateNotFound) |
1881 | |
1882 | - self.assertIn("Formula 'local:mickey-21' was not found", str(error)) |
1883 | + self.assertEquals(str(error), |
1884 | + "Formula 'local:mickey-21' was not found") |
Looks good. +1 with these sorted:
[1]
+ raise EnvironmentPend ing("no machines have addresses assigned yet")
Was already like that, but can you please fix the message with s/no/No/?
[2]
+ def _cannot_ connect( self, failure, machine): trap(NoConnecti on, ConnectionTimeo utException) launch_ time: instance_ id, str(failure. value)) )
five_ minutes_ ago = datetime. datetime. now() - timedelta( minutes= 5) instance_ id, str(failure. value)) )
+ failure.
+ if not machine.
+ raise EnvironmentPending(
+ "machine %s may not be ready (%s)"
+ % (machine.
+
if machine.launch_time > five_minutes_ago:
- raise NoConnection("Can't yet connect to started machine: %s" %
- str(failure.value))
+ raise EnvironmentPending(
+ "machine %s is probably not ready (%s)"
+ % (machine.
return failure
Feels like a lot of subtlety and complexity for something simple.
Please kill both launch_time checks, and use this error message:
"Can't connect to machine %s (perhaps still initializing): %s"
As a minor hint for future changes, please note the message
style (capital case, colon before underlying error message).