Merge lp:~natefinch/juju-ci-tools/logrot into lp:juju-ci-tools

Proposed by Nate Finch
Status: Merged
Merged at revision: 976
Proposed branch: lp:~natefinch/juju-ci-tools/logrot
Merge into: lp:juju-ci-tools
Diff against target: 1215 lines (+661/-135)
8 files modified
assess_log_rotation.py (+228/-0)
assess_recovery.py (+4/-21)
check_blockers.py (+4/-6)
jujupy.py (+98/-5)
test_assess_log_rotation.py (+154/-0)
test_assess_recovery.py (+0/-60)
test_deploy_stack.py (+17/-17)
test_jujupy.py (+156/-26)
To merge this branch: bzr merge lp:~natefinch/juju-ci-tools/logrot
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+259750@code.launchpad.net

Description of the change

Implements action do and fetch in jujupy, and uses them in tests for log rotation. Requires new charm in the repository, which will be proposed shortly.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

Environment is deprecated. It should not be used for new scripts, and it should not be extended.

If you're going to remove '-e', you need to specify the environment somehow. Since "action fetch" and "action do" both support -e, I don't think you actually want to remove it. You just want to make the command handling smarter, so that it puts the -e after "do" or "fetch".

Please allow the juju under test to be specified at the commandline.

Please use temp_bootstrap_env(). At minimum, that will ensure that 'test-mode' is always True, but it will also allow us to support configuration variations such as changing the tools-url.

It would be nice if the test was self-contained. It is important that unit-fill-logs-0 and fill-machine be actions, or could they be run via "juju run" or "juju ssh"? That would allow the test script to use a generic charm instead.

Please add unit tests for log_rotation.py.

Please rename it to something that indicates it is a test. I suggest "assess_log_rotation". ("test" itself indicates that it is a unit test).

Juju 1.22 is still under development. Please ensure that these actions work with 1.22 by specifying the appropriate feature flag when the version is 1.22. See EnvJujuClient24 for an example.

review: Needs Fixing
Revision history for this message
Nate Finch (natefinch) wrote :

The test intentionally uses a charm, because it represents a real world situation where the charm is the thing outputting a lot of logging. I don't know that I could reasonably create a juju run command that would do the same thing. This seemed like the most easy to understand way to get the job done. I could use juju shh to get the size of the logs, but since I already had the charm and actions, it seemed easy enough to just make that another action.

Revision history for this message
Aaron Bentley (abentley) wrote :

> The test intentionally uses a charm, because it represents a real world
> situation where the charm is the thing outputting a lot of logging. I don't
> know that I could reasonably create a juju run command that would do the same
> thing.

You can log in a hook context and juju run has a hook context, though, right?

I'll accept a version that is not self-contained, I just wondered whether it was possible.

Revision history for this message
Nate Finch (natefinch) wrote :

I think I addressed all your concerns. I ended up refactoring some of the helper functions in assess_recovery, since they were needed for my test as well.

Revision history for this message
Aaron Bentley (abentley) wrote :

Thanks.

Please don't add things to deploy_stack unless they are used by deploy_stack (i.e. deploy_job) itself. (The history is that deploy_stack was the first script, so some of its functionality has been reused in other scripts.)

Please don't add anything at all to jujuconfig. That is essentially an import from https://launchpad.net/juju-tools, and it would be best if they don't diverge.

I see that you have not addressed the inline comments about KeyError and tuple formattting. Please go back and make sure you've addressed all the inline comments.

Revision history for this message
Nate Finch (natefinch) wrote :

Sorry about missing your inline comments... I haven't done a review on launchpad in about a year, so forgot that I had to scroll down to look for more comments.

I have a patch coming that will address all these issues.

lp:~natefinch/juju-ci-tools/logrot updated
961. By Nate Finch

more review changes

Revision history for this message
Aaron Bentley (abentley) wrote :

I think that you haven't run "make lint", because I see some lines longer than 79 characters. Please run "make lint" and fix any issues you find.

Please document all functions in assess_log_rotation.py

Please test parse_args.

See also inline comments.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :

Also, I think you missed this:
Juju 1.22 is still under development. Please ensure that these actions work with 1.22 by specifying the appropriate feature flag when the version is 1.22. See EnvJujuClient24 for an example.

lp:~natefinch/juju-ci-tools/logrot updated
962. By Nate Finch

more code review changes

963. By Nate Finch

merge

964. By Nate Finch

set longer timeout for fill log actions, and fix -e testing problem

Revision history for this message
Nate Finch (natefinch) wrote :

Ok, for real now, I think I addressed everything.

lp:~natefinch/juju-ci-tools/logrot updated
965. By Nate Finch

remove unneeded wait

Revision history for this message
Aaron Bentley (abentley) wrote :

This is almost landable, but there are still a few issues.

review: Needs Fixing
Revision history for this message
Aaron Bentley (abentley) wrote :

Also, TestEnvJujuClient22 needs client_class set to EnvJujuClient22.

lp:~natefinch/juju-ci-tools/logrot updated
966. By Nate Finch

more code review changes

Revision history for this message
Nate Finch (natefinch) wrote :

Ok, updated once again. Addressed your concerns.

lp:~natefinch/juju-ci-tools/logrot updated
967. By Nate Finch

one more fix to TestEnvJujuClient22

Revision history for this message
Aaron Bentley (abentley) wrote :

