Merge lp:~hazmat/pyjuju/lxc-killpid into lp:pyjuju
- lxc-killpid
- Merge into trunk
Proposed by
Kapil Thangavelu
Status: | Work in progress |
---|---|
Proposed branch: | lp:~hazmat/pyjuju/lxc-killpid |
Merge into: | lp:pyjuju |
Diff against target: |
725 lines (+210/-136) (has conflicts) 7 files modified
juju/lib/service.py (+55/-49) juju/lib/tests/test_service.py (+56/-28) juju/providers/local/__init__.py (+4/-2) juju/providers/local/agent.py (+11/-13) juju/providers/local/files.py (+36/-10) juju/providers/local/tests/test_agent.py (+24/-15) juju/providers/local/tests/test_files.py (+24/-19) Text conflict in juju/providers/local/files.py Text conflict in juju/providers/local/tests/test_agent.py Text conflict in juju/providers/local/tests/test_files.py |
To merge this branch: | bzr merge lp:~hazmat/pyjuju/lxc-killpid |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+125333@code.launchpad.net |
Commit message
Description of the change
clean up lxc process management
Adopted from ben's branch of the same name. Ensure better killing of processes
on destroy env, and additionally expand scope to all services utilized by local
env (ie file-server was still using upstart). Also ensures compatibility with
dev branches since py path was previously being stripped.
To post a comment you must log in.
Revision history for this message
Clint Byrum (clint-fewbar) wrote : | # |
I believe much of this is superseded by lp:~clint-fewbar/juju/local-cloud-img , so I'm setting this back to Work In Progress until that branch is reviewed.
Unmerged revisions
- 569. By Kapil Thangavelu
-
pre mp cleanup
- 568. By Kapil Thangavelu
-
cleanup commented out block
- 567. By Kapil Thangavelu
-
destroy-env passes juju dir to storage server
- 566. By Kapil Thangavelu
-
file-server compatibility
- 565. By Kapil Thangavelu
-
add some real tests, and fix several broken bits
- 564. By Benjamin Saller
-
properly kill daemons, properly pass juju_dir on destroy-
environment/ container destruction
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'juju/lib/service.py' | |||
2 | --- juju/lib/service.py 2012-08-03 12:28:29 +0000 | |||
3 | +++ juju/lib/service.py 2012-09-19 20:35:23 +0000 | |||
4 | @@ -1,4 +1,4 @@ | |||
6 | 1 | from twisted.internet.defer import inlineCallbacks | 1 | from twisted.internet.defer import inlineCallbacks, Deferred |
7 | 2 | from twisted.internet.threads import deferToThread | 2 | from twisted.internet.threads import deferToThread |
8 | 3 | 3 | ||
9 | 4 | from juju.errors import ServiceError | 4 | from juju.errors import ServiceError |
10 | @@ -9,20 +9,31 @@ | |||
11 | 9 | 9 | ||
12 | 10 | def _check_call(args, env=None, output_path=None): | 10 | def _check_call(args, env=None, output_path=None): |
13 | 11 | if not output_path: | 11 | if not output_path: |
17 | 12 | output_path = os.devnull | 12 | f = open(os.devnull, "a") |
18 | 13 | 13 | else: | |
19 | 14 | with open(output_path, "a") as f: | 14 | check_path = os.path.exists(output_path) and output_path \ |
20 | 15 | or os.path.dirname(output_path) | ||
21 | 16 | if os.access(check_path, os.W_OK): | ||
22 | 17 | f = open(output_path, "a") | ||
23 | 18 | else: | ||
24 | 19 | raise ValueError( | ||
25 | 20 | "Output path inaccessible %s" % output_path) | ||
26 | 21 | try: | ||
27 | 15 | return subprocess.check_call( | 22 | return subprocess.check_call( |
28 | 16 | args, | 23 | args, |
29 | 17 | stdout=f.fileno(), stderr=f.fileno(), | 24 | stdout=f.fileno(), stderr=f.fileno(), |
30 | 18 | env=env) | 25 | env=env) |
31 | 26 | finally: | ||
32 | 27 | f.close() | ||
33 | 19 | 28 | ||
34 | 20 | 29 | ||
35 | 21 | def _cat(filename, use_sudo=False): | 30 | def _cat(filename, use_sudo=False): |
36 | 22 | args = ("cat", filename) | 31 | args = ("cat", filename) |
37 | 23 | if use_sudo and not os.access(filename, os.R_OK): | 32 | if use_sudo and not os.access(filename, os.R_OK): |
38 | 24 | args = ("sudo",) + args | 33 | args = ("sudo",) + args |
40 | 25 | 34 | elif os.path.exists(filename): | |
41 | 35 | with open(filename) as fh: | ||
42 | 36 | return (0, fh.read()) | ||
43 | 26 | p = subprocess.Popen( | 37 | p = subprocess.Popen( |
44 | 27 | args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | 38 | args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
45 | 28 | stdout_data, _ = p.communicate() | 39 | stdout_data, _ = p.communicate() |
46 | @@ -37,48 +48,45 @@ | |||
47 | 37 | specifies the location of the pid file that is used to track this service. | 48 | specifies the location of the pid file that is used to track this service. |
48 | 38 | 49 | ||
49 | 39 | """ | 50 | """ |
51 | 40 | def __init__(self, name, pidfile, use_sudo=False): | 51 | def __init__(self, name, pidfile, output_path=None, use_sudo=False): |
52 | 41 | self._name = name | 52 | self._name = name |
53 | 42 | self._use_sudo = use_sudo | 53 | self._use_sudo = use_sudo |
54 | 43 | self._description = None | 54 | self._description = None |
55 | 44 | self._environ = None | 55 | self._environ = None |
56 | 45 | self._command = None | 56 | self._command = None |
59 | 46 | self._output_path = None | 57 | self._daemon = True |
60 | 47 | 58 | self.output_path = output_path | |
61 | 48 | self._pid_path = pidfile | 59 | self._pid_path = pidfile |
62 | 49 | self._pid = None | 60 | self._pid = None |
63 | 50 | 61 | ||
64 | 51 | @property | ||
65 | 52 | def output_path(self): | ||
66 | 53 | if self._output_path is not None: | ||
67 | 54 | return self._output_path | ||
68 | 55 | return "/tmp/%s.output" % self._name | ||
69 | 56 | |||
70 | 57 | @output_path.setter | ||
71 | 58 | def output_path(self, path): | ||
72 | 59 | self._output_path = path | ||
73 | 60 | 62 | ||
74 | 61 | def set_description(self, description): | 63 | def set_description(self, description): |
75 | 62 | self._description = description | 64 | self._description = description |
76 | 63 | 65 | ||
77 | 66 | def set_daemon(self, value): | ||
78 | 67 | self._daemon = bool(value) | ||
79 | 68 | |||
80 | 64 | def set_environ(self, environ): | 69 | def set_environ(self, environ): |
81 | 65 | for k, v in environ.items(): | 70 | for k, v in environ.items(): |
82 | 66 | environ[k] = str(v) | 71 | environ[k] = str(v) |
83 | 67 | self._environ = environ | 72 | self._environ = environ |
84 | 68 | 73 | ||
85 | 74 | def set_output_path(self, output_path): | ||
86 | 75 | self._output_path = output_path | ||
87 | 76 | |||
88 | 69 | def set_command(self, command): | 77 | def set_command(self, command): |
96 | 70 | if "--pidfile" not in command: | 78 | if self._daemon: |
97 | 71 | command += ["--pidfile", self._pid_path] | 79 | if "--pidfile" not in command: |
98 | 72 | else: | 80 | command.extend(["--pidfile", self._pid_path]) |
99 | 73 | # pid file is in command (consume it for get_pid) | 81 | else: |
100 | 74 | idx = command.index("--pidfile") | 82 | # pid file is in command (consume it for get_pid) |
101 | 75 | self._pid_path = command[idx+1] | 83 | idx = command.index("--pidfile") |
102 | 76 | 84 | self._pid_path = command[idx+1] | |
103 | 77 | self._command = command | 85 | self._command = command |
104 | 78 | 86 | ||
105 | 79 | @inlineCallbacks | 87 | @inlineCallbacks |
106 | 80 | def _trash_output(self): | 88 | def _trash_output(self): |
108 | 81 | if os.path.exists(self.output_path): | 89 | if self.output_path and os.path.exists(self.output_path): |
109 | 82 | # Just using os.unlink will fail when we're running TEST_SUDO | 90 | # Just using os.unlink will fail when we're running TEST_SUDO |
110 | 83 | # tests which hit this code path (because root will own | 91 | # tests which hit this code path (because root will own |
111 | 84 | # self.output_path) | 92 | # self.output_path) |
112 | @@ -88,51 +96,52 @@ | |||
113 | 88 | yield self._call("rm", "-f", self._pid_path) | 96 | yield self._call("rm", "-f", self._pid_path) |
114 | 89 | 97 | ||
115 | 90 | def _call(self, *args, **kwargs): | 98 | def _call(self, *args, **kwargs): |
116 | 99 | # sudo evn with -E will strip pythonpath so pass it to the command. | ||
117 | 91 | if self._use_sudo: | 100 | if self._use_sudo: |
119 | 92 | args = ("sudo", "-E") + args | 101 | if self._environ: |
120 | 102 | _args = ["%s=%s" % (k, v) for k, v in self._environ.items()] | ||
121 | 103 | else: | ||
122 | 104 | _args = [] | ||
123 | 105 | _args.insert(0, "sudo") | ||
124 | 106 | _args.extend(args) | ||
125 | 107 | args = _args | ||
126 | 93 | return deferToThread(_check_call, args, env=self._environ, | 108 | return deferToThread(_check_call, args, env=self._environ, |
127 | 94 | output_path=self.output_path) | 109 | output_path=self.output_path) |
128 | 95 | 110 | ||
129 | 96 | def install(self): | ||
130 | 97 | if self._command is None: | ||
131 | 98 | raise ServiceError("Cannot manage agent: %s no command set" % ( | ||
132 | 99 | self._name)) | ||
133 | 100 | |||
134 | 101 | @inlineCallbacks | 111 | @inlineCallbacks |
135 | 102 | def start(self): | 112 | def start(self): |
136 | 103 | if (yield self.is_running()): | 113 | if (yield self.is_running()): |
137 | 104 | raise ServiceError( | 114 | raise ServiceError( |
138 | 105 | "%s already running: pid (%s)" % ( | 115 | "%s already running: pid (%s)" % ( |
139 | 106 | self._name, self.get_pid())) | 116 | self._name, self.get_pid())) |
140 | 107 | |||
141 | 108 | if not self.is_installed(): | ||
142 | 109 | yield self.install() | ||
143 | 110 | |||
144 | 111 | yield self._trash_output() | 117 | yield self._trash_output() |
145 | 112 | yield self._call(*self._command, output_path=self.output_path) | 118 | yield self._call(*self._command, output_path=self.output_path) |
146 | 119 | yield self._sleep(0.1) | ||
147 | 120 | |||
148 | 121 | def _sleep(self, delay): | ||
149 | 122 | """Non-blocking sleep.""" | ||
150 | 123 | from twisted.internet import reactor | ||
151 | 124 | deferred = Deferred() | ||
152 | 125 | reactor.callLater(delay, deferred.callback, None) | ||
153 | 126 | return deferred | ||
154 | 113 | 127 | ||
155 | 114 | @inlineCallbacks | 128 | @inlineCallbacks |
156 | 115 | def destroy(self): | 129 | def destroy(self): |
159 | 116 | if (yield self.is_running()): | 130 | if not (yield self.is_running()): |
160 | 117 | yield self._call("kill", self.get_pid()) | 131 | return |
161 | 132 | yield self._call("kill", str(self.get_pid())) | ||
162 | 118 | yield self._trash_output() | 133 | yield self._trash_output() |
163 | 119 | 134 | ||
164 | 120 | def get_pid(self): | 135 | def get_pid(self): |
165 | 121 | if self._pid != None: | 136 | if self._pid != None: |
166 | 122 | return self._pid | 137 | return self._pid |
167 | 123 | |||
168 | 124 | if not os.path.exists(self._pid_path): | ||
169 | 125 | return None | ||
170 | 126 | r, data = _cat(self._pid_path, use_sudo=self._use_sudo) | 138 | r, data = _cat(self._pid_path, use_sudo=self._use_sudo) |
171 | 127 | if r != 0: | 139 | if r != 0: |
172 | 128 | return None | 140 | return None |
177 | 129 | 141 | data = data.strip() | |
178 | 130 | # verify that pid is a number but leave | 142 | if not data.isdigit(): |
175 | 131 | # it as a string suitable for passing to kill | ||
176 | 132 | if not data.strip().isdigit(): | ||
179 | 133 | return None | 143 | return None |
182 | 134 | pid = data.strip() | 144 | self._pid = int(data) |
181 | 135 | self._pid = pid | ||
183 | 136 | return self._pid | 145 | return self._pid |
184 | 137 | 146 | ||
185 | 138 | def is_running(self): | 147 | def is_running(self): |
186 | @@ -143,6 +152,3 @@ | |||
187 | 143 | if not os.path.exists(proc_file): | 152 | if not os.path.exists(proc_file): |
188 | 144 | return False | 153 | return False |
189 | 145 | return True | 154 | return True |
190 | 146 | |||
191 | 147 | def is_installed(self): | ||
192 | 148 | return False | ||
193 | 149 | 155 | ||
194 | === modified file 'juju/lib/tests/test_service.py' | |||
195 | --- juju/lib/tests/test_service.py 2012-08-03 12:28:29 +0000 | |||
196 | +++ juju/lib/tests/test_service.py 2012-09-19 20:35:23 +0000 | |||
197 | @@ -3,31 +3,25 @@ | |||
198 | 3 | from juju.lib.testing import TestCase | 3 | from juju.lib.testing import TestCase |
199 | 4 | from juju.lib.mocker import MATCH, KWARGS | 4 | from juju.lib.mocker import MATCH, KWARGS |
200 | 5 | from juju.lib.service import TwistedDaemonService | 5 | from juju.lib.service import TwistedDaemonService |
201 | 6 | from juju.lib.lxc.tests.test_lxc import uses_sudo | ||
202 | 7 | from juju.state.utils import get_open_port | ||
203 | 6 | 8 | ||
204 | 7 | import os | 9 | import os |
206 | 8 | 10 | import subprocess | |
207 | 9 | 11 | ||
208 | 10 | class TwistedDaemonServiceTest(TestCase): | 12 | class TwistedDaemonServiceTest(TestCase): |
209 | 11 | 13 | ||
210 | 12 | @inlineCallbacks | 14 | @inlineCallbacks |
211 | 13 | def setUp(self): | 15 | def setUp(self): |
212 | 14 | yield super(TwistedDaemonServiceTest, self).setUp() | 16 | yield super(TwistedDaemonServiceTest, self).setUp() |
219 | 15 | self.setup_service() | 17 | self.pid_path = self.makeFile() |
220 | 16 | 18 | service = TwistedDaemonService( | |
221 | 17 | def setup_service(self): | 19 | "juju-machine-agent", self.pid_path, use_sudo=False) |
216 | 18 | service = TwistedDaemonService("juju-machine-agent", | ||
217 | 19 | "/tmp/.juju-test.pid", | ||
218 | 20 | use_sudo=False) | ||
222 | 21 | service.set_description("Juju machine agent") | 20 | service.set_description("Juju machine agent") |
223 | 22 | service.set_environ({"JUJU_MACHINE_ID": 0}) | 21 | service.set_environ({"JUJU_MACHINE_ID": 0}) |
225 | 23 | service.set_command(["/bin/true", ]) | 22 | service.set_command(["/bin/true"]) |
226 | 24 | self.service = service | 23 | self.service = service |
227 | 25 | 24 | ||
228 | 26 | if os.path.exists("/tmp/.juju-test.pid"): | ||
229 | 27 | os.remove("/tmp/.juju-test.pid") | ||
230 | 28 | |||
231 | 29 | return service | ||
232 | 30 | |||
233 | 31 | def setup_mock(self): | 25 | def setup_mock(self): |
234 | 32 | self.check_call = self.mocker.replace("subprocess.check_call") | 26 | self.check_call = self.mocker.replace("subprocess.check_call") |
235 | 33 | 27 | ||
236 | @@ -44,16 +38,13 @@ | |||
237 | 44 | self.setup_mock() | 38 | self.setup_mock() |
238 | 45 | self.mock_call("/bin/true") | 39 | self.mock_call("/bin/true") |
239 | 46 | self.mocker.replay() | 40 | self.mocker.replay() |
240 | 47 | |||
241 | 48 | yield self.service.start() | 41 | yield self.service.start() |
242 | 49 | 42 | ||
250 | 50 | def test_set_output_path(self): | 43 | def test_simple_service_failure(self): |
251 | 51 | # defaults work | 44 | self.service.set_command(["/bin/false"]) |
252 | 52 | self.assertEqual(self.service.output_path, | 45 | return self.assertFailure( |
253 | 53 | "/tmp/juju-machine-agent.output") | 46 | self.service.start(), |
254 | 54 | # override works | 47 | subprocess.CalledProcessError) |
248 | 55 | self.service.output_path = "/tmp/valid.log" | ||
249 | 56 | self.assertEqual(self.service.output_path, "/tmp/valid.log") | ||
255 | 57 | 48 | ||
256 | 58 | @inlineCallbacks | 49 | @inlineCallbacks |
257 | 59 | def test_simple_service_start_destroy(self): | 50 | def test_simple_service_start_destroy(self): |
258 | @@ -81,20 +72,57 @@ | |||
259 | 81 | def test_webservice_start(self): | 72 | def test_webservice_start(self): |
260 | 82 | # test using a real twisted service (with --pidfile) | 73 | # test using a real twisted service (with --pidfile) |
261 | 83 | # arg ordering matters here so we set pidfile manually | 74 | # arg ordering matters here so we set pidfile manually |
262 | 75 | |||
263 | 76 | pid_file = self.makeFile() | ||
264 | 77 | log_file = self.makeFile() | ||
265 | 78 | web_dir = self.makeDir() | ||
266 | 79 | |||
267 | 84 | self.service.set_command([ | 80 | self.service.set_command([ |
268 | 85 | "env", "twistd", | 81 | "env", "twistd", |
271 | 86 | "--pidfile", "/tmp/.juju-test.pid", | 82 | "--pidfile", pid_file, |
272 | 87 | "--logfile", "/tmp/.juju-test.log", | 83 | "--logfile", log_file, |
273 | 88 | "web", | 84 | "web", |
276 | 89 | "--port", "9871", | 85 | "--port", str(get_open_port()), |
277 | 90 | "--path", "/lib", | 86 | "--path", web_dir, |
278 | 91 | ]) | 87 | ]) |
279 | 92 | 88 | ||
280 | 93 | yield self.service.start() | 89 | yield self.service.start() |
282 | 94 | yield self.sleep(0.5) | 90 | yield self.sleep(0.1) |
283 | 91 | |||
284 | 95 | self.assertTrue(self.service.get_pid()) | 92 | self.assertTrue(self.service.get_pid()) |
285 | 96 | self.assertTrue(self.service.is_running()) | 93 | self.assertTrue(self.service.is_running()) |
287 | 97 | self.assertTrue(os.path.exists("/tmp/.juju-test.pid")) | 94 | self.assertTrue(os.path.exists(pid_file)) |
288 | 98 | yield self.service.destroy() | 95 | yield self.service.destroy() |
289 | 99 | yield self.sleep(0.1) | 96 | yield self.sleep(0.1) |
290 | 100 | self.assertFalse(os.path.exists("/tmp/.juju-test.pid")) | ||
291 | 101 | \ No newline at end of file | 97 | \ No newline at end of file |
292 | 98 | self.assertFalse(self.service.is_running()) | ||
293 | 99 | |||
294 | 100 | @uses_sudo | ||
295 | 101 | @inlineCallbacks | ||
296 | 102 | def test_sudo_env_vars(self): | ||
297 | 103 | self.service.set_daemon(False) | ||
298 | 104 | self.service.set_environ( | ||
299 | 105 | {"JUJU_MACHINE_ID": 0, "PYTHONPATH": "foo2"}) | ||
300 | 106 | self.service.set_command(["/usr/bin/env"]) | ||
301 | 107 | self.service.output_path = self.makeFile() | ||
302 | 108 | yield self.service.start() | ||
303 | 109 | |||
304 | 110 | with open(self.service.output_path) as fh: | ||
305 | 111 | contents = fh.read() | ||
306 | 112 | self.assertIn("PYTHONPATH=foo2", contents) | ||
307 | 113 | self.assertIn("JUJU_MACHINE_ID=0", contents) | ||
308 | 114 | |||
309 | 115 | |||
310 | 116 | @uses_sudo | ||
311 | 117 | @inlineCallbacks | ||
312 | 118 | def test_command_tuple(self): | ||
313 | 119 | self.service.set_daemon(False) | ||
314 | 120 | self.service.output_path = self.makeFile() | ||
315 | 121 | self.service.set_environ( | ||
316 | 122 | {"JUJU_MACHINE_ID": 0, "PYTHONPATH": "foo2"}) | ||
317 | 123 | self.service.set_command(("/usr/bin/env",)) | ||
318 | 124 | yield self.service.start() | ||
319 | 125 | |||
320 | 126 | with open(self.service.output_path) as fh: | ||
321 | 127 | contents = fh.read() | ||
322 | 128 | self.assertIn("PYTHONPATH=foo2", contents) | ||
323 | 129 | self.assertIn("JUJU_MACHINE_ID=0", contents) | ||
324 | 102 | 130 | ||
325 | === modified file 'juju/providers/local/__init__.py' | |||
326 | --- juju/providers/local/__init__.py 2012-07-20 17:28:17 +0000 | |||
327 | +++ juju/providers/local/__init__.py 2012-09-19 20:35:23 +0000 | |||
328 | @@ -114,6 +114,7 @@ | |||
329 | 114 | log.info("Starting storage server...") | 114 | log.info("Starting storage server...") |
330 | 115 | storage_server = StorageServer( | 115 | storage_server = StorageServer( |
331 | 116 | self._qualified_name, | 116 | self._qualified_name, |
332 | 117 | self._directory, | ||
333 | 117 | storage_dir=os.path.join(self._directory, "files"), | 118 | storage_dir=os.path.join(self._directory, "files"), |
334 | 118 | host=net_attributes["ip"]["address"], | 119 | host=net_attributes["ip"]["address"], |
335 | 119 | port=get_open_port(net_attributes["ip"]["address"]), | 120 | port=get_open_port(net_attributes["ip"]["address"]), |
336 | @@ -172,12 +173,13 @@ | |||
337 | 172 | 173 | ||
338 | 173 | # Stop the machine agent | 174 | # Stop the machine agent |
339 | 174 | log.debug("Stopping machine agent...") | 175 | log.debug("Stopping machine agent...") |
341 | 175 | agent = ManagedMachineAgent(self._qualified_name) | 176 | agent = ManagedMachineAgent(self._qualified_name, |
342 | 177 | juju_directory=self._directory) | ||
343 | 176 | yield agent.stop() | 178 | yield agent.stop() |
344 | 177 | 179 | ||
345 | 178 | # Stop the storage server | 180 | # Stop the storage server |
346 | 179 | log.debug("Stopping storage server...") | 181 | log.debug("Stopping storage server...") |
348 | 180 | storage_server = StorageServer(self._qualified_name) | 182 | storage_server = StorageServer(self._qualified_name, self._directory) |
349 | 181 | yield storage_server.stop() | 183 | yield storage_server.stop() |
350 | 182 | 184 | ||
351 | 183 | # Stop zookeeper | 185 | # Stop zookeeper |
352 | 184 | 186 | ||
353 | === modified file 'juju/providers/local/agent.py' | |||
354 | --- juju/providers/local/agent.py 2012-08-03 10:55:21 +0000 | |||
355 | +++ juju/providers/local/agent.py 2012-09-19 20:35:23 +0000 | |||
356 | @@ -1,18 +1,13 @@ | |||
357 | 1 | import os | ||
358 | 1 | import sys | 2 | import sys |
361 | 2 | import tempfile | 3 | |
362 | 3 | 4 | from twisted.internet.defer import inlineCallbacks | |
363 | 5 | |||
364 | 6 | from juju.errors import ServiceError | ||
365 | 4 | from juju.lib.service import TwistedDaemonService | 7 | from juju.lib.service import TwistedDaemonService |
366 | 5 | from juju.providers.common.cloudinit import get_default_origin, BRANCH | 8 | from juju.providers.common.cloudinit import get_default_origin, BRANCH |
367 | 6 | 9 | ||
368 | 7 | 10 | ||
369 | 8 | def get_temp_filename(basename): | ||
370 | 9 | if basename: | ||
371 | 10 | basename += "-" | ||
372 | 11 | |||
373 | 12 | fd, fn = tempfile.mkstemp(prefix=basename, suffix=".pid") | ||
374 | 13 | return fn | ||
375 | 14 | |||
376 | 15 | |||
377 | 16 | class ManagedMachineAgent(object): | 11 | class ManagedMachineAgent(object): |
378 | 17 | 12 | ||
379 | 18 | agent_module = "juju.agents.machine" | 13 | agent_module = "juju.agents.machine" |
380 | @@ -51,15 +46,18 @@ | |||
381 | 51 | if public_key: | 46 | if public_key: |
382 | 52 | env["JUJU_PUBLIC_KEY"] = public_key | 47 | env["JUJU_PUBLIC_KEY"] = public_key |
383 | 53 | 48 | ||
385 | 54 | pidfile = get_temp_filename(juju_unit_namespace) | 49 | pidfile = os.path.join(juju_directory, |
386 | 50 | "%s-machine-agent.pid" % ( | ||
387 | 51 | juju_unit_namespace)) | ||
388 | 52 | |||
389 | 55 | self._service = TwistedDaemonService( | 53 | self._service = TwistedDaemonService( |
390 | 56 | "juju-%s-machine-agent" % juju_unit_namespace, | 54 | "juju-%s-machine-agent" % juju_unit_namespace, |
393 | 57 | pidfile, | 55 | pidfile, use_sudo=True) |
392 | 58 | use_sudo=True) | ||
394 | 59 | self._service.set_description( | 56 | self._service.set_description( |
395 | 60 | "Juju machine agent for %s" % juju_unit_namespace) | 57 | "Juju machine agent for %s" % juju_unit_namespace) |
396 | 61 | self._service.set_environ(env) | 58 | self._service.set_environ(env) |
397 | 62 | self._service.output_path = log_file | 59 | self._service.output_path = log_file |
398 | 60 | self._logfile = log_file | ||
399 | 63 | 61 | ||
400 | 64 | self._service_args = [ | 62 | self._service_args = [ |
401 | 65 | "/usr/bin/python", "-m", self.agent_module, | 63 | "/usr/bin/python", "-m", self.agent_module, |
402 | 66 | 64 | ||
403 | === modified file 'juju/providers/local/files.py' | |||
404 | --- juju/providers/local/files.py 2012-09-10 03:20:20 +0000 | |||
405 | +++ juju/providers/local/files.py 2012-09-19 20:35:23 +0000 | |||
406 | @@ -5,9 +5,14 @@ | |||
407 | 5 | from twisted.internet.error import ConnectionRefusedError | 5 | from twisted.internet.error import ConnectionRefusedError |
408 | 6 | from twisted.web.client import getPage | 6 | from twisted.web.client import getPage |
409 | 7 | 7 | ||
410 | 8 | <<<<<<< TREE | ||
411 | 8 | from juju.errors import ProviderError, FileNotFound | 9 | from juju.errors import ProviderError, FileNotFound |
412 | 9 | from juju.lib import serializer | 10 | from juju.lib import serializer |
413 | 10 | from juju.lib.upstart import UpstartService | 11 | from juju.lib.upstart import UpstartService |
414 | 12 | ======= | ||
415 | 13 | from juju.errors import ProviderError, FileNotFound, ServiceError | ||
416 | 14 | from juju.lib.service import TwistedDaemonService | ||
417 | 15 | >>>>>>> MERGE-SOURCE | ||
418 | 11 | from juju.providers.common.files import FileStorage | 16 | from juju.providers.common.files import FileStorage |
419 | 12 | 17 | ||
420 | 13 | 18 | ||
421 | @@ -16,8 +21,8 @@ | |||
422 | 16 | 21 | ||
423 | 17 | class StorageServer(object): | 22 | class StorageServer(object): |
424 | 18 | 23 | ||
427 | 19 | def __init__(self, juju_unit_namespace, storage_dir=None, | 24 | def __init__(self, juju_unit_namespace, juju_directory, |
428 | 20 | host=None, port=None, logfile=None): | 25 | storage_dir=None, host=None, port=None, logfile=None): |
429 | 21 | """Management facade for a web server on top of the provider storage. | 26 | """Management facade for a web server on top of the provider storage. |
430 | 22 | 27 | ||
431 | 23 | :param juju_unit_namespace: For disambiguation. | 28 | :param juju_unit_namespace: For disambiguation. |
432 | @@ -32,17 +37,21 @@ | |||
433 | 32 | self._port = port | 37 | self._port = port |
434 | 33 | self._logfile = logfile | 38 | self._logfile = logfile |
435 | 34 | 39 | ||
438 | 35 | self._service = UpstartService( | 40 | self._pid_file = os.path.join( |
439 | 36 | "juju-%s-file-storage" % juju_unit_namespace, use_sudo=True) | 41 | juju_directory, "%s-file-server.pid" % juju_unit_namespace) |
440 | 42 | |||
441 | 43 | self._service = TwistedDaemonService( | ||
442 | 44 | "juju-%s-file-storage" % juju_unit_namespace, | ||
443 | 45 | self._pid_file, use_sudo=False) | ||
444 | 46 | self._service.output_path = '/tmp/files.output' | ||
445 | 37 | self._service.set_description( | 47 | self._service.set_description( |
446 | 38 | "Juju file storage for %s" % juju_unit_namespace) | 48 | "Juju file storage for %s" % juju_unit_namespace) |
447 | 39 | self._service_args = [ | 49 | self._service_args = [ |
448 | 40 | "twistd", | 50 | "twistd", |
449 | 41 | "--nodaemon", | ||
450 | 42 | "--uid", str(os.getuid()), | 51 | "--uid", str(os.getuid()), |
451 | 43 | "--gid", str(os.getgid()), | 52 | "--gid", str(os.getgid()), |
452 | 53 | "--pidfile", self._pid_file, | ||
453 | 44 | "--logfile", logfile, | 54 | "--logfile", logfile, |
454 | 45 | "--pidfile=", | ||
455 | 46 | "-d", self._storage_dir, | 55 | "-d", self._storage_dir, |
456 | 47 | "web", | 56 | "web", |
457 | 48 | "--port", "tcp:%s:interface=%s" % (self._port, self._host), | 57 | "--port", "tcp:%s:interface=%s" % (self._port, self._host), |
458 | @@ -74,23 +83,40 @@ | |||
459 | 74 | except IOError: | 83 | except IOError: |
460 | 75 | raise AssertionError("logfile not writable by this user") | 84 | raise AssertionError("logfile not writable by this user") |
461 | 76 | 85 | ||
462 | 77 | |||
463 | 78 | storage = LocalStorage(self._storage_dir) | 86 | storage = LocalStorage(self._storage_dir) |
464 | 79 | yield storage.put( | 87 | yield storage.put( |
465 | 80 | SERVER_URL_KEY, | 88 | SERVER_URL_KEY, |
466 | 81 | StringIO(serializer.dump( | 89 | StringIO(serializer.dump( |
467 | 82 | {"storage-url": "http://%s:%s/" % (self._host, self._port)}))) | 90 | {"storage-url": "http://%s:%s/" % (self._host, self._port)}))) |
468 | 83 | 91 | ||
470 | 84 | self._service.set_command(" ".join(self._service_args)) | 92 | self._service.set_command(self._service_args) |
471 | 85 | yield self._service.start() | 93 | yield self._service.start() |
472 | 86 | 94 | ||
475 | 87 | def get_pid(self): | 95 | # Capture the error for the user. |
476 | 88 | return self._service.get_pid() | 96 | if not self._service.is_running(): |
477 | 97 | content = self._capture_error() | ||
478 | 98 | raise ServiceError( | ||
479 | 99 | "Failed to start file-storage server: got output:\n" | ||
480 | 100 | "%s" % content) | ||
481 | 101 | |||
482 | 102 | def _capture_error(self): | ||
483 | 103 | if os.path.exists(self._logfile): | ||
484 | 104 | with open(self._logfile) as fh: | ||
485 | 105 | content = fh.read() | ||
486 | 106 | else: | ||
487 | 107 | content = "" | ||
488 | 108 | return content | ||
489 | 89 | 109 | ||
490 | 90 | def stop(self): | 110 | def stop(self): |
491 | 91 | """Stop the storage server.""" | 111 | """Stop the storage server.""" |
492 | 92 | return self._service.destroy() | 112 | return self._service.destroy() |
493 | 93 | 113 | ||
494 | 114 | # Passthrough testing support | ||
495 | 115 | def is_running(self): | ||
496 | 116 | return self._service.is_running() | ||
497 | 117 | |||
498 | 118 | def get_pid(self): | ||
499 | 119 | return self._service.get_pid() | ||
500 | 94 | 120 | ||
501 | 95 | class LocalStorage(FileStorage): | 121 | class LocalStorage(FileStorage): |
502 | 96 | 122 | ||
503 | 97 | 123 | ||
504 | === modified file 'juju/providers/local/tests/test_agent.py' | |||
505 | --- juju/providers/local/tests/test_agent.py 2012-09-10 03:20:20 +0000 | |||
506 | +++ juju/providers/local/tests/test_agent.py 2012-09-19 20:35:23 +0000 | |||
507 | @@ -1,9 +1,16 @@ | |||
508 | 1 | import os | 1 | import os |
509 | 2 | import tempfile | 2 | import tempfile |
510 | 3 | import subprocess | 3 | import subprocess |
514 | 4 | 4 | <<<<<<< TREE | |
515 | 5 | from twisted.internet.defer import inlineCallbacks | 5 | |
516 | 6 | 6 | from twisted.internet.defer import inlineCallbacks | |
517 | 7 | |||
518 | 8 | ======= | ||
519 | 9 | |||
520 | 10 | from twisted.internet.defer import inlineCallbacks | ||
521 | 11 | |||
522 | 12 | from juju.errors import ServiceError | ||
523 | 13 | >>>>>>> MERGE-SOURCE | ||
524 | 7 | from juju.lib.lxc.tests.test_lxc import uses_sudo | 14 | from juju.lib.lxc.tests.test_lxc import uses_sudo |
525 | 8 | from juju.lib.testing import TestCase | 15 | from juju.lib.testing import TestCase |
526 | 9 | from juju.tests.common import get_test_zookeeper_address | 16 | from juju.tests.common import get_test_zookeeper_address |
527 | @@ -17,8 +24,9 @@ | |||
528 | 17 | subprocess_calls = [] | 24 | subprocess_calls = [] |
529 | 18 | 25 | ||
530 | 19 | def intercept_args(args, **kwargs): | 26 | def intercept_args(args, **kwargs): |
531 | 27 | #print "intercept", args, kwargs | ||
532 | 20 | self.assertEquals(args[0], "sudo") | 28 | self.assertEquals(args[0], "sudo") |
534 | 21 | self.assertEquals(args[1], "-E") | 29 | #self.assertEquals(args[1], "-E") |
535 | 22 | if args[2] == "rm": | 30 | if args[2] == "rm": |
536 | 23 | return 0 | 31 | return 0 |
537 | 24 | subprocess_calls.append(args) | 32 | subprocess_calls.append(args) |
538 | @@ -35,28 +43,26 @@ | |||
539 | 35 | juju_directory=juju_directory, | 43 | juju_directory=juju_directory, |
540 | 36 | log_file=log_file, | 44 | log_file=log_file, |
541 | 37 | juju_origin="lp:juju/trunk") | 45 | juju_origin="lp:juju/trunk") |
542 | 38 | |||
543 | 39 | try: | ||
544 | 40 | os.remove(agent._service.output_path) | ||
545 | 41 | except OSError: | ||
546 | 42 | pass # just make sure it's not there, so the .start() | ||
547 | 43 | # doesn't insert a spurious rm | ||
548 | 44 | yield agent.start() | 46 | yield agent.start() |
549 | 45 | 47 | ||
550 | 46 | start = subprocess_calls[0] | 48 | start = subprocess_calls[0] |
553 | 47 | self.assertEquals(start, ( | 49 | self.assertEquals(start[start.index("/usr/bin/python"):], [ |
554 | 48 | "sudo", "-E", "/usr/bin/python", "-m", "juju.agents.machine", | 50 | "/usr/bin/python", "-m", "juju.agents.machine", |
555 | 49 | "--logfile", log_file, | 51 | "--logfile", log_file, |
556 | 50 | "--zookeeper-servers", get_test_zookeeper_address(), | 52 | "--zookeeper-servers", get_test_zookeeper_address(), |
557 | 51 | "--juju-directory", juju_directory, | 53 | "--juju-directory", juju_directory, |
558 | 52 | "--machine-id", "0", | 54 | "--machine-id", "0", |
559 | 53 | "--session-file", "/var/run/juju/ns1-machine-agent.zksession", | 55 | "--session-file", "/var/run/juju/ns1-machine-agent.zksession", |
561 | 54 | "--pidfile", agent._service._pid_path)) | 56 | "--pidfile", agent._service._pid_path]) |
562 | 55 | 57 | ||
563 | 56 | @uses_sudo | 58 | @uses_sudo |
564 | 57 | @inlineCallbacks | 59 | @inlineCallbacks |
565 | 58 | def test_managed_agent_root(self): | 60 | def test_managed_agent_root(self): |
566 | 59 | juju_directory = self.makeDir() | 61 | juju_directory = self.makeDir() |
567 | 62 | os.mkdir(os.path.join(juju_directory, "charms")) | ||
568 | 63 | os.mkdir(os.path.join(juju_directory, "state")) | ||
569 | 64 | os.mkdir(os.path.join(juju_directory, "units")) | ||
570 | 65 | |||
571 | 60 | log_file = tempfile.mktemp() | 66 | log_file = tempfile.mktemp() |
572 | 61 | 67 | ||
573 | 62 | # The pid file and log file get written as root | 68 | # The pid file and log file get written as root |
574 | @@ -64,6 +70,7 @@ | |||
575 | 64 | subprocess.check_call( | 70 | subprocess.check_call( |
576 | 65 | ["sudo", "rm", "-f", cleanup_file], stderr=subprocess.STDOUT) | 71 | ["sudo", "rm", "-f", cleanup_file], stderr=subprocess.STDOUT) |
577 | 66 | self.addCleanup(cleanup_root_file, log_file) | 72 | self.addCleanup(cleanup_root_file, log_file) |
578 | 73 | #self.addCleanup(cleanup_root_file, juju_directory) | ||
579 | 67 | 74 | ||
580 | 68 | agent = ManagedMachineAgent( | 75 | agent = ManagedMachineAgent( |
581 | 69 | "test-ns", machine_id="0", | 76 | "test-ns", machine_id="0", |
582 | @@ -75,13 +82,15 @@ | |||
583 | 75 | agent.agent_module = "juju.agents.dummy" | 82 | agent.agent_module = "juju.agents.dummy" |
584 | 76 | self.assertFalse((yield agent.is_running())) | 83 | self.assertFalse((yield agent.is_running())) |
585 | 77 | yield agent.start() | 84 | yield agent.start() |
586 | 85 | |||
587 | 78 | # Give a moment for the process to start and write its config | 86 | # Give a moment for the process to start and write its config |
588 | 79 | yield self.sleep(0.1) | 87 | yield self.sleep(0.1) |
589 | 80 | self.assertTrue((yield agent.is_running())) | 88 | self.assertTrue((yield agent.is_running())) |
590 | 81 | 89 | ||
593 | 82 | # running start again is fine, detects the process is running | 90 | # running start again raises an error, detects the process is running |
594 | 83 | yield agent.start() | 91 | self.assertFailure(agent.start(), ServiceError) |
595 | 84 | yield agent.stop() | 92 | yield agent.stop() |
596 | 93 | yield self.sleep(0.1) | ||
597 | 85 | self.assertFalse((yield agent.is_running())) | 94 | self.assertFalse((yield agent.is_running())) |
598 | 86 | 95 | ||
599 | 87 | # running stop again is fine, detects the process is stopped. | 96 | # running stop again is fine, detects the process is stopped. |
600 | 88 | 97 | ||
601 | === modified file 'juju/providers/local/tests/test_files.py' | |||
602 | --- juju/providers/local/tests/test_files.py 2012-09-10 03:20:20 +0000 | |||
603 | +++ juju/providers/local/tests/test_files.py 2012-09-19 20:35:23 +0000 | |||
604 | @@ -7,8 +7,11 @@ | |||
605 | 7 | from twisted.web.client import getPage | 7 | from twisted.web.client import getPage |
606 | 8 | 8 | ||
607 | 9 | from juju.errors import ProviderError, ServiceError | 9 | from juju.errors import ProviderError, ServiceError |
608 | 10 | <<<<<<< TREE | ||
609 | 10 | from juju.lib import serializer | 11 | from juju.lib import serializer |
610 | 11 | from juju.lib.lxc.tests.test_lxc import uses_sudo | 12 | from juju.lib.lxc.tests.test_lxc import uses_sudo |
611 | 13 | ======= | ||
612 | 14 | >>>>>>> MERGE-SOURCE | ||
613 | 12 | from juju.lib.testing import TestCase | 15 | from juju.lib.testing import TestCase |
614 | 13 | from juju.lib.upstart import UpstartService | 16 | from juju.lib.upstart import UpstartService |
615 | 14 | from juju.providers.local.files import ( | 17 | from juju.providers.local.files import ( |
616 | @@ -21,12 +24,15 @@ | |||
617 | 21 | @inlineCallbacks | 24 | @inlineCallbacks |
618 | 22 | def setUp(self): | 25 | def setUp(self): |
619 | 23 | yield super(WebFileStorageTest, self).setUp() | 26 | yield super(WebFileStorageTest, self).setUp() |
621 | 24 | self._storage_path = self.makeDir() | 27 | self._juju_dir = self.makeDir() |
622 | 28 | self._storage_path = os.path.join(self._juju_dir, "files") | ||
623 | 29 | os.mkdir(self._storage_path) | ||
624 | 25 | self._logfile = self.makeFile() | 30 | self._logfile = self.makeFile() |
625 | 26 | self._storage = LocalStorage(self._storage_path) | 31 | self._storage = LocalStorage(self._storage_path) |
626 | 27 | self._port = get_open_port() | 32 | self._port = get_open_port() |
627 | 28 | self._server = StorageServer( | 33 | self._server = StorageServer( |
629 | 29 | "ns1", self._storage_path, "localhost", self._port, self._logfile) | 34 | "ns1", self._juju_dir, |
630 | 35 | self._storage_path, "localhost", self._port, self._logfile) | ||
631 | 30 | 36 | ||
632 | 31 | @inlineCallbacks | 37 | @inlineCallbacks |
633 | 32 | def wait_for_server(self, server): | 38 | def wait_for_server(self, server): |
634 | @@ -34,7 +40,7 @@ | |||
635 | 34 | yield self.sleep(0.1) | 40 | yield self.sleep(0.1) |
636 | 35 | 41 | ||
637 | 36 | def test_start_missing_args(self): | 42 | def test_start_missing_args(self): |
639 | 37 | server = StorageServer("ns1", self._storage_path) | 43 | server = StorageServer("ns1", self._juju_dir, self._storage_path) |
640 | 38 | return self.assertFailure(server.start(), AssertionError) | 44 | return self.assertFailure(server.start(), AssertionError) |
641 | 39 | 45 | ||
642 | 40 | def test_start_invalid_directory(self): | 46 | def test_start_invalid_directory(self): |
643 | @@ -42,7 +48,7 @@ | |||
644 | 42 | return self.assertFailure(self._server.start(), AssertionError) | 48 | return self.assertFailure(self._server.start(), AssertionError) |
645 | 43 | 49 | ||
646 | 44 | @inlineCallbacks | 50 | @inlineCallbacks |
648 | 45 | def test_upstart(self): | 51 | def xtest_upstart(self): |
649 | 46 | subprocess_calls = [] | 52 | subprocess_calls = [] |
650 | 47 | 53 | ||
651 | 48 | def intercept_args(args, **kwargs): | 54 | def intercept_args(args, **kwargs): |
652 | @@ -96,13 +102,15 @@ | |||
653 | 96 | self._port, self._storage_path)) | 102 | self._port, self._storage_path)) |
654 | 97 | self.assertEquals(exec_, expect_exec) | 103 | self.assertEquals(exec_, expect_exec) |
655 | 98 | 104 | ||
656 | 99 | @uses_sudo | ||
657 | 100 | @inlineCallbacks | 105 | @inlineCallbacks |
658 | 101 | def test_start_stop(self): | 106 | def test_start_stop(self): |
659 | 102 | yield self._storage.put("abc", StringIO("hello world")) | 107 | yield self._storage.put("abc", StringIO("hello world")) |
660 | 108 | |||
661 | 103 | yield self._server.start() | 109 | yield self._server.start() |
662 | 110 | |||
663 | 104 | # Starting multiple times is fine. | 111 | # Starting multiple times is fine. |
665 | 105 | yield self._server.start() | 112 | yield self.assertFailure(self._server.start(), ServiceError) |
666 | 113 | |||
667 | 106 | storage_url = yield self._storage.get_url("abc") | 114 | storage_url = yield self._storage.get_url("abc") |
668 | 107 | 115 | ||
669 | 108 | # It might not have started actually accepting connections yet... | 116 | # It might not have started actually accepting connections yet... |
670 | @@ -112,28 +120,26 @@ | |||
671 | 112 | # Check that it can be killed by the current user (ie, is not running | 120 | # Check that it can be killed by the current user (ie, is not running |
672 | 113 | # as root) and still comes back up | 121 | # as root) and still comes back up |
673 | 114 | old_pid = yield self._server.get_pid() | 122 | old_pid = yield self._server.get_pid() |
674 | 123 | |||
675 | 115 | os.kill(old_pid, signal.SIGKILL) | 124 | os.kill(old_pid, signal.SIGKILL) |
682 | 116 | new_pid = yield self._server.get_pid() | 125 | yield self.sleep(0.1) |
683 | 117 | self.assertNotEquals(old_pid, new_pid) | 126 | self.assertFalse(self._server.is_running()) |
678 | 118 | |||
679 | 119 | # Give it a moment to actually start serving again | ||
680 | 120 | yield self.wait_for_server(self._server) | ||
681 | 121 | self.assertEqual((yield getPage(storage_url)), "hello world") | ||
684 | 122 | 127 | ||
685 | 123 | yield self._server.stop() | 128 | yield self._server.stop() |
686 | 124 | # Stopping multiple times is fine too. | 129 | # Stopping multiple times is fine too. |
687 | 125 | yield self._server.stop() | 130 | yield self._server.stop() |
688 | 126 | 131 | ||
689 | 127 | @uses_sudo | ||
690 | 128 | @inlineCallbacks | 132 | @inlineCallbacks |
691 | 129 | def test_namespacing(self): | 133 | def test_namespacing(self): |
692 | 134 | alt_juju_dir = self.makeDir() | ||
693 | 130 | alt_storage_path = self.makeDir() | 135 | alt_storage_path = self.makeDir() |
694 | 131 | alt_storage = LocalStorage(alt_storage_path) | 136 | alt_storage = LocalStorage(alt_storage_path) |
695 | 132 | yield alt_storage.put("some-path", StringIO("alternative")) | 137 | yield alt_storage.put("some-path", StringIO("alternative")) |
696 | 133 | yield self._storage.put("some-path", StringIO("original")) | 138 | yield self._storage.put("some-path", StringIO("original")) |
697 | 134 | 139 | ||
698 | 135 | alt_server = StorageServer( | 140 | alt_server = StorageServer( |
700 | 136 | "ns2", alt_storage_path, "localhost", get_open_port(), | 141 | "ns2", alt_juju_dir, |
701 | 142 | alt_storage_path, "localhost", get_open_port(), | ||
702 | 137 | self.makeFile()) | 143 | self.makeFile()) |
703 | 138 | yield alt_server.start() | 144 | yield alt_server.start() |
704 | 139 | yield self._server.start() | 145 | yield self._server.start() |
705 | @@ -150,18 +156,17 @@ | |||
706 | 150 | yield alt_server.stop() | 156 | yield alt_server.stop() |
707 | 151 | yield self._server.stop() | 157 | yield self._server.stop() |
708 | 152 | 158 | ||
709 | 153 | @uses_sudo | ||
710 | 154 | @inlineCallbacks | 159 | @inlineCallbacks |
711 | 155 | def test_capture_errors(self): | 160 | def test_capture_errors(self): |
712 | 156 | self._port = get_open_port() | 161 | self._port = get_open_port() |
713 | 157 | self._server = StorageServer( | 162 | self._server = StorageServer( |
716 | 158 | "borken", self._storage_path, "lol borken", self._port, | 163 | "borken", self._juju_dir, self._storage_path, |
717 | 159 | self._logfile) | 164 | "lol borken", self._port, self._logfile) |
718 | 165 | |||
719 | 160 | d = self._server.start() | 166 | d = self._server.start() |
720 | 161 | e = yield self.assertFailure(d, ServiceError) | 167 | e = yield self.assertFailure(d, ServiceError) |
721 | 162 | self.assertTrue(str(e).startswith( | 168 | self.assertTrue(str(e).startswith( |
724 | 163 | "Failed to start job juju-borken-file-storage; got output:\n")) | 169 | "Failed to start file-storage server: got output:\n")) |
723 | 164 | self.assertIn("Wrong number of arguments", str(e)) | ||
725 | 165 | yield self._server.stop() | 170 | yield self._server.stop() |
726 | 166 | 171 | ||
727 | 167 | 172 |
Reviewers: mp+125333_ code.launchpad. net,
Message:
Please take a look.
Description:
clean up lxc process management
Adopted from ben's branch of the same name. Ensure better killing of
processes
on destroy env, and additionally expand scope to all services utilized
by local
env (ie file-server was still using upstart). Also ensures compatibility
with
dev branches since py path was previously being stripped.
https:/ /code.launchpad .net/~hazmat/ juju/lxc- killpid/ +merge/ 125333
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/6533057/
Affected files: tests/test_ service. py local/_ _init__ .py local/agent. py local/files. py local/tests/ test_agent. py local/tests/ test_files. py
A [revision details]
M juju/lib/service.py
M juju/lib/
M juju/providers/
M juju/providers/
M juju/providers/
M juju/providers/
M juju/providers/