Merge lp:~natefinch/juju-ci-tools/logrot into lp:juju-ci-tools
- logrot
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+259750@code.launchpad.net |
Commit message
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.
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.
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.
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.
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:/
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.
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.
- 961. By Nate Finch
-
more review changes
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_
Please test parse_args.
See also inline comments.
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.
- 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
Nate Finch (natefinch) wrote : | # |
Ok, for real now, I think I addressed everything.
- 965. By Nate Finch
-
remove unneeded wait
Aaron Bentley (abentley) wrote : | # |
This is almost landable, but there are still a few issues.
Aaron Bentley (abentley) wrote : | # |
Also, TestEnvJujuClient22 needs client_class set to EnvJujuClient22.
- 966. By Nate Finch
-
more code review changes
Nate Finch (natefinch) wrote : | # |
Ok, updated once again. Addressed your concerns.
- 967. By Nate Finch
-
one more fix to TestEnvJujuClient22
Aaron Bentley (abentley) wrote : | # |
Thank you for you changes. This looks landable.
Preview Diff
1 | === added file 'assess_log_rotation.py' | |||
2 | --- assess_log_rotation.py 1970-01-01 00:00:00 +0000 | |||
3 | +++ assess_log_rotation.py 2015-06-03 15:41:41 +0000 | |||
4 | @@ -0,0 +1,228 @@ | |||
5 | 1 | #!/usr/bin/env python | ||
6 | 2 | from __future__ import print_function | ||
7 | 3 | |||
8 | 4 | __metaclass__ = type | ||
9 | 5 | |||
10 | 6 | from argparse import ArgumentParser | ||
11 | 7 | from datetime import datetime | ||
12 | 8 | import logging | ||
13 | 9 | import re | ||
14 | 10 | |||
15 | 11 | from deploy_stack import ( | ||
16 | 12 | dump_env_logs, | ||
17 | 13 | get_machine_dns_name, | ||
18 | 14 | ) | ||
19 | 15 | from jujuconfig import ( | ||
20 | 16 | get_juju_home, | ||
21 | 17 | ) | ||
22 | 18 | from jujupy import ( | ||
23 | 19 | make_client, | ||
24 | 20 | parse_new_state_server_from_error, | ||
25 | 21 | temp_bootstrap_env, | ||
26 | 22 | yaml_loads, | ||
27 | 23 | ) | ||
28 | 24 | from utility import ( | ||
29 | 25 | print_now, | ||
30 | 26 | ) | ||
31 | 27 | |||
32 | 28 | |||
33 | 29 | class LogRotateError(Exception): | ||
34 | 30 | |||
35 | 31 | ''' LogRotate test Exception base class. ''' | ||
36 | 32 | |||
37 | 33 | def __init__(self, message): | ||
38 | 34 | super(LogRotateError, self).__init__(message) | ||
39 | 35 | |||
40 | 36 | |||
41 | 37 | def test_unit_rotation(client): | ||
42 | 38 | """Tests unit log rotation.""" | ||
43 | 39 | test_rotation(client, | ||
44 | 40 | "/var/log/juju/unit-fill-logs-0.log", | ||
45 | 41 | "unit-fill-logs-0", | ||
46 | 42 | "fill-unit", | ||
47 | 43 | "unit-size", | ||
48 | 44 | "megs=300") | ||
49 | 45 | |||
50 | 46 | |||
51 | 47 | def test_machine_rotation(client): | ||
52 | 48 | """Tests machine log rotation.""" | ||
53 | 49 | test_rotation(client, | ||
54 | 50 | "/var/log/juju/machine-1.log", | ||
55 | 51 | "machine-1", | ||
56 | 52 | "fill-machine", | ||
57 | 53 | "machine-size", "megs=300", "machine=1") | ||
58 | 54 | |||
59 | 55 | |||
60 | 56 | def test_rotation(client, logfile, prefix, fill_action, size_action, *args): | ||
61 | 57 | """A reusable help for testing log rotation.log | ||
62 | 58 | |||
63 | 59 | Deploys the fill-logs charm and uses it to fill the machine or unit log and | ||
64 | 60 | test that the logs roll over correctly. | ||
65 | 61 | """ | ||
66 | 62 | |||
67 | 63 | # the rotation point should be 300 megs, so let's make sure we hit that.hit | ||
68 | 64 | # we'll obviously already have some data in the logs, so adding exactly | ||
69 | 65 | # 300megs should do the trick. | ||
70 | 66 | |||
71 | 67 | # we run do_fetch here so that we wait for fill-logs to finish. | ||
72 | 68 | client.action_do_fetch("fill-logs/0", fill_action, "3m", *args) | ||
73 | 69 | out = client.action_do_fetch("fill-logs/0", size_action) | ||
74 | 70 | action_output = yaml_loads(out) | ||
75 | 71 | |||
76 | 72 | # Now we should have one primary log file, and one backup log file. | ||
77 | 73 | # The backup should be approximately 300 megs. | ||
78 | 74 | # The primary should be below 300. | ||
79 | 75 | |||
80 | 76 | check_log0(logfile, action_output) | ||
81 | 77 | check_expected_backup("log1", prefix, action_output) | ||
82 | 78 | |||
83 | 79 | # we should only have one backup, not two. | ||
84 | 80 | check_for_extra_backup("log2", action_output) | ||
85 | 81 | |||
86 | 82 | # do it all again, this should generate a second backup. | ||
87 | 83 | |||
88 | 84 | client.action_do_fetch("fill-logs/0", fill_action, "3m", *args) | ||
89 | 85 | out = client.action_do_fetch("fill-logs/0", size_action) | ||
90 | 86 | action_output = yaml_loads(out) | ||
91 | 87 | |||
92 | 88 | # we should have two backups. | ||
93 | 89 | check_log0(logfile, action_output) | ||
94 | 90 | check_expected_backup("log1", prefix, action_output) | ||
95 | 91 | check_expected_backup("log2", prefix, action_output) | ||
96 | 92 | |||
97 | 93 | check_for_extra_backup("log3", action_output) | ||
98 | 94 | |||
99 | 95 | # one more time... we should still only have 2 backups and primary | ||
100 | 96 | |||
101 | 97 | client.action_do_fetch("fill-logs/0", fill_action, "3m", *args) | ||
102 | 98 | out = client.action_do_fetch("fill-logs/0", size_action) | ||
103 | 99 | action_output = yaml_loads(out) | ||
104 | 100 | |||
105 | 101 | check_log0(logfile, action_output) | ||
106 | 102 | check_expected_backup("log1", prefix, action_output) | ||
107 | 103 | check_expected_backup("log2", prefix, action_output) | ||
108 | 104 | |||
109 | 105 | # we should have two backups. | ||
110 | 106 | check_for_extra_backup("log3", action_output) | ||
111 | 107 | |||
112 | 108 | |||
113 | 109 | def check_for_extra_backup(logname, action_output): | ||
114 | 110 | """Check that there are no extra backup files left behind.""" | ||
115 | 111 | log = action_output["results"]["result-map"].get(logname) | ||
116 | 112 | if log is None: | ||
117 | 113 | # this is correct | ||
118 | 114 | return | ||
119 | 115 | # log exists. | ||
120 | 116 | name = log.get("name") | ||
121 | 117 | if name is None: | ||
122 | 118 | name = "(no name)" | ||
123 | 119 | raise LogRotateError("Extra backup log after rotation: " + name) | ||
124 | 120 | |||
125 | 121 | |||
126 | 122 | def check_expected_backup(key, logprefix, action_output): | ||
127 | 123 | """Check that there the expected backup files exists and is close to 300MB. | ||
128 | 124 | """ | ||
129 | 125 | log = action_output["results"]["result-map"].get(key) | ||
130 | 126 | if log is None: | ||
131 | 127 | raise LogRotateError( | ||
132 | 128 | "Missing backup log '{}' after rotation.".format(key)) | ||
133 | 129 | |||
134 | 130 | backup_pattern = "/var/log/juju/%s-(.+?)\.log" % logprefix | ||
135 | 131 | |||
136 | 132 | log_name = log["name"] | ||
137 | 133 | matches = re.match(backup_pattern, log_name) | ||
138 | 134 | if matches is None: | ||
139 | 135 | raise LogRotateError( | ||
140 | 136 | "Rotated log '%s' does not match pattern '%s'." % | ||
141 | 137 | (log_name, backup_pattern)) | ||
142 | 138 | |||
143 | 139 | size = int(log["size"]) | ||
144 | 140 | if size < 299 or size > 301: | ||
145 | 141 | raise LogRotateError( | ||
146 | 142 | "Backup log '%s' should be ~300MB, but is %sMB." % | ||
147 | 143 | (log_name, size)) | ||
148 | 144 | |||
149 | 145 | dt = matches.groups()[0] | ||
150 | 146 | dt_pattern = "%Y-%m-%dT%H-%M-%S.%f" | ||
151 | 147 | |||
152 | 148 | try: | ||
153 | 149 | # note - we have to use datetime's strptime because time's doesn't | ||
154 | 150 | # support partial seconds. | ||
155 | 151 | dt = datetime.strptime(dt, dt_pattern) | ||
156 | 152 | except Exception: | ||
157 | 153 | raise LogRotateError( | ||
158 | 154 | "Log for %s has invalid datetime appended: %s" % (log_name, dt)) | ||
159 | 155 | |||
160 | 156 | |||
161 | 157 | def check_log0(expected, action_output): | ||
162 | 158 | """Check that log0 exists and is not over 299MB""" | ||
163 | 159 | log = action_output["results"]["result-map"].get("log0") | ||
164 | 160 | if log is None: | ||
165 | 161 | raise LogRotateError("No log returned from size action.") | ||
166 | 162 | |||
167 | 163 | name = log["name"] | ||
168 | 164 | if name != expected: | ||
169 | 165 | raise LogRotateError( | ||
170 | 166 | "Wrong unit name: Expected: %s, actual: %s" % (expected, name)) | ||
171 | 167 | |||
172 | 168 | size = int(log["size"]) | ||
173 | 169 | if size > 299: | ||
174 | 170 | raise LogRotateError( | ||
175 | 171 | "Log0 too big. Expected < 300MB, got: %sMB" % size) | ||
176 | 172 | |||
177 | 173 | |||
178 | 174 | def parse_args(argv=None): | ||
179 | 175 | """Parse all arguments.""" | ||
180 | 176 | parser = ArgumentParser('Test log rotation.') | ||
181 | 177 | parser.add_argument( | ||
182 | 178 | '--debug', action='store_true', default=False, | ||
183 | 179 | help='Use --debug juju logging.') | ||
184 | 180 | parser.add_argument( | ||
185 | 181 | 'agent', | ||
186 | 182 | help='Which agent log rotation to test.', | ||
187 | 183 | choices=['machine', 'unit']) | ||
188 | 184 | parser.add_argument( | ||
189 | 185 | 'juju_path', help='Directory your juju binary lives in.') | ||
190 | 186 | parser.add_argument( | ||
191 | 187 | 'env_name', help='Juju environment name to run tests in.') | ||
192 | 188 | parser.add_argument('logs', help='Directory to store logs in.') | ||
193 | 189 | parser.add_argument( | ||
194 | 190 | 'temp_env_name', nargs='?', | ||
195 | 191 | help='Temporary environment name to use for this test.') | ||
196 | 192 | return parser.parse_args(argv) | ||
197 | 193 | |||
198 | 194 | |||
199 | 195 | def main(): | ||
200 | 196 | args = parse_args() | ||
201 | 197 | log_dir = args.logs | ||
202 | 198 | |||
203 | 199 | client = make_client( | ||
204 | 200 | args.juju_path, args.debug, args.env_name, args.temp_env_name) | ||
205 | 201 | client.destroy_environment() | ||
206 | 202 | juju_home = get_juju_home() | ||
207 | 203 | try: | ||
208 | 204 | with temp_bootstrap_env(juju_home, client): | ||
209 | 205 | client.bootstrap() | ||
210 | 206 | bootstrap_host = get_machine_dns_name(client, 0) | ||
211 | 207 | client.get_status(60) | ||
212 | 208 | client.juju("deploy", ('local:trusty/fill-logs',)) | ||
213 | 209 | |||
214 | 210 | if args.agent == "unit": | ||
215 | 211 | test_unit_rotation(client) | ||
216 | 212 | if args.agent == "machine": | ||
217 | 213 | test_machine_rotation(client) | ||
218 | 214 | except Exception as e: | ||
219 | 215 | logging.exception(e) | ||
220 | 216 | try: | ||
221 | 217 | if bootstrap_host is None: | ||
222 | 218 | bootstrap_host = parse_new_state_server_from_error(e) | ||
223 | 219 | dump_env_logs(client, bootstrap_host, log_dir) | ||
224 | 220 | except Exception as e: | ||
225 | 221 | print_now("exception while dumping logs:\n") | ||
226 | 222 | logging.exception(e) | ||
227 | 223 | finally: | ||
228 | 224 | client.destroy_environment() | ||
229 | 225 | |||
230 | 226 | |||
231 | 227 | if __name__ == '__main__': | ||
232 | 228 | main() | ||
233 | 0 | 229 | ||
234 | === modified file 'assess_recovery.py' | |||
235 | --- assess_recovery.py 2015-02-11 17:42:41 +0000 | |||
236 | +++ assess_recovery.py 2015-06-03 15:41:41 +0000 | |||
237 | @@ -20,16 +20,16 @@ | |||
238 | 20 | from jujuconfig import ( | 20 | from jujuconfig import ( |
239 | 21 | get_jenv_path, | 21 | get_jenv_path, |
240 | 22 | get_juju_home, | 22 | get_juju_home, |
242 | 23 | ) | 23 | ) |
243 | 24 | from jujupy import ( | 24 | from jujupy import ( |
244 | 25 | EnvJujuClient, | ||
245 | 26 | SimpleEnvironment, | ||
246 | 27 | temp_bootstrap_env, | 25 | temp_bootstrap_env, |
247 | 28 | until_timeout, | 26 | until_timeout, |
248 | 27 | make_client, | ||
249 | 28 | parse_new_state_server_from_error, | ||
250 | 29 | ) | 29 | ) |
251 | 30 | from substrate import ( | 30 | from substrate import ( |
252 | 31 | terminate_instances, | 31 | terminate_instances, |
254 | 32 | ) | 32 | ) |
255 | 33 | from utility import ( | 33 | from utility import ( |
256 | 34 | ensure_deleted, | 34 | ensure_deleted, |
257 | 35 | print_now, | 35 | print_now, |
258 | @@ -133,14 +133,6 @@ | |||
259 | 133 | print_now("PASS") | 133 | print_now("PASS") |
260 | 134 | 134 | ||
261 | 135 | 135 | ||
262 | 136 | def parse_new_state_server_from_error(error): | ||
263 | 137 | output = str(error) + getattr(error, 'output', '') | ||
264 | 138 | matches = re.findall(r'Attempting to connect to (.*):22', output) | ||
265 | 139 | if matches: | ||
266 | 140 | return matches[-1] | ||
267 | 141 | return None | ||
268 | 142 | |||
269 | 143 | |||
270 | 144 | def parse_args(argv=None): | 136 | def parse_args(argv=None): |
271 | 145 | parser = ArgumentParser('Test recovery strategies.') | 137 | parser = ArgumentParser('Test recovery strategies.') |
272 | 146 | parser.add_argument( | 138 | parser.add_argument( |
273 | @@ -167,15 +159,6 @@ | |||
274 | 167 | return parser.parse_args(argv) | 159 | return parser.parse_args(argv) |
275 | 168 | 160 | ||
276 | 169 | 161 | ||
277 | 170 | def make_client(juju_path, debug, env_name, temp_env_name): | ||
278 | 171 | env = SimpleEnvironment.from_config(env_name) | ||
279 | 172 | if temp_env_name is not None: | ||
280 | 173 | env.environment = temp_env_name | ||
281 | 174 | env.config['name'] = temp_env_name | ||
282 | 175 | full_path = os.path.join(juju_path, 'juju') | ||
283 | 176 | return EnvJujuClient.by_version(env, full_path, debug) | ||
284 | 177 | |||
285 | 178 | |||
286 | 179 | def main(): | 162 | def main(): |
287 | 180 | args = parse_args() | 163 | args = parse_args() |
288 | 181 | log_dir = args.logs | 164 | log_dir = args.logs |
289 | 182 | 165 | ||
290 | === modified file 'check_blockers.py' | |||
291 | --- check_blockers.py 2015-05-15 19:45:42 +0000 | |||
292 | +++ check_blockers.py 2015-06-03 15:41:41 +0000 | |||
293 | @@ -51,12 +51,10 @@ | |||
294 | 51 | subparsers = parser.add_subparsers(help='sub-command help', dest="command") | 51 | subparsers = parser.add_subparsers(help='sub-command help', dest="command") |
295 | 52 | check_parser = subparsers.add_parser( | 52 | check_parser = subparsers.add_parser( |
296 | 53 | 'check', help='Check if merges are blocked for a branch.') | 53 | 'check', help='Check if merges are blocked for a branch.') |
303 | 54 | check_parser.add_argument( | 54 | check_parser.add_argument('branch', default='master', nargs='?', |
304 | 55 | 'branch', default='master', nargs='?', | 55 | help='The branch to merge into.') |
305 | 56 | help='The branch to merge into.') | 56 | check_parser.add_argument('pull_request', default=None, nargs='?', |
306 | 57 | check_parser.add_argument( | 57 | help='The pull request to be merged') |
301 | 58 | 'pull_request', default=None, nargs='?', | ||
302 | 59 | help='The pull request to be merged') | ||
307 | 60 | update_parser = subparsers.add_parser( | 58 | update_parser = subparsers.add_parser( |
308 | 61 | 'update', help='Update blocking for a branch that passed CI.') | 59 | 'update', help='Update blocking for a branch that passed CI.') |
309 | 62 | update_parser.add_argument( | 60 | update_parser.add_argument( |
310 | 63 | 61 | ||
311 | === modified file 'jujupy.py' | |||
312 | --- jujupy.py 2015-06-01 21:06:33 +0000 | |||
313 | +++ jujupy.py 2015-06-03 15:41:41 +0000 | |||
314 | @@ -6,7 +6,7 @@ | |||
315 | 6 | from contextlib import ( | 6 | from contextlib import ( |
316 | 7 | contextmanager, | 7 | contextmanager, |
317 | 8 | nested, | 8 | nested, |
319 | 9 | ) | 9 | ) |
320 | 10 | from cStringIO import StringIO | 10 | from cStringIO import StringIO |
321 | 11 | from datetime import timedelta | 11 | from datetime import timedelta |
322 | 12 | import errno | 12 | import errno |
323 | @@ -25,7 +25,7 @@ | |||
324 | 25 | get_jenv_path, | 25 | get_jenv_path, |
325 | 26 | get_juju_home, | 26 | get_juju_home, |
326 | 27 | get_selected_environment, | 27 | get_selected_environment, |
328 | 28 | ) | 28 | ) |
329 | 29 | from utility import ( | 29 | from utility import ( |
330 | 30 | check_free_disk_space, | 30 | check_free_disk_space, |
331 | 31 | ensure_deleted, | 31 | ensure_deleted, |
332 | @@ -34,7 +34,7 @@ | |||
333 | 34 | scoped_environ, | 34 | scoped_environ, |
334 | 35 | temp_dir, | 35 | temp_dir, |
335 | 36 | until_timeout, | 36 | until_timeout, |
337 | 37 | ) | 37 | ) |
338 | 38 | 38 | ||
339 | 39 | 39 | ||
340 | 40 | WIN_JUJU_CMD = os.path.join('\\', 'Progra~2', 'Juju', 'juju.exe') | 40 | WIN_JUJU_CMD = os.path.join('\\', 'Progra~2', 'Juju', 'juju.exe') |
341 | @@ -42,6 +42,14 @@ | |||
342 | 42 | JUJU_DEV_FEATURE_FLAGS = 'JUJU_DEV_FEATURE_FLAGS' | 42 | JUJU_DEV_FEATURE_FLAGS = 'JUJU_DEV_FEATURE_FLAGS' |
343 | 43 | 43 | ||
344 | 44 | 44 | ||
345 | 45 | def parse_new_state_server_from_error(error): | ||
346 | 46 | output = str(error) + getattr(error, 'output', '') | ||
347 | 47 | matches = re.findall(r'Attempting to connect to (.*):22', output) | ||
348 | 48 | if matches: | ||
349 | 49 | return matches[-1] | ||
350 | 50 | return None | ||
351 | 51 | |||
352 | 52 | |||
353 | 45 | class ErroredUnit(Exception): | 53 | class ErroredUnit(Exception): |
354 | 46 | 54 | ||
355 | 47 | def __init__(self, unit_name, state): | 55 | def __init__(self, unit_name, state): |
356 | @@ -55,6 +63,15 @@ | |||
357 | 55 | return yaml.safe_load(StringIO(yaml_str)) | 63 | return yaml.safe_load(StringIO(yaml_str)) |
358 | 56 | 64 | ||
359 | 57 | 65 | ||
360 | 66 | def make_client(juju_path, debug, env_name, temp_env_name): | ||
361 | 67 | env = SimpleEnvironment.from_config(env_name) | ||
362 | 68 | if temp_env_name is not None: | ||
363 | 69 | env.environment = temp_env_name | ||
364 | 70 | env.config['name'] = temp_env_name | ||
365 | 71 | full_path = os.path.join(juju_path, 'juju') | ||
366 | 72 | return EnvJujuClient.by_version(env, full_path, debug) | ||
367 | 73 | |||
368 | 74 | |||
369 | 58 | class CannotConnectEnv(subprocess.CalledProcessError): | 75 | class CannotConnectEnv(subprocess.CalledProcessError): |
370 | 59 | 76 | ||
371 | 60 | def __init__(self, e): | 77 | def __init__(self, e): |
372 | @@ -156,6 +173,8 @@ | |||
373 | 156 | full_path = os.path.abspath(juju_path) | 173 | full_path = os.path.abspath(juju_path) |
374 | 157 | if version.startswith('1.16'): | 174 | if version.startswith('1.16'): |
375 | 158 | raise Exception('Unsupported juju: %s' % version) | 175 | raise Exception('Unsupported juju: %s' % version) |
376 | 176 | elif re.match('^1\.22[.-]', version): | ||
377 | 177 | return EnvJujuClient22(env, version, full_path, debug=debug) | ||
378 | 159 | elif re.match('^1\.24[.-]', version): | 178 | elif re.match('^1\.24[.-]', version): |
379 | 160 | return EnvJujuClient24(env, version, full_path, debug=debug) | 179 | return EnvJujuClient24(env, version, full_path, debug=debug) |
380 | 161 | elif re.match('^1\.25[.-]', version): | 180 | elif re.match('^1\.25[.-]', version): |
381 | @@ -174,6 +193,10 @@ | |||
382 | 174 | else: | 193 | else: |
383 | 175 | prefix = ('timeout', '%.2fs' % timeout) | 194 | prefix = ('timeout', '%.2fs' % timeout) |
384 | 176 | logging = '--debug' if self.debug else '--show-log' | 195 | logging = '--debug' if self.debug else '--show-log' |
385 | 196 | |||
386 | 197 | # we split the command here so that the caller can control where the -e | ||
387 | 198 | # <env> flag goes. Everything in the command string is put before the | ||
388 | 199 | # -e flag. | ||
389 | 177 | command = command.split() | 200 | command = command.split() |
390 | 178 | return prefix + ('juju', logging,) + tuple(command) + e_arg + args | 201 | return prefix + ('juju', logging,) + tuple(command) + e_arg + args |
391 | 179 | 202 | ||
392 | @@ -197,6 +220,7 @@ | |||
393 | 197 | env['PATH']) | 220 | env['PATH']) |
394 | 198 | if juju_home is not None: | 221 | if juju_home is not None: |
395 | 199 | env['JUJU_HOME'] = juju_home | 222 | env['JUJU_HOME'] = juju_home |
396 | 223 | |||
397 | 200 | return env | 224 | return env |
398 | 201 | 225 | ||
399 | 202 | def get_bootstrap_args(self, upload_tools): | 226 | def get_bootstrap_args(self, upload_tools): |
400 | @@ -243,8 +267,15 @@ | |||
401 | 243 | ensure_deleted(jenv_path) | 267 | ensure_deleted(jenv_path) |
402 | 244 | 268 | ||
403 | 245 | def get_juju_output(self, command, *args, **kwargs): | 269 | def get_juju_output(self, command, *args, **kwargs): |
404 | 270 | """Call a juju command and return the output. | ||
405 | 271 | |||
406 | 272 | Sub process will be called as 'juju <command> <args> <kwargs>'. Note | ||
407 | 273 | that <command> may be a space delimited list of arguments. The -e | ||
408 | 274 | <environment> flag will be placed after <command> and before args. | ||
409 | 275 | """ | ||
410 | 246 | args = self._full_args(command, False, args, | 276 | args = self._full_args(command, False, args, |
412 | 247 | timeout=kwargs.get('timeout')) | 277 | timeout=kwargs.get('timeout'), |
413 | 278 | include_e=kwargs.get('include_e', True)) | ||
414 | 248 | env = self._shell_environ() | 279 | env = self._shell_environ() |
415 | 249 | with tempfile.TemporaryFile() as stderr: | 280 | with tempfile.TemporaryFile() as stderr: |
416 | 250 | try: | 281 | try: |
417 | @@ -478,6 +509,67 @@ | |||
418 | 478 | print_now("State-Server backup at %s" % backup_file_path) | 509 | print_now("State-Server backup at %s" % backup_file_path) |
419 | 479 | return backup_file_path | 510 | return backup_file_path |
420 | 480 | 511 | ||
421 | 512 | def action_fetch(self, id, action=None, timeout="1m"): | ||
422 | 513 | """Fetches the results of the action with the given id. | ||
423 | 514 | |||
424 | 515 | Will wait for up to 1 minute for the action results. | ||
425 | 516 | The action name here is just used for an more informational error in | ||
426 | 517 | cases where it's available. | ||
427 | 518 | Returns the yaml output of the fetched action. | ||
428 | 519 | """ | ||
429 | 520 | # the command has to be "action fetch" so that the -e <env> args are | ||
430 | 521 | # placed after "fetch", since that's where action requires them to be. | ||
431 | 522 | out = self.get_juju_output("action fetch", id, "--wait", timeout) | ||
432 | 523 | status = yaml_loads(out)["status"] | ||
433 | 524 | if status != "completed": | ||
434 | 525 | name = "" | ||
435 | 526 | if action is not None: | ||
436 | 527 | name = " " + action | ||
437 | 528 | raise Exception( | ||
438 | 529 | "timed out waiting for action%s to complete during fetch" % | ||
439 | 530 | name) | ||
440 | 531 | return out | ||
441 | 532 | |||
442 | 533 | def action_do(self, unit, action, *args): | ||
443 | 534 | """Performs the given action on the given unit. | ||
444 | 535 | |||
445 | 536 | Action params should be given as args in the form foo=bar. | ||
446 | 537 | Returns the id of the queued action. | ||
447 | 538 | """ | ||
448 | 539 | args = (unit, action) + args | ||
449 | 540 | |||
450 | 541 | # the command has to be "action do" so that the -e <env> args are | ||
451 | 542 | # placed after "do", since that's where action requires them to be. | ||
452 | 543 | output = self.get_juju_output("action do", *args) | ||
453 | 544 | action_id_pattern = re.compile( | ||
454 | 545 | 'Action queued with id: ([a-f0-9\-]{36})') | ||
455 | 546 | match = action_id_pattern.search(output) | ||
456 | 547 | if match is None: | ||
457 | 548 | raise Exception("Action id not found in output: %s" % | ||
458 | 549 | output) | ||
459 | 550 | return match.group(1) | ||
460 | 551 | |||
461 | 552 | def action_do_fetch(self, unit, action, timeout="1m", *args): | ||
462 | 553 | """Performs the given action on the given unit and waits for the results. | ||
463 | 554 | |||
464 | 555 | Action params should be given as args in the form foo=bar. | ||
465 | 556 | Returns the yaml output of the action. | ||
466 | 557 | """ | ||
467 | 558 | id = self.action_do(unit, action, *args) | ||
468 | 559 | return self.action_fetch(id, action, timeout) | ||
469 | 560 | |||
470 | 561 | |||
471 | 562 | class EnvJujuClient22(EnvJujuClient): | ||
472 | 563 | |||
473 | 564 | def _shell_environ(self, juju_home=None): | ||
474 | 565 | """Generate a suitable shell environment. | ||
475 | 566 | |||
476 | 567 | Juju's directory must be in the PATH to support plugins. | ||
477 | 568 | """ | ||
478 | 569 | env = super(EnvJujuClient22, self)._shell_environ(juju_home) | ||
479 | 570 | env[JUJU_DEV_FEATURE_FLAGS] = 'actions' | ||
480 | 571 | return env | ||
481 | 572 | |||
482 | 481 | 573 | ||
483 | 482 | class EnvJujuClient25(EnvJujuClient): | 574 | class EnvJujuClient25(EnvJujuClient): |
484 | 483 | 575 | ||
485 | @@ -493,7 +585,8 @@ | |||
486 | 493 | 585 | ||
487 | 494 | 586 | ||
488 | 495 | class EnvJujuClient24(EnvJujuClient25): | 587 | class EnvJujuClient24(EnvJujuClient25): |
490 | 496 | """Currently, same feature set as juju 2.5""" | 588 | |
491 | 589 | """Currently, same feature set as juju 25""" | ||
492 | 497 | 590 | ||
493 | 498 | 591 | ||
494 | 499 | def get_local_root(juju_home, env): | 592 | def get_local_root(juju_home, env): |
495 | 500 | 593 | ||
496 | === added file 'test_assess_log_rotation.py' | |||
497 | --- test_assess_log_rotation.py 1970-01-01 00:00:00 +0000 | |||
498 | +++ test_assess_log_rotation.py 2015-06-03 15:41:41 +0000 | |||
499 | @@ -0,0 +1,154 @@ | |||
500 | 1 | from argparse import Namespace | ||
501 | 2 | from unittest import TestCase | ||
502 | 3 | from assess_log_rotation import ( | ||
503 | 4 | check_for_extra_backup, | ||
504 | 5 | check_expected_backup, | ||
505 | 6 | check_log0, | ||
506 | 7 | LogRotateError, | ||
507 | 8 | parse_args, | ||
508 | 9 | ) | ||
509 | 10 | from jujupy import yaml_loads | ||
510 | 11 | |||
511 | 12 | good_yaml = \ | ||
512 | 13 | """ | ||
513 | 14 | results: | ||
514 | 15 | result-map: | ||
515 | 16 | log0: | ||
516 | 17 | name: /var/log/juju/unit-fill-logs-0.log | ||
517 | 18 | size: "25" | ||
518 | 19 | log1: | ||
519 | 20 | name: /var/log/juju/unit-fill-logs-0-2015-05-21T09-57-03.123.log | ||
520 | 21 | size: "299" | ||
521 | 22 | log1: | ||
522 | 23 | name: /var/log/juju/unit-fill-logs-0-2015-05-22T12-57-03.123.log | ||
523 | 24 | size: "300" | ||
524 | 25 | status: completed | ||
525 | 26 | timing: | ||
526 | 27 | completed: 2015-05-21 09:57:03 -0400 EDT | ||
527 | 28 | enqueued: 2015-05-21 09:56:59 -0400 EDT | ||
528 | 29 | started: 2015-05-21 09:57:02 -0400 EDT | ||
529 | 30 | """ | ||
530 | 31 | |||
531 | 32 | good_obj = yaml_loads(good_yaml) | ||
532 | 33 | |||
533 | 34 | big_yaml = \ | ||
534 | 35 | """ | ||
535 | 36 | results: | ||
536 | 37 | result-map: | ||
537 | 38 | log0: | ||
538 | 39 | name: /var/log/juju/unit-fill-logs-0.log | ||
539 | 40 | size: "400" | ||
540 | 41 | log1: | ||
541 | 42 | name: /var/log/juju/unit-fill-logs-0-2015-05-21T09-57-03.123.log | ||
542 | 43 | size: "400" | ||
543 | 44 | log2: | ||
544 | 45 | name: /var/log/juju/unit-fill-logs-0-not-a-valid-timestamp.log | ||
545 | 46 | size: "299" | ||
546 | 47 | log3: | ||
547 | 48 | name: something-just-plain-bad.log | ||
548 | 49 | size: "299" | ||
549 | 50 | status: completed | ||
550 | 51 | timing: | ||
551 | 52 | completed: 2015-05-21 09:57:03 -0400 EDT | ||
552 | 53 | enqueued: 2015-05-21 09:56:59 -0400 EDT | ||
553 | 54 | started: 2015-05-21 09:57:02 -0400 EDT | ||
554 | 55 | """ | ||
555 | 56 | |||
556 | 57 | big_obj = yaml_loads(big_yaml) | ||
557 | 58 | |||
558 | 59 | no_files_yaml = \ | ||
559 | 60 | """ | ||
560 | 61 | results: | ||
561 | 62 | result-map: | ||
562 | 63 | status: completed | ||
563 | 64 | timing: | ||
564 | 65 | completed: 2015-05-21 09:57:03 -0400 EDT | ||
565 | 66 | enqueued: 2015-05-21 09:56:59 -0400 EDT | ||
566 | 67 | started: 2015-05-21 09:57:02 -0400 EDT | ||
567 | 68 | """ | ||
568 | 69 | |||
569 | 70 | no_files_obj = yaml_loads(no_files_yaml) | ||
570 | 71 | |||
571 | 72 | |||
572 | 73 | class TestCheckForExtraBackup(TestCase): | ||
573 | 74 | |||
574 | 75 | def test_not_found(self): | ||
575 | 76 | try: | ||
576 | 77 | # log2 should not be found, and thus no exception. | ||
577 | 78 | check_for_extra_backup("log2", good_obj) | ||
578 | 79 | except Exception as e: | ||
579 | 80 | self.fail("unexpected exception: %s" % e.msg) | ||
580 | 81 | |||
581 | 82 | def test_find_extra(self): | ||
582 | 83 | with self.assertRaises(LogRotateError): | ||
583 | 84 | # log1 should be found, and thus cause an exception. | ||
584 | 85 | check_for_extra_backup("log1", good_obj) | ||
585 | 86 | |||
586 | 87 | |||
587 | 88 | class TestCheckBackup(TestCase): | ||
588 | 89 | |||
589 | 90 | def test_exists(self): | ||
590 | 91 | try: | ||
591 | 92 | # log1 should be found, and thus no exception. | ||
592 | 93 | check_expected_backup("log1", "unit-fill-logs-0", good_obj) | ||
593 | 94 | except Exception as e: | ||
594 | 95 | self.fail("unexpected exception: %s" % e.msg) | ||
595 | 96 | |||
596 | 97 | def test_not_found(self): | ||
597 | 98 | with self.assertRaises(LogRotateError): | ||
598 | 99 | # log2 should not be found, and thus cause an exception. | ||
599 | 100 | check_expected_backup("log2", "unit-fill-logs-0", good_obj) | ||
600 | 101 | |||
601 | 102 | def test_too_big(self): | ||
602 | 103 | with self.assertRaises(LogRotateError): | ||
603 | 104 | # log1 is too big, and thus should cause an exception. | ||
604 | 105 | check_expected_backup("log1", "unit-fill-logs-0", big_obj) | ||
605 | 106 | |||
606 | 107 | def test_bad_timestamp(self): | ||
607 | 108 | with self.assertRaises(LogRotateError): | ||
608 | 109 | # log2 has an invalid timestamp, and thus should cause an | ||
609 | 110 | # exception. | ||
610 | 111 | check_expected_backup("log2", "unit-fill-logs-0", big_obj) | ||
611 | 112 | |||
612 | 113 | def test_bad_name(self): | ||
613 | 114 | with self.assertRaises(LogRotateError): | ||
614 | 115 | # log3 has a completely invalid name, and thus should cause an | ||
615 | 116 | # exception. | ||
616 | 117 | check_expected_backup("log3", "unit-fill-logs-0", big_obj) | ||
617 | 118 | |||
618 | 119 | |||
619 | 120 | class TestCheckLog0(TestCase): | ||
620 | 121 | |||
621 | 122 | def test_exists(self): | ||
622 | 123 | try: | ||
623 | 124 | # log0 should be found, and thus no exception. | ||
624 | 125 | check_log0("/var/log/juju/unit-fill-logs-0.log", good_obj) | ||
625 | 126 | except Exception as e: | ||
626 | 127 | self.fail("unexpected exception: %s" % e.msg) | ||
627 | 128 | |||
628 | 129 | def test_not_found(self): | ||
629 | 130 | with self.assertRaises(AttributeError): | ||
630 | 131 | # There's no value under result-map, which causes the yaml parser | ||
631 | 132 | # to consider it None, and thus it'll cause an AttributeError | ||
632 | 133 | check_log0("/var/log/juju/unit-fill-logs-0.log", no_files_obj) | ||
633 | 134 | |||
634 | 135 | def test_too_big(self): | ||
635 | 136 | with self.assertRaises(LogRotateError): | ||
636 | 137 | # log0 is too big, and thus should cause an exception. | ||
637 | 138 | check_log0( | ||
638 | 139 | "/var/log/juju/unit-fill-logs-0.log", big_obj) | ||
639 | 140 | |||
640 | 141 | |||
641 | 142 | class TestParseArgs(TestCase): | ||
642 | 143 | |||
643 | 144 | def test_parse_args(self): | ||
644 | 145 | args = parse_args(['machine', 'b', 'c', 'd', 'e']) | ||
645 | 146 | self.assertEqual(args, Namespace( | ||
646 | 147 | agent='machine', juju_path='b', env_name='c', logs='d', | ||
647 | 148 | temp_env_name='e', debug=False)) | ||
648 | 149 | |||
649 | 150 | def test_parse_args_debug(self): | ||
650 | 151 | args = parse_args(['--debug', 'unit', 'b', 'c', 'd', 'e']) | ||
651 | 152 | self.assertEqual(args, Namespace( | ||
652 | 153 | agent='unit', juju_path='b', env_name='c', logs='d', | ||
653 | 154 | temp_env_name='e', debug=True)) | ||
654 | 0 | 155 | ||
655 | === modified file 'test_assess_recovery.py' | |||
656 | --- test_assess_recovery.py 2015-02-11 17:49:42 +0000 | |||
657 | +++ test_assess_recovery.py 2015-06-03 15:41:41 +0000 | |||
658 | @@ -1,68 +1,8 @@ | |||
659 | 1 | from contextlib import contextmanager | ||
660 | 2 | import os | ||
661 | 3 | from subprocess import CalledProcessError | ||
662 | 4 | from textwrap import dedent | ||
663 | 5 | from unittest import TestCase | 1 | from unittest import TestCase |
664 | 6 | 2 | ||
665 | 7 | from mock import patch | ||
666 | 8 | |||
667 | 9 | from assess_recovery import ( | 3 | from assess_recovery import ( |
668 | 10 | make_client, | ||
669 | 11 | parse_new_state_server_from_error, | ||
670 | 12 | parse_args, | 4 | parse_args, |
671 | 13 | ) | 5 | ) |
672 | 14 | from jujupy import _temp_env as temp_env | ||
673 | 15 | from utility import temp_dir | ||
674 | 16 | |||
675 | 17 | |||
676 | 18 | class AssessRecoveryTestCase(TestCase): | ||
677 | 19 | |||
678 | 20 | def test_parse_new_state_server_from_error(self): | ||
679 | 21 | output = dedent(""" | ||
680 | 22 | Waiting for address | ||
681 | 23 | Attempting to connect to 10.0.0.202:22 | ||
682 | 24 | Attempting to connect to 1.2.3.4:22 | ||
683 | 25 | The fingerprint for the ECDSA key sent by the remote host is | ||
684 | 26 | """) | ||
685 | 27 | error = CalledProcessError(1, ['foo'], output) | ||
686 | 28 | address = parse_new_state_server_from_error(error) | ||
687 | 29 | self.assertEqual('1.2.3.4', address) | ||
688 | 30 | |||
689 | 31 | |||
690 | 32 | class TestMakeClient(TestCase): | ||
691 | 33 | |||
692 | 34 | @contextmanager | ||
693 | 35 | def make_client_cxt(self): | ||
694 | 36 | td = temp_dir() | ||
695 | 37 | te = temp_env({'environments': {'foo': { | ||
696 | 38 | 'orig-name': 'foo', 'name': 'foo'}}}) | ||
697 | 39 | with td as juju_path, te, patch('subprocess.Popen', | ||
698 | 40 | side_effect=ValueError): | ||
699 | 41 | with patch('subprocess.check_output') as co_mock: | ||
700 | 42 | co_mock.return_value = '1.18' | ||
701 | 43 | yield juju_path | ||
702 | 44 | |||
703 | 45 | def test_make_client(self): | ||
704 | 46 | with self.make_client_cxt() as juju_path: | ||
705 | 47 | client = make_client(juju_path, False, 'foo', 'bar') | ||
706 | 48 | self.assertEqual(client.full_path, os.path.join(juju_path, 'juju')) | ||
707 | 49 | self.assertEqual(client.debug, False) | ||
708 | 50 | self.assertEqual(client.env.config['orig-name'], 'foo') | ||
709 | 51 | self.assertEqual(client.env.config['name'], 'bar') | ||
710 | 52 | self.assertEqual(client.env.environment, 'bar') | ||
711 | 53 | |||
712 | 54 | def test_make_client_debug(self): | ||
713 | 55 | with self.make_client_cxt() as juju_path: | ||
714 | 56 | client = make_client(juju_path, True, 'foo', 'bar') | ||
715 | 57 | self.assertEqual(client.debug, True) | ||
716 | 58 | |||
717 | 59 | def test_make_client_no_temp_env_name(self): | ||
718 | 60 | with self.make_client_cxt() as juju_path: | ||
719 | 61 | client = make_client(juju_path, False, 'foo', None) | ||
720 | 62 | self.assertEqual(client.full_path, os.path.join(juju_path, 'juju')) | ||
721 | 63 | self.assertEqual(client.env.config['orig-name'], 'foo') | ||
722 | 64 | self.assertEqual(client.env.config['name'], 'foo') | ||
723 | 65 | self.assertEqual(client.env.environment, 'foo') | ||
724 | 66 | 6 | ||
725 | 67 | 7 | ||
726 | 68 | class TestParseArgs(TestCase): | 8 | class TestParseArgs(TestCase): |
727 | 69 | 9 | ||
728 | === modified file 'test_deploy_stack.py' | |||
729 | --- test_deploy_stack.py 2015-05-28 22:29:53 +0000 | |||
730 | +++ test_deploy_stack.py 2015-06-03 15:41:41 +0000 | |||
731 | @@ -1,7 +1,7 @@ | |||
732 | 1 | from argparse import ( | 1 | from argparse import ( |
733 | 2 | ArgumentParser, | 2 | ArgumentParser, |
734 | 3 | Namespace, | 3 | Namespace, |
736 | 4 | ) | 4 | ) |
737 | 5 | from contextlib import contextmanager | 5 | from contextlib import contextmanager |
738 | 6 | import json | 6 | import json |
739 | 7 | import logging | 7 | import logging |
740 | @@ -14,7 +14,7 @@ | |||
741 | 14 | from mock import ( | 14 | from mock import ( |
742 | 15 | call, | 15 | call, |
743 | 16 | patch, | 16 | patch, |
745 | 17 | ) | 17 | ) |
746 | 18 | import yaml | 18 | import yaml |
747 | 19 | 19 | ||
748 | 20 | from deploy_stack import ( | 20 | from deploy_stack import ( |
749 | @@ -50,7 +50,7 @@ | |||
750 | 50 | ) | 50 | ) |
751 | 51 | from test_jujupy import ( | 51 | from test_jujupy import ( |
752 | 52 | assert_juju_call, | 52 | assert_juju_call, |
754 | 53 | ) | 53 | ) |
755 | 54 | from utility import temp_dir | 54 | from utility import temp_dir |
756 | 55 | 55 | ||
757 | 56 | 56 | ||
758 | @@ -274,7 +274,7 @@ | |||
759 | 274 | side_effect=subprocess.CalledProcessError('', '')): | 274 | side_effect=subprocess.CalledProcessError('', '')): |
760 | 275 | with patch('subprocess.call', autospec=True) as c_mock: | 275 | with patch('subprocess.call', autospec=True) as c_mock: |
761 | 276 | with self.assertRaises(subprocess.CalledProcessError): | 276 | with self.assertRaises(subprocess.CalledProcessError): |
763 | 277 | run_instances(1, 'qux') | 277 | run_instances(1, 'qux') |
764 | 278 | c_mock.assert_called_with(['euca-terminate-instances', 'i-foo']) | 278 | c_mock.assert_called_with(['euca-terminate-instances', 'i-foo']) |
765 | 279 | 279 | ||
766 | 280 | def test_assess_juju_run(self): | 280 | def test_assess_juju_run(self): |
767 | @@ -524,7 +524,7 @@ | |||
768 | 524 | ('juju', '--show-log', 'status', '-e', 'foo'): status, | 524 | ('juju', '--show-log', 'status', '-e', 'foo'): status, |
769 | 525 | ('juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0', | 525 | ('juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0', |
770 | 526 | GET_TOKEN_SCRIPT): 'fake-token', | 526 | GET_TOKEN_SCRIPT): 'fake-token', |
772 | 527 | } | 527 | } |
773 | 528 | return output[args] | 528 | return output[args] |
774 | 529 | 529 | ||
775 | 530 | with patch('subprocess.check_output', side_effect=output, | 530 | with patch('subprocess.check_output', side_effect=output, |
776 | @@ -582,7 +582,7 @@ | |||
777 | 582 | cls.RUN_UNAME: juju_run_out, | 582 | cls.RUN_UNAME: juju_run_out, |
778 | 583 | cls.VERSION: '1.38', | 583 | cls.VERSION: '1.38', |
779 | 584 | cls.GET_ENV: 'testing' | 584 | cls.GET_ENV: 'testing' |
781 | 585 | } | 585 | } |
782 | 586 | return output[args] | 586 | return output[args] |
783 | 587 | 587 | ||
784 | 588 | @contextmanager | 588 | @contextmanager |
785 | @@ -590,11 +590,11 @@ | |||
786 | 590 | with patch('subprocess.check_output', side_effect=self.upgrade_output, | 590 | with patch('subprocess.check_output', side_effect=self.upgrade_output, |
787 | 591 | autospec=True) as co_mock: | 591 | autospec=True) as co_mock: |
788 | 592 | with patch('subprocess.check_call', autospec=True) as cc_mock: | 592 | with patch('subprocess.check_call', autospec=True) as cc_mock: |
794 | 593 | with patch('deploy_stack.check_token', autospec=True): | 593 | with patch('deploy_stack.check_token', autospec=True): |
795 | 594 | with patch('deploy_stack.get_random_string', | 594 | with patch('deploy_stack.get_random_string', |
796 | 595 | return_value="FAKETOKEN", autospec=True): | 595 | return_value="FAKETOKEN", autospec=True): |
797 | 596 | with patch('sys.stdout', autospec=True): | 596 | with patch('sys.stdout', autospec=True): |
798 | 597 | yield (co_mock, cc_mock) | 597 | yield (co_mock, cc_mock) |
799 | 598 | 598 | ||
800 | 599 | def test_assess_upgrade(self): | 599 | def test_assess_upgrade(self): |
801 | 600 | env = SimpleEnvironment('foo', {'type': 'foo'}) | 600 | env = SimpleEnvironment('foo', {'type': 'foo'}) |
802 | @@ -643,7 +643,7 @@ | |||
803 | 643 | status = yaml.dump({ | 643 | status = yaml.dump({ |
804 | 644 | 'machines': {0: {'agent-version': '1.18.17'}}, | 644 | 'machines': {0: {'agent-version': '1.18.17'}}, |
805 | 645 | 'services': {}, | 645 | 'services': {}, |
807 | 646 | }) | 646 | }) |
808 | 647 | 647 | ||
809 | 648 | def test_prepare_environment(self): | 648 | def test_prepare_environment(self): |
810 | 649 | client = self.get_client() | 649 | client = self.get_client() |
811 | @@ -699,7 +699,7 @@ | |||
812 | 699 | client = EnvJujuClient(SimpleEnvironment( | 699 | client = EnvJujuClient(SimpleEnvironment( |
813 | 700 | 'foo', {'type': 'paas'}), '1.23', 'path') | 700 | 'foo', {'type': 'paas'}), '1.23', 'path') |
814 | 701 | self.addContext(patch('deploy_stack.get_machine_dns_name', | 701 | self.addContext(patch('deploy_stack.get_machine_dns_name', |
816 | 702 | return_value='foo')) | 702 | return_value='foo')) |
817 | 703 | c_mock = self.addContext(patch('subprocess.call')) | 703 | c_mock = self.addContext(patch('subprocess.call')) |
818 | 704 | with boot_context('bar', client, None, [], None, None, None, None, | 704 | with boot_context('bar', client, None, [], None, None, None, None, |
819 | 705 | keep_env=False, upload_tools=False): | 705 | keep_env=False, upload_tools=False): |
820 | @@ -718,7 +718,7 @@ | |||
821 | 718 | client = EnvJujuClient(SimpleEnvironment( | 718 | client = EnvJujuClient(SimpleEnvironment( |
822 | 719 | 'foo', {'type': 'paas'}), '1.23', 'path') | 719 | 'foo', {'type': 'paas'}), '1.23', 'path') |
823 | 720 | self.addContext(patch('deploy_stack.get_machine_dns_name', | 720 | self.addContext(patch('deploy_stack.get_machine_dns_name', |
825 | 721 | return_value='foo')) | 721 | return_value='foo')) |
826 | 722 | c_mock = self.addContext(patch('subprocess.call')) | 722 | c_mock = self.addContext(patch('subprocess.call')) |
827 | 723 | with boot_context('bar', client, None, [], None, None, None, None, | 723 | with boot_context('bar', client, None, [], None, None, None, None, |
828 | 724 | keep_env=True, upload_tools=False): | 724 | keep_env=True, upload_tools=False): |
829 | @@ -735,7 +735,7 @@ | |||
830 | 735 | client = EnvJujuClient(SimpleEnvironment( | 735 | client = EnvJujuClient(SimpleEnvironment( |
831 | 736 | 'foo', {'type': 'paas'}), '1.23', 'path') | 736 | 'foo', {'type': 'paas'}), '1.23', 'path') |
832 | 737 | self.addContext(patch('deploy_stack.get_machine_dns_name', | 737 | self.addContext(patch('deploy_stack.get_machine_dns_name', |
834 | 738 | return_value='foo')) | 738 | return_value='foo')) |
835 | 739 | self.addContext(patch('subprocess.call')) | 739 | self.addContext(patch('subprocess.call')) |
836 | 740 | with boot_context('bar', client, None, [], None, None, None, None, | 740 | with boot_context('bar', client, None, [], None, None, None, None, |
837 | 741 | keep_env=False, upload_tools=True): | 741 | keep_env=False, upload_tools=True): |
838 | @@ -749,7 +749,7 @@ | |||
839 | 749 | client = EnvJujuClient(SimpleEnvironment( | 749 | client = EnvJujuClient(SimpleEnvironment( |
840 | 750 | 'foo', {'type': 'paas'}), '1.23', 'path') | 750 | 'foo', {'type': 'paas'}), '1.23', 'path') |
841 | 751 | self.addContext(patch('deploy_stack.get_machine_dns_name', | 751 | self.addContext(patch('deploy_stack.get_machine_dns_name', |
843 | 752 | return_value='foo')) | 752 | return_value='foo')) |
844 | 753 | self.addContext(patch('subprocess.call')) | 753 | self.addContext(patch('subprocess.call')) |
845 | 754 | ue_mock = self.addContext( | 754 | ue_mock = self.addContext( |
846 | 755 | patch('deploy_stack.update_env', wraps=update_env)) | 755 | patch('deploy_stack.update_env', wraps=update_env)) |
847 | @@ -783,7 +783,7 @@ | |||
848 | 783 | upgrade=False, | 783 | upgrade=False, |
849 | 784 | verbose=False, | 784 | verbose=False, |
850 | 785 | upload_tools=False, | 785 | upload_tools=False, |
852 | 786 | )) | 786 | )) |
853 | 787 | 787 | ||
854 | 788 | def test_upload_tools(self): | 788 | def test_upload_tools(self): |
855 | 789 | args = deploy_job_parse_args(['foo', 'bar', 'baz', '--upload-tools']) | 789 | args = deploy_job_parse_args(['foo', 'bar', 'baz', '--upload-tools']) |
856 | 790 | 790 | ||
857 | === modified file 'test_jujupy.py' | |||
858 | --- test_jujupy.py 2015-06-01 21:06:33 +0000 | |||
859 | +++ test_jujupy.py 2015-06-03 15:41:41 +0000 | |||
860 | @@ -4,7 +4,7 @@ | |||
861 | 4 | from datetime import ( | 4 | from datetime import ( |
862 | 5 | datetime, | 5 | datetime, |
863 | 6 | timedelta, | 6 | timedelta, |
865 | 7 | ) | 7 | ) |
866 | 8 | import os | 8 | import os |
867 | 9 | import shutil | 9 | import shutil |
868 | 10 | import StringIO | 10 | import StringIO |
869 | @@ -24,11 +24,12 @@ | |||
870 | 24 | get_environments_path, | 24 | get_environments_path, |
871 | 25 | get_jenv_path, | 25 | get_jenv_path, |
872 | 26 | NoSuchEnvironment, | 26 | NoSuchEnvironment, |
874 | 27 | ) | 27 | ) |
875 | 28 | from jujupy import ( | 28 | from jujupy import ( |
876 | 29 | CannotConnectEnv, | 29 | CannotConnectEnv, |
877 | 30 | Environment, | 30 | Environment, |
878 | 31 | EnvJujuClient, | 31 | EnvJujuClient, |
879 | 32 | EnvJujuClient22, | ||
880 | 32 | EnvJujuClient24, | 33 | EnvJujuClient24, |
881 | 33 | EnvJujuClient25, | 34 | EnvJujuClient25, |
882 | 34 | ErroredUnit, | 35 | ErroredUnit, |
883 | @@ -41,11 +42,13 @@ | |||
884 | 41 | temp_bootstrap_env, | 42 | temp_bootstrap_env, |
885 | 42 | _temp_env as temp_env, | 43 | _temp_env as temp_env, |
886 | 43 | uniquify_local, | 44 | uniquify_local, |
887 | 45 | make_client, | ||
888 | 46 | parse_new_state_server_from_error, | ||
889 | 44 | ) | 47 | ) |
890 | 45 | from utility import ( | 48 | from utility import ( |
891 | 46 | scoped_environ, | 49 | scoped_environ, |
892 | 47 | temp_dir, | 50 | temp_dir, |
894 | 48 | ) | 51 | ) |
895 | 49 | 52 | ||
896 | 50 | 53 | ||
897 | 51 | def assert_juju_call(test_case, mock_method, client, expected_args, | 54 | def assert_juju_call(test_case, mock_method, client, expected_args, |
898 | @@ -98,7 +101,8 @@ | |||
899 | 98 | SimpleEnvironment('baz', {'type': 'cloudsigma'}), | 101 | SimpleEnvironment('baz', {'type': 'cloudsigma'}), |
900 | 99 | '1.25-foobar', 'path') | 102 | '1.25-foobar', 'path') |
901 | 100 | env = client._shell_environ() | 103 | env = client._shell_environ() |
903 | 101 | self.assertEqual(env[JUJU_DEV_FEATURE_FLAGS], 'cloudsigma') | 104 | "".split() |
904 | 105 | self.assertTrue('cloudsigma' in env[JUJU_DEV_FEATURE_FLAGS].split(",")) | ||
905 | 102 | 106 | ||
906 | 103 | def test__shell_environ_juju_home(self): | 107 | def test__shell_environ_juju_home(self): |
907 | 104 | client = self.client_class( | 108 | client = self.client_class( |
908 | @@ -107,6 +111,23 @@ | |||
909 | 107 | self.assertEqual(env['JUJU_HOME'], 'asdf') | 111 | self.assertEqual(env['JUJU_HOME'], 'asdf') |
910 | 108 | 112 | ||
911 | 109 | 113 | ||
912 | 114 | class TestEnvJujuClient22(ClientTest): | ||
913 | 115 | |||
914 | 116 | client_class = EnvJujuClient22 | ||
915 | 117 | |||
916 | 118 | def test__shell_environ(self): | ||
917 | 119 | client = self.client_class( | ||
918 | 120 | SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path') | ||
919 | 121 | env = client._shell_environ() | ||
920 | 122 | self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS), 'actions') | ||
921 | 123 | |||
922 | 124 | def test__shell_environ_juju_home(self): | ||
923 | 125 | client = self.client_class( | ||
924 | 126 | SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path') | ||
925 | 127 | env = client._shell_environ(juju_home='asdf') | ||
926 | 128 | self.assertEqual(env['JUJU_HOME'], 'asdf') | ||
927 | 129 | |||
928 | 130 | |||
929 | 110 | class TestEnvJujuClient24(TestEnvJujuClient25): | 131 | class TestEnvJujuClient24(TestEnvJujuClient25): |
930 | 111 | 132 | ||
931 | 112 | client_class = EnvJujuClient25 | 133 | client_class = EnvJujuClient25 |
932 | @@ -165,6 +186,7 @@ | |||
933 | 165 | yield '1.16' | 186 | yield '1.16' |
934 | 166 | yield '1.16.1' | 187 | yield '1.16.1' |
935 | 167 | yield '1.15' | 188 | yield '1.15' |
936 | 189 | yield '1.22.1' | ||
937 | 168 | yield '1.24-alpha1' | 190 | yield '1.24-alpha1' |
938 | 169 | yield '1.24.7' | 191 | yield '1.24.7' |
939 | 170 | yield '1.25.1' | 192 | yield '1.25.1' |
940 | @@ -185,6 +207,8 @@ | |||
941 | 185 | self.assertIs(EnvJujuClient, type(client)) | 207 | self.assertIs(EnvJujuClient, type(client)) |
942 | 186 | self.assertEqual('1.15', client.version) | 208 | self.assertEqual('1.15', client.version) |
943 | 187 | client = EnvJujuClient.by_version(None) | 209 | client = EnvJujuClient.by_version(None) |
944 | 210 | self.assertIs(type(client), EnvJujuClient22) | ||
945 | 211 | client = EnvJujuClient.by_version(None) | ||
946 | 188 | self.assertIs(type(client), EnvJujuClient24) | 212 | self.assertIs(type(client), EnvJujuClient24) |
947 | 189 | self.assertEqual(client.version, '1.24-alpha1') | 213 | self.assertEqual(client.version, '1.24-alpha1') |
948 | 190 | client = EnvJujuClient.by_version(None) | 214 | client = EnvJujuClient.by_version(None) |
949 | @@ -231,7 +255,7 @@ | |||
950 | 231 | client = EnvJujuClient(env, None, 'my/juju/bin') | 255 | client = EnvJujuClient(env, None, 'my/juju/bin') |
951 | 232 | full = client._full_args('action bar', False, ('baz', 'qux')) | 256 | full = client._full_args('action bar', False, ('baz', 'qux')) |
952 | 233 | self.assertEqual(( | 257 | self.assertEqual(( |
954 | 234 | 'juju', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux'), | 258 | 'juju', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux', ), |
955 | 235 | full) | 259 | full) |
956 | 236 | 260 | ||
957 | 237 | def test_bootstrap_hpcloud(self): | 261 | def test_bootstrap_hpcloud(self): |
958 | @@ -577,9 +601,9 @@ | |||
959 | 577 | '0': {'state-server-member-status': 'has-vote'}, | 601 | '0': {'state-server-member-status': 'has-vote'}, |
960 | 578 | '1': {'state-server-member-status': 'has-vote'}, | 602 | '1': {'state-server-member-status': 'has-vote'}, |
961 | 579 | '2': {'state-server-member-status': 'has-vote'}, | 603 | '2': {'state-server-member-status': 'has-vote'}, |
963 | 580 | }, | 604 | }, |
964 | 581 | 'services': {}, | 605 | 'services': {}, |
966 | 582 | }) | 606 | }) |
967 | 583 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) | 607 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) |
968 | 584 | with patch.object(client, 'get_juju_output', return_value=value): | 608 | with patch.object(client, 'get_juju_output', return_value=value): |
969 | 585 | client.wait_for_ha() | 609 | client.wait_for_ha() |
970 | @@ -590,9 +614,9 @@ | |||
971 | 590 | '0': {'state-server-member-status': 'no-vote'}, | 614 | '0': {'state-server-member-status': 'no-vote'}, |
972 | 591 | '1': {'state-server-member-status': 'no-vote'}, | 615 | '1': {'state-server-member-status': 'no-vote'}, |
973 | 592 | '2': {'state-server-member-status': 'no-vote'}, | 616 | '2': {'state-server-member-status': 'no-vote'}, |
975 | 593 | }, | 617 | }, |
976 | 594 | 'services': {}, | 618 | 'services': {}, |
978 | 595 | }) | 619 | }) |
979 | 596 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) | 620 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) |
980 | 597 | with patch('sys.stdout'): | 621 | with patch('sys.stdout'): |
981 | 598 | with patch.object(client, 'get_juju_output', return_value=value): | 622 | with patch.object(client, 'get_juju_output', return_value=value): |
982 | @@ -606,9 +630,9 @@ | |||
983 | 606 | 'machines': { | 630 | 'machines': { |
984 | 607 | '0': {'state-server-member-status': 'has-vote'}, | 631 | '0': {'state-server-member-status': 'has-vote'}, |
985 | 608 | '1': {'state-server-member-status': 'has-vote'}, | 632 | '1': {'state-server-member-status': 'has-vote'}, |
987 | 609 | }, | 633 | }, |
988 | 610 | 'services': {}, | 634 | 'services': {}, |
990 | 611 | }) | 635 | }) |
991 | 612 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) | 636 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) |
992 | 613 | with patch('jujupy.until_timeout', lambda x: range(0)): | 637 | with patch('jujupy.until_timeout', lambda x: range(0)): |
993 | 614 | with patch.object(client, 'get_juju_output', return_value=value): | 638 | with patch.object(client, 'get_juju_output', return_value=value): |
994 | @@ -621,7 +645,7 @@ | |||
995 | 621 | value = yaml.safe_dump({ | 645 | value = yaml.safe_dump({ |
996 | 622 | 'machines': { | 646 | 'machines': { |
997 | 623 | '0': {'agent-state': 'started'}, | 647 | '0': {'agent-state': 'started'}, |
999 | 624 | }, | 648 | }, |
1000 | 625 | 'services': { | 649 | 'services': { |
1001 | 626 | 'jenkins': { | 650 | 'jenkins': { |
1002 | 627 | 'units': { | 651 | 'units': { |
1003 | @@ -638,9 +662,9 @@ | |||
1004 | 638 | value = yaml.safe_dump({ | 662 | value = yaml.safe_dump({ |
1005 | 639 | 'machines': { | 663 | 'machines': { |
1006 | 640 | '0': {'agent-state': 'started'}, | 664 | '0': {'agent-state': 'started'}, |
1008 | 641 | }, | 665 | }, |
1009 | 642 | 'services': {}, | 666 | 'services': {}, |
1011 | 643 | }) | 667 | }) |
1012 | 644 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) | 668 | client = EnvJujuClient(SimpleEnvironment('local'), None, None) |
1013 | 645 | with patch('jujupy.until_timeout', lambda x: range(0)): | 669 | with patch('jujupy.until_timeout', lambda x: range(0)): |
1014 | 646 | with patch.object(client, 'get_juju_output', return_value=value): | 670 | with patch.object(client, 'get_juju_output', return_value=value): |
1015 | @@ -981,7 +1005,7 @@ | |||
1016 | 981 | 'root-dir': get_local_root(fake_home, client.env), | 1005 | 'root-dir': get_local_root(fake_home, client.env), |
1017 | 982 | 'agent-version': agent_version, | 1006 | 'agent-version': agent_version, |
1018 | 983 | 'test-mode': True, | 1007 | 'test-mode': True, |
1020 | 984 | }}}) | 1008 | }}}) |
1021 | 985 | stub_bootstrap() | 1009 | stub_bootstrap() |
1022 | 986 | 1010 | ||
1023 | 987 | def test_temp_bootstrap_env_provides_dir(self): | 1011 | def test_temp_bootstrap_env_provides_dir(self): |
1024 | @@ -1052,7 +1076,7 @@ | |||
1025 | 1052 | self.assertEqual(mock_cfds.mock_calls, [ | 1076 | self.assertEqual(mock_cfds.mock_calls, [ |
1026 | 1053 | call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'), | 1077 | call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'), |
1027 | 1054 | call('/var/lib/lxc', 2000000, 'LXC containers'), | 1078 | call('/var/lib/lxc', 2000000, 'LXC containers'), |
1029 | 1055 | ]) | 1079 | ]) |
1030 | 1056 | 1080 | ||
1031 | 1057 | def test_check_space_local_kvm(self): | 1081 | def test_check_space_local_kvm(self): |
1032 | 1058 | env = SimpleEnvironment('qux', {'type': 'local', 'container': 'kvm'}) | 1082 | env = SimpleEnvironment('qux', {'type': 'local', 'container': 'kvm'}) |
1033 | @@ -1064,7 +1088,7 @@ | |||
1034 | 1064 | self.assertEqual(mock_cfds.mock_calls, [ | 1088 | self.assertEqual(mock_cfds.mock_calls, [ |
1035 | 1065 | call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'), | 1089 | call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'), |
1036 | 1066 | call('/var/lib/uvtool/libvirt/images', 2000000, 'KVM disk files'), | 1090 | call('/var/lib/uvtool/libvirt/images', 2000000, 'KVM disk files'), |
1038 | 1067 | ]) | 1091 | ]) |
1039 | 1068 | 1092 | ||
1040 | 1069 | def test_error_on_jenv(self): | 1093 | def test_error_on_jenv(self): |
1041 | 1070 | env = SimpleEnvironment('qux', {'type': 'local'}) | 1094 | env = SimpleEnvironment('qux', {'type': 'local'}) |
1042 | @@ -1337,7 +1361,7 @@ | |||
1043 | 1337 | self.assertEqual(list(status.iter_machines(containers=True)), [ | 1361 | self.assertEqual(list(status.iter_machines(containers=True)), [ |
1044 | 1338 | ('1', status.status['machines']['1']), | 1362 | ('1', status.status['machines']['1']), |
1045 | 1339 | ('1/lxc/0', {'baz': 'qux'}), | 1363 | ('1/lxc/0', {'baz': 'qux'}), |
1047 | 1340 | ]) | 1364 | ]) |
1048 | 1341 | 1365 | ||
1049 | 1342 | def test_agent_items_empty(self): | 1366 | def test_agent_items_empty(self): |
1050 | 1343 | status = Status({'machines': {}, 'services': {}}, '') | 1367 | status = Status({'machines': {}, 'services': {}}, '') |
1051 | @@ -1483,7 +1507,7 @@ | |||
1052 | 1483 | status.get_subordinate_units('dummy-sink'), [ | 1507 | status.get_subordinate_units('dummy-sink'), [ |
1053 | 1484 | {'chaos-monkey/1': {'agent-state': 'started'}}, | 1508 | {'chaos-monkey/1': {'agent-state': 'started'}}, |
1054 | 1485 | {'chaos-monkey/2': {'agent-state': 'started'}}] | 1509 | {'chaos-monkey/2': {'agent-state': 'started'}}] |
1056 | 1486 | ) | 1510 | ) |
1057 | 1487 | with self.assertRaisesRegexp(KeyError, 'foo'): | 1511 | with self.assertRaisesRegexp(KeyError, 'foo'): |
1058 | 1488 | status.get_subordinate_units('foo') | 1512 | status.get_subordinate_units('foo') |
1059 | 1489 | 1513 | ||
1060 | @@ -1593,7 +1617,7 @@ | |||
1061 | 1593 | 'machines': {'0': { | 1617 | 'machines': {'0': { |
1062 | 1594 | 'agent-state-info': failure}}, | 1618 | 'agent-state-info': failure}}, |
1063 | 1595 | 'services': {}, | 1619 | 'services': {}, |
1065 | 1596 | }, '') | 1620 | }, '') |
1066 | 1597 | with self.assertRaises(ErroredUnit) as e_cxt: | 1621 | with self.assertRaises(ErroredUnit) as e_cxt: |
1067 | 1598 | status.check_agents_started() | 1622 | status.check_agents_started() |
1068 | 1599 | e = e_cxt.exception | 1623 | e = e_cxt.exception |
1069 | @@ -1653,14 +1677,14 @@ | |||
1070 | 1653 | old_status = Status({ | 1677 | old_status = Status({ |
1071 | 1654 | 'machines': { | 1678 | 'machines': { |
1072 | 1655 | 'bar': 'bar_info', | 1679 | 'bar': 'bar_info', |
1075 | 1656 | } | 1680 | } |
1076 | 1657 | }, '') | 1681 | }, '') |
1077 | 1658 | new_status = Status({ | 1682 | new_status = Status({ |
1078 | 1659 | 'machines': { | 1683 | 'machines': { |
1079 | 1660 | 'foo': 'foo_info', | 1684 | 'foo': 'foo_info', |
1080 | 1661 | 'bar': 'bar_info', | 1685 | 'bar': 'bar_info', |
1083 | 1662 | } | 1686 | } |
1084 | 1663 | }, '') | 1687 | }, '') |
1085 | 1664 | self.assertItemsEqual(new_status.iter_new_machines(old_status), | 1688 | self.assertItemsEqual(new_status.iter_new_machines(old_status), |
1086 | 1665 | [('foo', 'foo_info')]) | 1689 | [('foo', 'foo_info')]) |
1087 | 1666 | 1690 | ||
1088 | @@ -1669,8 +1693,8 @@ | |||
1089 | 1669 | 'machines': { | 1693 | 'machines': { |
1090 | 1670 | '0': {'instance-id': 'foo-bar'}, | 1694 | '0': {'instance-id': 'foo-bar'}, |
1091 | 1671 | '1': {}, | 1695 | '1': {}, |
1094 | 1672 | } | 1696 | } |
1095 | 1673 | }, '') | 1697 | }, '') |
1096 | 1674 | self.assertEqual(status.get_instance_id('0'), 'foo-bar') | 1698 | self.assertEqual(status.get_instance_id('0'), 'foo-bar') |
1097 | 1675 | with self.assertRaises(KeyError): | 1699 | with self.assertRaises(KeyError): |
1098 | 1676 | status.get_instance_id('1') | 1700 | status.get_instance_id('1') |
1099 | @@ -1965,6 +1989,62 @@ | |||
1100 | 1965 | mock_get.assert_called_with(env, 'tools-metadata-url') | 1989 | mock_get.assert_called_with(env, 'tools-metadata-url') |
1101 | 1966 | self.assertEqual(0, mock_set.call_count) | 1990 | self.assertEqual(0, mock_set.call_count) |
1102 | 1967 | 1991 | ||
1103 | 1992 | def test_action_do(self): | ||
1104 | 1993 | client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}), | ||
1105 | 1994 | '1.23-series-arch', None) | ||
1106 | 1995 | with patch.object(EnvJujuClient, 'get_juju_output') as mock: | ||
1107 | 1996 | mock.return_value = \ | ||
1108 | 1997 | "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9" | ||
1109 | 1998 | id = client.action_do("foo/0", "myaction", "param=5") | ||
1110 | 1999 | self.assertEqual(id, "5a92ec93-d4be-4399-82dc-7431dbfd08f9") | ||
1111 | 2000 | mock.assert_called_once_with( | ||
1112 | 2001 | 'action do', 'foo/0', 'myaction', "param=5" | ||
1113 | 2002 | ) | ||
1114 | 2003 | |||
1115 | 2004 | def test_action_do_error(self): | ||
1116 | 2005 | client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}), | ||
1117 | 2006 | '1.23-series-arch', None) | ||
1118 | 2007 | with patch.object(EnvJujuClient, 'get_juju_output') as mock: | ||
1119 | 2008 | mock.return_value = "some bad text" | ||
1120 | 2009 | with self.assertRaisesRegexp(Exception, | ||
1121 | 2010 | "Action id not found in output"): | ||
1122 | 2011 | client.action_do("foo/0", "myaction", "param=5") | ||
1123 | 2012 | |||
1124 | 2013 | def test_action_fetch(self): | ||
1125 | 2014 | client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}), | ||
1126 | 2015 | '1.23-series-arch', None) | ||
1127 | 2016 | with patch.object(EnvJujuClient, 'get_juju_output') as mock: | ||
1128 | 2017 | ret = "status: completed\nfoo: bar" | ||
1129 | 2018 | mock.return_value = ret | ||
1130 | 2019 | out = client.action_fetch("123") | ||
1131 | 2020 | self.assertEqual(out, ret) | ||
1132 | 2021 | mock.assert_called_once_with( | ||
1133 | 2022 | 'action fetch', '123', "--wait", "1m" | ||
1134 | 2023 | ) | ||
1135 | 2024 | |||
1136 | 2025 | def test_action_fetch_timeout(self): | ||
1137 | 2026 | client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}), | ||
1138 | 2027 | '1.23-series-arch', None) | ||
1139 | 2028 | ret = "status: pending\nfoo: bar" | ||
1140 | 2029 | with patch.object(EnvJujuClient, | ||
1141 | 2030 | 'get_juju_output', return_value=ret): | ||
1142 | 2031 | with self.assertRaisesRegexp(Exception, | ||
1143 | 2032 | "timed out waiting for action"): | ||
1144 | 2033 | client.action_fetch("123") | ||
1145 | 2034 | |||
1146 | 2035 | def test_action_do_fetch(self): | ||
1147 | 2036 | client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}), | ||
1148 | 2037 | '1.23-series-arch', None) | ||
1149 | 2038 | with patch.object(EnvJujuClient, 'get_juju_output') as mock: | ||
1150 | 2039 | ret = "status: completed\nfoo: bar" | ||
1151 | 2040 | # setting side_effect to an iterable will return the next value | ||
1152 | 2041 | # from the list each time the function is called. | ||
1153 | 2042 | mock.side_effect = [ | ||
1154 | 2043 | "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9", | ||
1155 | 2044 | ret] | ||
1156 | 2045 | out = client.action_do_fetch("foo/0", "myaction", "param=5") | ||
1157 | 2046 | self.assertEqual(out, ret) | ||
1158 | 2047 | |||
1159 | 1968 | 2048 | ||
1160 | 1969 | class TestGroupReporter(TestCase): | 2049 | class TestGroupReporter(TestCase): |
1161 | 1970 | 2050 | ||
1162 | @@ -2099,3 +2179,53 @@ | |||
1163 | 2099 | ]) | 2179 | ]) |
1164 | 2100 | reporter.finish() | 2180 | reporter.finish() |
1165 | 2101 | self.assertEqual(sio.getvalue(), changes[-1] + "\n") | 2181 | self.assertEqual(sio.getvalue(), changes[-1] + "\n") |
1166 | 2182 | |||
1167 | 2183 | |||
1168 | 2184 | class TestMakeClient(TestCase): | ||
1169 | 2185 | |||
1170 | 2186 | @contextmanager | ||
1171 | 2187 | def make_client_cxt(self): | ||
1172 | 2188 | td = temp_dir() | ||
1173 | 2189 | te = temp_env({'environments': {'foo': { | ||
1174 | 2190 | 'orig-name': 'foo', 'name': 'foo'}}}) | ||
1175 | 2191 | with td as juju_path, te, patch('subprocess.Popen', | ||
1176 | 2192 | side_effect=ValueError): | ||
1177 | 2193 | with patch('subprocess.check_output') as co_mock: | ||
1178 | 2194 | co_mock.return_value = '1.18' | ||
1179 | 2195 | yield juju_path | ||
1180 | 2196 | |||
1181 | 2197 | def test_make_client(self): | ||
1182 | 2198 | with self.make_client_cxt() as juju_path: | ||
1183 | 2199 | client = make_client(juju_path, False, 'foo', 'bar') | ||
1184 | 2200 | self.assertEqual(client.full_path, os.path.join(juju_path, 'juju')) | ||
1185 | 2201 | self.assertEqual(client.debug, False) | ||
1186 | 2202 | self.assertEqual(client.env.config['orig-name'], 'foo') | ||
1187 | 2203 | self.assertEqual(client.env.config['name'], 'bar') | ||
1188 | 2204 | self.assertEqual(client.env.environment, 'bar') | ||
1189 | 2205 | |||
1190 | 2206 | def test_make_client_debug(self): | ||
1191 | 2207 | with self.make_client_cxt() as juju_path: | ||
1192 | 2208 | client = make_client(juju_path, True, 'foo', 'bar') | ||
1193 | 2209 | self.assertEqual(client.debug, True) | ||
1194 | 2210 | |||
1195 | 2211 | def test_make_client_no_temp_env_name(self): | ||
1196 | 2212 | with self.make_client_cxt() as juju_path: | ||
1197 | 2213 | client = make_client(juju_path, False, 'foo', None) | ||
1198 | 2214 | self.assertEqual(client.full_path, os.path.join(juju_path, 'juju')) | ||
1199 | 2215 | self.assertEqual(client.env.config['orig-name'], 'foo') | ||
1200 | 2216 | self.assertEqual(client.env.config['name'], 'foo') | ||
1201 | 2217 | self.assertEqual(client.env.environment, 'foo') | ||
1202 | 2218 | |||
1203 | 2219 | |||
1204 | 2220 | class AssessParseStateServerFromErrorTestCase(TestCase): | ||
1205 | 2221 | |||
1206 | 2222 | def test_parse_new_state_server_from_error(self): | ||
1207 | 2223 | output = dedent(""" | ||
1208 | 2224 | Waiting for address | ||
1209 | 2225 | Attempting to connect to 10.0.0.202:22 | ||
1210 | 2226 | Attempting to connect to 1.2.3.4:22 | ||
1211 | 2227 | The fingerprint for the ECDSA key sent by the remote host is | ||
1212 | 2228 | """) | ||
1213 | 2229 | error = subprocess.CalledProcessError(1, ['foo'], output) | ||
1214 | 2230 | address = parse_new_state_server_from_error(error) | ||
1215 | 2231 | self.assertEqual('1.2.3.4', address) |
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.