Merge lp:~mew/charm-helpers/refactor-to-core into lp:charm-helpers
- refactor-to-core
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 12 |
Proposed branch: | lp:~mew/charm-helpers/refactor-to-core |
Merge into: | lp:charm-helpers |
Diff against target: |
1381 lines (+395/-421) 14 files modified
README.test (+3/-0) charmhelpers/contrib/charmhelpers/__init__.py (+41/-135) charmhelpers/contrib/charmsupport/nrpe.py (+8/-2) charmhelpers/contrib/jujugui/utils.py (+9/-8) charmhelpers/core/hookenv.py (+60/-7) charmhelpers/core/host.py (+1/-1) charmhelpers/fetch/__init__.py (+46/-0) charmhelpers/payload/__init__.py (+1/-0) charmhelpers/payload/execd.py (+18/-13) tests/contrib/charmhelpers/test_charmhelpers.py (+0/-184) tests/contrib/jujugui/test_utils.py (+6/-10) tests/core/test_hookenv.py (+146/-41) tests/core/test_host.py (+2/-3) tests/payload/test_execd.py (+54/-17) |
To merge this branch: | bzr merge lp:~mew/charm-helpers/refactor-to-core |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francesco Banconi (community) | Approve | ||
Brad Crittenden (community) | code | Approve | |
Charm Helper Maintainers | Pending | ||
Review via email: mp+164980@code.launchpad.net |
Commit message
Refactor charmsupport and charmhelpers(
Description of the change
This doozy of an MP moves the bulk of library code into a main module structure:
core: low-level code for interacting with the charm environment
hookenv: work with the charm's juju environment
host: perform common configuration actions on the host
fetch: work with external sources such as apt, Launchpad, Github, and others
payload: work with charm payloads (code injected before deployment)
This release is NOT stable, as (for example) some code in host.py will eventually land in the fetch module.
Matthew Wedgwood (mew) wrote : | # |
Those client-only pieces don't really fit the objectives of charm-helpers to be a framework/library for use inside charms. I agree that they are useful, but they should live outside the project (probably in charm-tools).
- 20. By Matthew Wedgwood
-
[agy] Fix upstart detection, initscript tests, and README (for testing)
Francesco Banconi (frankban) wrote : | # |
This is a very nice incremental step Matthew, thank you.
The tests runs well.
"make lint" requires flake8 to be installed (I did it with "sudo pip install flake8").
There are several lint/pep8 failures, and I understand that fixing them is not the goal of this branch.
For this reason feel free to ignore the minors below.
267 + config,
268 + local_unit,
269 + log,
270 + relation_ids,
271 + relation_set,
272 + )
Minor: these lines could be dedented.
395 + for key in ('provides'
Missing whitespace after ','.
Preview Diff
1 | === modified file 'README.test' |
2 | --- README.test 2013-05-13 13:06:50 +0000 |
3 | +++ README.test 2013-05-23 20:50:31 +0000 |
4 | @@ -2,3 +2,6 @@ |
5 | ----------------------------------- |
6 | python-shelltoolbox |
7 | python-tempita |
8 | +python-nose |
9 | +python-mock |
10 | +python-testtools |
11 | |
12 | === modified file 'charmhelpers/contrib/charmhelpers/__init__.py' |
13 | --- charmhelpers/contrib/charmhelpers/__init__.py 2013-05-11 20:25:54 +0000 |
14 | +++ charmhelpers/contrib/charmhelpers/__init__.py 2013-05-23 20:50:31 +0000 |
15 | @@ -1,160 +1,60 @@ |
16 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
17 | # GNU Affero General Public License version 3 (see the file LICENSE). |
18 | |
19 | +import warnings |
20 | +warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning) |
21 | + |
22 | """Helper functions for writing Juju charms in Python.""" |
23 | |
24 | __metaclass__ = type |
25 | -__all__ = ['get_config', |
26 | - 'log', |
27 | - 'log_entry', |
28 | - 'log_exit', |
29 | - 'relation_get', |
30 | - 'relation_set', |
31 | - 'relation_ids', |
32 | - 'relation_list', |
33 | - 'config_get', |
34 | - 'unit_get', |
35 | - 'open_port', |
36 | - 'close_port', |
37 | - 'service_control', |
38 | - 'unit_info', |
39 | - 'wait_for_machine', |
40 | - 'wait_for_page_contents', |
41 | - 'wait_for_relation', |
42 | - 'wait_for_unit', |
43 | +__all__ = [ |
44 | + #'get_config', # core.hookenv.config() |
45 | + #'log', # core.hookenv.log() |
46 | + #'log_entry', # core.hookenv.log() |
47 | + #'log_exit', # core.hookenv.log() |
48 | + #'relation_get', # core.hookenv.relation_get() |
49 | + #'relation_set', # core.hookenv.relation_set() |
50 | + #'relation_ids', # core.hookenv.relation_ids() |
51 | + #'relation_list', # core.hookenv.relation_units() |
52 | + #'config_get', # core.hookenv.config() |
53 | + #'unit_get', # core.hookenv.unit_get() |
54 | + #'open_port', # core.hookenv.open_port() |
55 | + #'close_port', # core.hookenv.close_port() |
56 | + #'service_control', # core.host.service() |
57 | + 'unit_info', # client-side, NOT IMPLEMENTED |
58 | + 'wait_for_machine', # client-side, NOT IMPLEMENTED |
59 | + 'wait_for_page_contents', # client-side, NOT IMPLEMENTED |
60 | + 'wait_for_relation', # client-side, NOT IMPLEMENTED |
61 | + 'wait_for_unit', # client-side, NOT IMPLEMENTED |
62 | ] |
63 | |
64 | -from collections import namedtuple |
65 | -import json |
66 | import operator |
67 | from shelltoolbox import ( |
68 | command, |
69 | - script_name, |
70 | - run, |
71 | ) |
72 | import tempfile |
73 | import time |
74 | import urllib2 |
75 | import yaml |
76 | -from subprocess import CalledProcessError |
77 | - |
78 | |
79 | SLEEP_AMOUNT = 0.1 |
80 | -Env = namedtuple('Env', 'uid gid home') |
81 | # We create a juju_status Command here because it makes testing much, |
82 | # much easier. |
83 | juju_status = lambda: command('juju')('status') |
84 | |
85 | - |
86 | -def log(message, juju_log=command('juju-log')): |
87 | - return juju_log('--', message) |
88 | - |
89 | - |
90 | -def log_entry(): |
91 | - log("--> Entering {}".format(script_name())) |
92 | - |
93 | - |
94 | -def log_exit(): |
95 | - log("<-- Exiting {}".format(script_name())) |
96 | - |
97 | - |
98 | -def get_config(): |
99 | - _config_get = command('config-get', '--format=json') |
100 | - return json.loads(_config_get()) |
101 | - |
102 | - |
103 | -def relation_get(attribute=None, unit=None, rid=None): |
104 | - cmd = command('relation-get') |
105 | - if attribute is None and unit is None and rid is None: |
106 | - return cmd().strip() |
107 | - _args = [] |
108 | - if rid: |
109 | - _args.append('-r') |
110 | - _args.append(rid) |
111 | - if attribute is not None: |
112 | - _args.append(attribute) |
113 | - if unit: |
114 | - _args.append(unit) |
115 | - return cmd(*_args).strip() |
116 | - |
117 | - |
118 | -def relation_set(**kwargs): |
119 | - cmd = command('relation-set') |
120 | - args = ['{}={}'.format(k, v) for k, v in kwargs.items()] |
121 | - cmd(*args) |
122 | - |
123 | - |
124 | -def relation_ids(relation_name): |
125 | - cmd = command('relation-ids') |
126 | - args = [relation_name] |
127 | - return cmd(*args).split() |
128 | - |
129 | - |
130 | -def relation_list(rid=None): |
131 | - cmd = command('relation-list') |
132 | - args = [] |
133 | - if rid: |
134 | - args.append('-r') |
135 | - args.append(rid) |
136 | - return cmd(*args).split() |
137 | - |
138 | - |
139 | -def config_get(attribute): |
140 | - cmd = command('config-get') |
141 | - args = [attribute] |
142 | - return cmd(*args).strip() |
143 | - |
144 | - |
145 | -def unit_get(attribute): |
146 | - cmd = command('unit-get') |
147 | - args = [attribute] |
148 | - return cmd(*args).strip() |
149 | - |
150 | - |
151 | -def open_port(port, protocol="TCP"): |
152 | - cmd = command('open-port') |
153 | - args = ['{}/{}'.format(port, protocol)] |
154 | - cmd(*args) |
155 | - |
156 | - |
157 | -def close_port(port, protocol="TCP"): |
158 | - cmd = command('close-port') |
159 | - args = ['{}/{}'.format(port, protocol)] |
160 | - cmd(*args) |
161 | - |
162 | -START = "start" |
163 | -RESTART = "restart" |
164 | -STOP = "stop" |
165 | -RELOAD = "reload" |
166 | - |
167 | - |
168 | -def service_control(service_name, action): |
169 | - cmd = command('service') |
170 | - args = [service_name, action] |
171 | - try: |
172 | - if action == RESTART: |
173 | - try: |
174 | - cmd(*args) |
175 | - except CalledProcessError: |
176 | - service_control(service_name, START) |
177 | - else: |
178 | - cmd(*args) |
179 | - except CalledProcessError: |
180 | - log("Failed to perform {} on service {}".format(action, service_name)) |
181 | - |
182 | - |
183 | -def configure_source(update=False): |
184 | - source = config_get('source') |
185 | - if ((source.startswith('ppa:') or |
186 | - source.startswith('cloud:') or |
187 | - source.startswith('http:'))): |
188 | - run('add-apt-repository', source) |
189 | - if source.startswith("http:"): |
190 | - run('apt-key', 'import', config_get('key')) |
191 | - if update: |
192 | - run('apt-get', 'update') |
193 | - |
194 | - |
195 | +# re-implemented as charmhelpers.fetch.configure_sources() |
196 | +#def configure_source(update=False): |
197 | +# source = config_get('source') |
198 | +# if ((source.startswith('ppa:') or |
199 | +# source.startswith('cloud:') or |
200 | +# source.startswith('http:'))): |
201 | +# run('add-apt-repository', source) |
202 | +# if source.startswith("http:"): |
203 | +# run('apt-key', 'import', config_get('key')) |
204 | +# if update: |
205 | +# run('apt-get', 'update') |
206 | + |
207 | +# DEPRECATED: client-side only |
208 | def make_charm_config_file(charm_config): |
209 | charm_config_file = tempfile.NamedTemporaryFile() |
210 | charm_config_file.write(yaml.dump(charm_config)) |
211 | @@ -165,6 +65,7 @@ |
212 | return charm_config_file |
213 | |
214 | |
215 | +# DEPRECATED: client-side only |
216 | def unit_info(service_name, item_name, data=None, unit=None): |
217 | if data is None: |
218 | data = yaml.safe_load(juju_status()) |
219 | @@ -189,10 +90,12 @@ |
220 | return item |
221 | |
222 | |
223 | +# DEPRECATED: client-side only |
224 | def get_machine_data(): |
225 | return yaml.safe_load(juju_status())['machines'] |
226 | |
227 | |
228 | +# DEPRECATED: client-side only |
229 | def wait_for_machine(num_machines=1, timeout=300): |
230 | """Wait `timeout` seconds for `num_machines` machines to come up. |
231 | |
232 | @@ -232,6 +135,7 @@ |
233 | return num_machines, time.time() - start_time |
234 | |
235 | |
236 | +# DEPRECATED: client-side only |
237 | def wait_for_unit(service_name, timeout=480): |
238 | """Wait `timeout` seconds for a given service name to come up.""" |
239 | wait_for_machine(num_machines=1) |
240 | @@ -247,6 +151,7 @@ |
241 | raise RuntimeError('unit did not start, agent-state: ' + state) |
242 | |
243 | |
244 | +# DEPRECATED: client-side only |
245 | def wait_for_relation(service_name, relation_name, timeout=120): |
246 | """Wait `timeout` seconds for a given relation to come up.""" |
247 | start_time = time.time() |
248 | @@ -259,6 +164,7 @@ |
249 | time.sleep(SLEEP_AMOUNT) |
250 | |
251 | |
252 | +# DEPRECATED: client-side only |
253 | def wait_for_page_contents(url, contents, timeout=120, validate=None): |
254 | if validate is None: |
255 | validate = operator.contains |
256 | |
257 | === modified file 'charmhelpers/contrib/charmsupport/nrpe.py' |
258 | --- charmhelpers/contrib/charmsupport/nrpe.py 2013-05-11 20:05:13 +0000 |
259 | +++ charmhelpers/contrib/charmsupport/nrpe.py 2013-05-23 20:50:31 +0000 |
260 | @@ -12,8 +12,14 @@ |
261 | import shlex |
262 | import yaml |
263 | |
264 | -from hookenv import config, local_unit, log, relation_ids, relation_set |
265 | -from host import service |
266 | +from charmhelpers.core.hookenv import ( |
267 | + config, |
268 | + local_unit, |
269 | + log, |
270 | + relation_ids, |
271 | + relation_set, |
272 | + ) |
273 | +from charmhelpers.core.host import service |
274 | |
275 | # This module adds compatibility with the nrpe-external-master and plain nrpe |
276 | # subordinate charms. To use it in your charm: |
277 | |
278 | === modified file 'charmhelpers/contrib/jujugui/utils.py' |
279 | --- charmhelpers/contrib/jujugui/utils.py 2013-05-11 20:39:53 +0000 |
280 | +++ charmhelpers/contrib/jujugui/utils.py 2013-05-23 20:50:31 +0000 |
281 | @@ -64,11 +64,12 @@ |
282 | search_file, |
283 | su, |
284 | ) |
285 | -from charmhelpers.contrib.charmhelpers import ( |
286 | - START, |
287 | - get_config, |
288 | +from charmhelpers.core.host import ( |
289 | + service_start, |
290 | +) |
291 | +from charmhelpers.core.hookenv import ( |
292 | log, |
293 | - service_control, |
294 | + config, |
295 | unit_get, |
296 | ) |
297 | |
298 | @@ -263,9 +264,9 @@ |
299 | global results_log |
300 | if results_log is not None: |
301 | return |
302 | - config = get_config() |
303 | + cfg = config() |
304 | logging.basicConfig( |
305 | - filename=config['command-log-file'], |
306 | + filename=cfg['command-log-file'], |
307 | level=logging.INFO, |
308 | format="%(asctime)s: %(name)s@%(levelname)s %(message)s") |
309 | results_log = logging.getLogger('juju-gui') |
310 | @@ -295,7 +296,7 @@ |
311 | render_to_file('config/juju-api-improv.conf.template', context, config_path) |
312 | log('Starting the staging backend.') |
313 | with su('root'): |
314 | - service_control(IMPROV, START) |
315 | + service_start(IMPROV) |
316 | |
317 | |
318 | def start_agent( |
319 | @@ -317,7 +318,7 @@ |
320 | render_to_file('config/juju-api-agent.conf.template', context, config_path) |
321 | log('Starting API agent.') |
322 | with su('root'): |
323 | - service_control(AGENT, START) |
324 | + service_start(AGENT) |
325 | |
326 | |
327 | def start_gui( |
328 | |
329 | === added directory 'charmhelpers/core' |
330 | === added file 'charmhelpers/core/__init__.py' |
331 | === renamed file 'charmhelpers/contrib/charmsupport/hookenv.py' => 'charmhelpers/core/hookenv.py' |
332 | --- charmhelpers/contrib/charmsupport/hookenv.py 2013-05-11 20:24:29 +0000 |
333 | +++ charmhelpers/core/hookenv.py 2013-05-23 20:50:31 +0000 |
334 | @@ -1,8 +1,8 @@ |
335 | "Interactions with the Juju environment" |
336 | -# Copyright 2012 Canonical Ltd. |
337 | +# Copyright 2013 Canonical Ltd. |
338 | # |
339 | # Authors: |
340 | -# Matthew Wedgwood <matthew.wedgwood@canonical.com> |
341 | +# Charm Helpers Developers <juju@lists.ubuntu.com> |
342 | |
343 | import os |
344 | import json |
345 | @@ -62,8 +62,11 @@ |
346 | """A convenient bundling of the current execution context""" |
347 | context = {} |
348 | context['conf'] = config() |
349 | + context['reltype'] = relation_type() |
350 | + context['relid'] = relation_id() |
351 | context['unit'] = local_unit() |
352 | - context['rel'] = relations_of_type() |
353 | + context['rels'] = relations() |
354 | + context['rel'] = relation_get() |
355 | context['env'] = os.environ |
356 | return context |
357 | |
358 | @@ -112,11 +115,13 @@ |
359 | if rid: |
360 | _args.append('-r') |
361 | _args.append(rid) |
362 | - if attribute is not None: |
363 | - _args.append(attribute) |
364 | + _args.append(attribute or '-') |
365 | if unit: |
366 | _args.append(unit) |
367 | - return json.loads(subprocess.check_output(_args)) |
368 | + try: |
369 | + return json.loads(subprocess.check_output(_args)) |
370 | + except ValueError: |
371 | + return None |
372 | |
373 | |
374 | def relation_set(relation_id=None, **kwargs): |
375 | @@ -134,7 +139,8 @@ |
376 | relid_cmd_line = ['relation-ids', '--format=json'] |
377 | if reltype is not None: |
378 | relid_cmd_line.append(reltype) |
379 | - return json.loads(subprocess.check_output(relid_cmd_line)) |
380 | + return json.loads(subprocess.check_output(relid_cmd_line)) |
381 | + return [] |
382 | |
383 | |
384 | def related_units(relid=None): |
385 | @@ -179,6 +185,53 @@ |
386 | return relation_data |
387 | |
388 | |
389 | +def relation_types(): |
390 | + "Get a list of relation types supported by this charm" |
391 | + charmdir = os.environ.get('CHARM_DIR', '') |
392 | + mdf = open(os.path.join(charmdir, 'metadata.yaml')) |
393 | + md = yaml.safe_load(mdf) |
394 | + rel_types = [] |
395 | + for key in ('provides','requires','peers'): |
396 | + section = md.get(key) |
397 | + if section: |
398 | + rel_types.extend(section.keys()) |
399 | + mdf.close() |
400 | + return rel_types |
401 | + |
402 | + |
403 | +def relations(): |
404 | + rels = {} |
405 | + for reltype in relation_types(): |
406 | + relids = {} |
407 | + for relid in relation_ids(reltype): |
408 | + units = {} |
409 | + for unit in related_units(relid): |
410 | + reldata = relation_get(unit=unit, rid=relid) |
411 | + units[unit] = reldata |
412 | + relids[relid] = units |
413 | + rels[reltype] = relids |
414 | + return rels |
415 | + |
416 | + |
417 | +def open_port(port, protocol="TCP"): |
418 | + "Open a service network port" |
419 | + _args = ['open-port'] |
420 | + _args.append('{}/{}'.format(port, protocol)) |
421 | + subprocess.check_call(_args) |
422 | + |
423 | + |
424 | +def close_port(port, protocol="TCP"): |
425 | + "Close a service network port" |
426 | + _args = ['close-port'] |
427 | + _args.append('{}/{}'.format(port, protocol)) |
428 | + subprocess.check_call(_args) |
429 | + |
430 | + |
431 | +def unit_get(attribute): |
432 | + _args = ['unit-get', attribute] |
433 | + return subprocess.check_output(_args).strip() |
434 | + |
435 | + |
436 | class UnregisteredHookError(Exception): |
437 | pass |
438 | |
439 | |
440 | === renamed file 'charmhelpers/contrib/charmsupport/host.py' => 'charmhelpers/core/host.py' |
441 | --- charmhelpers/contrib/charmsupport/host.py 2013-05-11 20:05:13 +0000 |
442 | +++ charmhelpers/core/host.py 2013-05-23 20:50:31 +0000 |
443 | @@ -23,7 +23,7 @@ |
444 | |
445 | def service(action, service_name): |
446 | cmd = None |
447 | - if os.path.exists(os.path.join('/etc/init', service_name)): |
448 | + if os.path.exists(os.path.join('/etc/init', '%s.conf' % service_name)): |
449 | cmd = ['initctl', action, service_name] |
450 | elif os.path.exists(os.path.join('/etc/init.d', service_name)): |
451 | cmd = [os.path.join('/etc/init.d', service_name), action] |
452 | |
453 | === added directory 'charmhelpers/fetch' |
454 | === added file 'charmhelpers/fetch/__init__.py' |
455 | --- charmhelpers/fetch/__init__.py 1970-01-01 00:00:00 +0000 |
456 | +++ charmhelpers/fetch/__init__.py 2013-05-23 20:50:31 +0000 |
457 | @@ -0,0 +1,46 @@ |
458 | +from yaml import safe_load |
459 | +from core.hookenv import config_get |
460 | +from subprocess import check_call |
461 | + |
462 | + |
463 | +def add_source(source, key=None): |
464 | + if ((source.startswith('ppa:') or |
465 | + source.startswith('cloud:') or |
466 | + source.startswith('http:'))): |
467 | + check_call('add-apt-repository', source) |
468 | + if key: |
469 | + check_call('apt-key', 'import', key) |
470 | + |
471 | + |
472 | +class SourceConfigError(Exception): |
473 | + pass |
474 | + |
475 | + |
476 | +def configure_sources(update=False, |
477 | + sources_var='install_sources', |
478 | + keys_var='install_keys'): |
479 | + """ |
480 | + Configure multiple sources from charm configuration |
481 | + |
482 | + Example config: |
483 | + install_sources: |
484 | + - "ppa:foo" |
485 | + - "http://example.com/repo precise main" |
486 | + install_keys: |
487 | + - null |
488 | + - "a1b2c3d4" |
489 | + |
490 | + Note that 'null' (a.k.a. None) should not be quoted. |
491 | + """ |
492 | + sources = safe_load(config_get(sources_var)) |
493 | + keys = safe_load(config_get(keys_var)) |
494 | + if isinstance(sources, basestring) and isinstance(keys, basestring): |
495 | + add_source(sources, keys) |
496 | + else: |
497 | + if not len(sources) == len(keys): |
498 | + msg = 'Install sources and keys lists are different lengths' |
499 | + raise SourceConfigError(msg) |
500 | + for src_num in range(len(sources)): |
501 | + add_source(sources[src_num], sources[src_num]) |
502 | + if update: |
503 | + check_call(('apt-get', 'update')) |
504 | |
505 | === added directory 'charmhelpers/payload' |
506 | === added file 'charmhelpers/payload/__init__.py' |
507 | --- charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000 |
508 | +++ charmhelpers/payload/__init__.py 2013-05-23 20:50:31 +0000 |
509 | @@ -0,0 +1,1 @@ |
510 | +"Tools for working with files injected into a charm just before deployment." |
511 | |
512 | === renamed file 'charmhelpers/contrib/charmsupport/execd.py' => 'charmhelpers/payload/execd.py' |
513 | --- charmhelpers/contrib/charmsupport/execd.py 2013-05-11 20:05:13 +0000 |
514 | +++ charmhelpers/payload/execd.py 2013-05-23 20:50:31 +0000 |
515 | @@ -3,32 +3,37 @@ |
516 | import os |
517 | import sys |
518 | import subprocess |
519 | -import hookenv |
520 | +from charmhelpers.core import hookenv |
521 | |
522 | |
523 | def default_execd_dir(): |
524 | return os.path.join(os.environ['CHARM_DIR'],'exec.d') |
525 | |
526 | |
527 | -def execd_modules(execd_dir=None): |
528 | +def execd_module_paths(execd_dir=None): |
529 | if not execd_dir: |
530 | execd_dir = default_execd_dir() |
531 | for subpath in os.listdir(execd_dir): |
532 | module = os.path.join(execd_dir, subpath) |
533 | if os.path.isdir(module): |
534 | - yield module |
535 | - |
536 | - |
537 | -def execd_run(submodule, execd_dir=None, die_on_error=False): |
538 | - for module_path in execd_modules(execd_dir): |
539 | + yield module |
540 | + |
541 | + |
542 | +def execd_submodule_paths(submodule, execd_dir=None): |
543 | + for module_path in execd_module_paths(execd_dir): |
544 | path = os.path.join(module_path, submodule) |
545 | if os.access(path, os.X_OK) and os.path.isfile(path): |
546 | - try: |
547 | - subprocess.check_call(path, shell=True) |
548 | - except subprocess.CalledProcessError as e: |
549 | - hookenv.log(e.output) |
550 | - if die_on_error: |
551 | - sys.exit(e.returncode) |
552 | + yield path |
553 | + |
554 | + |
555 | +def execd_run(submodule, execd_dir=None, die_on_error=False): |
556 | + for submodule_path in execd_submodule_paths(submodule, execd_dir): |
557 | + try: |
558 | + subprocess.check_call(submodule_path, shell=True) |
559 | + except subprocess.CalledProcessError as e: |
560 | + hookenv.log(e.output) |
561 | + if die_on_error: |
562 | + sys.exit(e.returncode) |
563 | |
564 | |
565 | def execd_preinstall(execd_dir=None): |
566 | |
567 | === modified file 'tests/contrib/charmhelpers/test_charmhelpers.py' |
568 | --- tests/contrib/charmhelpers/test_charmhelpers.py 2013-05-11 20:30:34 +0000 |
569 | +++ tests/contrib/charmhelpers/test_charmhelpers.py 2013-05-23 20:50:31 +0000 |
570 | @@ -3,7 +3,6 @@ |
571 | import unittest |
572 | import yaml |
573 | |
574 | -from simplejson import dumps |
575 | from StringIO import StringIO |
576 | from testtools import TestCase |
577 | |
578 | @@ -14,8 +13,6 @@ |
579 | sys.path.insert(0, 'helpers/python') |
580 | from charmhelpers.contrib import charmhelpers |
581 | |
582 | -from subprocess import CalledProcessError |
583 | - |
584 | |
585 | class CharmHelpersTestCase(TestCase): |
586 | """A basic test case for Python charm helpers.""" |
587 | @@ -73,145 +70,6 @@ |
588 | self._make_juju_status_dict( |
589 | num_units, service_name, unit_state, machine_state)) |
590 | |
591 | - def test_get_config(self): |
592 | - # get_config returns the contents of the current charm |
593 | - # configuration, as returned by config-get --format=json. |
594 | - mock_config = {'key': 'value'} |
595 | - |
596 | - # Monkey-patch shelltoolbox.command to avoid having to call out |
597 | - # to config-get. |
598 | - self._patch_command(lambda: dumps(mock_config)) |
599 | - self.assertEqual(mock_config, charmhelpers.get_config()) |
600 | - |
601 | - def test_config_get(self): |
602 | - # config_get is used to retrieve individual configuration elements |
603 | - mock_config = {'key': 'value'} |
604 | - |
605 | - # Monkey-patch shelltoolbox.command to avoid having to call out |
606 | - # to config-get. |
607 | - self._patch_command(lambda *args: mock_config[args[0]]) |
608 | - self.assertEqual(mock_config['key'], charmhelpers.config_get('key')) |
609 | - |
610 | - def test_unit_get(self): |
611 | - # unit_get is used to retrieve individual configuration elements |
612 | - mock_config = {'key': 'value'} |
613 | - |
614 | - # Monkey-patch shelltoolbox.command to avoid having to call out |
615 | - # to unit-get. |
616 | - self._patch_command(lambda *args: mock_config[args[0]]) |
617 | - self.assertEqual(mock_config['key'], charmhelpers.unit_get('key')) |
618 | - |
619 | - def test_relation_get(self): |
620 | - # relation_get returns the value of a given relation variable, |
621 | - # as returned by relation-get $VAR. |
622 | - mock_relation_values = { |
623 | - 'foo': 'bar', |
624 | - 'spam': 'eggs'} |
625 | - self._patch_command(lambda *args: mock_relation_values[args[0]]) |
626 | - self.assertEqual('bar', charmhelpers.relation_get('foo')) |
627 | - self.assertEqual('eggs', charmhelpers.relation_get('spam')) |
628 | - |
629 | - self._patch_command(lambda *args: mock_relation_values[args[2]]) |
630 | - self.assertEqual('bar', |
631 | - charmhelpers.relation_get('foo', 'test', 'test:1')) |
632 | - self.assertEqual('eggs', |
633 | - charmhelpers.relation_get('spam', 'test', 'test:1')) |
634 | - |
635 | - self._patch_command(lambda *args: '%s' % mock_relation_values) |
636 | - self.assertEqual("{'foo': 'bar', 'spam': 'eggs'}", |
637 | - charmhelpers.relation_get()) |
638 | - |
639 | - def test_relation_set(self): |
640 | - # relation_set calls out to relation-set and passes key=value |
641 | - # pairs to it. |
642 | - items_set = {} |
643 | - |
644 | - def mock_relation_set(*args): |
645 | - for arg in args: |
646 | - key, value = arg.split("=") |
647 | - items_set[key] = value |
648 | - self._patch_command(mock_relation_set) |
649 | - charmhelpers.relation_set(foo='bar', spam='eggs') |
650 | - self.assertEqual('bar', items_set.get('foo')) |
651 | - self.assertEqual('eggs', items_set.get('spam')) |
652 | - |
653 | - def test_relation_ids(self): |
654 | - # relation_ids returns a list of relations id for the given |
655 | - # named relation |
656 | - mock_relation_ids = {'test': 'test:1 test:2'} |
657 | - self._patch_command(lambda *args: mock_relation_ids[args[0]]) |
658 | - self.assertEqual(mock_relation_ids['test'].split(), |
659 | - charmhelpers.relation_ids('test')) |
660 | - |
661 | - def test_relation_list(self): |
662 | - # relation_list returns a list of unit names either for the current |
663 | - # context or for the provided relation ID |
664 | - mock_unit_names = {'test:1': 'test/0 test/1 test/2', |
665 | - 'test:2': 'test/3 test/4 test/5'} |
666 | - |
667 | - # Patch command for current context use base - context = test:1 |
668 | - self._patch_command(lambda: mock_unit_names['test:1']) |
669 | - self.assertEqual(mock_unit_names['test:1'].split(), |
670 | - charmhelpers.relation_list()) |
671 | - # Patch command for provided relation-id |
672 | - self._patch_command(lambda *args: mock_unit_names[args[1]]) |
673 | - self.assertEqual(mock_unit_names['test:2'].split(), |
674 | - charmhelpers.relation_list(rid='test:2')) |
675 | - |
676 | - def test_open_close_port(self): |
677 | - # expose calls open-port with port/protocol parameters |
678 | - ports_set = [] |
679 | - |
680 | - def mock_open_port(*args): |
681 | - for arg in args: |
682 | - ports_set.append(arg) |
683 | - |
684 | - def mock_close_port(*args): |
685 | - if args[0] in ports_set: |
686 | - ports_set.remove(args[0]) |
687 | - # Monkey patch in the open-port mock |
688 | - self._patch_command(mock_open_port) |
689 | - charmhelpers.open_port(80, "TCP") |
690 | - charmhelpers.open_port(90, "UDP") |
691 | - charmhelpers.open_port(100) |
692 | - self.assertTrue("80/TCP" in ports_set) |
693 | - self.assertTrue("90/UDP" in ports_set) |
694 | - self.assertTrue("100/TCP" in ports_set) |
695 | - # Monkey patch in the close-port mock function |
696 | - self._patch_command(mock_close_port) |
697 | - charmhelpers.close_port(80, "TCP") |
698 | - charmhelpers.close_port(90, "UDP") |
699 | - charmhelpers.close_port(100) |
700 | - # ports_set should now be empty |
701 | - self.assertEquals(len(ports_set), 0) |
702 | - |
703 | - def test_service_control(self): |
704 | - # Collect commands that have been run |
705 | - commands_set = {} |
706 | - |
707 | - def mock_service(*args): |
708 | - service = args[0] |
709 | - action = args[1] |
710 | - if service not in commands_set: |
711 | - commands_set[service] = [] |
712 | - if ((len(commands_set[service]) > 1 and |
713 | - commands_set[service][-1] == 'stop' and |
714 | - action == 'restart')): |
715 | - # Service is stopped - so needs 'start' |
716 | - # action as restart will fail |
717 | - commands_set[service].append(action) |
718 | - raise CalledProcessError(1, repr(args)) |
719 | - else: |
720 | - commands_set[service].append(action) |
721 | - |
722 | - result = ['start', 'stop', 'restart', 'start'] |
723 | - |
724 | - # Monkey patch service command |
725 | - self._patch_command(mock_service) |
726 | - charmhelpers.service_control('myservice', 'start') |
727 | - charmhelpers.service_control('myservice', 'stop') |
728 | - charmhelpers.service_control('myservice', 'restart') |
729 | - self.assertEquals(result, commands_set['myservice']) |
730 | |
731 | def test_make_charm_config_file(self): |
732 | # make_charm_config_file() writes the passed configuration to a |
733 | @@ -428,48 +286,6 @@ |
734 | RuntimeError, charmhelpers.wait_for_page_contents, |
735 | 'http://example.com', "This will error", timeout=0) |
736 | |
737 | - def test_log(self): |
738 | - # The "log" function forwards a string on to the juju-log command. |
739 | - logged = [] |
740 | - |
741 | - def juju_log(*args): |
742 | - logged.append(args) |
743 | - charmhelpers.log('This is a log message', juju_log) |
744 | - # Since we only logged one message, juju-log was only called once.. |
745 | - self.assertEqual(len(logged), 1) |
746 | - # The message was included in the arguments passed to juju-log. |
747 | - self.assertIn('This is a log message', logged[0]) |
748 | - |
749 | - def test_log_escapes_message(self): |
750 | - # The Go version of juju-log interprets any string begining with two |
751 | - # hyphens ("--") as a command-line switch, even if the third character |
752 | - # is non-alphanumeric. This is different behavior than the Python |
753 | - # version of juju-log. Therefore we signfiy the end of options by |
754 | - # inserting the string " -- " just before the log message. |
755 | - logged = [] |
756 | - |
757 | - def juju_log(*args): |
758 | - logged.append(args) |
759 | - charmhelpers.log('This is a log message', juju_log) |
760 | - # The call to juju-log includes the " -- " string before the message. |
761 | - self.assertEqual([('--', 'This is a log message')], logged) |
762 | - |
763 | - def test_log_entry(self): |
764 | - # The log_entry function logs a message about the script starting. |
765 | - logged = [] |
766 | - self.patch(charmhelpers, 'log', logged.append) |
767 | - self.patch(charmhelpers, 'script_name', lambda: 'SCRIPT-NAME') |
768 | - charmhelpers.log_entry() |
769 | - self.assertEqual(['--> Entering SCRIPT-NAME'], logged) |
770 | - |
771 | - def test_log_exit(self): |
772 | - # The log_exit function logs a message about the script ending. |
773 | - logged = [] |
774 | - self.patch(charmhelpers, 'log', logged.append) |
775 | - self.patch(charmhelpers, 'script_name', lambda: 'SCRIPT-NAME') |
776 | - charmhelpers.log_exit() |
777 | - self.assertEqual(['<-- Exiting SCRIPT-NAME'], logged) |
778 | - |
779 | |
780 | if __name__ == '__main__': |
781 | unittest.main() |
782 | |
783 | === modified file 'tests/contrib/jujugui/test_utils.py' |
784 | --- tests/contrib/jujugui/test_utils.py 2013-05-11 20:39:53 +0000 |
785 | +++ tests/contrib/jujugui/test_utils.py 2013-05-23 20:50:31 +0000 |
786 | @@ -468,17 +468,17 @@ |
787 | class TestCmdLog(unittest.TestCase): |
788 | |
789 | def setUp(self): |
790 | - # Patch the charmhelpers 'command', which powers get_config. The |
791 | + # Patch the utils 'config', which powers get_config. The |
792 | # result of this is the mock_config dictionary will be returned. |
793 | # The monkey patch is undone in the tearDown. |
794 | - self.command = charmhelpers.command |
795 | + self.config = utils.config |
796 | fd, self.log_file_name = tempfile.mkstemp() |
797 | os.close(fd) |
798 | mock_config = {'command-log-file': self.log_file_name} |
799 | - charmhelpers.command = lambda *args: lambda: dumps(mock_config) |
800 | + utils.config = lambda *args: mock_config |
801 | |
802 | def tearDown(self): |
803 | - charmhelpers.command = self.command |
804 | + utils.config = self.config |
805 | os.unlink(self.log_file_name) |
806 | |
807 | def test_contents_logged(self): |
808 | @@ -491,16 +491,14 @@ |
809 | |
810 | def setUp(self): |
811 | self.service_names = [] |
812 | - self.actions = [] |
813 | self.svc_ctl_call_count = 0 |
814 | self.fake_zk_address = '192.168.5.26' |
815 | # Monkey patches. |
816 | self.command = charmhelpers.command |
817 | |
818 | - def service_control_mock(service_name, action): |
819 | + def service_start_mock(service_name): |
820 | self.svc_ctl_call_count += 1 |
821 | self.service_names.append(service_name) |
822 | - self.actions.append(action) |
823 | |
824 | def noop(*args): |
825 | pass |
826 | @@ -522,7 +520,7 @@ |
827 | self.files[os.path.basename(dest)] = fp.read() |
828 | |
829 | self.functions = dict( |
830 | - service_control=(utils.service_control, service_control_mock), |
831 | + service_start=(utils.service_start, service_start_mock), |
832 | log=(utils.log, noop), |
833 | su=(utils.su, su), |
834 | run=(utils.run, noop), |
835 | @@ -555,7 +553,6 @@ |
836 | self.assertTrue(self.ssl_cert_path in conf) |
837 | self.assertEqual(self.svc_ctl_call_count, 1) |
838 | self.assertEqual(self.service_names, ['juju-api-improv']) |
839 | - self.assertEqual(self.actions, [charmhelpers.START]) |
840 | |
841 | def test_start_agent(self): |
842 | start_agent(self.ssl_cert_path, 'config') |
843 | @@ -565,7 +562,6 @@ |
844 | self.assertTrue(self.ssl_cert_path in conf) |
845 | self.assertEqual(self.svc_ctl_call_count, 1) |
846 | self.assertEqual(self.service_names, ['juju-api-agent']) |
847 | - self.assertEqual(self.actions, [charmhelpers.START]) |
848 | |
849 | def test_start_gui(self): |
850 | ssl_cert_path = '/tmp/certificates/' |
851 | |
852 | === added directory 'tests/core' |
853 | === added file 'tests/core/__init__.py' |
854 | === renamed file 'tests/contrib/charmsupport/test_hookenv.py' => 'tests/core/test_hookenv.py' |
855 | --- tests/contrib/charmsupport/test_hookenv.py 2013-05-11 20:24:29 +0000 |
856 | +++ tests/core/test_hookenv.py 2013-05-23 20:50:31 +0000 |
857 | @@ -1,11 +1,26 @@ |
858 | import json |
859 | |
860 | -from mock import patch, call, MagicMock |
861 | +from mock import patch, call, mock_open |
862 | +from StringIO import StringIO |
863 | +from mock import MagicMock |
864 | from testtools import TestCase |
865 | import yaml |
866 | |
867 | -from charmhelpers.contrib.charmsupport import hookenv |
868 | +from charmhelpers.core import hookenv |
869 | |
870 | +CHARM_METADATA = """name: testmock |
871 | +summary: test mock summary |
872 | +description: test mock description |
873 | +requires: |
874 | + testreqs: |
875 | + interface: mock |
876 | +provides: |
877 | + testprov: |
878 | + interface: mock |
879 | +peers: |
880 | + testpeer: |
881 | + interface: mock |
882 | +""" |
883 | |
884 | class SerializableTest(TestCase): |
885 | def test_serializes_object_to_json(self): |
886 | @@ -105,14 +120,14 @@ |
887 | check_output.assert_called_with(['config-get', 'baz', '--format=json']) |
888 | |
889 | @patch('subprocess.check_output') |
890 | - @patch('charmhelpers.contrib.charmsupport.hookenv.log') |
891 | + @patch('charmhelpers.core.hookenv.log') |
892 | def test_logs_and_reraises_on_config_error(self, log, check_output): |
893 | error = 'some error' |
894 | check_output.side_effect = ValueError(error) |
895 | |
896 | self.assertRaisesRegexp(ValueError, error, hookenv.config) |
897 | |
898 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
899 | + @patch('charmhelpers.core.hookenv.os') |
900 | def test_gets_the_local_unit(self, os_): |
901 | os_.environ = { |
902 | 'JUJU_UNIT_NAME': 'foo', |
903 | @@ -120,7 +135,7 @@ |
904 | |
905 | self.assertEqual(hookenv.local_unit(), 'foo') |
906 | |
907 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
908 | + @patch('charmhelpers.core.hookenv.os') |
909 | def test_checks_that_is_running_in_relation_hook(self, os_): |
910 | os_.environ = { |
911 | 'JUJU_RELATION': 'foo', |
912 | @@ -128,7 +143,7 @@ |
913 | |
914 | self.assertTrue(hookenv.in_relation_hook()) |
915 | |
916 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
917 | + @patch('charmhelpers.core.hookenv.os') |
918 | def test_checks_that_is_not_running_in_relation_hook(self, os_): |
919 | os_.environ = { |
920 | 'bar': 'foo', |
921 | @@ -136,7 +151,7 @@ |
922 | |
923 | self.assertFalse(hookenv.in_relation_hook()) |
924 | |
925 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
926 | + @patch('charmhelpers.core.hookenv.os') |
927 | def test_gets_the_relation_type(self, os_): |
928 | os_.environ = { |
929 | 'JUJU_RELATION': 'foo', |
930 | @@ -144,13 +159,13 @@ |
931 | |
932 | self.assertEqual(hookenv.relation_type(), 'foo') |
933 | |
934 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
935 | + @patch('charmhelpers.core.hookenv.os') |
936 | def test_relation_type_none_if_not_in_environment(self, os_): |
937 | os_.environ = {} |
938 | self.assertEqual(hookenv.relation_type(), None) |
939 | |
940 | @patch('subprocess.check_output') |
941 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type') |
942 | + @patch('charmhelpers.core.hookenv.relation_type') |
943 | def test_gets_relation_ids(self, relation_type, check_output): |
944 | ids = [1, 2, 3] |
945 | check_output.return_value = json.dumps(ids) |
946 | @@ -164,7 +179,7 @@ |
947 | reltype]) |
948 | |
949 | @patch('subprocess.check_output') |
950 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type') |
951 | + @patch('charmhelpers.core.hookenv.relation_type') |
952 | def test_relation_ids_no_relation_type(self, relation_type, check_output): |
953 | ids = [1, 2, 3] |
954 | check_output.return_value = json.dumps(ids) |
955 | @@ -172,11 +187,10 @@ |
956 | |
957 | result = hookenv.relation_ids() |
958 | |
959 | - self.assertEqual(result, ids) |
960 | - check_output.assert_called_with(['relation-ids', '--format=json']) |
961 | + self.assertEqual(result, []) |
962 | |
963 | @patch('subprocess.check_output') |
964 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type') |
965 | + @patch('charmhelpers.core.hookenv.relation_type') |
966 | def test_gets_relation_ids_for_type(self, relation_type, check_output): |
967 | ids = [1, 2, 3] |
968 | check_output.return_value = json.dumps(ids) |
969 | @@ -190,7 +204,7 @@ |
970 | self.assertFalse(relation_type.called) |
971 | |
972 | @patch('subprocess.check_output') |
973 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id') |
974 | + @patch('charmhelpers.core.hookenv.relation_id') |
975 | def test_gets_related_units(self, relation_id, check_output): |
976 | relid = 123 |
977 | units = ['foo', 'bar'] |
978 | @@ -204,7 +218,7 @@ |
979 | '-r', relid]) |
980 | |
981 | @patch('subprocess.check_output') |
982 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id') |
983 | + @patch('charmhelpers.core.hookenv.relation_id') |
984 | def test_related_units_no_relation(self, relation_id, check_output): |
985 | units = ['foo', 'bar'] |
986 | relation_id.return_value = None |
987 | @@ -216,7 +230,7 @@ |
988 | check_output.assert_called_with(['relation-list', '--format=json']) |
989 | |
990 | @patch('subprocess.check_output') |
991 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id') |
992 | + @patch('charmhelpers.core.hookenv.relation_id') |
993 | def test_gets_related_units_for_id(self, relation_id, check_output): |
994 | relid = 123 |
995 | units = ['foo', 'bar'] |
996 | @@ -229,7 +243,7 @@ |
997 | '-r', relid]) |
998 | self.assertFalse(relation_id.called) |
999 | |
1000 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
1001 | + @patch('charmhelpers.core.hookenv.os') |
1002 | def test_gets_the_remote_unit(self, os_): |
1003 | os_.environ = { |
1004 | 'JUJU_REMOTE_UNIT': 'foo', |
1005 | @@ -237,8 +251,8 @@ |
1006 | |
1007 | self.assertEqual(hookenv.remote_unit(), 'foo') |
1008 | |
1009 | - @patch('charmhelpers.contrib.charmsupport.hookenv.remote_unit') |
1010 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_get') |
1011 | + @patch('charmhelpers.core.hookenv.remote_unit') |
1012 | + @patch('charmhelpers.core.hookenv.relation_get') |
1013 | def test_gets_relation_for_unit(self, relation_get, remote_unit): |
1014 | unit = 'foo-unit' |
1015 | raw_relation = { |
1016 | @@ -254,8 +268,8 @@ |
1017 | self.assertEqual(getattr(result, 'baz-list'), ['1', '2', '3']) |
1018 | relation_get.assert_called_with(unit=unit, rid=None) |
1019 | |
1020 | - @patch('charmhelpers.contrib.charmsupport.hookenv.remote_unit') |
1021 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_get') |
1022 | + @patch('charmhelpers.core.hookenv.remote_unit') |
1023 | + @patch('charmhelpers.core.hookenv.relation_get') |
1024 | def test_gets_relation_for_specific_unit(self, relation_get, remote_unit): |
1025 | unit = 'foo-unit' |
1026 | raw_relation = { |
1027 | @@ -271,9 +285,9 @@ |
1028 | relation_get.assert_called_with(unit=unit, rid=None) |
1029 | self.assertFalse(remote_unit.called) |
1030 | |
1031 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids') |
1032 | - @patch('charmhelpers.contrib.charmsupport.hookenv.related_units') |
1033 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_for_unit') |
1034 | + @patch('charmhelpers.core.hookenv.relation_ids') |
1035 | + @patch('charmhelpers.core.hookenv.related_units') |
1036 | + @patch('charmhelpers.core.hookenv.relation_for_unit') |
1037 | def test_gets_relations_for_id(self, relation_for_unit, related_units, |
1038 | relation_ids): |
1039 | relid = 123 |
1040 | @@ -298,9 +312,9 @@ |
1041 | call('bar', relid), |
1042 | ]) |
1043 | |
1044 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids') |
1045 | - @patch('charmhelpers.contrib.charmsupport.hookenv.related_units') |
1046 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_for_unit') |
1047 | + @patch('charmhelpers.core.hookenv.relation_ids') |
1048 | + @patch('charmhelpers.core.hookenv.related_units') |
1049 | + @patch('charmhelpers.core.hookenv.relation_for_unit') |
1050 | def test_gets_relations_for_specific_id(self, relation_for_unit, |
1051 | related_units, relation_ids): |
1052 | relid = 123 |
1053 | @@ -325,10 +339,10 @@ |
1054 | ]) |
1055 | self.assertFalse(relation_ids.called) |
1056 | |
1057 | - @patch('charmhelpers.contrib.charmsupport.hookenv.in_relation_hook') |
1058 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type') |
1059 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids') |
1060 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relations_for_id') |
1061 | + @patch('charmhelpers.core.hookenv.in_relation_hook') |
1062 | + @patch('charmhelpers.core.hookenv.relation_type') |
1063 | + @patch('charmhelpers.core.hookenv.relation_ids') |
1064 | + @patch('charmhelpers.core.hookenv.relations_for_id') |
1065 | def test_gets_relations_for_type(self, relations_for_id, relation_ids, |
1066 | relation_type, in_relation_hook): |
1067 | reltype = 'foo-type' |
1068 | @@ -366,27 +380,66 @@ |
1069 | call(234), |
1070 | ]) |
1071 | |
1072 | - @patch('charmhelpers.contrib.charmsupport.hookenv.config') |
1073 | - @patch('charmhelpers.contrib.charmsupport.hookenv.local_unit') |
1074 | - @patch('charmhelpers.contrib.charmsupport.hookenv.relations_of_type') |
1075 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
1076 | - def test_gets_execution_environment(self, os_, relations_of_type, |
1077 | - local_unit, config): |
1078 | + @patch('charmhelpers.core.hookenv.relation_types') |
1079 | + @patch('charmhelpers.core.hookenv.relation_ids') |
1080 | + @patch('charmhelpers.core.hookenv.related_units') |
1081 | + @patch('charmhelpers.core.hookenv.relation_get') |
1082 | + def test_gets_relations(self, relation_get, related_units, |
1083 | + relation_ids, relation_types): |
1084 | + relation_types.return_value = ['t1','t2'] |
1085 | + relation_ids.return_value = ['i1'] |
1086 | + related_units.return_value = ['u1','u2'] |
1087 | + relation_get.return_value = {'key': 'val'} |
1088 | + |
1089 | + result = hookenv.relations() |
1090 | + |
1091 | + self.assertEqual(result, { |
1092 | + 't1': { |
1093 | + 'i1': { |
1094 | + 'u1': {'key': 'val'}, |
1095 | + 'u2': {'key': 'val'}, |
1096 | + }, |
1097 | + }, |
1098 | + 't2': { |
1099 | + 'i1': { |
1100 | + 'u1': {'key': 'val'}, |
1101 | + 'u2': {'key': 'val'}, |
1102 | + }, |
1103 | + }, |
1104 | + }) |
1105 | + |
1106 | + |
1107 | + @patch('charmhelpers.core.hookenv.config') |
1108 | + @patch('charmhelpers.core.hookenv.relation_type') |
1109 | + @patch('charmhelpers.core.hookenv.local_unit') |
1110 | + @patch('charmhelpers.core.hookenv.relation_id') |
1111 | + @patch('charmhelpers.core.hookenv.relations') |
1112 | + @patch('charmhelpers.core.hookenv.relation_get') |
1113 | + @patch('charmhelpers.core.hookenv.os') |
1114 | + def test_gets_execution_environment(self, os_, relations_get, |
1115 | + relations, relation_id, local_unit, |
1116 | + relation_type, config): |
1117 | config.return_value = 'some-config' |
1118 | + relation_type.return_value = 'some-type' |
1119 | local_unit.return_value = 'some-unit' |
1120 | - relations_of_type.return_value = 'some-relations' |
1121 | + relation_id.return_value = 'some-id' |
1122 | + relations.return_value = 'all-relations' |
1123 | + relations_get.return_value = 'some-relations' |
1124 | os_.environ = 'some-environment' |
1125 | |
1126 | result = hookenv.execution_environment() |
1127 | |
1128 | self.assertEqual(result, { |
1129 | 'conf': 'some-config', |
1130 | + 'reltype': 'some-type', |
1131 | 'unit': 'some-unit', |
1132 | + 'relid': 'some-id', |
1133 | 'rel': 'some-relations', |
1134 | + 'rels': 'all-relations', |
1135 | 'env': 'some-environment', |
1136 | }) |
1137 | |
1138 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
1139 | + @patch('charmhelpers.core.hookenv.os') |
1140 | def test_gets_the_relation_id(self, os_): |
1141 | os_.environ = { |
1142 | 'JUJU_RELATION_ID': 'foo', |
1143 | @@ -394,7 +447,7 @@ |
1144 | |
1145 | self.assertEqual(hookenv.relation_id(), 'foo') |
1146 | |
1147 | - @patch('charmhelpers.contrib.charmsupport.hookenv.os') |
1148 | + @patch('charmhelpers.core.hookenv.os') |
1149 | def test_relation_id_none_if_no_env(self, os_): |
1150 | os_.environ = {} |
1151 | self.assertEqual(hookenv.relation_id(), None) |
1152 | @@ -407,7 +460,7 @@ |
1153 | result = hookenv.relation_get() |
1154 | |
1155 | self.assertEqual(result['foo'], 'BAR') |
1156 | - check_output.assert_called_with(['relation-get', '--format=json']) |
1157 | + check_output.assert_called_with(['relation-get', '--format=json', '-']) |
1158 | |
1159 | @patch('subprocess.check_output') |
1160 | def test_gets_relation_with_scope(self, check_output): |
1161 | @@ -443,6 +496,57 @@ |
1162 | check_output.assert_called_with(['relation-get', '--format=json', '-r', |
1163 | 123, 'baz-scope', 'baz-unit']) |
1164 | |
1165 | + @patch('subprocess.check_call') |
1166 | + def test_sets_relation(self, check_call_): |
1167 | + hookenv.relation_set(foo="bar") |
1168 | + check_call_.assert_called_with(['relation-set','foo=bar']) |
1169 | + |
1170 | + |
1171 | + @patch('subprocess.check_call') |
1172 | + def test_sets_relation_with_relation_id(self, check_call_): |
1173 | + hookenv.relation_set(relation_id="foo", bar="baz") |
1174 | + check_call_.assert_called_with(['relation-set', '-r', 'foo', |
1175 | + 'bar=baz']) |
1176 | + |
1177 | + |
1178 | + def test_lists_relation_types(self): |
1179 | + open_ = mock_open() |
1180 | + open_.return_value = StringIO(CHARM_METADATA) |
1181 | + with patch('charmhelpers.core.hookenv.open', open_, create=True): |
1182 | + with patch.dict('os.environ', {'CHARM_DIR': '/var/empty'}): |
1183 | + reltypes = set(hookenv.relation_types()) |
1184 | + open_.assert_called_once_with('/var/empty/metadata.yaml') |
1185 | + self.assertEqual(set(('testreqs','testprov','testpeer')), reltypes) |
1186 | + |
1187 | + |
1188 | + @patch('subprocess.check_call') |
1189 | + def test_opens_port(self, check_call_): |
1190 | + hookenv.open_port(443, "TCP") |
1191 | + hookenv.open_port(80) |
1192 | + hookenv.open_port(100, "UDP") |
1193 | + calls = [call(['open-port','443/TCP']), |
1194 | + call(['open-port','80/TCP']), |
1195 | + call(['open-port','100/UDP']), |
1196 | + ] |
1197 | + check_call_.assert_has_calls(calls) |
1198 | + |
1199 | + |
1200 | + @patch('subprocess.check_call') |
1201 | + def test_closes_port(self, check_call_): |
1202 | + hookenv.close_port(443, "TCP") |
1203 | + hookenv.close_port(80) |
1204 | + hookenv.close_port(100, "UDP") |
1205 | + calls = [call(['close-port','443/TCP']), |
1206 | + call(['close-port','80/TCP']), |
1207 | + call(['close-port','100/UDP']), |
1208 | + ] |
1209 | + check_call_.assert_has_calls(calls) |
1210 | + |
1211 | + @patch('subprocess.check_output') |
1212 | + def test_gets_unit_attribute(self, check_output_): |
1213 | + hookenv.unit_get('foo') |
1214 | + check_output_.assert_called_with(['unit-get', 'foo']) |
1215 | + |
1216 | |
1217 | class HooksTest(TestCase): |
1218 | def test_runs_a_registered_function(self): |
1219 | @@ -488,3 +592,4 @@ |
1220 | self.assertRaises(hookenv.UnregisteredHookError, hooks.execute, |
1221 | ['brew']) |
1222 | self.assertEqual(execs, [True]) |
1223 | + |
1224 | |
1225 | === renamed file 'tests/contrib/charmsupport/test_host.py' => 'tests/core/test_host.py' |
1226 | --- tests/contrib/charmsupport/test_host.py 2013-05-11 20:24:29 +0000 |
1227 | +++ tests/core/test_host.py 2013-05-23 20:50:31 +0000 |
1228 | @@ -5,7 +5,7 @@ |
1229 | from mock import patch, call, MagicMock |
1230 | from testtools import TestCase |
1231 | |
1232 | -from charmhelpers.contrib.charmsupport import host |
1233 | +from charmhelpers.core import host |
1234 | |
1235 | |
1236 | MOUNT_LINES = (""" |
1237 | @@ -52,7 +52,7 @@ |
1238 | self.assertTrue(result) |
1239 | mock_call.assert_called_with(['initctl', action, service_name]) |
1240 | exists.assert_has_calls([ |
1241 | - call(os.path.join('/etc/init', service_name)), |
1242 | + call(os.path.join('/etc/init', '%s.conf' % service_name)), |
1243 | ]) |
1244 | |
1245 | @patch('subprocess.call') |
1246 | @@ -71,7 +71,6 @@ |
1247 | mock_call.assert_called_with([os.path.join('/etc/init.d', |
1248 | service_name), action]) |
1249 | exists.assert_has_calls([ |
1250 | - call(os.path.join('/etc/init', service_name)), |
1251 | call(os.path.join('/etc/init.d', service_name)), |
1252 | ]) |
1253 | |
1254 | |
1255 | === added directory 'tests/payload' |
1256 | === added file 'tests/payload/__init__.py' |
1257 | === renamed file 'tests/contrib/charmsupport/test_execd.py' => 'tests/payload/test_execd.py' |
1258 | --- tests/contrib/charmsupport/test_execd.py 2013-05-11 20:24:29 +0000 |
1259 | +++ tests/payload/test_execd.py 2013-05-23 20:50:31 +0000 |
1260 | @@ -2,10 +2,11 @@ |
1261 | from mock import patch, call |
1262 | import os |
1263 | import shutil |
1264 | +from subprocess import CalledProcessError |
1265 | |
1266 | from tempfile import mkdtemp |
1267 | |
1268 | -from charmhelpers.contrib.charmsupport import execd |
1269 | +from charmhelpers.payload import execd |
1270 | |
1271 | class ExecDBaseTestCase(TestCase): |
1272 | def setUp(self): |
1273 | @@ -30,7 +31,7 @@ |
1274 | |
1275 | self.assertEqual(expected, default_dir) |
1276 | |
1277 | - @patch('charmhelpers.contrib.charmsupport.execd.execd_run') |
1278 | + @patch('charmhelpers.payload.execd.execd_run') |
1279 | def test_execd_preinstall_calls_charm_pre_install(self, mock_execd_run): |
1280 | execd_dir = 'testdir' |
1281 | execd.execd_preinstall(execd_dir) |
1282 | @@ -38,7 +39,7 @@ |
1283 | mock_execd_run.assert_called_with(execd_dir, 'charm-pre-install') |
1284 | |
1285 | |
1286 | - @patch('charmhelpers.contrib.charmsupport.execd.default_execd_dir', return_value='foo') |
1287 | + @patch('charmhelpers.payload.execd.default_execd_dir', return_value='foo') |
1288 | @patch('os.listdir', return_value=['a','b','c']) |
1289 | @patch('os.path.isdir', return_value=True) |
1290 | def test_execd_module_list_from_env(self, mock_isdir, mock_listdir, |
1291 | @@ -46,7 +47,7 @@ |
1292 | module_names = ['a','b','c'] |
1293 | mock_listdir.return_value = module_names |
1294 | |
1295 | - modules = list(execd.execd_modules()) |
1296 | + modules = list(execd.execd_module_paths()) |
1297 | |
1298 | expected = [os.path.join('foo', d) for d in module_names] |
1299 | self.assertEqual(modules, expected) |
1300 | @@ -55,7 +56,7 @@ |
1301 | mock_isdir.assert_has_calls([call(d) for d in expected]) |
1302 | |
1303 | |
1304 | - @patch('charmhelpers.contrib.charmsupport.execd.default_execd_dir') |
1305 | + @patch('charmhelpers.payload.execd.default_execd_dir') |
1306 | @patch('os.listdir', return_value=['a','b','c']) |
1307 | @patch('os.path.isdir', return_value=True) |
1308 | def test_execd_module_list_with_dir(self, mock_isdir, mock_listdir, |
1309 | @@ -63,7 +64,7 @@ |
1310 | module_names = ['a','b','c'] |
1311 | mock_listdir.return_value = module_names |
1312 | |
1313 | - modules = list(execd.execd_modules('foo')) |
1314 | + modules = list(execd.execd_module_paths('foo')) |
1315 | |
1316 | expected = [os.path.join('foo', d) for d in module_names] |
1317 | self.assertEqual(modules, expected) |
1318 | @@ -73,16 +74,52 @@ |
1319 | mock_isdir.assert_has_calls([call(d) for d in expected]) |
1320 | |
1321 | |
1322 | - @patch('subprocess.check_call') |
1323 | - @patch('charmhelpers.contrib.charmsupport.hookenv.log') |
1324 | @patch('os.path.isfile', return_value=True) |
1325 | @patch('os.access', return_value=True) |
1326 | - @patch('charmhelpers.contrib.charmsupport.execd.execd_modules', return_value=['a','b']) |
1327 | - def test_execd_run(self, mock_modules, mock_access, mock_isfile, |
1328 | - mock_log, mock_call): |
1329 | - submodule = 'charm-foo' |
1330 | - |
1331 | - execd.execd_run(submodule) |
1332 | - |
1333 | - paths = [os.path.join(m, submodule) for m in ('a','b')] |
1334 | - mock_call.assert_has_calls([call(d, shell=True) for d in paths]) |
1335 | + @patch('charmhelpers.payload.execd.execd_module_paths') |
1336 | + def test_execd_submodule_list(self, modlist_, access_, isfile_): |
1337 | + module_list = ['a','b','c'] |
1338 | + modlist_.return_value = module_list |
1339 | + submodules = [s for s in execd.execd_submodule_paths('sm')] |
1340 | + |
1341 | + expected = [os.path.join(d, 'sm') for d in module_list] |
1342 | + self.assertEqual(submodules, expected) |
1343 | + |
1344 | + |
1345 | + @patch('subprocess.check_call') |
1346 | + @patch('charmhelpers.payload.execd.execd_submodule_paths') |
1347 | + def test_execd_run(self, submods_, call_): |
1348 | + submod_list = ['a','b','c'] |
1349 | + submods_.return_value = submod_list |
1350 | + execd.execd_run('foo') |
1351 | + |
1352 | + submods_.assert_called_with('foo', None) |
1353 | + call_.assert_has_calls([call(d, shell=True) for d in submod_list]) |
1354 | + |
1355 | + |
1356 | + @patch('subprocess.check_call') |
1357 | + @patch('charmhelpers.payload.execd.execd_submodule_paths') |
1358 | + @patch('charmhelpers.core.hookenv.log') |
1359 | + def test_execd_run_logs_exception(self, log_, submods_, check_call_): |
1360 | + submod_list = ['a','b','c'] |
1361 | + submods_.return_value = submod_list |
1362 | + err_msg = 'darn' |
1363 | + check_call_.side_effect = CalledProcessError(1, 'cmd', err_msg) |
1364 | + |
1365 | + execd.execd_run('foo') |
1366 | + log_.assert_called_with(err_msg) |
1367 | + |
1368 | + |
1369 | + @patch('subprocess.check_call') |
1370 | + @patch('charmhelpers.payload.execd.execd_submodule_paths') |
1371 | + @patch('charmhelpers.core.hookenv.log') |
1372 | + @patch('sys.exit') |
1373 | + def test_execd_run_dies_with_return_code(self, exit_, log_, submods_, |
1374 | + check_call_): |
1375 | + submod_list = ['a','b','c'] |
1376 | + submods_.return_value = submod_list |
1377 | + retcode = 9 |
1378 | + check_call_.side_effect = CalledProcessError(retcode, 'cmd') |
1379 | + |
1380 | + execd.execd_run('foo', die_on_error=True) |
1381 | + exit_.assert_called_with(retcode) |
Hi Matthew,
Thanks for doing this reorganization. Lots of work and gets the project moving.
I couldn't get 'make test' to run until I installed
python-nose
python-mock
python-testtools
Could you add those to README.test?
The temporary mapping in __ALL__ for stuff moved from charmhelpers/ __init_ _.py is a nice touch.
A lot of the things you marked 'Deprecated: client-only' are functions that we used in testing our charms. They are actually quite nice. I'd like to see the 'Deprecation' tag removed and try to find a place to put them. If you'd like to see them in use have a look at the buildbot-master charm.
Overall I like this direction.