Merge lp:~ericsnowcurrently/landscape-charm/tools-unit-tests-for-script into lp:~landscape/landscape-charm/tools
- tools-unit-tests-for-script
- Merge into tools
Proposed by
Eric Snow
Status: | Merged |
---|---|
Approved by: | Eric Snow |
Approved revision: | 28 |
Merged at revision: | 23 |
Proposed branch: | lp:~ericsnowcurrently/landscape-charm/tools-unit-tests-for-script |
Merge into: | lp:~landscape/landscape-charm/tools |
Diff against target: |
1130 lines (+910/-78) 2 files modified
collect-logs (+161/-78) test_collect-logs.py (+749/-0) |
To merge this branch: | bzr merge lp:~ericsnowcurrently/landscape-charm/tools-unit-tests-for-script |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Approve | ||
Benji York (community) | Approve | ||
Review via email: mp+303061@code.launchpad.net |
Commit message
Add unit tests for the collect-logs script.
Description of the change
Add unit tests for the collect-logs script.
Testing instructions:
Run the tests with "python -m unittest test_collect-logs".
To post a comment you must log in.
Revision history for this message
Chad Smith (chad.smith) wrote : | # |
+1 plus the fix we talked about in irc to avoid hitting an external "real" juju when running execfile
https:/
review:
Approve
Revision history for this message
Eric Snow (ericsnowcurrently) : | # |
- 22. By Eric Snow
-
Do not call juju_status() as a default keyword argument.
- 23. By Eric Snow
-
typo
- 24. By Eric Snow
-
_touch() -> _create_file()
- 25. By Eric Snow
-
Clarify some comments.
- 26. By Eric Snow
-
Add missing test docstrings.
- 27. By Eric Snow
-
Explicitly pass the juju command in to the various functions.
- 28. By Eric Snow
-
Add CommandWrapper.
- 29. By Eric Snow
-
Add the Juju class.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'collect-logs' |
2 | --- collect-logs 2016-07-29 19:41:15 +0000 |
3 | +++ collect-logs 2016-08-23 00:10:59 +0000 |
4 | @@ -1,15 +1,18 @@ |
5 | #!/usr/bin/python |
6 | |
7 | +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter |
8 | +import errno |
9 | +from functools import partial |
10 | import logging |
11 | import multiprocessing |
12 | import os |
13 | import shutil |
14 | +from subprocess import ( |
15 | + CalledProcessError, check_call, check_output, call, STDOUT) |
16 | import sys |
17 | -import tempfile |
18 | +from tempfile import mkdtemp |
19 | + |
20 | import yaml |
21 | -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter |
22 | -from subprocess import ( |
23 | - CalledProcessError, check_call, check_output, call, STDOUT) |
24 | |
25 | |
26 | log = logging.getLogger("collect-logs") |
27 | @@ -21,17 +24,61 @@ |
28 | "/var/lib/landscape/client"] |
29 | EXCLUDED = ["/var/lib/landscape/client/package/hash-id", |
30 | "/var/lib/juju/containers/juju-*-lxc-template"] |
31 | - |
32 | - |
33 | -def juju_status(): |
34 | +LANDSCAPE_JUJU_HOME = "/var/lib/landscape/juju-homes" |
35 | + |
36 | +JUJU = "juju" |
37 | + |
38 | + |
39 | +class Juju(object): |
40 | + """A wrapper around a juju binary.""" |
41 | + |
42 | + def __init__(self, binary_path=JUJU, model=None, env=None, sudo=False): |
43 | + self.binary_path = binary_path |
44 | + self.model = model |
45 | + self.sudo = sudo |
46 | + self.env = env |
47 | + |
48 | + def status_args(self): |
49 | + """Return the subprocess.* args for a status command.""" |
50 | + args = self._resolve("status", "--format=yaml") |
51 | + if self.model: |
52 | + args.append("-e" + self.model) |
53 | + return args |
54 | + |
55 | + def ssh_args(self, unit, cmd): |
56 | + """Return the subprocess.* args for an SSH command.""" |
57 | + return self._resolve("ssh", unit, cmd) |
58 | + |
59 | + def pull_args(self, unit, source, target="."): |
60 | + """Return the subprocess.* args for an SCP command.""" |
61 | + source = "{}:{}".format(unit, source) |
62 | + return self._resolve("scp", source, target) |
63 | + |
64 | + def push_args(self, unit, source, target): |
65 | + """Return the subprocess.* args for an SCP command.""" |
66 | + target = "{}:{}".format(unit, target) |
67 | + return self._resolve("scp", source, target) |
68 | + |
69 | + def _resolve(self, sub, *subargs): |
70 | + """Return the subprocess.* args for the juju subcommand.""" |
71 | + args = [self.binary_path, sub] |
72 | + if self.sudo: |
73 | + args.insert(0, "sudo") |
74 | + args.extend(subargs) |
75 | + return args |
76 | + |
77 | + |
78 | +def juju_status(juju): |
79 | """Return a juju status structure.""" |
80 | - cmd = ["juju", "status", "--format=yaml"] |
81 | - output = check_output(cmd).decode("utf-8").strip() |
82 | + output = check_output(juju.status_args(), env=juju.env) |
83 | + output = output.decode("utf-8").strip() |
84 | return yaml.load(output) |
85 | |
86 | |
87 | -def get_units(status=juju_status()): |
88 | +def get_units(juju, status=None): |
89 | """Return a list with all units.""" |
90 | + if status is None: |
91 | + status = juju_status(juju) |
92 | units = [] |
93 | if "services" in status: |
94 | applications = status["services"] |
95 | @@ -49,12 +96,13 @@ |
96 | return units |
97 | |
98 | |
99 | -def _create_ps_output_file(unit): |
100 | +def _create_ps_output_file(juju, unit): |
101 | """List running processes and redirect them to a file.""" |
102 | + log.info("Collecting ps output on unit {}".format(unit)) |
103 | ps_cmd = "ps fauxww | sudo tee /var/log/ps-fauxww.txt" |
104 | + args = juju.ssh_args(unit, ps_cmd) |
105 | try: |
106 | - log.info("Collecting ps output on unit {}".format(unit)) |
107 | - check_output(["juju", "ssh", unit, ps_cmd], stderr=STDOUT) |
108 | + check_output(args, stderr=STDOUT, env=juju.env) |
109 | except CalledProcessError as e: |
110 | log.warning( |
111 | "Failed to collect running processes on unit {}".format(unit)) |
112 | @@ -62,7 +110,8 @@ |
113 | log.warning(e.returncode) |
114 | |
115 | |
116 | -def _create_log_tarball(unit): |
117 | +def _create_log_tarball(juju, unit): |
118 | + log.info("Creating tarball on unit {}".format(unit)) |
119 | exclude = " ".join(["--exclude=%s" % x for x in EXCLUDED]) |
120 | logs = "$(sudo sh -c \"ls -1d %s 2>/dev/null\")" % " ".join(LOGS) |
121 | # note, tar commands can fail since we are backing up log files |
122 | @@ -75,9 +124,9 @@ |
123 | logsuffix = "bootstrap" |
124 | cmd = "{} {} -czf /tmp/logs_{}.tar.gz {}".format( |
125 | tar_cmd, exclude, logsuffix, logs) |
126 | + args = juju.ssh_args(unit, cmd) |
127 | try: |
128 | - log.info("Creating tarball on unit {}".format(unit)) |
129 | - check_output(["juju", "ssh", unit, cmd], stderr=STDOUT) |
130 | + check_output(args, stderr=STDOUT, env=juju.env) |
131 | except CalledProcessError as e: |
132 | log.warning( |
133 | "Failed to create remote log tarball on unit {}".format(unit)) |
134 | @@ -85,7 +134,26 @@ |
135 | log.warning(e.returncode) |
136 | |
137 | |
138 | -def collect_logs(): |
139 | +def download_log_from_unit(juju, unit): |
140 | + log.info("Downloading tarball from unit %s" % unit) |
141 | + unit_filename = unit.replace("/", "-") |
142 | + if unit == "0": |
143 | + unit_filename = "bootstrap" |
144 | + remote_filename = "logs_%s.tar.gz" % unit_filename |
145 | + try: |
146 | + args = juju.pull_args(unit, "/tmp/" + remote_filename) |
147 | + call(args, env=juju.env) |
148 | + os.mkdir(unit_filename) |
149 | + call(["tar", "-C", unit_filename, "-xzf", remote_filename]) |
150 | + os.unlink(remote_filename) |
151 | + except: |
152 | + log.warning("error collecting logs from %s, skipping" % unit) |
153 | + finally: |
154 | + if os.path.exists(remote_filename): |
155 | + os.unlink(remote_filename) |
156 | + |
157 | + |
158 | +def collect_logs(juju): |
159 | """ |
160 | Remotely, on each unit, create a tarball with the requested log files |
161 | or directories, if they exist. If a requested log does not exist on a |
162 | @@ -93,18 +161,24 @@ |
163 | After each tarball is created, it's downloaded to the current directory |
164 | and expanded, and the tarball is then deleted. |
165 | """ |
166 | - units = get_units() |
167 | + units = get_units(juju) |
168 | # include bootstrap |
169 | units.append("0") |
170 | + |
171 | log.info("Collecting running processes for all units including bootstrap") |
172 | - pool = multiprocessing.Pool(processes=4) |
173 | - map(_create_ps_output_file, units) |
174 | + map(partial(_create_ps_output_file, juju), units) |
175 | |
176 | log.info("Creating remote tarball in parallel for units %s" % ( |
177 | ",".join(units))) |
178 | - map(_create_log_tarball, units) |
179 | + map(partial(_create_log_tarball, juju), units) |
180 | log.info("Downloading logs from units") |
181 | - pool.map(download_log_from_unit, units) |
182 | + |
183 | + _mp_map(partial(download_log_from_unit, juju), units) |
184 | + |
185 | + |
186 | +def _mp_map(func, args): |
187 | + pool = multiprocessing.Pool(processes=4) |
188 | + pool.map(func, args) |
189 | |
190 | |
191 | def get_landscape_unit(units): |
192 | @@ -120,7 +194,7 @@ |
193 | return units[0] |
194 | |
195 | |
196 | -def disable_ssh_proxy(landscape_unit): |
197 | +def disable_ssh_proxy(juju, landscape_unit): |
198 | """ |
199 | Workaround for #1607076: disable the proxy-ssh juju environment setting |
200 | for the inner cloud so we can juju ssh into it. |
201 | @@ -131,70 +205,75 @@ |
202 | cmd = cmd_prefix.format(juju_home, juju_home, disable_cmd) |
203 | log.info("Disabling proxy-ssh in the juju environment on " |
204 | "{}".format(landscape_unit)) |
205 | + args = juju.ssh_args(landscape_unit, cmd) |
206 | try: |
207 | - check_output(["juju", "ssh", landscape_unit, cmd], stderr=STDOUT) |
208 | + check_output(args, stderr=STDOUT, env=juju.env) |
209 | except CalledProcessError as e: |
210 | log.warning("Couldn't disable proxy-ssh in the inner environment," |
211 | " collecting inner logs might fail.") |
212 | log.warning("Error was:\n{}".format(e.output)) |
213 | |
214 | -def collect_inner_logs(): |
215 | + |
216 | +def collect_inner_logs(juju): |
217 | """Collect logs from an inner landscape[-server]/0 unit.""" |
218 | - collect_logs = "/tmp/collect-logs" |
219 | - landscape_juju_home = "/var/lib/landscape/juju-homes" |
220 | - collect_run = ( |
221 | - "JUJU_HOME=%s/`sudo ls -rt %s/ | tail -1`" |
222 | - " %s /tmp/inner-logs.tar.gz" % ( |
223 | - landscape_juju_home, landscape_juju_home, collect_logs)) |
224 | - poke_inner_env = ( |
225 | - "sudo JUJU_HOME=%s/`sudo ls -rt %s/ | tail -1` juju status" % |
226 | - (landscape_juju_home, landscape_juju_home)) |
227 | - units = get_units() |
228 | + units = get_units(juju) |
229 | landscape_unit = get_landscape_unit(units) |
230 | if not landscape_unit: |
231 | log.info("No landscape[-server]/N found, skipping") |
232 | return |
233 | log.info("Found landscape unit %s" % landscape_unit) |
234 | + |
235 | + disable_ssh_proxy(juju, landscape_unit) |
236 | + |
237 | + # Make sure that there *is* an inner model. |
238 | + poke_inner_env = ( |
239 | + "sudo JUJU_HOME=%s/`sudo ls -rt %s/ | tail -1` juju status" % |
240 | + (LANDSCAPE_JUJU_HOME, LANDSCAPE_JUJU_HOME)) |
241 | + args = juju.ssh_args(landscape_unit, poke_inner_env) |
242 | try: |
243 | - check_output(["juju", "ssh", landscape_unit, poke_inner_env]) |
244 | + check_output(args, env=juju.env) |
245 | except CalledProcessError: |
246 | log.info("No active inner environment found on %s, skipping" % |
247 | landscape_unit) |
248 | return |
249 | - disable_ssh_proxy(landscape_unit) |
250 | - call(["juju", "scp", PRG, "%s:%s" % (landscape_unit, collect_logs)]) |
251 | - call(["juju", "ssh", landscape_unit, "sudo rm -rf /tmp/inner-logs.tar.*"]) |
252 | + |
253 | + # Prepare to get the logs from the inner model. |
254 | + collect_logs = "/tmp/collect-logs" |
255 | + args = juju.push_args(landscape_unit, PRG, collect_logs) |
256 | + call(args, env=juju.env) |
257 | + filename = "inner-logs.tar.gz" |
258 | + inner_filename = os.path.join("/tmp", filename) |
259 | + args = juju.ssh_args(landscape_unit, "sudo rm -rf " + inner_filename) |
260 | + call(args, env=juju.env) |
261 | + |
262 | + # Collect the logs for the inner model. |
263 | log.info("Collecting Logs on inner environment") |
264 | - check_call( |
265 | - ["juju", "ssh", landscape_unit, "sudo -u landscape %s" % collect_run]) |
266 | + collect_run = ("JUJU_HOME=%s/`sudo ls -rt %s/ | tail -1` %s %s" |
267 | + ) % (LANDSCAPE_JUJU_HOME, LANDSCAPE_JUJU_HOME, |
268 | + collect_logs, inner_filename) |
269 | + args = juju.ssh_args(landscape_unit, "sudo -u landscape %s" % collect_run) |
270 | + check_call(args, env=juju.env) |
271 | + |
272 | + # Copy the inner logs into a local directory. |
273 | log.info("Copying inner environment back") |
274 | - check_call(["juju", "scp", "%s:/tmp/inner-logs.tar.gz" % landscape_unit, |
275 | - "."]) |
276 | - cwd = os.getcwd() |
277 | - inner_dir = "landscape-0-inner-logs" |
278 | - os.mkdir(inner_dir) |
279 | - os.chdir(inner_dir) |
280 | - check_call(["tar", "-zxf", "%s/inner-logs.tar.gz" % cwd]) |
281 | - os.chdir(cwd) |
282 | - check_call(["rm", "-rf", "inner-logs.tar.gz"]) |
283 | - |
284 | - |
285 | -def download_log_from_unit(unit): |
286 | - log.info("Downloading tarball from unit %s" % unit) |
287 | - unit_filename = unit.replace("/", "-") |
288 | - if unit == "0": |
289 | - unit_filename = "bootstrap" |
290 | + args = juju.pull_args(landscape_unit, inner_filename) |
291 | + check_call(args, env=juju.env) |
292 | try: |
293 | - remote_filename = "logs_%s.tar.gz" % unit_filename |
294 | - call(["juju", "scp", "%s:/tmp/%s" % (unit, remote_filename), "."]) |
295 | - os.mkdir(unit_filename) |
296 | - call(["tar", "-C", unit_filename, "-xzf", remote_filename]) |
297 | - os.unlink(remote_filename) |
298 | - except: |
299 | - log.warning("error collecting logs from %s, skipping" % unit) |
300 | + cwd = os.getcwd() |
301 | + inner_dir = "landscape-0-inner-logs" |
302 | + os.mkdir(inner_dir) |
303 | + os.chdir(inner_dir) |
304 | + try: |
305 | + check_call(["tar", "-zxf", os.path.join(cwd, filename)]) |
306 | + finally: |
307 | + os.chdir(cwd) |
308 | finally: |
309 | - if os.path.exists(remote_filename): |
310 | - os.unlink(remote_filename) |
311 | + try: |
312 | + os.remove(filename) |
313 | + except OSError as e: |
314 | + if e.errno != errno.ENOENT: |
315 | + log.warning( |
316 | + "failed to remove inner logs tarball: {}".format(e)) |
317 | |
318 | |
319 | def bundle_logs(tmpdir, tarfile, extrafiles=[]): |
320 | @@ -224,7 +303,7 @@ |
321 | # get rid of the tmpdir prefix |
322 | args.extend(["--transform", "s,{}/,,".format(tmpdir[1:])]) |
323 | # need absolute paths since tmpdir isn't the cwd |
324 | - args.extend(os.path.join(tmpdir, d) for d in os.listdir(tmpdir)) |
325 | + args.extend(os.path.join(tmpdir, d) for d in sorted(os.listdir(tmpdir))) |
326 | if extrafiles: |
327 | args.extend(extrafiles) |
328 | call(args) |
329 | @@ -241,32 +320,36 @@ |
330 | return parser |
331 | |
332 | |
333 | -def main(): |
334 | - logging.basicConfig( |
335 | - level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s') |
336 | - parser = get_option_parser() |
337 | - args = parser.parse_args() |
338 | +def main(tarfile, extrafiles, juju=None): |
339 | + if juju is None: |
340 | + juju = Juju("juju") |
341 | + |
342 | # we need the absolute path because we will be changing |
343 | # the cwd |
344 | - tarfile = os.path.abspath(args.tarfile) |
345 | - tmpdir = tempfile.mkdtemp() |
346 | + tmpdir = mkdtemp() |
347 | cwd = os.getcwd() |
348 | # logs are collected inside a temporary directory |
349 | os.chdir(tmpdir) |
350 | try: |
351 | - collect_logs() |
352 | + collect_logs(juju) |
353 | try: |
354 | - collect_inner_logs() |
355 | + collect_inner_logs(juju) |
356 | except: |
357 | log.warning("Collecting inner logs failed, continuing") |
358 | # we create the final tarball outside of tmpdir to we can |
359 | # add the extrafiles to the tarball root |
360 | os.chdir(cwd) |
361 | - bundle_logs(tmpdir, tarfile, args.extrafiles) |
362 | + bundle_logs(tmpdir, tarfile, extrafiles) |
363 | log.info("created: %s" % tarfile) |
364 | finally: |
365 | call(["chmod", "-R", "u+w", tmpdir]) |
366 | shutil.rmtree(tmpdir) |
367 | |
368 | |
369 | -main() |
370 | +if __name__ == "__main__": |
371 | + logging.basicConfig( |
372 | + level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s') |
373 | + parser = get_option_parser() |
374 | + args = parser.parse_args(sys.argv[1:]) |
375 | + tarfile = os.path.abspath(args.tarfile) |
376 | + main(tarfile, args.extrafiles) |
377 | |
378 | === added file 'test_collect-logs.py' |
379 | --- test_collect-logs.py 1970-01-01 00:00:00 +0000 |
380 | +++ test_collect-logs.py 2016-08-23 00:10:59 +0000 |
381 | @@ -0,0 +1,749 @@ |
382 | +# Copyright 2016 Canonical Limited. All rights reserved. |
383 | + |
384 | +# To run: "python -m unittest test_collect-logs" |
385 | + |
386 | +import errno |
387 | +import os |
388 | +import os.path |
389 | +import shutil |
390 | +import subprocess |
391 | +import sys |
392 | +import tempfile |
393 | +from unittest import TestCase |
394 | + |
395 | +import mock |
396 | + |
397 | + |
398 | +__file__ = os.path.abspath(__file__) |
399 | + |
400 | +script = type(sys)("collect-logs") |
401 | +script.__file__ = os.path.abspath("collect-logs") |
402 | +execfile("collect-logs", script.__dict__) |
403 | + |
404 | + |
405 | +class FakeError(Exception): |
406 | + """A specific error for which to check.""" |
407 | + |
408 | + |
409 | +def _create_file(filename, data=None): |
410 | + """Create (or re-create) the identified file. |
411 | + |
412 | + If data is provided, it is written to the file. Otherwise it |
413 | + will be empty. |
414 | + |
415 | + The file's directory is created if necessary. |
416 | + """ |
417 | + dirname = os.path.dirname(os.path.abspath(filename)) |
418 | + try: |
419 | + os.makedirs(dirname) |
420 | + except OSError as e: |
421 | + if e.errno != errno.EEXIST: |
422 | + raise |
423 | + |
424 | + with open(filename, "w") as file: |
425 | + if data: |
426 | + file.write() |
427 | + |
428 | + |
429 | +class _BaseTestCase(TestCase): |
430 | + |
431 | + MOCKED = None |
432 | + |
433 | + def setUp(self): |
434 | + super(_BaseTestCase, self).setUp() |
435 | + |
436 | + self.orig_cwd = os.getcwd() |
437 | + self.cwd = tempfile.mkdtemp() |
438 | + os.chdir(self.cwd) |
439 | + |
440 | + self.tempdir = os.path.join(self.cwd, "tempdir") |
441 | + os.mkdir(self.tempdir) |
442 | + |
443 | + self.orig = {} |
444 | + for attr in self.MOCKED or (): |
445 | + self.orig[attr] = getattr(script, attr) |
446 | + setattr(script, attr, mock.Mock()) |
447 | + |
448 | + self.juju = script.Juju() |
449 | + |
450 | + def tearDown(self): |
451 | + for attr in self.MOCKED or (): |
452 | + setattr(script, attr, self.orig[attr]) |
453 | + |
454 | + shutil.rmtree(self.cwd) |
455 | + os.chdir(self.orig_cwd) |
456 | + |
457 | + super(_BaseTestCase, self).tearDown() |
458 | + |
459 | + def _create_tempfile(self, filename, data=None): |
460 | + """Create a file at the identified path, but rooted at the temp dir.""" |
461 | + _create_file(os.path.join(self.tempdir, filename), data) |
462 | + |
463 | + def assert_cwd(self, dirname): |
464 | + """Ensure that the CWD matches the given directory.""" |
465 | + cwd = os.getcwd() |
466 | + self.assertEqual(cwd, dirname) |
467 | + |
468 | + |
469 | +class MainTestCase(_BaseTestCase): |
470 | + |
471 | + MOCKED = ("collect_logs", "collect_inner_logs", "bundle_logs") |
472 | + |
473 | + def setUp(self): |
474 | + super(MainTestCase, self).setUp() |
475 | + |
476 | + self.orig_mkdtemp = script.mkdtemp |
477 | + script.mkdtemp = lambda: self.tempdir |
478 | + |
479 | + def tearDown(self): |
480 | + script.mkdtemp = self.orig_mkdtemp |
481 | + |
482 | + super(MainTestCase, self).tearDown() |
483 | + |
484 | + def test_success(self): |
485 | + """ |
486 | + main() calls collect_logs(), collect_inner_logs(), and bundle_logs(). |
487 | + """ |
488 | + tarfile = "/tmp/logs.tgz" |
489 | + extrafiles = ["spam.py"] |
490 | + |
491 | + script.main(tarfile, extrafiles, juju=self.juju) |
492 | + |
493 | + script.collect_logs.assert_called_once_with(self.juju) |
494 | + script.collect_inner_logs.assert_called_once_with(self.juju) |
495 | + script.bundle_logs.assert_called_once_with( |
496 | + self.tempdir, tarfile, extrafiles) |
497 | + self.assertFalse(os.path.exists(self.tempdir)) |
498 | + |
499 | + def test_in_correct_directories(self): |
500 | + """ |
501 | + main() calls its dependencies while in specific directories. |
502 | + """ |
503 | + script.collect_logs.side_effect = ( |
504 | + lambda _: self.assert_cwd(self.tempdir)) |
505 | + script.collect_inner_logs.side_effect = ( |
506 | + lambda _: self.assert_cwd(self.tempdir)) |
507 | + script.bundle_logs.side_effect = lambda *a: self.assert_cwd(self.cwd) |
508 | + tarfile = "/tmp/logs.tgz" |
509 | + extrafiles = ["spam.py"] |
510 | + |
511 | + script.main(tarfile, extrafiles, juju=self.juju) |
512 | + |
513 | + def test_cleanup(self): |
514 | + """ |
515 | + main() cleans up the temp dir it creates. |
516 | + """ |
517 | + tarfile = "/tmp/logs.tgz" |
518 | + extrafiles = ["spam.py"] |
519 | + |
520 | + script.main(tarfile, extrafiles, juju=self.juju) |
521 | + |
522 | + self.assertFalse(os.path.exists(self.tempdir)) |
523 | + |
524 | + def test_collect_logs_error(self): |
525 | + """ |
526 | + main() doesn't handle the error when collect_logs() fails. |
527 | + |
528 | + It still cleans up the temp dir. |
529 | + """ |
530 | + tarfile = "/tmp/logs.tgz" |
531 | + extrafiles = ["spam.py"] |
532 | + script.collect_logs.side_effect = FakeError() |
533 | + |
534 | + with self.assertRaises(FakeError): |
535 | + script.main(tarfile, extrafiles, juju=self.juju) |
536 | + |
537 | + script.collect_logs.assert_called_once_with(self.juju) |
538 | + script.collect_inner_logs.assert_not_called() |
539 | + script.bundle_logs.assert_not_called() |
540 | + self.assertFalse(os.path.exists(self.tempdir)) |
541 | + |
542 | + def test_collect_inner_logs_error(self): |
543 | + """ |
544 | + main() ignores the error when collect_inner_logs() fails. |
545 | + |
546 | + It still cleans up the temp dir. |
547 | + """ |
548 | + tarfile = "/tmp/logs.tgz" |
549 | + extrafiles = ["spam.py"] |
550 | + script.collect_inner_logs.side_effect = FakeError() |
551 | + |
552 | + script.main(tarfile, extrafiles, juju=self.juju) |
553 | + |
554 | + script.collect_logs.assert_called_once_with(self.juju) |
555 | + script.collect_inner_logs.assert_called_once_with(self.juju) |
556 | + script.bundle_logs.assert_called_once_with( |
557 | + self.tempdir, tarfile, extrafiles) |
558 | + self.assertFalse(os.path.exists(self.tempdir)) |
559 | + |
560 | + def test_bundle_logs_error(self): |
561 | + """ |
562 | + main() doesn't handle the error when bundle_logs() fails. |
563 | + |
564 | + It still cleans up the temp dir. |
565 | + """ |
566 | + tarfile = "/tmp/logs.tgz" |
567 | + extrafiles = ["spam.py"] |
568 | + script.bundle_logs.side_effect = FakeError() |
569 | + |
570 | + with self.assertRaises(FakeError): |
571 | + script.main(tarfile, extrafiles, juju=self.juju) |
572 | + |
573 | + script.collect_logs.assert_called_once_with(self.juju) |
574 | + script.collect_inner_logs.assert_called_once_with(self.juju) |
575 | + script.bundle_logs.assert_called_once_with( |
576 | + self.tempdir, tarfile, extrafiles) |
577 | + self.assertFalse(os.path.exists(self.tempdir)) |
578 | + |
579 | + |
580 | +class CollectLogsTestCase(_BaseTestCase): |
581 | + |
582 | + MOCKED = ("get_units", "check_output", "call") |
583 | + |
584 | + def setUp(self): |
585 | + super(CollectLogsTestCase, self).setUp() |
586 | + |
587 | + self.units = [ |
588 | + "landscape-server/0", |
589 | + "postgresql/0", |
590 | + "rabbitmq-server/0", |
591 | + "haproxy/0", |
592 | + ] |
593 | + script.get_units.return_value = self.units[:] |
594 | + |
595 | + self.mp_map_orig = script._mp_map |
596 | + script._mp_map = lambda f, a: map(f, a) |
597 | + |
598 | + os.chdir(self.tempdir) |
599 | + |
600 | + def tearDown(self): |
601 | + script._mp_map = self.mp_map_orig |
602 | + |
603 | + super(CollectLogsTestCase, self).tearDown() |
604 | + |
605 | + def _call_side_effect(self, cmd, env=None): |
606 | + """Perform the side effect of calling the mocked-out call().""" |
607 | + if cmd[0] == "tar": |
608 | + self.assertTrue(os.path.exists(cmd[-1])) |
609 | + return |
610 | + self.assertEqual(env, self.juju.env) |
611 | + self.assertEqual(cmd[0], "juju") |
612 | + _create_file(os.path.basename(cmd[2])) |
613 | + |
614 | + def test_success(self): |
615 | + """ |
616 | + collect_logs() gathers "ps" output and logs from each unit. |
617 | + """ |
618 | + script.call.side_effect = self._call_side_effect |
619 | + |
620 | + script.collect_logs(self.juju) |
621 | + |
622 | + script.get_units.assert_called_once_with(self.juju) |
623 | + expected = [] |
624 | + units = self.units + ["0"] |
625 | + # for _create_ps_output_file() |
626 | + for unit in units: |
627 | + cmd = "ps fauxww | sudo tee /var/log/ps-fauxww.txt" |
628 | + expected.append(mock.call(["juju", "ssh", unit, cmd], |
629 | + stderr=subprocess.STDOUT, |
630 | + env=None, |
631 | + )) |
632 | + # for _create_log_tarball() |
633 | + for unit in units: |
634 | + cmd = ("sudo tar --ignore-failed-read" |
635 | + " --exclude=/var/lib/landscape/client/package/hash-id" |
636 | + " --exclude=/var/lib/juju/containers/juju-*-lxc-template" |
637 | + " -czf /tmp/logs_{}.tar.gz" |
638 | + " $(sudo sh -c \"ls -1d {} 2>/dev/null\")" |
639 | + ).format( |
640 | + unit.replace("/", "-") if unit != "0" else "bootstrap", |
641 | + " ".join(["/var/log", |
642 | + "/etc/nova", |
643 | + "/etc/swift", |
644 | + "/etc/neutron", |
645 | + "/etc/apache2", |
646 | + "/etc/haproxy", |
647 | + "/etc/ceph", |
648 | + "/etc/glance", |
649 | + "/var/lib/lxc/*/rootfs/var/log", |
650 | + "/var/lib/juju/containers", |
651 | + "/var/lib/landscape/client", |
652 | + ]), |
653 | + ) |
654 | + expected.append(mock.call(["juju", "ssh", unit, cmd], |
655 | + stderr=subprocess.STDOUT, |
656 | + env=None, |
657 | + )) |
658 | + self.assertEqual(script.check_output.call_count, len(expected)) |
659 | + script.check_output.assert_has_calls(expected, any_order=True) |
660 | + expected = [] |
661 | + # for download_log_from_unit() |
662 | + for unit in units: |
663 | + name = unit.replace("/", "-") if unit != "0" else "bootstrap" |
664 | + filename = "logs_{}.tar.gz".format(name) |
665 | + source = "{}:/tmp/{}".format(unit, filename) |
666 | + expected.append(mock.call(["juju", "scp", source, "."], env=None)) |
667 | + expected.append(mock.call(["tar", "-C", name, "-xzf", filename])) |
668 | + self.assertFalse(os.path.exists(filename)) |
669 | + self.assertEqual(script.call.call_count, len(expected)) |
670 | + script.call.assert_has_calls(expected, any_order=True) |
671 | + |
672 | + def test_get_units_failure(self): |
673 | + """ |
674 | + collect_logs() does not handle errors from get_units(). |
675 | + """ |
676 | + script.get_units.side_effect = FakeError() |
677 | + |
678 | + with self.assertRaises(FakeError): |
679 | + script.collect_logs(self.juju) |
680 | + |
681 | + script.get_units.assert_called_once_with(self.juju) |
682 | + script.check_output.assert_not_called() |
683 | + script.call.assert_not_called() |
684 | + |
685 | + def test_check_output_failure(self): |
686 | + """ |
687 | + collect_logs() does not handle errors from check_output(). |
688 | + """ |
689 | + script.check_output.side_effect = [mock.DEFAULT, |
690 | + FakeError(), |
691 | + ] |
692 | + |
693 | + with self.assertRaises(FakeError): |
694 | + script.collect_logs(self.juju) |
695 | + |
696 | + script.get_units.assert_called_once_with(self.juju) |
697 | + self.assertEqual(script.check_output.call_count, 2) |
698 | + script.call.assert_not_called() |
699 | + |
700 | + def test_call_failure(self): |
701 | + """ |
702 | + collect_logs() does not handle errors from call(). |
703 | + """ |
704 | + def call_side_effect(cmd, env=None): |
705 | + # second use of call() for landscape-server/0 |
706 | + if script.call.call_count == 2: |
707 | + raise FakeError() |
708 | + # first use of call() for postgresql/0 |
709 | + if script.call.call_count == 3: |
710 | + raise FakeError() |
711 | + # all other uses of call() default to the normal side effect. |
712 | + return self._call_side_effect(cmd, env=env) |
713 | + script.call.side_effect = call_side_effect |
714 | + |
715 | + script.collect_logs(self.juju) |
716 | + |
717 | + script.get_units.assert_called_once_with(self.juju) |
718 | + units = self.units + ["0"] |
719 | + self.assertEqual(script.check_output.call_count, len(units) * 2) |
720 | + self.assertEqual(script.call.call_count, len(units) * 2 - 1) |
721 | + for unit in units: |
722 | + name = unit.replace("/", "-") if unit != "0" else "bootstrap" |
723 | + if unit == self.units[1]: |
724 | + self.assertFalse(os.path.exists(name)) |
725 | + else: |
726 | + self.assertTrue(os.path.exists(name)) |
727 | + filename = "logs_{}.tar.gz".format(name) |
728 | + self.assertFalse(os.path.exists(filename)) |
729 | + |
730 | + |
731 | +class CollectInnerLogsTestCase(_BaseTestCase): |
732 | + |
733 | + MOCKED = ("get_units", "check_output", "call", "check_call") |
734 | + |
735 | + def setUp(self): |
736 | + super(CollectInnerLogsTestCase, self).setUp() |
737 | + |
738 | + self.units = [ |
739 | + "landscape-server/0", |
740 | + "postgresql/0", |
741 | + "rabbitmq-server/0", |
742 | + "haproxy/0", |
743 | + ] |
744 | + script.get_units.return_value = self.units[:] |
745 | + |
746 | + os.chdir(self.tempdir) |
747 | + |
748 | + def assert_clean(self): |
749 | + """Ensure that collect_inner_logs cleaned up after itself.""" |
750 | + self.assert_cwd(self.tempdir) |
751 | + self.assertFalse(os.path.exists("inner-logs.tar.gz")) |
752 | + |
753 | + def test_success(self): |
754 | + """ |
755 | + collect_inner_logs() finds the inner model and runs collect-logs |
756 | + inside it. The resulting tarball is downloaded, extracted, and |
757 | + deleted. |
758 | + """ |
759 | + def check_call_side_effect(cmdenv, env=None): |
760 | + self.assertEqual(env, self.juju.env) |
761 | + if script.check_call.call_count == 2: |
762 | + self.assert_cwd(self.tempdir) |
763 | + self._create_tempfile("inner-logs.tar.gz") |
764 | + elif script.check_call.call_count == 3: |
765 | + cwd = os.path.join(self.tempdir, "landscape-0-inner-logs") |
766 | + self.assert_cwd(cwd) |
767 | + return None |
768 | + script.check_call.side_effect = check_call_side_effect |
769 | + |
770 | + script.collect_inner_logs(self.juju) |
771 | + |
772 | + # Check get_units() calls. |
773 | + script.get_units.assert_called_once_with(self.juju) |
774 | + # Check check_output() calls. |
775 | + expected = [] |
776 | + cmd = ("sudo JUJU_HOME=/var/lib/landscape/juju-homes/" |
777 | + "`sudo ls -rt /var/lib/landscape/juju-homes/ | tail -1`" |
778 | + " juju set-env proxy-ssh=false") |
779 | + expected.append(mock.call(["juju", "ssh", "landscape-server/0", cmd], |
780 | + stderr=subprocess.STDOUT, |
781 | + env=None)) |
782 | + cmd = ("sudo JUJU_HOME=/var/lib/landscape/juju-homes/" |
783 | + "`sudo ls -rt /var/lib/landscape/juju-homes/ | tail -1`" |
784 | + " juju status") |
785 | + expected.append(mock.call(["juju", "ssh", "landscape-server/0", cmd], |
786 | + env=None)) |
787 | + self.assertEqual(script.check_output.call_count, len(expected)) |
788 | + script.check_output.assert_has_calls(expected, any_order=True) |
789 | + # Check call() calls. |
790 | + expected = [ |
791 | + mock.call(["juju", "scp", |
792 | + os.path.join(os.path.dirname(__file__), "collect-logs"), |
793 | + "landscape-server/0:/tmp/collect-logs", |
794 | + ], env=None), |
795 | + mock.call(["juju", "ssh", |
796 | + "landscape-server/0", |
797 | + "sudo rm -rf /tmp/inner-logs.tar.gz", |
798 | + ], env=None), |
799 | + ] |
800 | + self.assertEqual(script.call.call_count, len(expected)) |
801 | + script.call.assert_has_calls(expected, any_order=True) |
802 | + # Check check_call() calls. |
803 | + cmd = ("sudo -u landscape" |
804 | + " JUJU_HOME=/var/lib/landscape/juju-homes/" |
805 | + "`sudo ls -rt /var/lib/landscape/juju-homes/ | tail -1`" |
806 | + " /tmp/collect-logs /tmp/inner-logs.tar.gz") |
807 | + expected = [ |
808 | + mock.call(["juju", "ssh", "landscape-server/0", cmd], env=None), |
809 | + mock.call(["juju", "scp", |
810 | + "landscape-server/0:/tmp/inner-logs.tar.gz", |
811 | + ".", |
812 | + ], env=None), |
813 | + mock.call(["tar", "-zxf", self.tempdir + "/inner-logs.tar.gz"]), |
814 | + ] |
815 | + self.assertEqual(script.check_call.call_count, len(expected)) |
816 | + script.check_call.assert_has_calls(expected, any_order=True) |
817 | + self.assert_clean() |
818 | + |
819 | + def test_with_legacy_landscape_unit(self): |
820 | + """ |
821 | + collect_inner_logs() correctly supports legacy landscape installations. |
822 | + """ |
823 | + self.units[0] = "landscape/0" |
824 | + script.get_units.return_value = self.units[:] |
825 | + |
826 | + script.collect_inner_logs(self.juju) |
827 | + |
828 | + expected = [] |
829 | + cmd = ("sudo JUJU_HOME=/var/lib/landscape/juju-homes/" |
830 | + "`sudo ls -rt /var/lib/landscape/juju-homes/ | tail -1`" |
831 | + " juju set-env proxy-ssh=false") |
832 | + expected.append(mock.call(["juju", "ssh", "landscape/0", cmd], |
833 | + stderr=subprocess.STDOUT, |
834 | + env=None)) |
835 | + cmd = ("sudo JUJU_HOME=/var/lib/landscape/juju-homes/" |
836 | + "`sudo ls -rt /var/lib/landscape/juju-homes/ | tail -1`" |
837 | + " juju status") |
838 | + expected.append(mock.call(["juju", "ssh", "landscape/0", cmd], |
839 | + env=None)) |
840 | + self.assertEqual(script.check_output.call_count, len(expected)) |
841 | + script.check_output.assert_has_calls(expected, any_order=True) |
842 | + self.assert_clean() |
843 | + |
844 | + def test_no_units(self): |
845 | + """ |
846 | + collect_inner_logs() is a noop if no units are found. |
847 | + """ |
848 | + script.get_units.return_value = [] |
849 | + |
850 | + script.collect_inner_logs(self.juju) |
851 | + |
852 | + script.get_units.assert_called_once_with(self.juju) |
853 | + script.check_output.assert_not_called() |
854 | + script.call.assert_not_called() |
855 | + script.check_call.assert_not_called() |
856 | + self.assert_clean() |
857 | + |
858 | + def test_no_landscape_server_unit(self): |
859 | + """ |
860 | + collect_inner_logs() is a noop if the landscape unit isn't found. |
861 | + """ |
862 | + del self.units[0] |
863 | + script.get_units.return_value = self.units[:] |
864 | + |
865 | + script.collect_inner_logs(self.juju) |
866 | + |
867 | + script.get_units.assert_called_once_with(self.juju) |
868 | + script.check_output.assert_not_called() |
869 | + script.call.assert_not_called() |
870 | + script.check_call.assert_not_called() |
871 | + self.assert_clean() |
872 | + |
873 | + def test_get_units_failure(self): |
874 | + """ |
875 | + collect_inner_logs() does not handle errors from get_units(). |
876 | + """ |
877 | + script.get_units.side_effect = FakeError() |
878 | + |
879 | + with self.assertRaises(FakeError): |
880 | + script.collect_inner_logs(self.juju) |
881 | + |
882 | + self.assertEqual(script.get_units.call_count, 1) |
883 | + script.check_output.assert_not_called() |
884 | + script.call.assert_not_called() |
885 | + script.check_call.assert_not_called() |
886 | + self.assert_cwd(self.tempdir) |
887 | + self.assert_clean() |
888 | + |
889 | + def test_check_output_failure_1(self): |
890 | + """ |
891 | + collect_inner_logs() does not handle non-CalledProcessError |
892 | + errors when disabling the SSH proxy. |
893 | + """ |
894 | + script.check_output.side_effect = FakeError() |
895 | + |
896 | + with self.assertRaises(FakeError): |
897 | + script.collect_inner_logs(self.juju) |
898 | + |
899 | + self.assertEqual(script.get_units.call_count, 1) |
900 | + self.assertEqual(script.check_output.call_count, 1) |
901 | + script.call.assert_not_called() |
902 | + script.check_call.assert_not_called() |
903 | + self.assert_cwd(self.tempdir) |
904 | + self.assert_clean() |
905 | + |
906 | + def test_check_output_failure_2(self): |
907 | + """ |
908 | + collect_inner_logs() does not handle non-CalledProcessError |
909 | + errors when verifying the inner model is bootstrapped. |
910 | + """ |
911 | + script.check_output.side_effect = [None, |
912 | + FakeError(), |
913 | + ] |
914 | + |
915 | + with self.assertRaises(FakeError): |
916 | + script.collect_inner_logs(self.juju) |
917 | + |
918 | + self.assertEqual(script.get_units.call_count, 1) |
919 | + self.assertEqual(script.check_output.call_count, 2) |
920 | + script.call.assert_not_called() |
921 | + script.check_call.assert_not_called() |
922 | + self.assert_cwd(self.tempdir) |
923 | + self.assert_clean() |
924 | + |
925 | + def test_call_failure_1(self): |
926 | + """ |
927 | + collect_inner_logs() does not handle errors from call(). |
928 | + """ |
929 | + script.call.side_effect = FakeError() |
930 | + |
931 | + with self.assertRaises(FakeError): |
932 | + script.collect_inner_logs(self.juju) |
933 | + |
934 | + self.assertEqual(script.get_units.call_count, 1) |
935 | + self.assertEqual(script.check_output.call_count, 2) |
936 | + self.assertEqual(script.call.call_count, 1) |
937 | + script.check_call.assert_not_called() |
938 | + self.assert_cwd(self.tempdir) |
939 | + self.assert_clean() |
940 | + |
941 | + def test_call_failure_2(self): |
942 | + """ |
943 | + collect_inner_logs() does not handle errors from call(). |
944 | + """ |
945 | + script.call.side_effect = [None, |
946 | + FakeError(), |
947 | + ] |
948 | + |
949 | + with self.assertRaises(FakeError): |
950 | + script.collect_inner_logs(self.juju) |
951 | + |
952 | + self.assertEqual(script.get_units.call_count, 1) |
953 | + self.assertEqual(script.check_output.call_count, 2) |
954 | + self.assertEqual(script.call.call_count, 2) |
955 | + script.check_call.assert_not_called() |
956 | + self.assert_clean() |
957 | + |
958 | + def test_check_call_failure_1(self): |
959 | + """ |
960 | + collect_inner_logs() does not handle errors when running |
961 | + collect-logs in the inner model. |
962 | + """ |
963 | + script.check_call.side_effect = FakeError() |
964 | + |
965 | + with self.assertRaises(FakeError): |
966 | + script.collect_inner_logs(self.juju) |
967 | + |
968 | + self.assertEqual(script.get_units.call_count, 1) |
969 | + self.assertEqual(script.check_output.call_count, 2) |
970 | + self.assertEqual(script.call.call_count, 2) |
971 | + self.assertEqual(script.check_call.call_count, 1) |
972 | + self.assert_clean() |
973 | + |
974 | + def test_check_call_failure_2(self): |
975 | + """ |
976 | + collect_inner_logs() does not handle errors downloading the |
977 | + collected logs from the inner model. |
978 | + |
979 | + It does clean up, however. |
980 | + """ |
981 | + script.check_call.side_effect = [None, |
982 | + FakeError(), |
983 | + ] |
984 | + |
985 | + with self.assertRaises(FakeError): |
986 | + script.collect_inner_logs(self.juju) |
987 | + |
988 | + self.assertEqual(script.get_units.call_count, 1) |
989 | + self.assertEqual(script.check_output.call_count, 2) |
990 | + self.assertEqual(script.call.call_count, 2) |
991 | + self.assertEqual(script.check_call.call_count, 2) |
992 | + self.assert_clean() |
993 | + |
994 | + def test_check_call_failure_3(self): |
995 | + """ |
996 | + collect_inner_logs() does not handle errors when unpacking |
997 | + the inner model tarball. |
998 | + |
999 | + It does clean up, however. |
1000 | + """ |
1001 | + def check_call_side_effect(cmd, env=None): |
1002 | + self.assertEqual(env, self.juju.env) |
1003 | + if script.check_call.call_count == 1: |
1004 | + return None |
1005 | + if script.check_call.call_count == 2: |
1006 | + self._create_tempfile("inner-logs.tar.gz") |
1007 | + return None |
1008 | + raise FakeError() |
1009 | + script.check_call.side_effect = check_call_side_effect |
1010 | + |
1011 | + with self.assertRaises(FakeError): |
1012 | + script.collect_inner_logs(self.juju) |
1013 | + |
1014 | + self.assertEqual(script.get_units.call_count, 1) |
1015 | + self.assertEqual(script.check_output.call_count, 2) |
1016 | + self.assertEqual(script.call.call_count, 2) |
1017 | + self.assertEqual(script.check_call.call_count, 3) |
1018 | + self.assert_clean() |
1019 | + |
1020 | + |
1021 | +class BundleLogsTestCase(_BaseTestCase): |
1022 | + |
1023 | + MOCKED = ("call",) |
1024 | + |
1025 | + def setUp(self): |
1026 | + """ |
1027 | + bundle_logs() creates a tarball holding the files in the tempdir. |
1028 | + """ |
1029 | + super(BundleLogsTestCase, self).setUp() |
1030 | + |
1031 | + os.chdir(self.tempdir) |
1032 | + |
1033 | + self._create_tempfile("bootstrap/var/log/syslog") |
1034 | + self._create_tempfile("bootstrap/var/log/juju/all-machines.log") |
1035 | + self._create_tempfile( |
1036 | + "bootstrap/var/lib/lxc/deadbeef/rootfs/var/log/syslog") |
1037 | + self._create_tempfile("bootstrap/var/lib/juju/containers") |
1038 | + self._create_tempfile("landscape-server-0/var/log/syslog") |
1039 | + self._create_tempfile("postgresql-0/var/log/syslog") |
1040 | + self._create_tempfile("rabbitmq-server-0/var/log/syslog") |
1041 | + self._create_tempfile("haproxy-0/var/log/syslog") |
1042 | + self._create_tempfile( |
1043 | + "landscape-0-inner-logs/bootstrap/var/log/syslog") |
1044 | + |
1045 | + self.extrafile = os.path.join(self.cwd, "spam.txt") |
1046 | + _create_file(self.extrafile) |
1047 | + |
1048 | + def test_success_with_extra(self): |
1049 | + """ |
1050 | + bundle_logs() works if extra files are included. |
1051 | + """ |
1052 | + tarfile = "/tmp/logs.tgz" |
1053 | + extrafiles = [self.extrafile] |
1054 | + |
1055 | + script.bundle_logs(self.tempdir, tarfile, extrafiles) |
1056 | + |
1057 | + script.call.assert_called_once_with( |
1058 | + ["tar", |
1059 | + "czf", tarfile, |
1060 | + "--transform", "s,{}/,,".format(self.tempdir[1:]), |
1061 | + os.path.join(self.tempdir, "bootstrap"), |
1062 | + os.path.join(self.tempdir, "haproxy-0"), |
1063 | + os.path.join(self.tempdir, "landscape-0-inner-logs"), |
1064 | + os.path.join(self.tempdir, "landscape-server-0"), |
1065 | + os.path.join(self.tempdir, "postgresql-0"), |
1066 | + os.path.join(self.tempdir, "rabbitmq-server-0"), |
1067 | + self.extrafile, |
1068 | + ], |
1069 | + ) |
1070 | + |
1071 | + def test_success_without_extra(self): |
1072 | + """ |
1073 | + bundle_logs() works if there aren't any extra files. |
1074 | + """ |
1075 | + tarfile = "/tmp/logs.tgz" |
1076 | + |
1077 | + script.bundle_logs(self.tempdir, tarfile) |
1078 | + |
1079 | + script.call.assert_called_once_with( |
1080 | + ["tar", |
1081 | + "czf", tarfile, |
1082 | + "--transform", "s,{}/,,".format(self.tempdir[1:]), |
1083 | + os.path.join(self.tempdir, "bootstrap"), |
1084 | + os.path.join(self.tempdir, "haproxy-0"), |
1085 | + os.path.join(self.tempdir, "landscape-0-inner-logs"), |
1086 | + os.path.join(self.tempdir, "landscape-server-0"), |
1087 | + os.path.join(self.tempdir, "postgresql-0"), |
1088 | + os.path.join(self.tempdir, "rabbitmq-server-0"), |
1089 | + ], |
1090 | + ) |
1091 | + |
1092 | + def test_success_no_files(self): |
1093 | + """ |
1094 | + bundle_logs() works even when the temp dir is empty. |
1095 | + """ |
1096 | + for filename in os.listdir(self.tempdir): |
1097 | + shutil.rmtree(os.path.join(self.tempdir, filename)) |
1098 | + tarfile = "/tmp/logs.tgz" |
1099 | + |
1100 | + script.bundle_logs(self.tempdir, tarfile) |
1101 | + |
1102 | + script.call.assert_called_once_with( |
1103 | + ["tar", |
1104 | + "czf", tarfile, |
1105 | + "--transform", "s,{}/,,".format(self.tempdir[1:]), |
1106 | + ], |
1107 | + ) |
1108 | + |
1109 | + def test_call_failure(self): |
1110 | + """ |
1111 | + bundle_logs() does not handle errors when creating the tarball. |
1112 | + """ |
1113 | + script.call.side_effect = FakeError() |
1114 | + tarfile = "/tmp/logs.tgz" |
1115 | + |
1116 | + with self.assertRaises(FakeError): |
1117 | + script.bundle_logs(self.tempdir, tarfile) |
1118 | + |
1119 | + script.call.assert_called_once_with( |
1120 | + ["tar", |
1121 | + "czf", tarfile, |
1122 | + "--transform", "s,{}/,,".format(self.tempdir[1:]), |
1123 | + os.path.join(self.tempdir, "bootstrap"), |
1124 | + os.path.join(self.tempdir, "haproxy-0"), |
1125 | + os.path.join(self.tempdir, "landscape-0-inner-logs"), |
1126 | + os.path.join(self.tempdir, "landscape-server-0"), |
1127 | + os.path.join(self.tempdir, "postgresql-0"), |
1128 | + os.path.join(self.tempdir, "rabbitmq-server-0"), |
1129 | + ], |
1130 | + ) |
Assuming the inline comments are addressed, this branch looks good to me.