Merge lp:~fwereade/pyjuju/agent-trash-old-sessions into lp:pyjuju
- agent-trash-old-sessions
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~fwereade/pyjuju/agent-trash-old-sessions | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~fwereade/pyjuju/upstartify-agents | ||||
Diff against target: |
911 lines (+293/-49) 29 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 (+175/-17) 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 (+2/-0) juju/lib/statemachine.py (+1/-1) 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 (+2/-0) juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers (+2/-0) juju/providers/common/tests/data/cloud_init_branch (+1/-0) juju/providers/common/tests/data/cloud_init_branch_trunk (+1/-0) juju/providers/common/tests/data/cloud_init_distro (+1/-0) juju/providers/common/tests/data/cloud_init_ppa (+1/-0) juju/providers/ec2/tests/data/bootstrap_cloud_init (+2/-0) juju/providers/ec2/tests/data/launch_cloud_init (+1/-0) juju/providers/ec2/tests/data/launch_cloud_init_branch (+1/-0) juju/providers/ec2/tests/data/launch_cloud_init_ppa (+1/-0) juju/providers/local/agent.py (+2/-1) juju/providers/local/tests/test_agent.py (+4/-3) juju/providers/orchestra/tests/data/bootstrap_user_data (+2/-0) juju/providers/orchestra/tests/data/launch_user_data (+1/-0) juju/unit/lifecycle.py (+3/-7) |
||||
To merge this branch: | bzr merge lp:~fwereade/pyjuju/agent-trash-old-sessions | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+83905@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-12-12.
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 : | # |
- 476. By William Reade
-
merge parent
William Reade (fwereade) wrote : | # |
rejecting due to abandonment of prereq
- 477. By William Reade
-
agents now require --session-file, while will be used to shoot old sessions in the head when the agent comes up, to ensure we're starting from clean ZK state
- 478. By William Reade
-
merge parent
- 479. By William Reade
-
merge parent
- 480. By William Reade
-
merge parent
- 481. By William Reade
-
merge parent
- 482. By William Reade
-
merge parent
- 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
Unmerged revisions
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 2011-12-02 14:44:25 +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 2011-12-02 14:44:25 +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 2011-12-02 14:44:25 +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 2011-12-02 14:44:25 +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 2011-12-02 14:44:25 +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 2011-12-02 14:44:24 +0000 |
146 | +++ juju/agents/tests/test_base.py 2011-12-02 14:44:25 +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,22 +336,39 @@ |
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 | + |
328 | + agent = BaseAgent() |
329 | + self.assertRaises( |
330 | + JujuError, |
331 | + agent.configure, |
332 | + data) |
333 | + |
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 | |
341 | agent = BaseAgent() |
342 | self.assertRaises( |
343 | @@ -341,7 +381,8 @@ |
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 +404,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 +422,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 +432,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 +548,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 2011-12-02 14:44:25 +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 2011-12-02 14:44:24 +0000 |
523 | +++ juju/agents/tests/test_unit.py 2011-12-02 14:44:25 +0000 |
524 | @@ -170,7 +170,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 | @@ -202,6 +203,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 2011-12-02 14:44:24 +0000 |
545 | +++ juju/agents/unit.py 2011-12-02 14:44:25 +0000 |
546 | @@ -80,10 +80,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 | @@ -98,6 +99,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, |
571 | self.unit_state, |
572 | |
573 | === modified file 'juju/control/tests/test_status.py' |
574 | --- juju/control/tests/test_status.py 2011-11-30 00:57:05 +0000 |
575 | +++ juju/control/tests/test_status.py 2011-12-02 14:44:25 +0000 |
576 | @@ -107,6 +107,7 @@ |
577 | options = TwistedOptionNamespace() |
578 | options["juju_directory"] = path |
579 | options["zookeeper_servers"] = get_test_zookeeper_address() |
580 | + options["session_file"] = self.makeFile() |
581 | for k, v in extra_options.items(): |
582 | options[k] = v |
583 | agent.configure(options) |
584 | @@ -302,6 +303,7 @@ |
585 | options = TwistedOptionNamespace() |
586 | options["juju_directory"] = self.makeDir() |
587 | options["zookeeper_servers"] = get_test_zookeeper_address() |
588 | + options["session_file"] = self.makeFile() |
589 | options["machine_id"] = "0" |
590 | agent.configure(options) |
591 | agent.set_watch_enabled(False) |
592 | |
593 | === modified file 'juju/lib/statemachine.py' |
594 | --- juju/lib/statemachine.py 2011-05-04 19:40:59 +0000 |
595 | +++ juju/lib/statemachine.py 2011-12-02 14:44:25 +0000 |
596 | @@ -213,7 +213,7 @@ |
597 | state_dict = yield self._load() |
598 | if not state_dict: |
599 | returnValue({}) |
600 | - returnValue(state_dict["state_variables"]) |
601 | + returnValue(state_dict.get("state_variables", {})) |
602 | |
603 | def set_observer(self, observer): |
604 | """Set a callback, that will be notified on state changes. |
605 | |
606 | === modified file 'juju/machine/tests/test_unit_deployment.py' |
607 | --- juju/machine/tests/test_unit_deployment.py 2011-12-02 14:44:24 +0000 |
608 | +++ juju/machine/tests/test_unit_deployment.py 2011-12-02 14:44:25 +0000 |
609 | @@ -114,7 +114,9 @@ |
610 | self.deployment.directory, "charm.log") |
611 | command = " ".join([ |
612 | sys.executable, "-m", "juju.agents.dummy", |
613 | - "--nodaemon", "--logfile", log_file]) |
614 | + "--nodaemon", "--logfile", log_file, |
615 | + "--session-file", |
616 | + "/var/run/juju/unit-wordpress-0-agent.zksession"]) |
617 | self.assertEquals(exec_, command) |
618 | d.addCallback(verify_upstart) |
619 | return d |
620 | |
621 | === modified file 'juju/machine/unit.py' |
622 | --- juju/machine/unit.py 2011-12-02 14:44:24 +0000 |
623 | +++ juju/machine/unit.py 2011-12-02 14:44:25 +0000 |
624 | @@ -87,7 +87,9 @@ |
625 | self.service.set_command(" ".join(( |
626 | sys.executable, "-m", self.unit_agent_module, |
627 | "--nodaemon", |
628 | - "--logfile", os.path.join(self.directory, "charm.log")))) |
629 | + "--logfile", os.path.join(self.directory, "charm.log"), |
630 | + "--session-file", |
631 | + "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) |
632 | return self.service.start() |
633 | |
634 | @inlineCallbacks |
635 | @@ -230,7 +232,9 @@ |
636 | sys.executable, |
637 | "-m", "juju.agents.unit", |
638 | "--nodaemon", |
639 | - "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name))) |
640 | + "--logfile", "/var/log/juju/unit-%s.log" % self.unit_path_name, |
641 | + "--session-file", |
642 | + "/var/run/juju/unit-%s-agent.zksession" % self.unit_path_name))) |
643 | yield service.install() |
644 | |
645 | # Create a symlink on the host for easier access to the unit log file |
646 | |
647 | === modified file 'juju/providers/common/cloudinit.py' |
648 | --- juju/providers/common/cloudinit.py 2011-12-02 14:44:24 +0000 |
649 | +++ juju/providers/common/cloudinit.py 2011-12-02 14:44:25 +0000 |
650 | @@ -48,7 +48,8 @@ |
651 | {"JUJU_MACHINE_ID": machine_id, "JUJU_ZOOKEEPER": zookeeper_hosts}) |
652 | service.set_command( |
653 | "python -m juju.agents.machine --nodaemon " |
654 | - "--logfile /var/log/juju/machine-agent.log") |
655 | + "--logfile /var/log/juju/machine-agent.log " |
656 | + "--session-file /var/run/juju/machine-agent.zksession") |
657 | return service.get_cloud_init_commands() |
658 | |
659 | |
660 | @@ -58,7 +59,8 @@ |
661 | service.set_environ({"JUJU_ZOOKEEPER": zookeeper_hosts}) |
662 | service.set_command( |
663 | "python -m juju.agents.provision --nodaemon " |
664 | - "--logfile /var/log/juju/provision-agent.log") |
665 | + "--logfile /var/log/juju/provision-agent.log " |
666 | + "--session-file /var/run/juju/provision-agent.zksession") |
667 | return service.get_cloud_init_commands() |
668 | |
669 | |
670 | |
671 | === modified file 'juju/providers/common/tests/data/cloud_init_bootstrap' |
672 | --- juju/providers/common/tests/data/cloud_init_bootstrap 2011-12-02 14:44:24 +0000 |
673 | +++ juju/providers/common/tests/data/cloud_init_bootstrap 2011-12-02 14:44:25 +0000 |
674 | @@ -28,6 +28,7 @@ |
675 | |
676 | |
677 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
678 | + --session-file /var/run/juju/machine-agent.zksession |
679 | |
680 | EOF |
681 | |
682 | @@ -50,6 +51,7 @@ |
683 | |
684 | |
685 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
686 | + --session-file /var/run/juju/provision-agent.zksession |
687 | |
688 | EOF |
689 | |
690 | |
691 | === modified file 'juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers' |
692 | --- juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers 2011-12-02 14:44:24 +0000 |
693 | +++ juju/providers/common/tests/data/cloud_init_bootstrap_zookeepers 2011-12-02 14:44:25 +0000 |
694 | @@ -28,6 +28,7 @@ |
695 | |
696 | |
697 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
698 | + --session-file /var/run/juju/machine-agent.zksession |
699 | |
700 | EOF |
701 | |
702 | @@ -50,6 +51,7 @@ |
703 | |
704 | |
705 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
706 | + --session-file /var/run/juju/provision-agent.zksession |
707 | |
708 | EOF |
709 | |
710 | |
711 | === modified file 'juju/providers/common/tests/data/cloud_init_branch' |
712 | --- juju/providers/common/tests/data/cloud_init_branch 2011-12-02 14:44:24 +0000 |
713 | +++ juju/providers/common/tests/data/cloud_init_branch 2011-12-02 14:44:25 +0000 |
714 | @@ -31,6 +31,7 @@ |
715 | |
716 | |
717 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
718 | + --session-file /var/run/juju/machine-agent.zksession |
719 | |
720 | EOF |
721 | |
722 | |
723 | === modified file 'juju/providers/common/tests/data/cloud_init_branch_trunk' |
724 | --- juju/providers/common/tests/data/cloud_init_branch_trunk 2011-12-02 14:44:24 +0000 |
725 | +++ juju/providers/common/tests/data/cloud_init_branch_trunk 2011-12-02 14:44:25 +0000 |
726 | @@ -31,6 +31,7 @@ |
727 | |
728 | |
729 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
730 | + --session-file /var/run/juju/machine-agent.zksession |
731 | |
732 | EOF |
733 | |
734 | |
735 | === modified file 'juju/providers/common/tests/data/cloud_init_distro' |
736 | --- juju/providers/common/tests/data/cloud_init_distro 2011-12-02 14:44:24 +0000 |
737 | +++ juju/providers/common/tests/data/cloud_init_distro 2011-12-02 14:44:25 +0000 |
738 | @@ -27,6 +27,7 @@ |
739 | |
740 | |
741 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
742 | + --session-file /var/run/juju/machine-agent.zksession |
743 | |
744 | EOF |
745 | |
746 | |
747 | === modified file 'juju/providers/common/tests/data/cloud_init_ppa' |
748 | --- juju/providers/common/tests/data/cloud_init_ppa 2011-12-02 14:44:24 +0000 |
749 | +++ juju/providers/common/tests/data/cloud_init_ppa 2011-12-02 14:44:25 +0000 |
750 | @@ -29,6 +29,7 @@ |
751 | |
752 | |
753 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
754 | + --session-file /var/run/juju/machine-agent.zksession |
755 | |
756 | EOF |
757 | |
758 | |
759 | === modified file 'juju/providers/ec2/tests/data/bootstrap_cloud_init' |
760 | --- juju/providers/ec2/tests/data/bootstrap_cloud_init 2011-12-02 14:44:24 +0000 |
761 | +++ juju/providers/ec2/tests/data/bootstrap_cloud_init 2011-12-02 14:44:25 +0000 |
762 | @@ -28,6 +28,7 @@ |
763 | |
764 | |
765 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
766 | + --session-file /var/run/juju/machine-agent.zksession |
767 | |
768 | EOF |
769 | |
770 | @@ -50,6 +51,7 @@ |
771 | |
772 | |
773 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
774 | + --session-file /var/run/juju/provision-agent.zksession |
775 | |
776 | EOF |
777 | |
778 | |
779 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init' |
780 | --- juju/providers/ec2/tests/data/launch_cloud_init 2011-12-02 14:44:24 +0000 |
781 | +++ juju/providers/ec2/tests/data/launch_cloud_init 2011-12-02 14:44:25 +0000 |
782 | @@ -27,6 +27,7 @@ |
783 | |
784 | |
785 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
786 | + --session-file /var/run/juju/machine-agent.zksession |
787 | |
788 | EOF |
789 | |
790 | |
791 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init_branch' |
792 | --- juju/providers/ec2/tests/data/launch_cloud_init_branch 2011-12-02 14:44:24 +0000 |
793 | +++ juju/providers/ec2/tests/data/launch_cloud_init_branch 2011-12-02 14:44:25 +0000 |
794 | @@ -31,6 +31,7 @@ |
795 | |
796 | |
797 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
798 | + --session-file /var/run/juju/machine-agent.zksession |
799 | |
800 | EOF |
801 | |
802 | |
803 | === modified file 'juju/providers/ec2/tests/data/launch_cloud_init_ppa' |
804 | --- juju/providers/ec2/tests/data/launch_cloud_init_ppa 2011-12-02 14:44:24 +0000 |
805 | +++ juju/providers/ec2/tests/data/launch_cloud_init_ppa 2011-12-02 14:44:25 +0000 |
806 | @@ -29,6 +29,7 @@ |
807 | |
808 | |
809 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
810 | + --session-file /var/run/juju/machine-agent.zksession |
811 | |
812 | EOF |
813 | |
814 | |
815 | === modified file 'juju/providers/local/agent.py' |
816 | --- juju/providers/local/agent.py 2011-12-02 14:44:24 +0000 |
817 | +++ juju/providers/local/agent.py 2011-12-02 14:44:25 +0000 |
818 | @@ -47,7 +47,8 @@ |
819 | self._service.set_environ(env) |
820 | self._service_args = [ |
821 | sys.executable, "-m", self.agent_module, |
822 | - "--nodaemon", "--logfile", log_file] |
823 | + "--nodaemon", "--logfile", log_file, |
824 | + "--session-file", "/var/run/juju/%s-machine-agent.zksession" % juju_unit_namespace] |
825 | |
826 | @property |
827 | def juju_origin(self): |
828 | |
829 | === modified file 'juju/providers/local/tests/test_agent.py' |
830 | --- juju/providers/local/tests/test_agent.py 2011-12-02 14:44:24 +0000 |
831 | +++ juju/providers/local/tests/test_agent.py 2011-12-02 14:44:25 +0000 |
832 | @@ -14,7 +14,6 @@ |
833 | |
834 | class ManagedAgentTest(TestCase): |
835 | |
836 | - |
837 | @inlineCallbacks |
838 | def test_managed_agent_config(self): |
839 | subprocess_calls = [] |
840 | @@ -60,8 +59,10 @@ |
841 | if line.startswith("exec"): |
842 | exec_ = line[5:-1] |
843 | |
844 | - expect_exec = "%s -m juju.agents.machine --nodaemon --logfile %s" % ( |
845 | - sys.executable, log_file) |
846 | + expect_exec = ( |
847 | + "%s -m juju.agents.machine --nodaemon --logfile %s " |
848 | + "--session-file /var/run/juju/ns1-machine-agent.zksession" |
849 | + % (sys.executable, log_file)) |
850 | self.assertEquals(exec_, expect_exec) |
851 | |
852 | env = dict((k, v.strip('"')) for (k, v) in env) |
853 | |
854 | === modified file 'juju/providers/orchestra/tests/data/bootstrap_user_data' |
855 | --- juju/providers/orchestra/tests/data/bootstrap_user_data 2011-12-02 14:44:24 +0000 |
856 | +++ juju/providers/orchestra/tests/data/bootstrap_user_data 2011-12-02 14:44:25 +0000 |
857 | @@ -27,6 +27,7 @@ |
858 | |
859 | |
860 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
861 | + --session-file /var/run/juju/machine-agent.zksession |
862 | |
863 | EOF |
864 | |
865 | @@ -49,6 +50,7 @@ |
866 | |
867 | |
868 | exec python -m juju.agents.provision --nodaemon --logfile /var/log/juju/provision-agent.log |
869 | + --session-file /var/run/juju/provision-agent.zksession |
870 | |
871 | EOF |
872 | |
873 | |
874 | === modified file 'juju/providers/orchestra/tests/data/launch_user_data' |
875 | --- juju/providers/orchestra/tests/data/launch_user_data 2011-12-02 14:44:24 +0000 |
876 | +++ juju/providers/orchestra/tests/data/launch_user_data 2011-12-02 14:44:25 +0000 |
877 | @@ -26,6 +26,7 @@ |
878 | |
879 | |
880 | exec python -m juju.agents.machine --nodaemon --logfile /var/log/juju/machine-agent.log |
881 | + --session-file /var/run/juju/machine-agent.zksession |
882 | |
883 | EOF |
884 | |
885 | |
886 | === modified file 'juju/unit/lifecycle.py' |
887 | --- juju/unit/lifecycle.py 2011-12-02 14:44:24 +0000 |
888 | +++ juju/unit/lifecycle.py 2011-12-02 14:44:25 +0000 |
889 | @@ -76,10 +76,6 @@ |
890 | watches = [] |
891 | |
892 | try: |
893 | - # Verify current state |
894 | - assert not self._running, "Already started" |
895 | - |
896 | - # Execute the start hook |
897 | if fire_hooks: |
898 | yield self._execute_hook("config-changed") |
899 | yield self._execute_hook("start") |
900 | @@ -239,9 +235,9 @@ |
901 | """Add and remove unit lifecycles per the service relations Determine. |
902 | """ |
903 | # changes relation delta of global zk state with our memory state. |
904 | - new_relations = dict([(service_relation.internal_relation_id, |
905 | - service_relation) for |
906 | - service_relation in new_relations]) |
907 | + new_relations = dict([ |
908 | + (service_relation.internal_relation_id, service_relation) |
909 | + for service_relation in new_relations]) |
910 | added = set(new_relations.keys()) - set(self._relations.keys()) |
911 | removed = set(self._relations.keys()) - set(new_relations.keys()) |
912 |
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.