Thank you for you changes. This looks landable.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'assess_log_rotation.py'
--- assess_log_rotation.py 1970-01-01 00:00:00 +0000
+++ assess_log_rotation.py 2015-06-03 15:41:41 +0000
@@ -0,0 +1,228 @@
1#!/usr/bin/env python
2from __future__ import print_function
3
4__metaclass__ = type
5
6from argparse import ArgumentParser
7from datetime import datetime
8import logging
9import re
10
11from deploy_stack import (
12 dump_env_logs,
13 get_machine_dns_name,
14)
15from jujuconfig import (
16 get_juju_home,
17)
18from jujupy import (
19 make_client,
20 parse_new_state_server_from_error,
21 temp_bootstrap_env,
22 yaml_loads,
23)
24from utility import (
25 print_now,
26)
27
28
29class LogRotateError(Exception):
30
31 ''' LogRotate test Exception base class. '''
32
33 def __init__(self, message):
34 super(LogRotateError, self).__init__(message)
35
36
37def test_unit_rotation(client):
38 """Tests unit log rotation."""
39 test_rotation(client,
40 "/var/log/juju/unit-fill-logs-0.log",
41 "unit-fill-logs-0",
42 "fill-unit",
43 "unit-size",
44 "megs=300")
45
46
47def test_machine_rotation(client):
48 """Tests machine log rotation."""
49 test_rotation(client,
50 "/var/log/juju/machine-1.log",
51 "machine-1",
52 "fill-machine",
53 "machine-size", "megs=300", "machine=1")
54
55
56def test_rotation(client, logfile, prefix, fill_action, size_action, *args):
57 """A reusable help for testing log rotation.log
58
59 Deploys the fill-logs charm and uses it to fill the machine or unit log and
60 test that the logs roll over correctly.
61 """
62
63 # the rotation point should be 300 megs, so let's make sure we hit that.hit
64 # we'll obviously already have some data in the logs, so adding exactly
65 # 300megs should do the trick.
66
67 # we run do_fetch here so that we wait for fill-logs to finish.
68 client.action_do_fetch("fill-logs/0", fill_action, "3m", *args)
69 out = client.action_do_fetch("fill-logs/0", size_action)
70 action_output = yaml_loads(out)
71
72 # Now we should have one primary log file, and one backup log file.
73 # The backup should be approximately 300 megs.
74 # The primary should be below 300.
75
76 check_log0(logfile, action_output)
77 check_expected_backup("log1", prefix, action_output)
78
79 # we should only have one backup, not two.
80 check_for_extra_backup("log2", action_output)
81
82 # do it all again, this should generate a second backup.
83
84 client.action_do_fetch("fill-logs/0", fill_action, "3m", *args)
85 out = client.action_do_fetch("fill-logs/0", size_action)
86 action_output = yaml_loads(out)
87
88 # we should have two backups.
89 check_log0(logfile, action_output)
90 check_expected_backup("log1", prefix, action_output)
91 check_expected_backup("log2", prefix, action_output)
92
93 check_for_extra_backup("log3", action_output)
94
95 # one more time... we should still only have 2 backups and primary
96
97 client.action_do_fetch("fill-logs/0", fill_action, "3m", *args)
98 out = client.action_do_fetch("fill-logs/0", size_action)
99 action_output = yaml_loads(out)
100
101 check_log0(logfile, action_output)
102 check_expected_backup("log1", prefix, action_output)
103 check_expected_backup("log2", prefix, action_output)
104
105 # we should have two backups.
106 check_for_extra_backup("log3", action_output)
107
108
109def check_for_extra_backup(logname, action_output):
110 """Check that there are no extra backup files left behind."""
111 log = action_output["results"]["result-map"].get(logname)
112 if log is None:
113 # this is correct
114 return
115 # log exists.
116 name = log.get("name")
117 if name is None:
118 name = "(no name)"
119 raise LogRotateError("Extra backup log after rotation: " + name)
120
121
122def check_expected_backup(key, logprefix, action_output):
123 """Check that there the expected backup files exists and is close to 300MB.
124 """
125 log = action_output["results"]["result-map"].get(key)
126 if log is None:
127 raise LogRotateError(
128 "Missing backup log '{}' after rotation.".format(key))
129
130 backup_pattern = "/var/log/juju/%s-(.+?)\.log" % logprefix
131
132 log_name = log["name"]
133 matches = re.match(backup_pattern, log_name)
134 if matches is None:
135 raise LogRotateError(
136 "Rotated log '%s' does not match pattern '%s'." %
137 (log_name, backup_pattern))
138
139 size = int(log["size"])
140 if size < 299 or size > 301:
141 raise LogRotateError(
142 "Backup log '%s' should be ~300MB, but is %sMB." %
143 (log_name, size))
144
145 dt = matches.groups()[0]
146 dt_pattern = "%Y-%m-%dT%H-%M-%S.%f"
147
148 try:
149 # note - we have to use datetime's strptime because time's doesn't
150 # support partial seconds.
151 dt = datetime.strptime(dt, dt_pattern)
152 except Exception:
153 raise LogRotateError(
154 "Log for %s has invalid datetime appended: %s" % (log_name, dt))
155
156
157def check_log0(expected, action_output):
158 """Check that log0 exists and is not over 299MB"""
159 log = action_output["results"]["result-map"].get("log0")
160 if log is None:
161 raise LogRotateError("No log returned from size action.")
162
163 name = log["name"]
164 if name != expected:
165 raise LogRotateError(
166 "Wrong unit name: Expected: %s, actual: %s" % (expected, name))
167
168 size = int(log["size"])
169 if size > 299:
170 raise LogRotateError(
171 "Log0 too big. Expected < 300MB, got: %sMB" % size)
172
173
174def parse_args(argv=None):
175 """Parse all arguments."""
176 parser = ArgumentParser('Test log rotation.')
177 parser.add_argument(
178 '--debug', action='store_true', default=False,
179 help='Use --debug juju logging.')
180 parser.add_argument(
181 'agent',
182 help='Which agent log rotation to test.',
183 choices=['machine', 'unit'])
184 parser.add_argument(
185 'juju_path', help='Directory your juju binary lives in.')
186 parser.add_argument(
187 'env_name', help='Juju environment name to run tests in.')
188 parser.add_argument('logs', help='Directory to store logs in.')
189 parser.add_argument(
190 'temp_env_name', nargs='?',
191 help='Temporary environment name to use for this test.')
192 return parser.parse_args(argv)
193
194
195def main():
196 args = parse_args()
197 log_dir = args.logs
198
199 client = make_client(
200 args.juju_path, args.debug, args.env_name, args.temp_env_name)
201 client.destroy_environment()
202 juju_home = get_juju_home()
203 try:
204 with temp_bootstrap_env(juju_home, client):
205 client.bootstrap()
206 bootstrap_host = get_machine_dns_name(client, 0)
207 client.get_status(60)
208 client.juju("deploy", ('local:trusty/fill-logs',))
209
210 if args.agent == "unit":
211 test_unit_rotation(client)
212 if args.agent == "machine":
213 test_machine_rotation(client)
214 except Exception as e:
215 logging.exception(e)
216 try:
217 if bootstrap_host is None:
218 bootstrap_host = parse_new_state_server_from_error(e)
219 dump_env_logs(client, bootstrap_host, log_dir)
220 except Exception as e:
221 print_now("exception while dumping logs:\n")
222 logging.exception(e)
223 finally:
224 client.destroy_environment()
225
226
227if __name__ == '__main__':
228 main()
0229
=== modified file 'assess_recovery.py'
--- assess_recovery.py 2015-02-11 17:42:41 +0000
+++ assess_recovery.py 2015-06-03 15:41:41 +0000
@@ -20,16 +20,16 @@
20from jujuconfig import (20from jujuconfig import (
21 get_jenv_path,21 get_jenv_path,
22 get_juju_home,22 get_juju_home,
23 )23)
24from jujupy import (24from jujupy import (
25 EnvJujuClient,
26 SimpleEnvironment,
27 temp_bootstrap_env,25 temp_bootstrap_env,
28 until_timeout,26 until_timeout,
27 make_client,
28 parse_new_state_server_from_error,
29)29)
30from substrate import (30from substrate import (
31 terminate_instances,31 terminate_instances,
32 )32)
33from utility import (33from utility import (
34 ensure_deleted,34 ensure_deleted,
35 print_now,35 print_now,
@@ -133,14 +133,6 @@
133 print_now("PASS")133 print_now("PASS")
134134
135135
136def parse_new_state_server_from_error(error):
137 output = str(error) + getattr(error, 'output', '')
138 matches = re.findall(r'Attempting to connect to (.*):22', output)
139 if matches:
140 return matches[-1]
141 return None
142
143
144def parse_args(argv=None):136def parse_args(argv=None):
145 parser = ArgumentParser('Test recovery strategies.')137 parser = ArgumentParser('Test recovery strategies.')
146 parser.add_argument(138 parser.add_argument(
@@ -167,15 +159,6 @@
167 return parser.parse_args(argv)159 return parser.parse_args(argv)
168160
169161
170def make_client(juju_path, debug, env_name, temp_env_name):
171 env = SimpleEnvironment.from_config(env_name)
172 if temp_env_name is not None:
173 env.environment = temp_env_name
174 env.config['name'] = temp_env_name
175 full_path = os.path.join(juju_path, 'juju')
176 return EnvJujuClient.by_version(env, full_path, debug)
177
178
179def main():162def main():
180 args = parse_args()163 args = parse_args()
181 log_dir = args.logs164 log_dir = args.logs
182165
=== modified file 'check_blockers.py'
--- check_blockers.py 2015-05-15 19:45:42 +0000
+++ check_blockers.py 2015-06-03 15:41:41 +0000
@@ -51,12 +51,10 @@
51 subparsers = parser.add_subparsers(help='sub-command help', dest="command")51 subparsers = parser.add_subparsers(help='sub-command help', dest="command")
52 check_parser = subparsers.add_parser(52 check_parser = subparsers.add_parser(
53 'check', help='Check if merges are blocked for a branch.')53 'check', help='Check if merges are blocked for a branch.')
54 check_parser.add_argument(54 check_parser.add_argument('branch', default='master', nargs='?',
55 'branch', default='master', nargs='?',55 help='The branch to merge into.')
56 help='The branch to merge into.')56 check_parser.add_argument('pull_request', default=None, nargs='?',
57 check_parser.add_argument(57 help='The pull request to be merged')
58 'pull_request', default=None, nargs='?',
59 help='The pull request to be merged')
60 update_parser = subparsers.add_parser(58 update_parser = subparsers.add_parser(
61 'update', help='Update blocking for a branch that passed CI.')59 'update', help='Update blocking for a branch that passed CI.')
62 update_parser.add_argument(60 update_parser.add_argument(
6361
=== modified file 'jujupy.py'
--- jujupy.py 2015-06-01 21:06:33 +0000
+++ jujupy.py 2015-06-03 15:41:41 +0000
@@ -6,7 +6,7 @@
6from contextlib import (6from contextlib import (
7 contextmanager,7 contextmanager,
8 nested,8 nested,
9 )9)
10from cStringIO import StringIO10from cStringIO import StringIO
11from datetime import timedelta11from datetime import timedelta
12import errno12import errno
@@ -25,7 +25,7 @@
25 get_jenv_path,25 get_jenv_path,
26 get_juju_home,26 get_juju_home,
27 get_selected_environment,27 get_selected_environment,
28 )28)
29from utility import (29from utility import (
30 check_free_disk_space,30 check_free_disk_space,
31 ensure_deleted,31 ensure_deleted,
@@ -34,7 +34,7 @@
34 scoped_environ,34 scoped_environ,
35 temp_dir,35 temp_dir,
36 until_timeout,36 until_timeout,
37 )37)
3838
3939
40WIN_JUJU_CMD = os.path.join('\\', 'Progra~2', 'Juju', 'juju.exe')40WIN_JUJU_CMD = os.path.join('\\', 'Progra~2', 'Juju', 'juju.exe')
@@ -42,6 +42,14 @@
42JUJU_DEV_FEATURE_FLAGS = 'JUJU_DEV_FEATURE_FLAGS'42JUJU_DEV_FEATURE_FLAGS = 'JUJU_DEV_FEATURE_FLAGS'
4343
4444
45def parse_new_state_server_from_error(error):
46 output = str(error) + getattr(error, 'output', '')
47 matches = re.findall(r'Attempting to connect to (.*):22', output)
48 if matches:
49 return matches[-1]
50 return None
51
52
45class ErroredUnit(Exception):53class ErroredUnit(Exception):
4654
47 def __init__(self, unit_name, state):55 def __init__(self, unit_name, state):
@@ -55,6 +63,15 @@
55 return yaml.safe_load(StringIO(yaml_str))63 return yaml.safe_load(StringIO(yaml_str))
5664
5765
66def make_client(juju_path, debug, env_name, temp_env_name):
67 env = SimpleEnvironment.from_config(env_name)
68 if temp_env_name is not None:
69 env.environment = temp_env_name
70 env.config['name'] = temp_env_name
71 full_path = os.path.join(juju_path, 'juju')
72 return EnvJujuClient.by_version(env, full_path, debug)
73
74
58class CannotConnectEnv(subprocess.CalledProcessError):75class CannotConnectEnv(subprocess.CalledProcessError):
5976
60 def __init__(self, e):77 def __init__(self, e):
@@ -156,6 +173,8 @@
156 full_path = os.path.abspath(juju_path)173 full_path = os.path.abspath(juju_path)
157 if version.startswith('1.16'):174 if version.startswith('1.16'):
158 raise Exception('Unsupported juju: %s' % version)175 raise Exception('Unsupported juju: %s' % version)
176 elif re.match('^1\.22[.-]', version):
177 return EnvJujuClient22(env, version, full_path, debug=debug)
159 elif re.match('^1\.24[.-]', version):178 elif re.match('^1\.24[.-]', version):
160 return EnvJujuClient24(env, version, full_path, debug=debug)179 return EnvJujuClient24(env, version, full_path, debug=debug)
161 elif re.match('^1\.25[.-]', version):180 elif re.match('^1\.25[.-]', version):
@@ -174,6 +193,10 @@
174 else:193 else:
175 prefix = ('timeout', '%.2fs' % timeout)194 prefix = ('timeout', '%.2fs' % timeout)
176 logging = '--debug' if self.debug else '--show-log'195 logging = '--debug' if self.debug else '--show-log'
196
197 # we split the command here so that the caller can control where the -e
198 # <env> flag goes. Everything in the command string is put before the
199 # -e flag.
177 command = command.split()200 command = command.split()
178 return prefix + ('juju', logging,) + tuple(command) + e_arg + args201 return prefix + ('juju', logging,) + tuple(command) + e_arg + args
179202
@@ -197,6 +220,7 @@
197 env['PATH'])220 env['PATH'])
198 if juju_home is not None:221 if juju_home is not None:
199 env['JUJU_HOME'] = juju_home222 env['JUJU_HOME'] = juju_home
223
200 return env224 return env
201225
202 def get_bootstrap_args(self, upload_tools):226 def get_bootstrap_args(self, upload_tools):
@@ -243,8 +267,15 @@
243 ensure_deleted(jenv_path)267 ensure_deleted(jenv_path)
244268
245 def get_juju_output(self, command, *args, **kwargs):269 def get_juju_output(self, command, *args, **kwargs):
270 """Call a juju command and return the output.
271
272 Sub process will be called as 'juju <command> <args> <kwargs>'. Note
273 that <command> may be a space delimited list of arguments. The -e
274 <environment> flag will be placed after <command> and before args.
275 """
246 args = self._full_args(command, False, args,276 args = self._full_args(command, False, args,
247 timeout=kwargs.get('timeout'))277 timeout=kwargs.get('timeout'),
278 include_e=kwargs.get('include_e', True))
248 env = self._shell_environ()279 env = self._shell_environ()
249 with tempfile.TemporaryFile() as stderr:280 with tempfile.TemporaryFile() as stderr:
250 try:281 try:
@@ -478,6 +509,67 @@
478 print_now("State-Server backup at %s" % backup_file_path)509 print_now("State-Server backup at %s" % backup_file_path)
479 return backup_file_path510 return backup_file_path
480511
512 def action_fetch(self, id, action=None, timeout="1m"):
513 """Fetches the results of the action with the given id.
514
515 Will wait for up to 1 minute for the action results.
516 The action name here is just used for an more informational error in
517 cases where it's available.
518 Returns the yaml output of the fetched action.
519 """
520 # the command has to be "action fetch" so that the -e <env> args are
521 # placed after "fetch", since that's where action requires them to be.
522 out = self.get_juju_output("action fetch", id, "--wait", timeout)
523 status = yaml_loads(out)["status"]
524 if status != "completed":
525 name = ""
526 if action is not None:
527 name = " " + action
528 raise Exception(
529 "timed out waiting for action%s to complete during fetch" %
530 name)
531 return out
532
533 def action_do(self, unit, action, *args):
534 """Performs the given action on the given unit.
535
536 Action params should be given as args in the form foo=bar.
537 Returns the id of the queued action.
538 """
539 args = (unit, action) + args
540
541 # the command has to be "action do" so that the -e <env> args are
542 # placed after "do", since that's where action requires them to be.
543 output = self.get_juju_output("action do", *args)
544 action_id_pattern = re.compile(
545 'Action queued with id: ([a-f0-9\-]{36})')
546 match = action_id_pattern.search(output)
547 if match is None:
548 raise Exception("Action id not found in output: %s" %
549 output)
550 return match.group(1)
551
552 def action_do_fetch(self, unit, action, timeout="1m", *args):
553 """Performs the given action on the given unit and waits for the results.
554
555 Action params should be given as args in the form foo=bar.
556 Returns the yaml output of the action.
557 """
558 id = self.action_do(unit, action, *args)
559 return self.action_fetch(id, action, timeout)
560
561
562class EnvJujuClient22(EnvJujuClient):
563
564 def _shell_environ(self, juju_home=None):
565 """Generate a suitable shell environment.
566
567 Juju's directory must be in the PATH to support plugins.
568 """
569 env = super(EnvJujuClient22, self)._shell_environ(juju_home)
570 env[JUJU_DEV_FEATURE_FLAGS] = 'actions'
571 return env
572
481573
482class EnvJujuClient25(EnvJujuClient):574class EnvJujuClient25(EnvJujuClient):
483575
@@ -493,7 +585,8 @@
493585
494586
495class EnvJujuClient24(EnvJujuClient25):587class EnvJujuClient24(EnvJujuClient25):
496 """Currently, same feature set as juju 2.5"""588
589 """Currently, same feature set as juju 25"""
497590
498591
499def get_local_root(juju_home, env):592def get_local_root(juju_home, env):
500593
=== added file 'test_assess_log_rotation.py'
--- test_assess_log_rotation.py 1970-01-01 00:00:00 +0000
+++ test_assess_log_rotation.py 2015-06-03 15:41:41 +0000
@@ -0,0 +1,154 @@
1from argparse import Namespace
2from unittest import TestCase
3from assess_log_rotation import (
4 check_for_extra_backup,
5 check_expected_backup,
6 check_log0,
7 LogRotateError,
8 parse_args,
9)
10from jujupy import yaml_loads
11
12good_yaml = \
13 """
14results:
15 result-map:
16 log0:
17 name: /var/log/juju/unit-fill-logs-0.log
18 size: "25"
19 log1:
20 name: /var/log/juju/unit-fill-logs-0-2015-05-21T09-57-03.123.log
21 size: "299"
22 log1:
23 name: /var/log/juju/unit-fill-logs-0-2015-05-22T12-57-03.123.log
24 size: "300"
25status: completed
26timing:
27 completed: 2015-05-21 09:57:03 -0400 EDT
28 enqueued: 2015-05-21 09:56:59 -0400 EDT
29 started: 2015-05-21 09:57:02 -0400 EDT
30"""
31
32good_obj = yaml_loads(good_yaml)
33
34big_yaml = \
35 """
36results:
37 result-map:
38 log0:
39 name: /var/log/juju/unit-fill-logs-0.log
40 size: "400"
41 log1:
42 name: /var/log/juju/unit-fill-logs-0-2015-05-21T09-57-03.123.log
43 size: "400"
44 log2:
45 name: /var/log/juju/unit-fill-logs-0-not-a-valid-timestamp.log
46 size: "299"
47 log3:
48 name: something-just-plain-bad.log
49 size: "299"
50status: completed
51timing:
52 completed: 2015-05-21 09:57:03 -0400 EDT
53 enqueued: 2015-05-21 09:56:59 -0400 EDT
54 started: 2015-05-21 09:57:02 -0400 EDT
55"""
56
57big_obj = yaml_loads(big_yaml)
58
59no_files_yaml = \
60 """
61results:
62 result-map:
63status: completed
64timing:
65 completed: 2015-05-21 09:57:03 -0400 EDT
66 enqueued: 2015-05-21 09:56:59 -0400 EDT
67 started: 2015-05-21 09:57:02 -0400 EDT
68"""
69
70no_files_obj = yaml_loads(no_files_yaml)
71
72
73class TestCheckForExtraBackup(TestCase):
74
75 def test_not_found(self):
76 try:
77 # log2 should not be found, and thus no exception.
78 check_for_extra_backup("log2", good_obj)
79 except Exception as e:
80 self.fail("unexpected exception: %s" % e.msg)
81
82 def test_find_extra(self):
83 with self.assertRaises(LogRotateError):
84 # log1 should be found, and thus cause an exception.
85 check_for_extra_backup("log1", good_obj)
86
87
88class TestCheckBackup(TestCase):
89
90 def test_exists(self):
91 try:
92 # log1 should be found, and thus no exception.
93 check_expected_backup("log1", "unit-fill-logs-0", good_obj)
94 except Exception as e:
95 self.fail("unexpected exception: %s" % e.msg)
96
97 def test_not_found(self):
98 with self.assertRaises(LogRotateError):
99 # log2 should not be found, and thus cause an exception.
100 check_expected_backup("log2", "unit-fill-logs-0", good_obj)
101
102 def test_too_big(self):
103 with self.assertRaises(LogRotateError):
104 # log1 is too big, and thus should cause an exception.
105 check_expected_backup("log1", "unit-fill-logs-0", big_obj)
106
107 def test_bad_timestamp(self):
108 with self.assertRaises(LogRotateError):
109 # log2 has an invalid timestamp, and thus should cause an
110 # exception.
111 check_expected_backup("log2", "unit-fill-logs-0", big_obj)
112
113 def test_bad_name(self):
114 with self.assertRaises(LogRotateError):
115 # log3 has a completely invalid name, and thus should cause an
116 # exception.
117 check_expected_backup("log3", "unit-fill-logs-0", big_obj)
118
119
120class TestCheckLog0(TestCase):
121
122 def test_exists(self):
123 try:
124 # log0 should be found, and thus no exception.
125 check_log0("/var/log/juju/unit-fill-logs-0.log", good_obj)
126 except Exception as e:
127 self.fail("unexpected exception: %s" % e.msg)
128
129 def test_not_found(self):
130 with self.assertRaises(AttributeError):
131 # There's no value under result-map, which causes the yaml parser
132 # to consider it None, and thus it'll cause an AttributeError
133 check_log0("/var/log/juju/unit-fill-logs-0.log", no_files_obj)
134
135 def test_too_big(self):
136 with self.assertRaises(LogRotateError):
137 # log0 is too big, and thus should cause an exception.
138 check_log0(
139 "/var/log/juju/unit-fill-logs-0.log", big_obj)
140
141
142class TestParseArgs(TestCase):
143
144 def test_parse_args(self):
145 args = parse_args(['machine', 'b', 'c', 'd', 'e'])
146 self.assertEqual(args, Namespace(
147 agent='machine', juju_path='b', env_name='c', logs='d',
148 temp_env_name='e', debug=False))
149
150 def test_parse_args_debug(self):
151 args = parse_args(['--debug', 'unit', 'b', 'c', 'd', 'e'])
152 self.assertEqual(args, Namespace(
153 agent='unit', juju_path='b', env_name='c', logs='d',
154 temp_env_name='e', debug=True))
0155
=== modified file 'test_assess_recovery.py'
--- test_assess_recovery.py 2015-02-11 17:49:42 +0000
+++ test_assess_recovery.py 2015-06-03 15:41:41 +0000
@@ -1,68 +1,8 @@
1from contextlib import contextmanager
2import os
3from subprocess import CalledProcessError
4from textwrap import dedent
5from unittest import TestCase1from unittest import TestCase
62
7from mock import patch
8
9from assess_recovery import (3from assess_recovery import (
10 make_client,
11 parse_new_state_server_from_error,
12 parse_args,4 parse_args,
13)5)
14from jujupy import _temp_env as temp_env
15from utility import temp_dir
16
17
18class AssessRecoveryTestCase(TestCase):
19
20 def test_parse_new_state_server_from_error(self):
21 output = dedent("""
22 Waiting for address
23 Attempting to connect to 10.0.0.202:22
24 Attempting to connect to 1.2.3.4:22
25 The fingerprint for the ECDSA key sent by the remote host is
26 """)
27 error = CalledProcessError(1, ['foo'], output)
28 address = parse_new_state_server_from_error(error)
29 self.assertEqual('1.2.3.4', address)
30
31
32class TestMakeClient(TestCase):
33
34 @contextmanager
35 def make_client_cxt(self):
36 td = temp_dir()
37 te = temp_env({'environments': {'foo': {
38 'orig-name': 'foo', 'name': 'foo'}}})
39 with td as juju_path, te, patch('subprocess.Popen',
40 side_effect=ValueError):
41 with patch('subprocess.check_output') as co_mock:
42 co_mock.return_value = '1.18'
43 yield juju_path
44
45 def test_make_client(self):
46 with self.make_client_cxt() as juju_path:
47 client = make_client(juju_path, False, 'foo', 'bar')
48 self.assertEqual(client.full_path, os.path.join(juju_path, 'juju'))
49 self.assertEqual(client.debug, False)
50 self.assertEqual(client.env.config['orig-name'], 'foo')
51 self.assertEqual(client.env.config['name'], 'bar')
52 self.assertEqual(client.env.environment, 'bar')
53
54 def test_make_client_debug(self):
55 with self.make_client_cxt() as juju_path:
56 client = make_client(juju_path, True, 'foo', 'bar')
57 self.assertEqual(client.debug, True)
58
59 def test_make_client_no_temp_env_name(self):
60 with self.make_client_cxt() as juju_path:
61 client = make_client(juju_path, False, 'foo', None)
62 self.assertEqual(client.full_path, os.path.join(juju_path, 'juju'))
63 self.assertEqual(client.env.config['orig-name'], 'foo')
64 self.assertEqual(client.env.config['name'], 'foo')
65 self.assertEqual(client.env.environment, 'foo')
666
677
68class TestParseArgs(TestCase):8class TestParseArgs(TestCase):
699
=== modified file 'test_deploy_stack.py'
--- test_deploy_stack.py 2015-05-28 22:29:53 +0000
+++ test_deploy_stack.py 2015-06-03 15:41:41 +0000
@@ -1,7 +1,7 @@
1from argparse import (1from argparse import (
2 ArgumentParser,2 ArgumentParser,
3 Namespace,3 Namespace,
4 )4)
5from contextlib import contextmanager5from contextlib import contextmanager
6import json6import json
7import logging7import logging
@@ -14,7 +14,7 @@
14from mock import (14from mock import (
15 call,15 call,
16 patch,16 patch,
17 )17)
18import yaml18import yaml
1919
20from deploy_stack import (20from deploy_stack import (
@@ -50,7 +50,7 @@
50)50)
51from test_jujupy import (51from test_jujupy import (
52 assert_juju_call,52 assert_juju_call,
53 )53)
54from utility import temp_dir54from utility import temp_dir
5555
5656
@@ -274,7 +274,7 @@
274 side_effect=subprocess.CalledProcessError('', '')):274 side_effect=subprocess.CalledProcessError('', '')):
275 with patch('subprocess.call', autospec=True) as c_mock:275 with patch('subprocess.call', autospec=True) as c_mock:
276 with self.assertRaises(subprocess.CalledProcessError):276 with self.assertRaises(subprocess.CalledProcessError):
277 run_instances(1, 'qux')277 run_instances(1, 'qux')
278 c_mock.assert_called_with(['euca-terminate-instances', 'i-foo'])278 c_mock.assert_called_with(['euca-terminate-instances', 'i-foo'])
279279
280 def test_assess_juju_run(self):280 def test_assess_juju_run(self):
@@ -524,7 +524,7 @@
524 ('juju', '--show-log', 'status', '-e', 'foo'): status,524 ('juju', '--show-log', 'status', '-e', 'foo'): status,
525 ('juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0',525 ('juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0',
526 GET_TOKEN_SCRIPT): 'fake-token',526 GET_TOKEN_SCRIPT): 'fake-token',
527 }527 }
528 return output[args]528 return output[args]
529529
530 with patch('subprocess.check_output', side_effect=output,530 with patch('subprocess.check_output', side_effect=output,
@@ -582,7 +582,7 @@
582 cls.RUN_UNAME: juju_run_out,582 cls.RUN_UNAME: juju_run_out,
583 cls.VERSION: '1.38',583 cls.VERSION: '1.38',
584 cls.GET_ENV: 'testing'584 cls.GET_ENV: 'testing'
585 }585 }
586 return output[args]586 return output[args]
587587
588 @contextmanager588 @contextmanager
@@ -590,11 +590,11 @@
590 with patch('subprocess.check_output', side_effect=self.upgrade_output,590 with patch('subprocess.check_output', side_effect=self.upgrade_output,
591 autospec=True) as co_mock:591 autospec=True) as co_mock:
592 with patch('subprocess.check_call', autospec=True) as cc_mock:592 with patch('subprocess.check_call', autospec=True) as cc_mock:
593 with patch('deploy_stack.check_token', autospec=True):593 with patch('deploy_stack.check_token', autospec=True):
594 with patch('deploy_stack.get_random_string',594 with patch('deploy_stack.get_random_string',
595 return_value="FAKETOKEN", autospec=True):595 return_value="FAKETOKEN", autospec=True):
596 with patch('sys.stdout', autospec=True):596 with patch('sys.stdout', autospec=True):
597 yield (co_mock, cc_mock)597 yield (co_mock, cc_mock)
598598
599 def test_assess_upgrade(self):599 def test_assess_upgrade(self):
600 env = SimpleEnvironment('foo', {'type': 'foo'})600 env = SimpleEnvironment('foo', {'type': 'foo'})
@@ -643,7 +643,7 @@
643 status = yaml.dump({643 status = yaml.dump({
644 'machines': {0: {'agent-version': '1.18.17'}},644 'machines': {0: {'agent-version': '1.18.17'}},
645 'services': {},645 'services': {},
646 })646 })
647647
648 def test_prepare_environment(self):648 def test_prepare_environment(self):
649 client = self.get_client()649 client = self.get_client()
@@ -699,7 +699,7 @@
699 client = EnvJujuClient(SimpleEnvironment(699 client = EnvJujuClient(SimpleEnvironment(
700 'foo', {'type': 'paas'}), '1.23', 'path')700 'foo', {'type': 'paas'}), '1.23', 'path')
701 self.addContext(patch('deploy_stack.get_machine_dns_name',701 self.addContext(patch('deploy_stack.get_machine_dns_name',
702 return_value='foo'))702 return_value='foo'))
703 c_mock = self.addContext(patch('subprocess.call'))703 c_mock = self.addContext(patch('subprocess.call'))
704 with boot_context('bar', client, None, [], None, None, None, None,704 with boot_context('bar', client, None, [], None, None, None, None,
705 keep_env=False, upload_tools=False):705 keep_env=False, upload_tools=False):
@@ -718,7 +718,7 @@
718 client = EnvJujuClient(SimpleEnvironment(718 client = EnvJujuClient(SimpleEnvironment(
719 'foo', {'type': 'paas'}), '1.23', 'path')719 'foo', {'type': 'paas'}), '1.23', 'path')
720 self.addContext(patch('deploy_stack.get_machine_dns_name',720 self.addContext(patch('deploy_stack.get_machine_dns_name',
721 return_value='foo'))721 return_value='foo'))
722 c_mock = self.addContext(patch('subprocess.call'))722 c_mock = self.addContext(patch('subprocess.call'))
723 with boot_context('bar', client, None, [], None, None, None, None,723 with boot_context('bar', client, None, [], None, None, None, None,
724 keep_env=True, upload_tools=False):724 keep_env=True, upload_tools=False):
@@ -735,7 +735,7 @@
735 client = EnvJujuClient(SimpleEnvironment(735 client = EnvJujuClient(SimpleEnvironment(
736 'foo', {'type': 'paas'}), '1.23', 'path')736 'foo', {'type': 'paas'}), '1.23', 'path')
737 self.addContext(patch('deploy_stack.get_machine_dns_name',737 self.addContext(patch('deploy_stack.get_machine_dns_name',
738 return_value='foo'))738 return_value='foo'))
739 self.addContext(patch('subprocess.call'))739 self.addContext(patch('subprocess.call'))
740 with boot_context('bar', client, None, [], None, None, None, None,740 with boot_context('bar', client, None, [], None, None, None, None,
741 keep_env=False, upload_tools=True):741 keep_env=False, upload_tools=True):
@@ -749,7 +749,7 @@
749 client = EnvJujuClient(SimpleEnvironment(749 client = EnvJujuClient(SimpleEnvironment(
750 'foo', {'type': 'paas'}), '1.23', 'path')750 'foo', {'type': 'paas'}), '1.23', 'path')
751 self.addContext(patch('deploy_stack.get_machine_dns_name',751 self.addContext(patch('deploy_stack.get_machine_dns_name',
752 return_value='foo'))752 return_value='foo'))
753 self.addContext(patch('subprocess.call'))753 self.addContext(patch('subprocess.call'))
754 ue_mock = self.addContext(754 ue_mock = self.addContext(
755 patch('deploy_stack.update_env', wraps=update_env))755 patch('deploy_stack.update_env', wraps=update_env))
@@ -783,7 +783,7 @@
783 upgrade=False,783 upgrade=False,
784 verbose=False,784 verbose=False,
785 upload_tools=False,785 upload_tools=False,
786 ))786 ))
787787
788 def test_upload_tools(self):788 def test_upload_tools(self):
789 args = deploy_job_parse_args(['foo', 'bar', 'baz', '--upload-tools'])789 args = deploy_job_parse_args(['foo', 'bar', 'baz', '--upload-tools'])
790790
=== modified file 'test_jujupy.py'
--- test_jujupy.py 2015-06-01 21:06:33 +0000
+++ test_jujupy.py 2015-06-03 15:41:41 +0000
@@ -4,7 +4,7 @@
4from datetime import (4from datetime import (
5 datetime,5 datetime,
6 timedelta,6 timedelta,
7 )7)
8import os8import os
9import shutil9import shutil
10import StringIO10import StringIO
@@ -24,11 +24,12 @@
24 get_environments_path,24 get_environments_path,
25 get_jenv_path,25 get_jenv_path,
26 NoSuchEnvironment,26 NoSuchEnvironment,
27 )27)
28from jujupy import (28from jujupy import (
29 CannotConnectEnv,29 CannotConnectEnv,
30 Environment,30 Environment,
31 EnvJujuClient,31 EnvJujuClient,
32 EnvJujuClient22,
32 EnvJujuClient24,33 EnvJujuClient24,
33 EnvJujuClient25,34 EnvJujuClient25,
34 ErroredUnit,35 ErroredUnit,
@@ -41,11 +42,13 @@
41 temp_bootstrap_env,42 temp_bootstrap_env,
42 _temp_env as temp_env,43 _temp_env as temp_env,
43 uniquify_local,44 uniquify_local,
45 make_client,
46 parse_new_state_server_from_error,
44)47)
45from utility import (48from utility import (
46 scoped_environ,49 scoped_environ,
47 temp_dir,50 temp_dir,
48 )51)
4952
5053
51def assert_juju_call(test_case, mock_method, client, expected_args,54def assert_juju_call(test_case, mock_method, client, expected_args,
@@ -98,7 +101,8 @@
98 SimpleEnvironment('baz', {'type': 'cloudsigma'}),101 SimpleEnvironment('baz', {'type': 'cloudsigma'}),
99 '1.25-foobar', 'path')102 '1.25-foobar', 'path')
100 env = client._shell_environ()103 env = client._shell_environ()
101 self.assertEqual(env[JUJU_DEV_FEATURE_FLAGS], 'cloudsigma')104 "".split()
105 self.assertTrue('cloudsigma' in env[JUJU_DEV_FEATURE_FLAGS].split(","))
102106
103 def test__shell_environ_juju_home(self):107 def test__shell_environ_juju_home(self):
104 client = self.client_class(108 client = self.client_class(
@@ -107,6 +111,23 @@
107 self.assertEqual(env['JUJU_HOME'], 'asdf')111 self.assertEqual(env['JUJU_HOME'], 'asdf')
108112
109113
114class TestEnvJujuClient22(ClientTest):
115
116 client_class = EnvJujuClient22
117
118 def test__shell_environ(self):
119 client = self.client_class(
120 SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path')
121 env = client._shell_environ()
122 self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS), 'actions')
123
124 def test__shell_environ_juju_home(self):
125 client = self.client_class(
126 SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path')
127 env = client._shell_environ(juju_home='asdf')
128 self.assertEqual(env['JUJU_HOME'], 'asdf')
129
130
110class TestEnvJujuClient24(TestEnvJujuClient25):131class TestEnvJujuClient24(TestEnvJujuClient25):
111132
112 client_class = EnvJujuClient25133 client_class = EnvJujuClient25
@@ -165,6 +186,7 @@
165 yield '1.16'186 yield '1.16'
166 yield '1.16.1'187 yield '1.16.1'
167 yield '1.15'188 yield '1.15'
189 yield '1.22.1'
168 yield '1.24-alpha1'190 yield '1.24-alpha1'
169 yield '1.24.7'191 yield '1.24.7'
170 yield '1.25.1'192 yield '1.25.1'
@@ -185,6 +207,8 @@
185 self.assertIs(EnvJujuClient, type(client))207 self.assertIs(EnvJujuClient, type(client))
186 self.assertEqual('1.15', client.version)208 self.assertEqual('1.15', client.version)
187 client = EnvJujuClient.by_version(None)209 client = EnvJujuClient.by_version(None)
210 self.assertIs(type(client), EnvJujuClient22)
211 client = EnvJujuClient.by_version(None)
188 self.assertIs(type(client), EnvJujuClient24)212 self.assertIs(type(client), EnvJujuClient24)
189 self.assertEqual(client.version, '1.24-alpha1')213 self.assertEqual(client.version, '1.24-alpha1')
190 client = EnvJujuClient.by_version(None)214 client = EnvJujuClient.by_version(None)
@@ -231,7 +255,7 @@
231 client = EnvJujuClient(env, None, 'my/juju/bin')255 client = EnvJujuClient(env, None, 'my/juju/bin')
232 full = client._full_args('action bar', False, ('baz', 'qux'))256 full = client._full_args('action bar', False, ('baz', 'qux'))
233 self.assertEqual((257 self.assertEqual((
234 'juju', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux'),258 'juju', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux', ),
235 full)259 full)
236260
237 def test_bootstrap_hpcloud(self):261 def test_bootstrap_hpcloud(self):
@@ -577,9 +601,9 @@
577 '0': {'state-server-member-status': 'has-vote'},601 '0': {'state-server-member-status': 'has-vote'},
578 '1': {'state-server-member-status': 'has-vote'},602 '1': {'state-server-member-status': 'has-vote'},
579 '2': {'state-server-member-status': 'has-vote'},603 '2': {'state-server-member-status': 'has-vote'},
580 },604 },
581 'services': {},605 'services': {},
582 })606 })
583 client = EnvJujuClient(SimpleEnvironment('local'), None, None)607 client = EnvJujuClient(SimpleEnvironment('local'), None, None)
584 with patch.object(client, 'get_juju_output', return_value=value):608 with patch.object(client, 'get_juju_output', return_value=value):
585 client.wait_for_ha()609 client.wait_for_ha()
@@ -590,9 +614,9 @@
590 '0': {'state-server-member-status': 'no-vote'},614 '0': {'state-server-member-status': 'no-vote'},
591 '1': {'state-server-member-status': 'no-vote'},615 '1': {'state-server-member-status': 'no-vote'},
592 '2': {'state-server-member-status': 'no-vote'},616 '2': {'state-server-member-status': 'no-vote'},
593 },617 },
594 'services': {},618 'services': {},
595 })619 })
596 client = EnvJujuClient(SimpleEnvironment('local'), None, None)620 client = EnvJujuClient(SimpleEnvironment('local'), None, None)
597 with patch('sys.stdout'):621 with patch('sys.stdout'):
598 with patch.object(client, 'get_juju_output', return_value=value):622 with patch.object(client, 'get_juju_output', return_value=value):
@@ -606,9 +630,9 @@
606 'machines': {630 'machines': {
607 '0': {'state-server-member-status': 'has-vote'},631 '0': {'state-server-member-status': 'has-vote'},
608 '1': {'state-server-member-status': 'has-vote'},632 '1': {'state-server-member-status': 'has-vote'},
609 },633 },
610 'services': {},634 'services': {},
611 })635 })
612 client = EnvJujuClient(SimpleEnvironment('local'), None, None)636 client = EnvJujuClient(SimpleEnvironment('local'), None, None)
613 with patch('jujupy.until_timeout', lambda x: range(0)):637 with patch('jujupy.until_timeout', lambda x: range(0)):
614 with patch.object(client, 'get_juju_output', return_value=value):638 with patch.object(client, 'get_juju_output', return_value=value):
@@ -621,7 +645,7 @@
621 value = yaml.safe_dump({645 value = yaml.safe_dump({
622 'machines': {646 'machines': {
623 '0': {'agent-state': 'started'},647 '0': {'agent-state': 'started'},
624 },648 },
625 'services': {649 'services': {
626 'jenkins': {650 'jenkins': {
627 'units': {651 'units': {
@@ -638,9 +662,9 @@
638 value = yaml.safe_dump({662 value = yaml.safe_dump({
639 'machines': {663 'machines': {
640 '0': {'agent-state': 'started'},664 '0': {'agent-state': 'started'},
641 },665 },
642 'services': {},666 'services': {},
643 })667 })
644 client = EnvJujuClient(SimpleEnvironment('local'), None, None)668 client = EnvJujuClient(SimpleEnvironment('local'), None, None)
645 with patch('jujupy.until_timeout', lambda x: range(0)):669 with patch('jujupy.until_timeout', lambda x: range(0)):
646 with patch.object(client, 'get_juju_output', return_value=value):670 with patch.object(client, 'get_juju_output', return_value=value):
@@ -981,7 +1005,7 @@
981 'root-dir': get_local_root(fake_home, client.env),1005 'root-dir': get_local_root(fake_home, client.env),
982 'agent-version': agent_version,1006 'agent-version': agent_version,
983 'test-mode': True,1007 'test-mode': True,
984 }}})1008 }}})
985 stub_bootstrap()1009 stub_bootstrap()
9861010
987 def test_temp_bootstrap_env_provides_dir(self):1011 def test_temp_bootstrap_env_provides_dir(self):
@@ -1052,7 +1076,7 @@
1052 self.assertEqual(mock_cfds.mock_calls, [1076 self.assertEqual(mock_cfds.mock_calls, [
1053 call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),1077 call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),
1054 call('/var/lib/lxc', 2000000, 'LXC containers'),1078 call('/var/lib/lxc', 2000000, 'LXC containers'),
1055 ])1079 ])
10561080
1057 def test_check_space_local_kvm(self):1081 def test_check_space_local_kvm(self):
1058 env = SimpleEnvironment('qux', {'type': 'local', 'container': 'kvm'})1082 env = SimpleEnvironment('qux', {'type': 'local', 'container': 'kvm'})
@@ -1064,7 +1088,7 @@
1064 self.assertEqual(mock_cfds.mock_calls, [1088 self.assertEqual(mock_cfds.mock_calls, [
1065 call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),1089 call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),
1066 call('/var/lib/uvtool/libvirt/images', 2000000, 'KVM disk files'),1090 call('/var/lib/uvtool/libvirt/images', 2000000, 'KVM disk files'),
1067 ])1091 ])
10681092
1069 def test_error_on_jenv(self):1093 def test_error_on_jenv(self):
1070 env = SimpleEnvironment('qux', {'type': 'local'})1094 env = SimpleEnvironment('qux', {'type': 'local'})
@@ -1337,7 +1361,7 @@
1337 self.assertEqual(list(status.iter_machines(containers=True)), [1361 self.assertEqual(list(status.iter_machines(containers=True)), [
1338 ('1', status.status['machines']['1']),1362 ('1', status.status['machines']['1']),
1339 ('1/lxc/0', {'baz': 'qux'}),1363 ('1/lxc/0', {'baz': 'qux'}),
1340 ])1364 ])
13411365
1342 def test_agent_items_empty(self):1366 def test_agent_items_empty(self):
1343 status = Status({'machines': {}, 'services': {}}, '')1367 status = Status({'machines': {}, 'services': {}}, '')
@@ -1483,7 +1507,7 @@
1483 status.get_subordinate_units('dummy-sink'), [1507 status.get_subordinate_units('dummy-sink'), [
1484 {'chaos-monkey/1': {'agent-state': 'started'}},1508 {'chaos-monkey/1': {'agent-state': 'started'}},
1485 {'chaos-monkey/2': {'agent-state': 'started'}}]1509 {'chaos-monkey/2': {'agent-state': 'started'}}]
1486 )1510 )
1487 with self.assertRaisesRegexp(KeyError, 'foo'):1511 with self.assertRaisesRegexp(KeyError, 'foo'):
1488 status.get_subordinate_units('foo')1512 status.get_subordinate_units('foo')
14891513
@@ -1593,7 +1617,7 @@
1593 'machines': {'0': {1617 'machines': {'0': {
1594 'agent-state-info': failure}},1618 'agent-state-info': failure}},
1595 'services': {},1619 'services': {},
1596 }, '')1620 }, '')
1597 with self.assertRaises(ErroredUnit) as e_cxt:1621 with self.assertRaises(ErroredUnit) as e_cxt:
1598 status.check_agents_started()1622 status.check_agents_started()
1599 e = e_cxt.exception1623 e = e_cxt.exception
@@ -1653,14 +1677,14 @@
1653 old_status = Status({1677 old_status = Status({
1654 'machines': {1678 'machines': {
1655 'bar': 'bar_info',1679 'bar': 'bar_info',
1656 }1680 }
1657 }, '')1681 }, '')
1658 new_status = Status({1682 new_status = Status({
1659 'machines': {1683 'machines': {
1660 'foo': 'foo_info',1684 'foo': 'foo_info',
1661 'bar': 'bar_info',1685 'bar': 'bar_info',
1662 }1686 }
1663 }, '')1687 }, '')
1664 self.assertItemsEqual(new_status.iter_new_machines(old_status),1688 self.assertItemsEqual(new_status.iter_new_machines(old_status),
1665 [('foo', 'foo_info')])1689 [('foo', 'foo_info')])
16661690
@@ -1669,8 +1693,8 @@
1669 'machines': {1693 'machines': {
1670 '0': {'instance-id': 'foo-bar'},1694 '0': {'instance-id': 'foo-bar'},
1671 '1': {},1695 '1': {},
1672 }1696 }
1673 }, '')1697 }, '')
1674 self.assertEqual(status.get_instance_id('0'), 'foo-bar')1698 self.assertEqual(status.get_instance_id('0'), 'foo-bar')
1675 with self.assertRaises(KeyError):1699 with self.assertRaises(KeyError):
1676 status.get_instance_id('1')1700 status.get_instance_id('1')
@@ -1965,6 +1989,62 @@
1965 mock_get.assert_called_with(env, 'tools-metadata-url')1989 mock_get.assert_called_with(env, 'tools-metadata-url')
1966 self.assertEqual(0, mock_set.call_count)1990 self.assertEqual(0, mock_set.call_count)
19671991
1992 def test_action_do(self):
1993 client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
1994 '1.23-series-arch', None)
1995 with patch.object(EnvJujuClient, 'get_juju_output') as mock:
1996 mock.return_value = \
1997 "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9"
1998 id = client.action_do("foo/0", "myaction", "param=5")
1999 self.assertEqual(id, "5a92ec93-d4be-4399-82dc-7431dbfd08f9")
2000 mock.assert_called_once_with(
2001 'action do', 'foo/0', 'myaction', "param=5"
2002 )
2003
2004 def test_action_do_error(self):
2005 client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
2006 '1.23-series-arch', None)
2007 with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2008 mock.return_value = "some bad text"
2009 with self.assertRaisesRegexp(Exception,
2010 "Action id not found in output"):
2011 client.action_do("foo/0", "myaction", "param=5")
2012
2013 def test_action_fetch(self):
2014 client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
2015 '1.23-series-arch', None)
2016 with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2017 ret = "status: completed\nfoo: bar"
2018 mock.return_value = ret
2019 out = client.action_fetch("123")
2020 self.assertEqual(out, ret)
2021 mock.assert_called_once_with(
2022 'action fetch', '123', "--wait", "1m"
2023 )
2024
2025 def test_action_fetch_timeout(self):
2026 client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
2027 '1.23-series-arch', None)
2028 ret = "status: pending\nfoo: bar"
2029 with patch.object(EnvJujuClient,
2030 'get_juju_output', return_value=ret):
2031 with self.assertRaisesRegexp(Exception,
2032 "timed out waiting for action"):
2033 client.action_fetch("123")
2034
2035 def test_action_do_fetch(self):
2036 client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
2037 '1.23-series-arch', None)
2038 with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2039 ret = "status: completed\nfoo: bar"
2040 # setting side_effect to an iterable will return the next value
2041 # from the list each time the function is called.
2042 mock.side_effect = [
2043 "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9",
2044 ret]
2045 out = client.action_do_fetch("foo/0", "myaction", "param=5")
2046 self.assertEqual(out, ret)
2047
19682048
1969class TestGroupReporter(TestCase):2049class TestGroupReporter(TestCase):
19702050
@@ -2099,3 +2179,53 @@
2099 ])2179 ])
2100 reporter.finish()2180 reporter.finish()
2101 self.assertEqual(sio.getvalue(), changes[-1] + "\n")2181 self.assertEqual(sio.getvalue(), changes[-1] + "\n")
2182
2183
2184class TestMakeClient(TestCase):
2185
2186 @contextmanager
2187 def make_client_cxt(self):
2188 td = temp_dir()
2189 te = temp_env({'environments': {'foo': {
2190 'orig-name': 'foo', 'name': 'foo'}}})
2191 with td as juju_path, te, patch('subprocess.Popen',
2192 side_effect=ValueError):
2193 with patch('subprocess.check_output') as co_mock:
2194 co_mock.return_value = '1.18'
2195 yield juju_path
2196
2197 def test_make_client(self):
2198 with self.make_client_cxt() as juju_path:
2199 client = make_client(juju_path, False, 'foo', 'bar')
2200 self.assertEqual(client.full_path, os.path.join(juju_path, 'juju'))
2201 self.assertEqual(client.debug, False)
2202 self.assertEqual(client.env.config['orig-name'], 'foo')
2203 self.assertEqual(client.env.config['name'], 'bar')
2204 self.assertEqual(client.env.environment, 'bar')
2205
2206 def test_make_client_debug(self):
2207 with self.make_client_cxt() as juju_path:
2208 client = make_client(juju_path, True, 'foo', 'bar')
2209 self.assertEqual(client.debug, True)
2210
2211 def test_make_client_no_temp_env_name(self):
2212 with self.make_client_cxt() as juju_path:
2213 client = make_client(juju_path, False, 'foo', None)
2214 self.assertEqual(client.full_path, os.path.join(juju_path, 'juju'))
2215 self.assertEqual(client.env.config['orig-name'], 'foo')
2216 self.assertEqual(client.env.config['name'], 'foo')
2217 self.assertEqual(client.env.environment, 'foo')
2218
2219
2220class AssessParseStateServerFromErrorTestCase(TestCase):
2221
2222 def test_parse_new_state_server_from_error(self):
2223 output = dedent("""
2224 Waiting for address
2225 Attempting to connect to 10.0.0.202:22
2226 Attempting to connect to 1.2.3.4:22
2227 The fingerprint for the ECDSA key sent by the remote host is
2228 """)
2229 error = subprocess.CalledProcessError(1, ['foo'], output)
2230 address = parse_new_state_server_from_error(error)
2231 self.assertEqual('1.2.3.4', address)

Subscribers

People subscribed via source and target branches