Merge lp:~fwereade/pyjuju/agent-trash-old-sessions into lp:pyjuju
- agent-trash-old-sessions
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Kapil Thangavelu | ||||
Approved revision: | 485 | ||||
Merged at revision: | 461 | ||||
Proposed branch: | lp:~fwereade/pyjuju/agent-trash-old-sessions | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~fwereade/pyjuju/upstartify-services | ||||
Diff against target: |
970 lines (+304/-68) 30 files modified
examples/oneiric/mysql/hooks/install (+2/-2) examples/oneiric/mysql/hooks/start (+3/-1) examples/oneiric/mysql/hooks/stop (+1/-1) juju/agents/base.py (+56/-3) juju/agents/tests/common.py (+1/-0) juju/agents/tests/test_base.py (+170/-22) juju/agents/tests/test_machine.py (+3/-4) juju/agents/tests/test_unit.py (+3/-1) juju/agents/unit.py (+8/-3) juju/control/tests/test_status.py (+1/-0) juju/machine/tests/test_unit_deployment.py (+3/-1) juju/machine/unit.py (+6/-2) juju/providers/common/cloudinit.py (+4/-2) juju/providers/common/tests/data/cloud_init_bootstrap (+4/-2) juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers (+4/-2) juju/providers/common/tests/data/cloud_init_branch (+2/-1) juju/providers/common/tests/data/cloud_init_branch_trunk (+2/-1) juju/providers/common/tests/data/cloud_init_distro (+2/-1) juju/providers/common/tests/data/cloud_init_ppa (+2/-1) juju/providers/ec2/tests/data/bootstrap_cloud_init (+4/-2) juju/providers/ec2/tests/data/launch_cloud_init (+2/-1) juju/providers/ec2/tests/data/launch_cloud_init_branch (+2/-1) juju/providers/ec2/tests/data/launch_cloud_init_ppa (+2/-1) juju/providers/local/agent.py (+3/-1) juju/providers/local/tests/test_agent.py (+1/-0) juju/providers/orchestra/tests/data/bootstrap_user_data (+4/-2) juju/providers/orchestra/tests/data/launch_user_data (+2/-1) juju/state/tests/test_security.py (+1/-0) juju/unit/lifecycle.py (+0/-4) juju/unit/tests/test_workflow.py (+6/-5) |
||||
To merge this branch: | bzr merge lp:~fwereade/pyjuju/agent-trash-old-sessions | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benjamin Saller (community) | Approve | ||
Kapil Thangavelu (community) | Approve | ||
Review via email: mp+85337@code.launchpad.net |
This proposal supersedes a proposal from 2011-11-30.
Commit message
Description of the change
Agents now require a --session-file argument, which is written to on connect and deleted on close.
If, on startup, the session file exists and refers to a valid session, the session is explicitly closed before any further work is done.
IMO this is the best approach because it minimises the number of cases we have to think about; rather than attempting to re-engage with an existing session (set up watches but skip ephemeral node creation... more considerations?), we always have to set up exactly the same state, whatever the reason we're restarting.
Session files are chmodded to 600 but, on reflection, I'm not sure this actually helps... a malicious hook could still plausibly hijack them. Thoughts?
The cloud-init runcmds have changed again, and these make up a tediously large proportion of the diff. Sorry for the noise :(.
Kapil Thangavelu (hazmat) wrote : Posted in a previous version of this proposal | # |
William Reade (fwereade) wrote : Posted in a previous version of this proposal | # |
rejecting due to abandonment of prereq
William Reade (fwereade) wrote : | # |
...and resubmitting because I've managed to untangle the prereqs.
- 478. By William Reade
-
merge parent
Gustavo Niemeyer (niemeyer) wrote : | # |
> However there is a caveat wrt to session closing and ephemeral nodes that's
> worth keeping in mind, namely that the ephemerals are coordination points for
> things like relation membership notification, and this will result in transient
> -depart/-join hook execution as the old session closes and the new one comes
> into place. I don't see any reasonable alternatives that are significantly more
> complex though.
This is a serious issue IMO, for the same reason that an agent stopping should not
stop the user's agent: a small bug in juju should not kill the highly resilient
and highly tested database server from the user.
There's a simple solution to this problem: anything monitoring an ephemeral presence
node should hold off for a grace period before taking action on it. After the grace
period is over, check back to see if the node is there again. In case the node is
still not around, then follow suit and take action. In case the node is back, re-watch
it and forget about the hiccup.
The same approach should be used across the board with other agents.
- 479. By William Reade
-
merge parent
- 480. By William Reade
-
merge parent
William Reade (fwereade) wrote : | # |
As Gustavo points out, we will indeed need a followup branch to ignore brief agent presence blips.
- 481. By William Reade
-
merge parent
- 482. By William Reade
-
merge parent
Benjamin Saller (bcsaller) wrote : | # |
LGTM + 1
A few minors
[0] test_session_
[1] aggressive use of newline
+ self.assertRaises(
+ JujuError,
+ agent.configure,
+ data)
+
- 483. By William Reade
-
merge parent
- 484. By William Reade
-
merge parent
- 485. By William Reade
-
address review point
- 486. By William Reade
-
merge parent
- 487. By William Reade
-
merge parent
- 488. By William Reade
-
merge parent
- 489. By William Reade
-
merge parent
- 490. By William Reade
-
merge parent
- 491. By William Reade
-
merge parent
- 492. By William Reade
-
merge parent
- 493. By William Reade
-
merge parent
Preview Diff
1 | === modified file 'examples/oneiric/mysql/hooks/install' |
2 | --- examples/oneiric/mysql/hooks/install 2011-09-15 18:56:08 +0000 |
3 | +++ examples/oneiric/mysql/hooks/install 2012-02-21 12:19:20 +0000 |
4 | @@ -21,5 +21,5 @@ |
5 | juju-log "Editing my.cnf to allow listening on all interfaces" |
6 | sed --in-place=old 's/127\.0\.0\.1/0.0.0.0/' /etc/mysql/my.cnf |
7 | |
8 | -juju-log "Restarting mysql service" |
9 | -service mysql restart |
10 | +juju-log "Stopping mysql service" |
11 | +service mysql stop |
12 | |
13 | === modified file 'examples/oneiric/mysql/hooks/start' |
14 | --- examples/oneiric/mysql/hooks/start 2011-02-03 01:23:43 +0000 |
15 | +++ examples/oneiric/mysql/hooks/start 2012-02-21 12:19:20 +0000 |
16 | @@ -1,1 +1,3 @@ |
17 | -#!/bin/bash |
18 | \ No newline at end of file |
19 | +#!/bin/bash |
20 | +juju-log "Starting mysql service" |
21 | +service mysql start || service mysql restart |
22 | |
23 | === modified file 'examples/oneiric/mysql/hooks/stop' |
24 | --- examples/oneiric/mysql/hooks/stop 2011-09-15 18:56:08 +0000 |
25 | +++ examples/oneiric/mysql/hooks/stop 2012-02-21 12:19:20 +0000 |
26 | @@ -1,3 +1,3 @@ |
27 | #!/bin/bash |
28 | juju-log "Stopping mysql service" |
29 | -/etc/init.d/mysql stop |
30 | +service mysql stop || true |
31 | |
32 | === modified file 'juju/agents/base.py' |
33 | --- juju/agents/base.py 2011-09-22 13:23:00 +0000 |
34 | +++ juju/agents/base.py 2012-02-21 12:19:20 +0000 |
35 | @@ -1,7 +1,9 @@ |
36 | import argparse |
37 | import os |
38 | +import logging |
39 | +import stat |
40 | import sys |
41 | -import logging |
42 | +import yaml |
43 | |
44 | import zookeeper |
45 | |
46 | @@ -18,6 +20,23 @@ |
47 | from juju.state.environment import GlobalSettingsStateManager |
48 | |
49 | |
50 | +def load_client_id(path): |
51 | + try: |
52 | + with open(path) as f: |
53 | + return yaml.load(f.read()) |
54 | + except IOError: |
55 | + return None |
56 | + |
57 | + |
58 | +def save_client_id(path, client_id): |
59 | + parent = os.path.dirname(path) |
60 | + if not os.path.exists(parent): |
61 | + os.makedirs(parent) |
62 | + with open(path, "w") as f: |
63 | + f.write(yaml.dump(client_id)) |
64 | + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) |
65 | + |
66 | + |
67 | class TwistedOptionNamespace(object): |
68 | """ |
69 | An argparse namespace implementation that is compatible with twisted |
70 | @@ -153,13 +172,40 @@ |
71 | "Invalid juju-directory %r, does not exist." % ( |
72 | options.get("juju_directory"))) |
73 | |
74 | + if options["session_file"] is None: |
75 | + raise JujuError("No session file specified") |
76 | + |
77 | self.config = options |
78 | |
79 | @inlineCallbacks |
80 | + def _kill_existing_session(self): |
81 | + try: |
82 | + # We might have died suddenly, in which case the session may |
83 | + # still be alive. If this is the case, shoot it in the head, so |
84 | + # it doesn't interfere with our attempts to recreate our state. |
85 | + # (We need to be able to recreate our state *anyway*, and it's |
86 | + # much simpler to force ourselves to recreate it every time than |
87 | + # it is to mess around partially recreating partial state.) |
88 | + client_id = load_client_id(self.config["session_file"]) |
89 | + if client_id is None: |
90 | + return |
91 | + temp_client = yield ZookeeperClient().connect( |
92 | + self.config["zookeeper_servers"], client_id=client_id) |
93 | + yield temp_client.close() |
94 | + except zookeeper.ZooKeeperException: |
95 | + # We don't really care what went wrong; just that we're not able |
96 | + # to connect using the old session, and therefore we should be ok |
97 | + # to start a fresh one without transient state hanging around. |
98 | + pass |
99 | + |
100 | + @inlineCallbacks |
101 | def connect(self): |
102 | """Return an authenticated connection to the juju zookeeper.""" |
103 | - hosts = self.config["zookeeper_servers"] |
104 | - self.client = yield ZookeeperClient().connect(hosts) |
105 | + yield self._kill_existing_session() |
106 | + self.client = yield ZookeeperClient().connect( |
107 | + self.config["zookeeper_servers"]) |
108 | + save_client_id( |
109 | + self.config["session_file"], self.client.client_id) |
110 | |
111 | principals = self.config.get("principals", ()) |
112 | for principal in principals: |
113 | @@ -200,6 +246,9 @@ |
114 | finally: |
115 | if self.client and self.client.connected: |
116 | self.client.close() |
117 | + session_file = self.config["session_file"] |
118 | + if os.path.exists(session_file): |
119 | + os.unlink(session_file) |
120 | |
121 | def set_watch_enabled(self, flag): |
122 | """Set boolean flag for whether this agent should watching zookeeper. |
123 | @@ -285,3 +334,7 @@ |
124 | parser.add_argument( |
125 | "--juju-directory", default=juju_home, type=os.path.abspath, |
126 | help="juju working directory ($JUJU_HOME)") |
127 | + |
128 | + parser.add_argument( |
129 | + "--session-file", default=None, type=os.path.abspath, |
130 | + help="like a pidfile, but for the zookeeper session id") |
131 | |
132 | === modified file 'juju/agents/tests/common.py' |
133 | --- juju/agents/tests/common.py 2011-09-15 19:24:47 +0000 |
134 | +++ juju/agents/tests/common.py 2012-02-21 12:19:20 +0000 |
135 | @@ -55,6 +55,7 @@ |
136 | options = TwistedOptionNamespace() |
137 | options["juju_directory"] = self.juju_directory |
138 | options["zookeeper_servers"] = get_test_zookeeper_address() |
139 | + options["session_file"] = self.makeFile() |
140 | return succeed(options) |
141 | |
142 | @inlineCallbacks |
143 | |
144 | === modified file 'juju/agents/tests/test_base.py' |
145 | --- juju/agents/tests/test_base.py 2012-02-21 12:19:19 +0000 |
146 | +++ juju/agents/tests/test_base.py 2012-02-21 12:19:20 +0000 |
147 | @@ -2,13 +2,14 @@ |
148 | import json |
149 | import logging |
150 | import os |
151 | +import stat |
152 | import sys |
153 | - |
154 | +import yaml |
155 | |
156 | from twisted.application.app import AppLogger |
157 | from twisted.application.service import IService, IServiceCollection |
158 | from twisted.internet.defer import ( |
159 | - succeed, Deferred, inlineCallbacks, returnValue) |
160 | + fail, succeed, Deferred, inlineCallbacks, returnValue) |
161 | from twisted.python.components import Componentized |
162 | from twisted.python import log |
163 | |
164 | @@ -20,6 +21,7 @@ |
165 | |
166 | from juju.agents.base import ( |
167 | BaseAgent, TwistedOptionNamespace, AgentRunner, AgentLogger) |
168 | +from juju.agents.dummy import DummyAgent |
169 | from juju.errors import NoConnection, JujuError |
170 | from juju.lib.zklog import ZookeeperHandler |
171 | |
172 | @@ -34,8 +36,8 @@ |
173 | @inlineCallbacks |
174 | def setUp(self): |
175 | yield super(BaseAgentTest, self).setUp() |
176 | - self.change_environment( |
177 | - JUJU_HOME=self.makeDir()) |
178 | + self.juju_home = self.makeDir() |
179 | + self.change_environment(JUJU_HOME=self.juju_home) |
180 | |
181 | def test_as_app(self): |
182 | """The agent class can be accessed as an application.""" |
183 | @@ -79,6 +81,8 @@ |
184 | # Agent options |
185 | self.assertEqual(parser.get_default("principals"), []) |
186 | self.assertEqual(parser.get_default("zookeeper_servers"), "") |
187 | + self.assertEqual(parser.get_default("juju_directory"), self.juju_home) |
188 | + self.assertEqual(parser.get_default("session_file"), None) |
189 | |
190 | def test_twistd_flags_correspond(self): |
191 | parser = argparse.ArgumentParser() |
192 | @@ -99,7 +103,8 @@ |
193 | log_file_path = self.makeFile() |
194 | |
195 | options = parser.parse_args( |
196 | - ["--logfile", log_file_path], namespace=TwistedOptionNamespace()) |
197 | + ["--logfile", log_file_path, "--session-file", self.makeFile()], |
198 | + namespace=TwistedOptionNamespace()) |
199 | |
200 | def match_observer(observer): |
201 | return isinstance(observer.im_self, log.PythonLoggingObserver) |
202 | @@ -180,8 +185,9 @@ |
203 | This will create an agent instance, parse the cli args, passes them to |
204 | the agent, and starts the agent runner. |
205 | """ |
206 | - self.change_args("es-agent", "--zookeeper-servers", |
207 | - get_test_zookeeper_address()) |
208 | + self.change_args( |
209 | + "es-agent", "--zookeeper-servers", get_test_zookeeper_address(), |
210 | + "--session-file", self.makeFile()) |
211 | runner = self.mocker.patch(AgentRunner) |
212 | runner.run() |
213 | mock_agent = self.mocker.patch(BaseAgent) |
214 | @@ -220,7 +226,8 @@ |
215 | |
216 | self.change_args( |
217 | "es-agent", "--nodaemon", |
218 | - "--zookeeper-servers", get_test_zookeeper_address()) |
219 | + "--zookeeper-servers", get_test_zookeeper_address(), |
220 | + "--session-file", self.makeFile()) |
221 | runner = self.mocker.patch(AgentRunner) |
222 | logger = self.mocker.patch(AppLogger) |
223 | logger.start(MATCH_APP) |
224 | @@ -230,6 +237,7 @@ |
225 | DummyAgent.run() |
226 | return started |
227 | |
228 | + @inlineCallbacks |
229 | def test_stop_service_stub_closes_agent(self): |
230 | """The base class agent, stopService will the stop method. |
231 | |
232 | @@ -238,6 +246,7 @@ |
233 | """ |
234 | mock_agent = self.mocker.patch(BaseAgent) |
235 | mock_client = self.mocker.mock(ZookeeperClient) |
236 | + session_file = self.makeFile() |
237 | |
238 | # connection is closed after agent.stop invoked. |
239 | with self.mocker.order(): |
240 | @@ -259,11 +268,17 @@ |
241 | self.mocker.result(mock_client) |
242 | mock_client.close() |
243 | |
244 | + # delete session file |
245 | + mock_agent.config |
246 | + self.mocker.result({"session_file": session_file}) |
247 | + |
248 | self.mocker.replay() |
249 | |
250 | agent = BaseAgent() |
251 | - return agent.stopService() |
252 | + yield agent.stopService() |
253 | + self.assertFalse(os.path.exists(session_file)) |
254 | |
255 | + @inlineCallbacks |
256 | def test_stop_service_stub_ignores_disconnected_agent(self): |
257 | """The base class agent, stopService will the stop method. |
258 | |
259 | @@ -271,6 +286,7 @@ |
260 | """ |
261 | mock_agent = self.mocker.patch(BaseAgent) |
262 | mock_client = self.mocker.mock(ZookeeperClient) |
263 | + session_file = self.makeFile() |
264 | |
265 | # connection is closed after agent.stop invoked. |
266 | with self.mocker.order(): |
267 | @@ -286,10 +302,14 @@ |
268 | mock_client.connected |
269 | self.mocker.result(False) |
270 | |
271 | + mock_agent.config |
272 | + self.mocker.result({"session_file": session_file}) |
273 | + |
274 | self.mocker.replay() |
275 | |
276 | agent = BaseAgent() |
277 | - return agent.stopService() |
278 | + yield agent.stopService() |
279 | + self.assertFalse(os.path.exists(session_file)) |
280 | |
281 | def test_run_base_raises_error(self): |
282 | """The base class agent, raises a notimplemented error when started.""" |
283 | @@ -297,12 +317,15 @@ |
284 | client.connect(get_test_zookeeper_address()) |
285 | client_mock = self.mocker.mock() |
286 | self.mocker.result(succeed(client_mock)) |
287 | + client_mock.client_id |
288 | + self.mocker.result((123, "abc")) |
289 | self.mocker.replay() |
290 | |
291 | agent = BaseAgent() |
292 | agent.configure({ |
293 | "zookeeper_servers": get_test_zookeeper_address(), |
294 | - "juju_directory": self.makeDir()}) |
295 | + "juju_directory": self.makeDir(), |
296 | + "session_file": self.makeFile()}) |
297 | d = agent.startService() |
298 | self.failUnlessFailure(d, NotImplementedError) |
299 | return d |
300 | @@ -313,35 +336,43 @@ |
301 | client = self.mocker.patch(ZookeeperClient) |
302 | client.connect("x2.example.com") |
303 | self.mocker.result(succeed(mock_client)) |
304 | + mock_client.client_id |
305 | + self.mocker.result((123, "abc")) |
306 | self.mocker.replay() |
307 | |
308 | agent = BaseAgent() |
309 | agent.configure({"zookeeper_servers": "x2.example.com", |
310 | - "juju_directory": self.makeDir()}) |
311 | + "juju_directory": self.makeDir(), |
312 | + "session_file": self.makeFile()}) |
313 | result = agent.connect() |
314 | self.assertEqual(result.result, mock_client) |
315 | self.assertEqual(agent.client, mock_client) |
316 | |
317 | - def test_non_existant_directory(self): |
318 | + def test_nonexistent_directory(self): |
319 | """If the juju directory does not exist an error should be raised. |
320 | """ |
321 | juju_directory = self.makeDir() |
322 | os.rmdir(juju_directory) |
323 | data = {"zookeeper_servers": get_test_zookeeper_address(), |
324 | - "juju_directory": juju_directory} |
325 | + "juju_directory": juju_directory, |
326 | + "session_file": self.makeFile()} |
327 | + self.assertRaises(JujuError, BaseAgent().configure, data) |
328 | |
329 | - agent = BaseAgent() |
330 | - self.assertRaises( |
331 | - JujuError, |
332 | - agent.configure, |
333 | - data) |
334 | + def test_bad_session_file(self): |
335 | + """If the session file cannot be created an error should be raised. |
336 | + """ |
337 | + data = {"zookeeper_servers": get_test_zookeeper_address(), |
338 | + "juju_directory": self.makeDir(), |
339 | + "session_file": None} |
340 | + self.assertRaises(JujuError, BaseAgent().configure, data) |
341 | |
342 | def test_directory_cli_option(self): |
343 | """The juju directory can be configured on the cli.""" |
344 | juju_directory = self.makeDir() |
345 | self.change_args( |
346 | "es-agent", "--zookeeper-servers", get_test_zookeeper_address(), |
347 | - "--juju-directory", juju_directory) |
348 | + "--juju-directory", juju_directory, |
349 | + "--session-file", self.makeFile()) |
350 | |
351 | agent = BaseAgent() |
352 | parser = argparse.ArgumentParser() |
353 | @@ -363,7 +394,9 @@ |
354 | agent = BaseAgent() |
355 | parser = argparse.ArgumentParser() |
356 | agent.setup_options(parser) |
357 | - options = parser.parse_args(namespace=TwistedOptionNamespace()) |
358 | + options = parser.parse_args( |
359 | + ["--session-file", self.makeFile()], |
360 | + namespace=TwistedOptionNamespace()) |
361 | agent.configure(options) |
362 | self.assertEqual( |
363 | agent.config["juju_directory"], juju_directory) |
364 | @@ -379,6 +412,8 @@ |
365 | client = self.mocker.patch(ZookeeperClient) |
366 | client.connect("x1.example.com") |
367 | self.mocker.result(succeed(client)) |
368 | + client.client_id |
369 | + self.mocker.result((123, "abc")) |
370 | client.add_auth("digest", "admin:abc") |
371 | client.add_auth("digest", "agent:xyz") |
372 | client.exists("/") |
373 | @@ -387,7 +422,105 @@ |
374 | agent = BaseAgent() |
375 | parser = argparse.ArgumentParser() |
376 | agent.setup_options(parser) |
377 | - options = parser.parse_args(namespace=TwistedOptionNamespace()) |
378 | + options = parser.parse_args( |
379 | + ["--session-file", self.makeFile()], |
380 | + namespace=TwistedOptionNamespace()) |
381 | + agent.configure(options) |
382 | + d = agent.startService() |
383 | + self.failUnlessFailure(d, NotImplementedError) |
384 | + return d |
385 | + |
386 | + def test_connect_closes_running_session(self): |
387 | + self.change_args("es-agent") |
388 | + self.change_environment( |
389 | + JUJU_HOME=self.makeDir(), |
390 | + JUJU_ZOOKEEPER="x1.example.com") |
391 | + |
392 | + session_file = self.makeFile() |
393 | + with open(session_file, "w") as f: |
394 | + f.write(yaml.dump((123, "abc"))) |
395 | + mock_client_1 = self.mocker.mock() |
396 | + client = self.mocker.patch(ZookeeperClient) |
397 | + client.connect("x1.example.com", client_id=(123, "abc")) |
398 | + self.mocker.result(succeed(mock_client_1)) |
399 | + mock_client_1.close() |
400 | + self.mocker.result(None) |
401 | + |
402 | + mock_client_2 = self.mocker.mock() |
403 | + client.connect("x1.example.com") |
404 | + self.mocker.result(succeed(mock_client_2)) |
405 | + mock_client_2.client_id |
406 | + self.mocker.result((456, "def")) |
407 | + self.mocker.replay() |
408 | + |
409 | + agent = BaseAgent() |
410 | + parser = argparse.ArgumentParser() |
411 | + agent.setup_options(parser) |
412 | + options = parser.parse_args( |
413 | + ["--session-file", session_file], |
414 | + namespace=TwistedOptionNamespace()) |
415 | + agent.configure(options) |
416 | + d = agent.startService() |
417 | + self.failUnlessFailure(d, NotImplementedError) |
418 | + return d |
419 | + |
420 | + def test_connect_handles_expired_session(self): |
421 | + self.change_args("es-agent") |
422 | + self.change_environment( |
423 | + JUJU_HOME=self.makeDir(), |
424 | + JUJU_ZOOKEEPER="x1.example.com") |
425 | + |
426 | + session_file = self.makeFile() |
427 | + with open(session_file, "w") as f: |
428 | + f.write(yaml.dump((123, "abc"))) |
429 | + client = self.mocker.patch(ZookeeperClient) |
430 | + client.connect("x1.example.com", client_id=(123, "abc")) |
431 | + self.mocker.result(fail(zookeeper.SessionExpiredException())) |
432 | + |
433 | + mock_client = self.mocker.mock() |
434 | + client.connect("x1.example.com") |
435 | + self.mocker.result(succeed(mock_client)) |
436 | + mock_client.client_id |
437 | + self.mocker.result((456, "def")) |
438 | + self.mocker.replay() |
439 | + |
440 | + agent = BaseAgent() |
441 | + parser = argparse.ArgumentParser() |
442 | + agent.setup_options(parser) |
443 | + options = parser.parse_args( |
444 | + ["--session-file", session_file], |
445 | + namespace=TwistedOptionNamespace()) |
446 | + agent.configure(options) |
447 | + d = agent.startService() |
448 | + self.failUnlessFailure(d, NotImplementedError) |
449 | + return d |
450 | + |
451 | + def test_connect_handles_nonsense_session(self): |
452 | + self.change_args("es-agent") |
453 | + self.change_environment( |
454 | + JUJU_HOME=self.makeDir(), |
455 | + JUJU_ZOOKEEPER="x1.example.com") |
456 | + |
457 | + session_file = self.makeFile() |
458 | + with open(session_file, "w") as f: |
459 | + f.write(yaml.dump("cheesy wotsits")) |
460 | + client = self.mocker.patch(ZookeeperClient) |
461 | + client.connect("x1.example.com", client_id="cheesy wotsits") |
462 | + self.mocker.result(fail(zookeeper.ZooKeeperException())) |
463 | + |
464 | + mock_client = self.mocker.mock() |
465 | + client.connect("x1.example.com") |
466 | + self.mocker.result(succeed(mock_client)) |
467 | + mock_client.client_id |
468 | + self.mocker.result((456, "def")) |
469 | + self.mocker.replay() |
470 | + |
471 | + agent = BaseAgent() |
472 | + parser = argparse.ArgumentParser() |
473 | + agent.setup_options(parser) |
474 | + options = parser.parse_args( |
475 | + ["--session-file", session_file], |
476 | + namespace=TwistedOptionNamespace()) |
477 | agent.configure(options) |
478 | d = agent.startService() |
479 | self.failUnlessFailure(d, NotImplementedError) |
480 | @@ -405,6 +538,21 @@ |
481 | agent.set_watch_enabled(False) |
482 | self.assertFalse(agent.get_watch_enabled()) |
483 | |
484 | + @inlineCallbacks |
485 | + def test_session_file_permissions(self): |
486 | + session_file = self.makeFile() |
487 | + agent = DummyAgent() |
488 | + agent.configure({ |
489 | + "session_file": session_file, |
490 | + "juju_directory": self.makeDir(), |
491 | + "zookeeper_servers": get_test_zookeeper_address()}) |
492 | + yield agent.startService() |
493 | + mode = os.stat(session_file).st_mode |
494 | + mask = stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO |
495 | + self.assertEquals(mode & mask, stat.S_IRUSR | stat.S_IWUSR) |
496 | + yield agent.stopService() |
497 | + self.assertFalse(os.path.exists(session_file)) |
498 | + |
499 | |
500 | class AgentDebugLogSettingsWatch(AgentTestBase): |
501 | |
502 | |
503 | === modified file 'juju/agents/tests/test_machine.py' |
504 | --- juju/agents/tests/test_machine.py 2011-10-05 13:59:44 +0000 |
505 | +++ juju/agents/tests/test_machine.py 2012-02-21 12:19:20 +0000 |
506 | @@ -120,10 +120,9 @@ |
507 | # initially setup by get_agent_config in setUp |
508 | self.change_environment(JUJU_MACHINE_ID="") |
509 | self.change_args("es-agent", |
510 | - "--zookeeper-servers", |
511 | - get_test_zookeeper_address(), |
512 | - "--juju-directory", |
513 | - self.makeDir()) |
514 | + "--zookeeper-servers", get_test_zookeeper_address(), |
515 | + "--juju-directory", self.makeDir(), |
516 | + "--session-file", self.makeFile()) |
517 | parser = argparse.ArgumentParser() |
518 | self.agent.setup_options(parser) |
519 | options = parser.parse_args(namespace=TwistedOptionNamespace()) |
520 | |
521 | === modified file 'juju/agents/tests/test_unit.py' |
522 | --- juju/agents/tests/test_unit.py 2012-02-21 12:19:19 +0000 |
523 | +++ juju/agents/tests/test_unit.py 2012-02-21 12:19:20 +0000 |
524 | @@ -186,7 +186,8 @@ |
525 | self.change_args( |
526 | "unit-agent", |
527 | "--juju-directory", self.makeDir(), |
528 | - "--zookeeper-servers", get_test_zookeeper_address()) |
529 | + "--zookeeper-servers", get_test_zookeeper_address(), |
530 | + "--session-file", self.makeFile()) |
531 | |
532 | parser = argparse.ArgumentParser() |
533 | self.agent.setup_options(parser) |
534 | @@ -218,6 +219,7 @@ |
535 | options = {} |
536 | options["juju_directory"] = self.juju_directory |
537 | options["zookeeper_servers"] = get_test_zookeeper_address() |
538 | + options["session_file"] = self.makeFile() |
539 | options["unit_name"] = "rabbit-1" |
540 | agent = self.agent_class() |
541 | agent.configure(options) |
542 | |
543 | === modified file 'juju/agents/unit.py' |
544 | --- juju/agents/unit.py 2012-02-21 12:19:19 +0000 |
545 | +++ juju/agents/unit.py 2012-02-21 12:19:20 +0000 |
546 | @@ -78,10 +78,11 @@ |
547 | self.config["juju_directory"], "state") |
548 | |
549 | # Setup the server portion of the cli api exposed to hooks. |
550 | + socket_path = os.path.join(self.unit_directory, HOOK_SOCKET_FILE) |
551 | + if os.path.exists(socket_path): |
552 | + os.unlink(socket_path) |
553 | from twisted.internet import reactor |
554 | - self.api_socket = reactor.listenUNIX( |
555 | - os.path.join(self.unit_directory, HOOK_SOCKET_FILE), |
556 | - self.api_factory) |
557 | + self.api_socket = reactor.listenUNIX(socket_path, self.api_factory) |
558 | |
559 | # Setup the unit state's address |
560 | address = yield get_unit_address(self.client) |
561 | @@ -96,6 +97,10 @@ |
562 | # Inform the system, we're alive. |
563 | yield self.unit_state.connect_agent() |
564 | |
565 | + # Start paying attention to the debug-log setting |
566 | + if self.get_watch_enabled(): |
567 | + yield self.unit_state.watch_hook_debug(self.cb_watch_hook_debug) |
568 | + |
569 | self.lifecycle = UnitLifecycle( |
570 | self.client, self.unit_state, self.service_state, |
571 | self.unit_directory, self.state_directory, self.executor) |
572 | |
573 | === modified file 'juju/control/tests/test_status.py' |
574 | --- juju/control/tests/test_status.py 2012-02-21 12:19:19 +0000 |
575 | +++ juju/control/tests/test_status.py 2012-02-21 12:19:20 +0000 |
576 | @@ -276,6 +276,7 @@ |
577 | options = TwistedOptionNamespace() |
578 | options["juju_directory"] = self.makeDir() |
579 | options["zookeeper_servers"] = get_test_zookeeper_address() |
580 | + options["session_file"] = self.makeFile() |
581 | options["machine_id"] = "0" |
582 | agent.configure(options) |
583 | agent.set_watch_enabled(False) |
584 | |
585 | === modified file 'juju/machine/tests/test_unit_deployment.py' |
586 | --- juju/machine/tests/test_unit_deployment.py 2012-02-21 12:19:19 +0000 |
587 | +++ juju/machine/tests/test_unit_deployment.py 2012-02-21 12:19:20 +0000 |
588 | @@ -131,7 +131,9 @@ |
589 | self.deployment.directory, "charm.log") |
590 | command = " ".join([ |
591 | "/usr/bin/python", "-m", "juju.agents.dummy", "--nodaemon", |
592 | - "--logfile", log_file, ">> /tmp/juju-wordpress-0.output 2>&1"]) |
593 | + "--logfile", log_file, "--session-file", |
594 | + "/var/run/juju/unit-wordpress-0-agent.zksession", |
595 | + ">> /tmp/juju-wordpress-0.output 2>&1"]) |
596 | self.assertEquals(exec_, command) |
597 | d.addCallback(verify_upstart) |
598 | return d |
599 | |
600 | === modified file 'juju/machine/unit.py' |
601 | --- juju/machine/unit.py 2012-02-21 12:19:19 +0000 |
602 | +++ juju/machine/unit.py 2012-02-21 12:19:20 +0000 |
603 | @@ -73,7 +73,9 @@ |
604 | self.service.set_command(" ".join(( |
605 | "/usr/bin/python", "-m", self.unit_agent_module, |
606 | "--nodaemon", |
607 | - "--logfile", os.path.join(self.directory, "charm.log")))) |
608 | + "--logfile", os.path.join(self.directory, "charm.log"), |
609 | + "--session-file", |
610 | + "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) |
611 | try: |
612 | yield self.service.start() |
613 | except ServiceError as e: |
614 | @@ -227,7 +229,9 @@ |
615 | "/usr/bin/python", |
616 | "-m", "juju.agents.unit", |
617 | "--nodaemon", |
618 | - "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name))) |
619 | + "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name, |
620 | + "--session-file", |
621 | + "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) |
622 | yield service.install() |
623 | |
624 | # Create symlinks on the host for easier access to the unit log files |
625 | |
626 | === modified file 'juju/providers/common/cloudinit.py' |
627 | --- juju/providers/common/cloudinit.py 2012-02-21 12:19:19 +0000 |
628 | +++ juju/providers/common/cloudinit.py 2012-02-21 12:19:20 +0000 |
629 | @@ -48,7 +48,8 @@ |
630 | {"JUJU_MACHINE_ID": machine_id, "JUJU_ZOOKEEPER": zookeeper_hosts}) |
631 | service.set_command( |
632 | "python -m juju.agents.machine --nodaemon " |
633 | - "--logfile /var/log/juju/machine-agent.log") |
634 | + "--logfile /var/log/juju/machine-agent.log " |
635 | + "--session-file /var/run/juju/machine-agent.zksession") |
636 | return service.get_cloud_init_commands() |
637 | |
638 | |
639 | @@ -58,7 +59,8 @@ |
640 | service.set_environ({"JUJU_ZOOKEEPER": zookeeper_hosts}) |
641 | service.set_command( |
642 | "python -m juju.agents.provision --nodaemon " |
643 | - "--logfile /var/log/juju/provision-agent.log") |
644 | + "--logfile /var/log/juju/provision-agent.log " |
645 | + "--session-file /var/run/juju/provision-agent.zksession") |
646 | return service.get_cloud_init_commands() |
647 | |
648 | |
649 | |
650 | === modified file 'juju/providers/common/tests/data/cloud_init_bootstrap' |
651 | --- juju/providers/common/tests/data/cloud_init_bootstrap 2012-02-21 12:19:19 +0000 |
652 | +++ juju/providers/common/tests/data/cloud_init_bootstrap 2012-02-21 12:19:20 +0000 |
653 | @@ -28,7 +28,8 @@ |
654 | |
655 | |
656 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
657 | - >> /tmp/juju-machine-agent.output 2>&1 |
658 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
659 | + 2>&1 |
660 | |
661 | EOF |
662 | |
663 | @@ -51,7 +52,8 @@ |
664 | |
665 | |
666 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
667 | - >> /tmp/juju-provision-agent.output 2>&1 |
668 | + --session-file /var/run/juju/provision-agent.zksession >> /tmp/juju-provision-agent.output |
669 | + 2>&1 |
670 | |
671 | EOF |
672 | |
673 | |
674 | === modified file 'juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers' |
675 | --- juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers 2012-02-21 12:19:19 +0000 |
676 | +++ juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers 2012-02-21 12:19:20 +0000 |
677 | @@ -28,7 +28,8 @@ |
678 | |
679 | |
680 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
681 | - >> /tmp/juju-machine-agent.output 2>&1 |
682 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
683 | + 2>&1 |
684 | |
685 | EOF |
686 | |
687 | @@ -51,7 +52,8 @@ |
688 | |
689 | |
690 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
691 | - >> /tmp/juju-provision-agent.output 2>&1 |
692 | + --session-file /var/run/juju/provision-agent.zksession >> /tmp/juju-provision-agent.output |
693 | + 2>&1 |
694 | |
695 | EOF |
696 | |
697 | |
698 | === modified file 'juju/providers/common/tests/data/cloud_init_branch' |
699 | --- juju/providers/common/tests/data/cloud_init_branch 2012-02-21 12:19:19 +0000 |
700 | +++ juju/providers/common/tests/data/cloud_init_branch 2012-02-21 12:19:20 +0000 |
701 | @@ -30,7 +30,8 @@ |
702 | |
703 | |
704 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
705 | - >> /tmp/juju-machine-agent.output 2>&1 |
706 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
707 | + 2>&1 |
708 | |
709 | EOF |
710 | |
711 | |
712 | === modified file 'juju/providers/common/tests/data/cloud_init_branch_trunk' |
713 | --- juju/providers/common/tests/data/cloud_init_branch_trunk 2012-02-21 12:19:19 +0000 |
714 | +++ juju/providers/common/tests/data/cloud_init_branch_trunk 2012-02-21 12:19:20 +0000 |
715 | @@ -30,7 +30,8 @@ |
716 | |
717 | |
718 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
719 | - >> /tmp/juju-machine-agent.output 2>&1 |
720 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
721 | + 2>&1 |
722 | |
723 | EOF |
724 | |
725 | |
726 | === modified file 'juju/providers/common/tests/data/cloud_init_distro' |
727 | --- juju/providers/common/tests/data/cloud_init_distro 2012-02-21 12:19:19 +0000 |
728 | +++ juju/providers/common/tests/data/cloud_init_distro 2012-02-21 12:19:20 +0000 |
729 | @@ -26,7 +26,8 @@ |
730 | |
731 | |
732 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
733 | - >> /tmp/juju-machine-agent.output 2>&1 |
734 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
735 | + 2>&1 |
736 | |
737 | EOF |
738 | |
739 | |
740 | === modified file 'juju/providers/common/tests/data/cloud_init_ppa' |
741 | --- juju/providers/common/tests/data/cloud_init_ppa 2012-02-21 12:19:19 +0000 |
742 | +++ juju/providers/common/tests/data/cloud_init_ppa 2012-02-21 12:19:20 +0000 |
743 | @@ -28,7 +28,8 @@ |
744 | |
745 | |
746 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
747 | - >> /tmp/juju-machine-agent.output 2>&1 |
748 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
749 | + 2>&1 |
750 | |
751 | EOF |
752 | |
753 | |
754 | === modified file 'juju/providers/ec2/tests/data/bootstrap_cloud_init' |
755 | --- juju/providers/ec2/tests/data/bootstrap_cloud_init 2012-02-21 12:19:19 +0000 |
756 | +++ juju/providers/ec2/tests/data/bootstrap_cloud_init 2012-02-21 12:19:20 +0000 |
757 | @@ -28,7 +28,8 @@ |
758 | |
759 | |
760 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
761 | - >> /tmp/juju-machine-agent.output 2>&1 |
762 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
763 | + 2>&1 |
764 | |
765 | EOF |
766 | |
767 | @@ -51,7 +52,8 @@ |
768 | |
769 | |
770 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
771 | - >> /tmp/juju-provision-agent.output 2>&1 |
772 | + --session-file /var/run/juju/provision-agent.zksession >> /tmp/juju-provision-agent.output |
773 | + 2>&1 |
774 | |
775 | EOF |
776 | |
777 | |
778 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init' |
779 | --- juju/providers/ec2/tests/data/launch_cloud_init 2012-02-21 12:19:19 +0000 |
780 | +++ juju/providers/ec2/tests/data/launch_cloud_init 2012-02-21 12:19:20 +0000 |
781 | @@ -26,7 +26,8 @@ |
782 | |
783 | |
784 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
785 | - >> /tmp/juju-machine-agent.output 2>&1 |
786 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
787 | + 2>&1 |
788 | |
789 | EOF |
790 | |
791 | |
792 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init_branch' |
793 | --- juju/providers/ec2/tests/data/launch_cloud_init_branch 2012-02-21 12:19:19 +0000 |
794 | +++ juju/providers/ec2/tests/data/launch_cloud_init_branch 2012-02-21 12:19:20 +0000 |
795 | @@ -30,7 +30,8 @@ |
796 | |
797 | |
798 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
799 | - >> /tmp/juju-machine-agent.output 2>&1 |
800 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
801 | + 2>&1 |
802 | |
803 | EOF |
804 | |
805 | |
806 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init_ppa' |
807 | --- juju/providers/ec2/tests/data/launch_cloud_init_ppa 2012-02-21 12:19:19 +0000 |
808 | +++ juju/providers/ec2/tests/data/launch_cloud_init_ppa 2012-02-21 12:19:20 +0000 |
809 | @@ -28,7 +28,8 @@ |
810 | |
811 | |
812 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
813 | - >> /tmp/juju-machine-agent.output 2>&1 |
814 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
815 | + 2>&1 |
816 | |
817 | EOF |
818 | |
819 | |
820 | === modified file 'juju/providers/local/agent.py' |
821 | --- juju/providers/local/agent.py 2012-02-21 12:19:19 +0000 |
822 | +++ juju/providers/local/agent.py 2012-02-21 12:19:20 +0000 |
823 | @@ -49,7 +49,9 @@ |
824 | self._service.set_environ(env) |
825 | self._service_args = [ |
826 | "/usr/bin/python", "-m", self.agent_module, |
827 | - "--nodaemon", "--logfile", log_file] |
828 | + "--nodaemon", "--logfile", log_file, |
829 | + "--session-file", |
830 | + "/var/run/juju/%s-machine-agent.zksession" % juju_unit_namespace] |
831 | |
832 | @property |
833 | def juju_origin(self): |
834 | |
835 | === modified file 'juju/providers/local/tests/test_agent.py' |
836 | --- juju/providers/local/tests/test_agent.py 2012-02-21 12:19:19 +0000 |
837 | +++ juju/providers/local/tests/test_agent.py 2012-02-21 12:19:20 +0000 |
838 | @@ -75,6 +75,7 @@ |
839 | |
840 | expect_exec = ( |
841 | "/usr/bin/python -m juju.agents.machine --nodaemon --logfile %s " |
842 | + "--session-file /var/run/juju/ns1-machine-agent.zksession " |
843 | ">> /tmp/juju-ns1-machine-agent.output 2>&1" |
844 | % log_file) |
845 | self.assertEquals(exec_, expect_exec) |
846 | |
847 | === modified file 'juju/providers/orchestra/tests/data/bootstrap_user_data' |
848 | --- juju/providers/orchestra/tests/data/bootstrap_user_data 2012-02-21 12:19:19 +0000 |
849 | +++ juju/providers/orchestra/tests/data/bootstrap_user_data 2012-02-21 12:19:20 +0000 |
850 | @@ -27,7 +27,8 @@ |
851 | |
852 | |
853 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
854 | - >> /tmp/juju-machine-agent.output 2>&1 |
855 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
856 | + 2>&1 |
857 | |
858 | EOF |
859 | |
860 | @@ -50,7 +51,8 @@ |
861 | |
862 | |
863 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
864 | - >> /tmp/juju-provision-agent.output 2>&1 |
865 | + --session-file /var/run/juju/provision-agent.zksession >> /tmp/juju-provision-agent.output |
866 | + 2>&1 |
867 | |
868 | EOF |
869 | |
870 | |
871 | === modified file 'juju/providers/orchestra/tests/data/launch_user_data' |
872 | --- juju/providers/orchestra/tests/data/launch_user_data 2012-02-21 12:19:19 +0000 |
873 | +++ juju/providers/orchestra/tests/data/launch_user_data 2012-02-21 12:19:20 +0000 |
874 | @@ -25,7 +25,8 @@ |
875 | |
876 | |
877 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
878 | - >> /tmp/juju-machine-agent.output 2>&1 |
879 | + --session-file /var/run/juju/machine-agent.zksession >> /tmp/juju-machine-agent.output |
880 | + 2>&1 |
881 | |
882 | EOF |
883 | |
884 | |
885 | === modified file 'juju/state/tests/test_security.py' |
886 | --- juju/state/tests/test_security.py 2011-12-08 20:17:21 +0000 |
887 | +++ juju/state/tests/test_security.py 2012-02-21 12:19:20 +0000 |
888 | @@ -67,6 +67,7 @@ |
889 | content, stat = yield client.get("/acl-test") |
890 | self.assertEqual(content, "content") |
891 | |
892 | + |
893 | class GroupPrincipalTests(TestCase): |
894 | |
895 | @inlineCallbacks |
896 | |
897 | === modified file 'juju/unit/lifecycle.py' |
898 | --- juju/unit/lifecycle.py 2012-02-21 12:19:19 +0000 |
899 | +++ juju/unit/lifecycle.py 2012-02-21 12:19:20 +0000 |
900 | @@ -145,10 +145,6 @@ |
901 | watches = [] |
902 | |
903 | try: |
904 | - # Verify current state |
905 | - assert not self._running, "Already started" |
906 | - |
907 | - # Execute the start hook |
908 | if fire_hooks: |
909 | yield self._execute_hook("config-changed") |
910 | yield self._execute_hook("start") |
911 | |
912 | === modified file 'juju/unit/tests/test_workflow.py' |
913 | --- juju/unit/tests/test_workflow.py 2012-02-21 12:19:19 +0000 |
914 | +++ juju/unit/tests/test_workflow.py 2012-02-21 12:19:20 +0000 |
915 | @@ -522,7 +522,6 @@ |
916 | yield self.assert_transition("upgrade_charm") |
917 | yield self.assert_state("started") |
918 | yield self.assert_charm_upgraded(True) |
919 | - |
920 | self.assert_hooks( |
921 | "install", "config-changed", "start", "upgrade-charm") |
922 | yield self.assert_history_concise( |
923 | @@ -530,12 +529,14 @@ |
924 | ("start", "started"), |
925 | ("upgrade_charm", "started")) |
926 | |
927 | + |
928 | @inlineCallbacks |
929 | def test_upgrade_error_retry(self): |
930 | """A hook error during an upgrade transitions to |
931 | upgrade_error. |
932 | """ |
933 | yield self.assert_transition("install") |
934 | + self.write_exit_hook("upgrade-charm", 1) |
935 | yield self.assert_state("started") |
936 | yield self.ready_upgrade(True) |
937 | |
938 | @@ -543,7 +544,8 @@ |
939 | yield self.assert_transition("upgrade_charm", False) |
940 | yield self.assert_state("charm_upgrade_error") |
941 | self.assertFalse(self.executor.running) |
942 | - # The upgrade should complete before the hook blows up. |
943 | + |
944 | + # The upgrade should have completed before the hook blew up. |
945 | yield self.assert_charm_upgraded(True) |
946 | |
947 | # The bad hook is still in place, but we don't run it again |
948 | @@ -551,7 +553,6 @@ |
949 | yield self.assert_state("started") |
950 | yield self.assert_charm_upgraded(True) |
951 | self.assertTrue(self.executor.running) |
952 | - |
953 | self.assert_hooks( |
954 | "install", "config-changed", "start", "upgrade-charm") |
955 | yield self.assert_history_concise( |
956 | @@ -568,12 +569,12 @@ |
957 | yield self.assert_transition("install") |
958 | yield self.assert_state("started") |
959 | yield self.ready_upgrade(True) |
960 | - |
961 | yield self.assert_charm_upgraded(False) |
962 | yield self.assert_transition("upgrade_charm", False) |
963 | yield self.assert_state("charm_upgrade_error") |
964 | self.assertFalse(self.executor.running) |
965 | - # The upgrade should complete before the hook blows up. |
966 | + |
967 | + # The upgrade should have completed before the hook blew up. |
968 | yield self.assert_charm_upgraded(True) |
969 | |
970 | yield self.assert_transition("retry_upgrade_charm_hook", False) |
Excerpts from William Reade's message of Wed Nov 30 09:56:37 UTC 2011: /bugs.launchpad .net/juju/ +bug/898082 /code.launchpad .net/~fwereade/ juju/agent- trash-old- sessions/ +merge/ 83905
> William Reade has proposed merging lp:~fwereade/juju/agent-trash-old-sessions into lp:juju with lp:~fwereade/juju/upstartify-agents as a prerequisite.
>
> Requested reviews:
> juju hackers (juju)
> Related bugs:
> Bug #898082 in juju: "immediate agent restart fails"
> https:/
>
> For more details, see:
> https:/
>
> Agents now require a --session-file argument, which is written to on connect and deleted on close.
>
> If, on startup, the session file exists and refers to a valid session, the session is explicitly closed before any further work is done.
>
> IMO this is the best approach because it minimises the number of cases we have to think about; rather than attempting to re-engage with an existing session (set up watches but skip ephemeral node creation... more considerations?), we always have to set up exactly the same state, whatever the reason we're restarting.
>
> Session files are chmodded to 600 but, on reflection, I'm not sure this actually helps... a malicious hook could still plausibly hijack them. Thoughts?
>
> The cloud-init runcmds have changed again, and these make up a tediously large proportion of the diff. Sorry for the noise :(.
Haven't looked at the diff, just a comment on the approach, this seems pretty
reasonable and simple. The watch callbacks (transient registration with libzk)
are gone so we can't usefully reconnect to the original session watches, we'd
have to restablish them anyways. Effectively sessions are primarily useful for
transient error activity not process restarts.
However there is a caveat wrt to session closing and ephemeral nodes that's
worth keeping in mind, namely that the ephemerals are coordination points for
things like relation membership notification, and this will result in transient
-depart/-join hook execution as the old session closes and the new one comes
into place. I don't see any reasonable alternatives that are significantly more
complex though.