Merge lp:~mew/charm-helpers/refactor-to-core into lp:charm-helpers

Proposed by Matthew Wedgwood
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
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(legacy) code in to core modules for 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.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

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.

review: Approve (code)
Revision history for this message
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)

Revision history for this message
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','requires','peers'):
Missing whitespace after ','.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.test'
--- README.test 2013-05-13 13:06:50 +0000
+++ README.test 2013-05-23 20:50:31 +0000
@@ -2,3 +2,6 @@
2-----------------------------------2-----------------------------------
3python-shelltoolbox3python-shelltoolbox
4python-tempita4python-tempita
5python-nose
6python-mock
7python-testtools
58
=== modified file 'charmhelpers/contrib/charmhelpers/__init__.py'
--- charmhelpers/contrib/charmhelpers/__init__.py 2013-05-11 20:25:54 +0000
+++ charmhelpers/contrib/charmhelpers/__init__.py 2013-05-23 20:50:31 +0000
@@ -1,160 +1,60 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import warnings
5warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning)
6
4"""Helper functions for writing Juju charms in Python."""7"""Helper functions for writing Juju charms in Python."""
58
6__metaclass__ = type9__metaclass__ = type
7__all__ = ['get_config',10__all__ = [
8 'log',11 #'get_config', # core.hookenv.config()
9 'log_entry',12 #'log', # core.hookenv.log()
10 'log_exit',13 #'log_entry', # core.hookenv.log()
11 'relation_get',14 #'log_exit', # core.hookenv.log()
12 'relation_set',15 #'relation_get', # core.hookenv.relation_get()
13 'relation_ids',16 #'relation_set', # core.hookenv.relation_set()
14 'relation_list',17 #'relation_ids', # core.hookenv.relation_ids()
15 'config_get',18 #'relation_list', # core.hookenv.relation_units()
16 'unit_get',19 #'config_get', # core.hookenv.config()
17 'open_port',20 #'unit_get', # core.hookenv.unit_get()
18 'close_port',21 #'open_port', # core.hookenv.open_port()
19 'service_control',22 #'close_port', # core.hookenv.close_port()
20 'unit_info',23 #'service_control', # core.host.service()
21 'wait_for_machine',24 'unit_info', # client-side, NOT IMPLEMENTED
22 'wait_for_page_contents',25 'wait_for_machine', # client-side, NOT IMPLEMENTED
23 'wait_for_relation',26 'wait_for_page_contents', # client-side, NOT IMPLEMENTED
24 'wait_for_unit',27 'wait_for_relation', # client-side, NOT IMPLEMENTED
28 'wait_for_unit', # client-side, NOT IMPLEMENTED
25 ]29 ]
2630
27from collections import namedtuple
28import json
29import operator31import operator
30from shelltoolbox import (32from shelltoolbox import (
31 command,33 command,
32 script_name,
33 run,
34)34)
35import tempfile35import tempfile
36import time36import time
37import urllib237import urllib2
38import yaml38import yaml
39from subprocess import CalledProcessError
40
4139
42SLEEP_AMOUNT = 0.140SLEEP_AMOUNT = 0.1
43Env = namedtuple('Env', 'uid gid home')
44# We create a juju_status Command here because it makes testing much,41# We create a juju_status Command here because it makes testing much,
45# much easier.42# much easier.
46juju_status = lambda: command('juju')('status')43juju_status = lambda: command('juju')('status')
4744
4845# re-implemented as charmhelpers.fetch.configure_sources()
49def log(message, juju_log=command('juju-log')):46#def configure_source(update=False):
50 return juju_log('--', message)47# source = config_get('source')
5148# if ((source.startswith('ppa:') or
5249# source.startswith('cloud:') or
53def log_entry():50# source.startswith('http:'))):
54 log("--> Entering {}".format(script_name()))51# run('add-apt-repository', source)
5552# if source.startswith("http:"):
5653# run('apt-key', 'import', config_get('key'))
57def log_exit():54# if update:
58 log("<-- Exiting {}".format(script_name()))55# run('apt-get', 'update')
5956
6057# DEPRECATED: client-side only
61def get_config():
62 _config_get = command('config-get', '--format=json')
63 return json.loads(_config_get())
64
65
66def relation_get(attribute=None, unit=None, rid=None):
67 cmd = command('relation-get')
68 if attribute is None and unit is None and rid is None:
69 return cmd().strip()
70 _args = []
71 if rid:
72 _args.append('-r')
73 _args.append(rid)
74 if attribute is not None:
75 _args.append(attribute)
76 if unit:
77 _args.append(unit)
78 return cmd(*_args).strip()
79
80
81def relation_set(**kwargs):
82 cmd = command('relation-set')
83 args = ['{}={}'.format(k, v) for k, v in kwargs.items()]
84 cmd(*args)
85
86
87def relation_ids(relation_name):
88 cmd = command('relation-ids')
89 args = [relation_name]
90 return cmd(*args).split()
91
92
93def relation_list(rid=None):
94 cmd = command('relation-list')
95 args = []
96 if rid:
97 args.append('-r')
98 args.append(rid)
99 return cmd(*args).split()
100
101
102def config_get(attribute):
103 cmd = command('config-get')
104 args = [attribute]
105 return cmd(*args).strip()
106
107
108def unit_get(attribute):
109 cmd = command('unit-get')
110 args = [attribute]
111 return cmd(*args).strip()
112
113
114def open_port(port, protocol="TCP"):
115 cmd = command('open-port')
116 args = ['{}/{}'.format(port, protocol)]
117 cmd(*args)
118
119
120def close_port(port, protocol="TCP"):
121 cmd = command('close-port')
122 args = ['{}/{}'.format(port, protocol)]
123 cmd(*args)
124
125START = "start"
126RESTART = "restart"
127STOP = "stop"
128RELOAD = "reload"
129
130
131def service_control(service_name, action):
132 cmd = command('service')
133 args = [service_name, action]
134 try:
135 if action == RESTART:
136 try:
137 cmd(*args)
138 except CalledProcessError:
139 service_control(service_name, START)
140 else:
141 cmd(*args)
142 except CalledProcessError:
143 log("Failed to perform {} on service {}".format(action, service_name))
144
145
146def configure_source(update=False):
147 source = config_get('source')
148 if ((source.startswith('ppa:') or
149 source.startswith('cloud:') or
150 source.startswith('http:'))):
151 run('add-apt-repository', source)
152 if source.startswith("http:"):
153 run('apt-key', 'import', config_get('key'))
154 if update:
155 run('apt-get', 'update')
156
157
158def make_charm_config_file(charm_config):58def make_charm_config_file(charm_config):
159 charm_config_file = tempfile.NamedTemporaryFile()59 charm_config_file = tempfile.NamedTemporaryFile()
160 charm_config_file.write(yaml.dump(charm_config))60 charm_config_file.write(yaml.dump(charm_config))
@@ -165,6 +65,7 @@
165 return charm_config_file65 return charm_config_file
16666
16767
68# DEPRECATED: client-side only
168def unit_info(service_name, item_name, data=None, unit=None):69def unit_info(service_name, item_name, data=None, unit=None):
169 if data is None:70 if data is None:
170 data = yaml.safe_load(juju_status())71 data = yaml.safe_load(juju_status())
@@ -189,10 +90,12 @@
189 return item90 return item
19091
19192
93# DEPRECATED: client-side only
192def get_machine_data():94def get_machine_data():
193 return yaml.safe_load(juju_status())['machines']95 return yaml.safe_load(juju_status())['machines']
19496
19597
98# DEPRECATED: client-side only
196def wait_for_machine(num_machines=1, timeout=300):99def wait_for_machine(num_machines=1, timeout=300):
197 """Wait `timeout` seconds for `num_machines` machines to come up.100 """Wait `timeout` seconds for `num_machines` machines to come up.
198101
@@ -232,6 +135,7 @@
232 return num_machines, time.time() - start_time135 return num_machines, time.time() - start_time
233136
234137
138# DEPRECATED: client-side only
235def wait_for_unit(service_name, timeout=480):139def wait_for_unit(service_name, timeout=480):
236 """Wait `timeout` seconds for a given service name to come up."""140 """Wait `timeout` seconds for a given service name to come up."""
237 wait_for_machine(num_machines=1)141 wait_for_machine(num_machines=1)
@@ -247,6 +151,7 @@
247 raise RuntimeError('unit did not start, agent-state: ' + state)151 raise RuntimeError('unit did not start, agent-state: ' + state)
248152
249153
154# DEPRECATED: client-side only
250def wait_for_relation(service_name, relation_name, timeout=120):155def wait_for_relation(service_name, relation_name, timeout=120):
251 """Wait `timeout` seconds for a given relation to come up."""156 """Wait `timeout` seconds for a given relation to come up."""
252 start_time = time.time()157 start_time = time.time()
@@ -259,6 +164,7 @@
259 time.sleep(SLEEP_AMOUNT)164 time.sleep(SLEEP_AMOUNT)
260165
261166
167# DEPRECATED: client-side only
262def wait_for_page_contents(url, contents, timeout=120, validate=None):168def wait_for_page_contents(url, contents, timeout=120, validate=None):
263 if validate is None:169 if validate is None:
264 validate = operator.contains170 validate = operator.contains
265171
=== modified file 'charmhelpers/contrib/charmsupport/nrpe.py'
--- charmhelpers/contrib/charmsupport/nrpe.py 2013-05-11 20:05:13 +0000
+++ charmhelpers/contrib/charmsupport/nrpe.py 2013-05-23 20:50:31 +0000
@@ -12,8 +12,14 @@
12import shlex12import shlex
13import yaml13import yaml
1414
15from hookenv import config, local_unit, log, relation_ids, relation_set15from charmhelpers.core.hookenv import (
16from host import service16 config,
17 local_unit,
18 log,
19 relation_ids,
20 relation_set,
21 )
22from charmhelpers.core.host import service
1723
18# This module adds compatibility with the nrpe-external-master and plain nrpe24# This module adds compatibility with the nrpe-external-master and plain nrpe
19# subordinate charms. To use it in your charm:25# subordinate charms. To use it in your charm:
2026
=== modified file 'charmhelpers/contrib/jujugui/utils.py'
--- charmhelpers/contrib/jujugui/utils.py 2013-05-11 20:39:53 +0000
+++ charmhelpers/contrib/jujugui/utils.py 2013-05-23 20:50:31 +0000
@@ -64,11 +64,12 @@
64 search_file,64 search_file,
65 su,65 su,
66)66)
67from charmhelpers.contrib.charmhelpers import (67from charmhelpers.core.host import (
68 START,68 service_start,
69 get_config,69)
70from charmhelpers.core.hookenv import (
70 log,71 log,
71 service_control,72 config,
72 unit_get,73 unit_get,
73)74)
7475
@@ -263,9 +264,9 @@
263 global results_log264 global results_log
264 if results_log is not None:265 if results_log is not None:
265 return266 return
266 config = get_config()267 cfg = config()
267 logging.basicConfig(268 logging.basicConfig(
268 filename=config['command-log-file'],269 filename=cfg['command-log-file'],
269 level=logging.INFO,270 level=logging.INFO,
270 format="%(asctime)s: %(name)s@%(levelname)s %(message)s")271 format="%(asctime)s: %(name)s@%(levelname)s %(message)s")
271 results_log = logging.getLogger('juju-gui')272 results_log = logging.getLogger('juju-gui')
@@ -295,7 +296,7 @@
295 render_to_file('config/juju-api-improv.conf.template', context, config_path)296 render_to_file('config/juju-api-improv.conf.template', context, config_path)
296 log('Starting the staging backend.')297 log('Starting the staging backend.')
297 with su('root'):298 with su('root'):
298 service_control(IMPROV, START)299 service_start(IMPROV)
299300
300301
301def start_agent(302def start_agent(
@@ -317,7 +318,7 @@
317 render_to_file('config/juju-api-agent.conf.template', context, config_path)318 render_to_file('config/juju-api-agent.conf.template', context, config_path)
318 log('Starting API agent.')319 log('Starting API agent.')
319 with su('root'):320 with su('root'):
320 service_control(AGENT, START)321 service_start(AGENT)
321322
322323
323def start_gui(324def start_gui(
324325
=== added directory 'charmhelpers/core'
=== added file 'charmhelpers/core/__init__.py'
=== renamed file 'charmhelpers/contrib/charmsupport/hookenv.py' => 'charmhelpers/core/hookenv.py'
--- charmhelpers/contrib/charmsupport/hookenv.py 2013-05-11 20:24:29 +0000
+++ charmhelpers/core/hookenv.py 2013-05-23 20:50:31 +0000
@@ -1,8 +1,8 @@
1"Interactions with the Juju environment"1"Interactions with the Juju environment"
2# Copyright 2012 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
3#3#
4# Authors:4# Authors:
5# Matthew Wedgwood <matthew.wedgwood@canonical.com>5# Charm Helpers Developers <juju@lists.ubuntu.com>
66
7import os7import os
8import json8import json
@@ -62,8 +62,11 @@
62 """A convenient bundling of the current execution context"""62 """A convenient bundling of the current execution context"""
63 context = {}63 context = {}
64 context['conf'] = config()64 context['conf'] = config()
65 context['reltype'] = relation_type()
66 context['relid'] = relation_id()
65 context['unit'] = local_unit()67 context['unit'] = local_unit()
66 context['rel'] = relations_of_type()68 context['rels'] = relations()
69 context['rel'] = relation_get()
67 context['env'] = os.environ70 context['env'] = os.environ
68 return context71 return context
6972
@@ -112,11 +115,13 @@
112 if rid:115 if rid:
113 _args.append('-r')116 _args.append('-r')
114 _args.append(rid)117 _args.append(rid)
115 if attribute is not None:118 _args.append(attribute or '-')
116 _args.append(attribute)
117 if unit:119 if unit:
118 _args.append(unit)120 _args.append(unit)
119 return json.loads(subprocess.check_output(_args))121 try:
122 return json.loads(subprocess.check_output(_args))
123 except ValueError:
124 return None
120125
121126
122def relation_set(relation_id=None, **kwargs):127def relation_set(relation_id=None, **kwargs):
@@ -134,7 +139,8 @@
134 relid_cmd_line = ['relation-ids', '--format=json']139 relid_cmd_line = ['relation-ids', '--format=json']
135 if reltype is not None:140 if reltype is not None:
136 relid_cmd_line.append(reltype)141 relid_cmd_line.append(reltype)
137 return json.loads(subprocess.check_output(relid_cmd_line))142 return json.loads(subprocess.check_output(relid_cmd_line))
143 return []
138144
139145
140def related_units(relid=None):146def related_units(relid=None):
@@ -179,6 +185,53 @@
179 return relation_data185 return relation_data
180186
181187
188def relation_types():
189 "Get a list of relation types supported by this charm"
190 charmdir = os.environ.get('CHARM_DIR', '')
191 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
192 md = yaml.safe_load(mdf)
193 rel_types = []
194 for key in ('provides','requires','peers'):
195 section = md.get(key)
196 if section:
197 rel_types.extend(section.keys())
198 mdf.close()
199 return rel_types
200
201
202def relations():
203 rels = {}
204 for reltype in relation_types():
205 relids = {}
206 for relid in relation_ids(reltype):
207 units = {}
208 for unit in related_units(relid):
209 reldata = relation_get(unit=unit, rid=relid)
210 units[unit] = reldata
211 relids[relid] = units
212 rels[reltype] = relids
213 return rels
214
215
216def open_port(port, protocol="TCP"):
217 "Open a service network port"
218 _args = ['open-port']
219 _args.append('{}/{}'.format(port, protocol))
220 subprocess.check_call(_args)
221
222
223def close_port(port, protocol="TCP"):
224 "Close a service network port"
225 _args = ['close-port']
226 _args.append('{}/{}'.format(port, protocol))
227 subprocess.check_call(_args)
228
229
230def unit_get(attribute):
231 _args = ['unit-get', attribute]
232 return subprocess.check_output(_args).strip()
233
234
182class UnregisteredHookError(Exception):235class UnregisteredHookError(Exception):
183 pass236 pass
184237
185238
=== renamed file 'charmhelpers/contrib/charmsupport/host.py' => 'charmhelpers/core/host.py'
--- charmhelpers/contrib/charmsupport/host.py 2013-05-11 20:05:13 +0000
+++ charmhelpers/core/host.py 2013-05-23 20:50:31 +0000
@@ -23,7 +23,7 @@
2323
24def service(action, service_name):24def service(action, service_name):
25 cmd = None25 cmd = None
26 if os.path.exists(os.path.join('/etc/init', service_name)):26 if os.path.exists(os.path.join('/etc/init', '%s.conf' % service_name)):
27 cmd = ['initctl', action, service_name]27 cmd = ['initctl', action, service_name]
28 elif os.path.exists(os.path.join('/etc/init.d', service_name)):28 elif os.path.exists(os.path.join('/etc/init.d', service_name)):
29 cmd = [os.path.join('/etc/init.d', service_name), action]29 cmd = [os.path.join('/etc/init.d', service_name), action]
3030
=== added directory 'charmhelpers/fetch'
=== added file 'charmhelpers/fetch/__init__.py'
--- charmhelpers/fetch/__init__.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/fetch/__init__.py 2013-05-23 20:50:31 +0000
@@ -0,0 +1,46 @@
1from yaml import safe_load
2from core.hookenv import config_get
3from subprocess import check_call
4
5
6def add_source(source, key=None):
7 if ((source.startswith('ppa:') or
8 source.startswith('cloud:') or
9 source.startswith('http:'))):
10 check_call('add-apt-repository', source)
11 if key:
12 check_call('apt-key', 'import', key)
13
14
15class SourceConfigError(Exception):
16 pass
17
18
19def configure_sources(update=False,
20 sources_var='install_sources',
21 keys_var='install_keys'):
22 """
23 Configure multiple sources from charm configuration
24
25 Example config:
26 install_sources:
27 - "ppa:foo"
28 - "http://example.com/repo precise main"
29 install_keys:
30 - null
31 - "a1b2c3d4"
32
33 Note that 'null' (a.k.a. None) should not be quoted.
34 """
35 sources = safe_load(config_get(sources_var))
36 keys = safe_load(config_get(keys_var))
37 if isinstance(sources, basestring) and isinstance(keys, basestring):
38 add_source(sources, keys)
39 else:
40 if not len(sources) == len(keys):
41 msg = 'Install sources and keys lists are different lengths'
42 raise SourceConfigError(msg)
43 for src_num in range(len(sources)):
44 add_source(sources[src_num], sources[src_num])
45 if update:
46 check_call(('apt-get', 'update'))
047
=== added directory 'charmhelpers/payload'
=== added file 'charmhelpers/payload/__init__.py'
--- charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/payload/__init__.py 2013-05-23 20:50:31 +0000
@@ -0,0 +1,1 @@
1"Tools for working with files injected into a charm just before deployment."
02
=== renamed file 'charmhelpers/contrib/charmsupport/execd.py' => 'charmhelpers/payload/execd.py'
--- charmhelpers/contrib/charmsupport/execd.py 2013-05-11 20:05:13 +0000
+++ charmhelpers/payload/execd.py 2013-05-23 20:50:31 +0000
@@ -3,32 +3,37 @@
3import os3import os
4import sys4import sys
5import subprocess5import subprocess
6import hookenv6from charmhelpers.core import hookenv
77
88
9def default_execd_dir():9def default_execd_dir():
10 return os.path.join(os.environ['CHARM_DIR'],'exec.d')10 return os.path.join(os.environ['CHARM_DIR'],'exec.d')
1111
1212
13def execd_modules(execd_dir=None):13def execd_module_paths(execd_dir=None):
14 if not execd_dir:14 if not execd_dir:
15 execd_dir = default_execd_dir()15 execd_dir = default_execd_dir()
16 for subpath in os.listdir(execd_dir):16 for subpath in os.listdir(execd_dir):
17 module = os.path.join(execd_dir, subpath)17 module = os.path.join(execd_dir, subpath)
18 if os.path.isdir(module):18 if os.path.isdir(module):
19 yield module19 yield module
2020
2121
22def execd_run(submodule, execd_dir=None, die_on_error=False):22def execd_submodule_paths(submodule, execd_dir=None):
23 for module_path in execd_modules(execd_dir):23 for module_path in execd_module_paths(execd_dir):
24 path = os.path.join(module_path, submodule)24 path = os.path.join(module_path, submodule)
25 if os.access(path, os.X_OK) and os.path.isfile(path):25 if os.access(path, os.X_OK) and os.path.isfile(path):
26 try:26 yield path
27 subprocess.check_call(path, shell=True)27
28 except subprocess.CalledProcessError as e:28
29 hookenv.log(e.output)29def execd_run(submodule, execd_dir=None, die_on_error=False):
30 if die_on_error:30 for submodule_path in execd_submodule_paths(submodule, execd_dir):
31 sys.exit(e.returncode)31 try:
32 subprocess.check_call(submodule_path, shell=True)
33 except subprocess.CalledProcessError as e:
34 hookenv.log(e.output)
35 if die_on_error:
36 sys.exit(e.returncode)
3237
3338
34def execd_preinstall(execd_dir=None):39def execd_preinstall(execd_dir=None):
3540
=== modified file 'tests/contrib/charmhelpers/test_charmhelpers.py'
--- tests/contrib/charmhelpers/test_charmhelpers.py 2013-05-11 20:30:34 +0000
+++ tests/contrib/charmhelpers/test_charmhelpers.py 2013-05-23 20:50:31 +0000
@@ -3,7 +3,6 @@
3import unittest3import unittest
4import yaml4import yaml
55
6from simplejson import dumps
7from StringIO import StringIO6from StringIO import StringIO
8from testtools import TestCase7from testtools import TestCase
98
@@ -14,8 +13,6 @@
14sys.path.insert(0, 'helpers/python')13sys.path.insert(0, 'helpers/python')
15from charmhelpers.contrib import charmhelpers14from charmhelpers.contrib import charmhelpers
1615
17from subprocess import CalledProcessError
18
1916
20class CharmHelpersTestCase(TestCase):17class CharmHelpersTestCase(TestCase):
21 """A basic test case for Python charm helpers."""18 """A basic test case for Python charm helpers."""
@@ -73,145 +70,6 @@
73 self._make_juju_status_dict(70 self._make_juju_status_dict(
74 num_units, service_name, unit_state, machine_state))71 num_units, service_name, unit_state, machine_state))
7572
76 def test_get_config(self):
77 # get_config returns the contents of the current charm
78 # configuration, as returned by config-get --format=json.
79 mock_config = {'key': 'value'}
80
81 # Monkey-patch shelltoolbox.command to avoid having to call out
82 # to config-get.
83 self._patch_command(lambda: dumps(mock_config))
84 self.assertEqual(mock_config, charmhelpers.get_config())
85
86 def test_config_get(self):
87 # config_get is used to retrieve individual configuration elements
88 mock_config = {'key': 'value'}
89
90 # Monkey-patch shelltoolbox.command to avoid having to call out
91 # to config-get.
92 self._patch_command(lambda *args: mock_config[args[0]])
93 self.assertEqual(mock_config['key'], charmhelpers.config_get('key'))
94
95 def test_unit_get(self):
96 # unit_get is used to retrieve individual configuration elements
97 mock_config = {'key': 'value'}
98
99 # Monkey-patch shelltoolbox.command to avoid having to call out
100 # to unit-get.
101 self._patch_command(lambda *args: mock_config[args[0]])
102 self.assertEqual(mock_config['key'], charmhelpers.unit_get('key'))
103
104 def test_relation_get(self):
105 # relation_get returns the value of a given relation variable,
106 # as returned by relation-get $VAR.
107 mock_relation_values = {
108 'foo': 'bar',
109 'spam': 'eggs'}
110 self._patch_command(lambda *args: mock_relation_values[args[0]])
111 self.assertEqual('bar', charmhelpers.relation_get('foo'))
112 self.assertEqual('eggs', charmhelpers.relation_get('spam'))
113
114 self._patch_command(lambda *args: mock_relation_values[args[2]])
115 self.assertEqual('bar',
116 charmhelpers.relation_get('foo', 'test', 'test:1'))
117 self.assertEqual('eggs',
118 charmhelpers.relation_get('spam', 'test', 'test:1'))
119
120 self._patch_command(lambda *args: '%s' % mock_relation_values)
121 self.assertEqual("{'foo': 'bar', 'spam': 'eggs'}",
122 charmhelpers.relation_get())
123
124 def test_relation_set(self):
125 # relation_set calls out to relation-set and passes key=value
126 # pairs to it.
127 items_set = {}
128
129 def mock_relation_set(*args):
130 for arg in args:
131 key, value = arg.split("=")
132 items_set[key] = value
133 self._patch_command(mock_relation_set)
134 charmhelpers.relation_set(foo='bar', spam='eggs')
135 self.assertEqual('bar', items_set.get('foo'))
136 self.assertEqual('eggs', items_set.get('spam'))
137
138 def test_relation_ids(self):
139 # relation_ids returns a list of relations id for the given
140 # named relation
141 mock_relation_ids = {'test': 'test:1 test:2'}
142 self._patch_command(lambda *args: mock_relation_ids[args[0]])
143 self.assertEqual(mock_relation_ids['test'].split(),
144 charmhelpers.relation_ids('test'))
145
146 def test_relation_list(self):
147 # relation_list returns a list of unit names either for the current
148 # context or for the provided relation ID
149 mock_unit_names = {'test:1': 'test/0 test/1 test/2',
150 'test:2': 'test/3 test/4 test/5'}
151
152 # Patch command for current context use base - context = test:1
153 self._patch_command(lambda: mock_unit_names['test:1'])
154 self.assertEqual(mock_unit_names['test:1'].split(),
155 charmhelpers.relation_list())
156 # Patch command for provided relation-id
157 self._patch_command(lambda *args: mock_unit_names[args[1]])
158 self.assertEqual(mock_unit_names['test:2'].split(),
159 charmhelpers.relation_list(rid='test:2'))
160
161 def test_open_close_port(self):
162 # expose calls open-port with port/protocol parameters
163 ports_set = []
164
165 def mock_open_port(*args):
166 for arg in args:
167 ports_set.append(arg)
168
169 def mock_close_port(*args):
170 if args[0] in ports_set:
171 ports_set.remove(args[0])
172 # Monkey patch in the open-port mock
173 self._patch_command(mock_open_port)
174 charmhelpers.open_port(80, "TCP")
175 charmhelpers.open_port(90, "UDP")
176 charmhelpers.open_port(100)
177 self.assertTrue("80/TCP" in ports_set)
178 self.assertTrue("90/UDP" in ports_set)
179 self.assertTrue("100/TCP" in ports_set)
180 # Monkey patch in the close-port mock function
181 self._patch_command(mock_close_port)
182 charmhelpers.close_port(80, "TCP")
183 charmhelpers.close_port(90, "UDP")
184 charmhelpers.close_port(100)
185 # ports_set should now be empty
186 self.assertEquals(len(ports_set), 0)
187
188 def test_service_control(self):
189 # Collect commands that have been run
190 commands_set = {}
191
192 def mock_service(*args):
193 service = args[0]
194 action = args[1]
195 if service not in commands_set:
196 commands_set[service] = []
197 if ((len(commands_set[service]) > 1 and
198 commands_set[service][-1] == 'stop' and
199 action == 'restart')):
200 # Service is stopped - so needs 'start'
201 # action as restart will fail
202 commands_set[service].append(action)
203 raise CalledProcessError(1, repr(args))
204 else:
205 commands_set[service].append(action)
206
207 result = ['start', 'stop', 'restart', 'start']
208
209 # Monkey patch service command
210 self._patch_command(mock_service)
211 charmhelpers.service_control('myservice', 'start')
212 charmhelpers.service_control('myservice', 'stop')
213 charmhelpers.service_control('myservice', 'restart')
214 self.assertEquals(result, commands_set['myservice'])
21573
216 def test_make_charm_config_file(self):74 def test_make_charm_config_file(self):
217 # make_charm_config_file() writes the passed configuration to a75 # make_charm_config_file() writes the passed configuration to a
@@ -428,48 +286,6 @@
428 RuntimeError, charmhelpers.wait_for_page_contents,286 RuntimeError, charmhelpers.wait_for_page_contents,
429 'http://example.com', "This will error", timeout=0)287 'http://example.com', "This will error", timeout=0)
430288
431 def test_log(self):
432 # The "log" function forwards a string on to the juju-log command.
433 logged = []
434
435 def juju_log(*args):
436 logged.append(args)
437 charmhelpers.log('This is a log message', juju_log)
438 # Since we only logged one message, juju-log was only called once..
439 self.assertEqual(len(logged), 1)
440 # The message was included in the arguments passed to juju-log.
441 self.assertIn('This is a log message', logged[0])
442
443 def test_log_escapes_message(self):
444 # The Go version of juju-log interprets any string begining with two
445 # hyphens ("--") as a command-line switch, even if the third character
446 # is non-alphanumeric. This is different behavior than the Python
447 # version of juju-log. Therefore we signfiy the end of options by
448 # inserting the string " -- " just before the log message.
449 logged = []
450
451 def juju_log(*args):
452 logged.append(args)
453 charmhelpers.log('This is a log message', juju_log)
454 # The call to juju-log includes the " -- " string before the message.
455 self.assertEqual([('--', 'This is a log message')], logged)
456
457 def test_log_entry(self):
458 # The log_entry function logs a message about the script starting.
459 logged = []
460 self.patch(charmhelpers, 'log', logged.append)
461 self.patch(charmhelpers, 'script_name', lambda: 'SCRIPT-NAME')
462 charmhelpers.log_entry()
463 self.assertEqual(['--> Entering SCRIPT-NAME'], logged)
464
465 def test_log_exit(self):
466 # The log_exit function logs a message about the script ending.
467 logged = []
468 self.patch(charmhelpers, 'log', logged.append)
469 self.patch(charmhelpers, 'script_name', lambda: 'SCRIPT-NAME')
470 charmhelpers.log_exit()
471 self.assertEqual(['<-- Exiting SCRIPT-NAME'], logged)
472
473289
474if __name__ == '__main__':290if __name__ == '__main__':
475 unittest.main()291 unittest.main()
476292
=== modified file 'tests/contrib/jujugui/test_utils.py'
--- tests/contrib/jujugui/test_utils.py 2013-05-11 20:39:53 +0000
+++ tests/contrib/jujugui/test_utils.py 2013-05-23 20:50:31 +0000
@@ -468,17 +468,17 @@
468class TestCmdLog(unittest.TestCase):468class TestCmdLog(unittest.TestCase):
469469
470 def setUp(self):470 def setUp(self):
471 # Patch the charmhelpers 'command', which powers get_config. The471 # Patch the utils 'config', which powers get_config. The
472 # result of this is the mock_config dictionary will be returned.472 # result of this is the mock_config dictionary will be returned.
473 # The monkey patch is undone in the tearDown.473 # The monkey patch is undone in the tearDown.
474 self.command = charmhelpers.command474 self.config = utils.config
475 fd, self.log_file_name = tempfile.mkstemp()475 fd, self.log_file_name = tempfile.mkstemp()
476 os.close(fd)476 os.close(fd)
477 mock_config = {'command-log-file': self.log_file_name}477 mock_config = {'command-log-file': self.log_file_name}
478 charmhelpers.command = lambda *args: lambda: dumps(mock_config)478 utils.config = lambda *args: mock_config
479479
480 def tearDown(self):480 def tearDown(self):
481 charmhelpers.command = self.command481 utils.config = self.config
482 os.unlink(self.log_file_name)482 os.unlink(self.log_file_name)
483483
484 def test_contents_logged(self):484 def test_contents_logged(self):
@@ -491,16 +491,14 @@
491491
492 def setUp(self):492 def setUp(self):
493 self.service_names = []493 self.service_names = []
494 self.actions = []
495 self.svc_ctl_call_count = 0494 self.svc_ctl_call_count = 0
496 self.fake_zk_address = '192.168.5.26'495 self.fake_zk_address = '192.168.5.26'
497 # Monkey patches.496 # Monkey patches.
498 self.command = charmhelpers.command497 self.command = charmhelpers.command
499498
500 def service_control_mock(service_name, action):499 def service_start_mock(service_name):
501 self.svc_ctl_call_count += 1500 self.svc_ctl_call_count += 1
502 self.service_names.append(service_name)501 self.service_names.append(service_name)
503 self.actions.append(action)
504502
505 def noop(*args):503 def noop(*args):
506 pass504 pass
@@ -522,7 +520,7 @@
522 self.files[os.path.basename(dest)] = fp.read()520 self.files[os.path.basename(dest)] = fp.read()
523521
524 self.functions = dict(522 self.functions = dict(
525 service_control=(utils.service_control, service_control_mock),523 service_start=(utils.service_start, service_start_mock),
526 log=(utils.log, noop),524 log=(utils.log, noop),
527 su=(utils.su, su),525 su=(utils.su, su),
528 run=(utils.run, noop),526 run=(utils.run, noop),
@@ -555,7 +553,6 @@
555 self.assertTrue(self.ssl_cert_path in conf)553 self.assertTrue(self.ssl_cert_path in conf)
556 self.assertEqual(self.svc_ctl_call_count, 1)554 self.assertEqual(self.svc_ctl_call_count, 1)
557 self.assertEqual(self.service_names, ['juju-api-improv'])555 self.assertEqual(self.service_names, ['juju-api-improv'])
558 self.assertEqual(self.actions, [charmhelpers.START])
559556
560 def test_start_agent(self):557 def test_start_agent(self):
561 start_agent(self.ssl_cert_path, 'config')558 start_agent(self.ssl_cert_path, 'config')
@@ -565,7 +562,6 @@
565 self.assertTrue(self.ssl_cert_path in conf)562 self.assertTrue(self.ssl_cert_path in conf)
566 self.assertEqual(self.svc_ctl_call_count, 1)563 self.assertEqual(self.svc_ctl_call_count, 1)
567 self.assertEqual(self.service_names, ['juju-api-agent'])564 self.assertEqual(self.service_names, ['juju-api-agent'])
568 self.assertEqual(self.actions, [charmhelpers.START])
569565
570 def test_start_gui(self):566 def test_start_gui(self):
571 ssl_cert_path = '/tmp/certificates/'567 ssl_cert_path = '/tmp/certificates/'
572568
=== added directory 'tests/core'
=== added file 'tests/core/__init__.py'
=== renamed file 'tests/contrib/charmsupport/test_hookenv.py' => 'tests/core/test_hookenv.py'
--- tests/contrib/charmsupport/test_hookenv.py 2013-05-11 20:24:29 +0000
+++ tests/core/test_hookenv.py 2013-05-23 20:50:31 +0000
@@ -1,11 +1,26 @@
1import json1import json
22
3from mock import patch, call, MagicMock3from mock import patch, call, mock_open
4from StringIO import StringIO
5from mock import MagicMock
4from testtools import TestCase6from testtools import TestCase
5import yaml7import yaml
68
7from charmhelpers.contrib.charmsupport import hookenv9from charmhelpers.core import hookenv
810
11CHARM_METADATA = """name: testmock
12summary: test mock summary
13description: test mock description
14requires:
15 testreqs:
16 interface: mock
17provides:
18 testprov:
19 interface: mock
20peers:
21 testpeer:
22 interface: mock
23"""
924
10class SerializableTest(TestCase):25class SerializableTest(TestCase):
11 def test_serializes_object_to_json(self):26 def test_serializes_object_to_json(self):
@@ -105,14 +120,14 @@
105 check_output.assert_called_with(['config-get', 'baz', '--format=json'])120 check_output.assert_called_with(['config-get', 'baz', '--format=json'])
106121
107 @patch('subprocess.check_output')122 @patch('subprocess.check_output')
108 @patch('charmhelpers.contrib.charmsupport.hookenv.log')123 @patch('charmhelpers.core.hookenv.log')
109 def test_logs_and_reraises_on_config_error(self, log, check_output):124 def test_logs_and_reraises_on_config_error(self, log, check_output):
110 error = 'some error'125 error = 'some error'
111 check_output.side_effect = ValueError(error)126 check_output.side_effect = ValueError(error)
112127
113 self.assertRaisesRegexp(ValueError, error, hookenv.config)128 self.assertRaisesRegexp(ValueError, error, hookenv.config)
114129
115 @patch('charmhelpers.contrib.charmsupport.hookenv.os')130 @patch('charmhelpers.core.hookenv.os')
116 def test_gets_the_local_unit(self, os_):131 def test_gets_the_local_unit(self, os_):
117 os_.environ = {132 os_.environ = {
118 'JUJU_UNIT_NAME': 'foo',133 'JUJU_UNIT_NAME': 'foo',
@@ -120,7 +135,7 @@
120135
121 self.assertEqual(hookenv.local_unit(), 'foo')136 self.assertEqual(hookenv.local_unit(), 'foo')
122137
123 @patch('charmhelpers.contrib.charmsupport.hookenv.os')138 @patch('charmhelpers.core.hookenv.os')
124 def test_checks_that_is_running_in_relation_hook(self, os_):139 def test_checks_that_is_running_in_relation_hook(self, os_):
125 os_.environ = {140 os_.environ = {
126 'JUJU_RELATION': 'foo',141 'JUJU_RELATION': 'foo',
@@ -128,7 +143,7 @@
128143
129 self.assertTrue(hookenv.in_relation_hook())144 self.assertTrue(hookenv.in_relation_hook())
130145
131 @patch('charmhelpers.contrib.charmsupport.hookenv.os')146 @patch('charmhelpers.core.hookenv.os')
132 def test_checks_that_is_not_running_in_relation_hook(self, os_):147 def test_checks_that_is_not_running_in_relation_hook(self, os_):
133 os_.environ = {148 os_.environ = {
134 'bar': 'foo',149 'bar': 'foo',
@@ -136,7 +151,7 @@
136151
137 self.assertFalse(hookenv.in_relation_hook())152 self.assertFalse(hookenv.in_relation_hook())
138153
139 @patch('charmhelpers.contrib.charmsupport.hookenv.os')154 @patch('charmhelpers.core.hookenv.os')
140 def test_gets_the_relation_type(self, os_):155 def test_gets_the_relation_type(self, os_):
141 os_.environ = {156 os_.environ = {
142 'JUJU_RELATION': 'foo',157 'JUJU_RELATION': 'foo',
@@ -144,13 +159,13 @@
144159
145 self.assertEqual(hookenv.relation_type(), 'foo')160 self.assertEqual(hookenv.relation_type(), 'foo')
146161
147 @patch('charmhelpers.contrib.charmsupport.hookenv.os')162 @patch('charmhelpers.core.hookenv.os')
148 def test_relation_type_none_if_not_in_environment(self, os_):163 def test_relation_type_none_if_not_in_environment(self, os_):
149 os_.environ = {}164 os_.environ = {}
150 self.assertEqual(hookenv.relation_type(), None)165 self.assertEqual(hookenv.relation_type(), None)
151166
152 @patch('subprocess.check_output')167 @patch('subprocess.check_output')
153 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type')168 @patch('charmhelpers.core.hookenv.relation_type')
154 def test_gets_relation_ids(self, relation_type, check_output):169 def test_gets_relation_ids(self, relation_type, check_output):
155 ids = [1, 2, 3]170 ids = [1, 2, 3]
156 check_output.return_value = json.dumps(ids)171 check_output.return_value = json.dumps(ids)
@@ -164,7 +179,7 @@
164 reltype])179 reltype])
165180
166 @patch('subprocess.check_output')181 @patch('subprocess.check_output')
167 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type')182 @patch('charmhelpers.core.hookenv.relation_type')
168 def test_relation_ids_no_relation_type(self, relation_type, check_output):183 def test_relation_ids_no_relation_type(self, relation_type, check_output):
169 ids = [1, 2, 3]184 ids = [1, 2, 3]
170 check_output.return_value = json.dumps(ids)185 check_output.return_value = json.dumps(ids)
@@ -172,11 +187,10 @@
172187
173 result = hookenv.relation_ids()188 result = hookenv.relation_ids()
174189
175 self.assertEqual(result, ids)190 self.assertEqual(result, [])
176 check_output.assert_called_with(['relation-ids', '--format=json'])
177191
178 @patch('subprocess.check_output')192 @patch('subprocess.check_output')
179 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type')193 @patch('charmhelpers.core.hookenv.relation_type')
180 def test_gets_relation_ids_for_type(self, relation_type, check_output):194 def test_gets_relation_ids_for_type(self, relation_type, check_output):
181 ids = [1, 2, 3]195 ids = [1, 2, 3]
182 check_output.return_value = json.dumps(ids)196 check_output.return_value = json.dumps(ids)
@@ -190,7 +204,7 @@
190 self.assertFalse(relation_type.called)204 self.assertFalse(relation_type.called)
191205
192 @patch('subprocess.check_output')206 @patch('subprocess.check_output')
193 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id')207 @patch('charmhelpers.core.hookenv.relation_id')
194 def test_gets_related_units(self, relation_id, check_output):208 def test_gets_related_units(self, relation_id, check_output):
195 relid = 123209 relid = 123
196 units = ['foo', 'bar']210 units = ['foo', 'bar']
@@ -204,7 +218,7 @@
204 '-r', relid])218 '-r', relid])
205219
206 @patch('subprocess.check_output')220 @patch('subprocess.check_output')
207 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id')221 @patch('charmhelpers.core.hookenv.relation_id')
208 def test_related_units_no_relation(self, relation_id, check_output):222 def test_related_units_no_relation(self, relation_id, check_output):
209 units = ['foo', 'bar']223 units = ['foo', 'bar']
210 relation_id.return_value = None224 relation_id.return_value = None
@@ -216,7 +230,7 @@
216 check_output.assert_called_with(['relation-list', '--format=json'])230 check_output.assert_called_with(['relation-list', '--format=json'])
217231
218 @patch('subprocess.check_output')232 @patch('subprocess.check_output')
219 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_id')233 @patch('charmhelpers.core.hookenv.relation_id')
220 def test_gets_related_units_for_id(self, relation_id, check_output):234 def test_gets_related_units_for_id(self, relation_id, check_output):
221 relid = 123235 relid = 123
222 units = ['foo', 'bar']236 units = ['foo', 'bar']
@@ -229,7 +243,7 @@
229 '-r', relid])243 '-r', relid])
230 self.assertFalse(relation_id.called)244 self.assertFalse(relation_id.called)
231245
232 @patch('charmhelpers.contrib.charmsupport.hookenv.os')246 @patch('charmhelpers.core.hookenv.os')
233 def test_gets_the_remote_unit(self, os_):247 def test_gets_the_remote_unit(self, os_):
234 os_.environ = {248 os_.environ = {
235 'JUJU_REMOTE_UNIT': 'foo',249 'JUJU_REMOTE_UNIT': 'foo',
@@ -237,8 +251,8 @@
237251
238 self.assertEqual(hookenv.remote_unit(), 'foo')252 self.assertEqual(hookenv.remote_unit(), 'foo')
239253
240 @patch('charmhelpers.contrib.charmsupport.hookenv.remote_unit')254 @patch('charmhelpers.core.hookenv.remote_unit')
241 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_get')255 @patch('charmhelpers.core.hookenv.relation_get')
242 def test_gets_relation_for_unit(self, relation_get, remote_unit):256 def test_gets_relation_for_unit(self, relation_get, remote_unit):
243 unit = 'foo-unit'257 unit = 'foo-unit'
244 raw_relation = {258 raw_relation = {
@@ -254,8 +268,8 @@
254 self.assertEqual(getattr(result, 'baz-list'), ['1', '2', '3'])268 self.assertEqual(getattr(result, 'baz-list'), ['1', '2', '3'])
255 relation_get.assert_called_with(unit=unit, rid=None)269 relation_get.assert_called_with(unit=unit, rid=None)
256270
257 @patch('charmhelpers.contrib.charmsupport.hookenv.remote_unit')271 @patch('charmhelpers.core.hookenv.remote_unit')
258 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_get')272 @patch('charmhelpers.core.hookenv.relation_get')
259 def test_gets_relation_for_specific_unit(self, relation_get, remote_unit):273 def test_gets_relation_for_specific_unit(self, relation_get, remote_unit):
260 unit = 'foo-unit'274 unit = 'foo-unit'
261 raw_relation = {275 raw_relation = {
@@ -271,9 +285,9 @@
271 relation_get.assert_called_with(unit=unit, rid=None)285 relation_get.assert_called_with(unit=unit, rid=None)
272 self.assertFalse(remote_unit.called)286 self.assertFalse(remote_unit.called)
273287
274 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids')288 @patch('charmhelpers.core.hookenv.relation_ids')
275 @patch('charmhelpers.contrib.charmsupport.hookenv.related_units')289 @patch('charmhelpers.core.hookenv.related_units')
276 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_for_unit')290 @patch('charmhelpers.core.hookenv.relation_for_unit')
277 def test_gets_relations_for_id(self, relation_for_unit, related_units,291 def test_gets_relations_for_id(self, relation_for_unit, related_units,
278 relation_ids):292 relation_ids):
279 relid = 123293 relid = 123
@@ -298,9 +312,9 @@
298 call('bar', relid),312 call('bar', relid),
299 ])313 ])
300314
301 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids')315 @patch('charmhelpers.core.hookenv.relation_ids')
302 @patch('charmhelpers.contrib.charmsupport.hookenv.related_units')316 @patch('charmhelpers.core.hookenv.related_units')
303 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_for_unit')317 @patch('charmhelpers.core.hookenv.relation_for_unit')
304 def test_gets_relations_for_specific_id(self, relation_for_unit,318 def test_gets_relations_for_specific_id(self, relation_for_unit,
305 related_units, relation_ids):319 related_units, relation_ids):
306 relid = 123320 relid = 123
@@ -325,10 +339,10 @@
325 ])339 ])
326 self.assertFalse(relation_ids.called)340 self.assertFalse(relation_ids.called)
327341
328 @patch('charmhelpers.contrib.charmsupport.hookenv.in_relation_hook')342 @patch('charmhelpers.core.hookenv.in_relation_hook')
329 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_type')343 @patch('charmhelpers.core.hookenv.relation_type')
330 @patch('charmhelpers.contrib.charmsupport.hookenv.relation_ids')344 @patch('charmhelpers.core.hookenv.relation_ids')
331 @patch('charmhelpers.contrib.charmsupport.hookenv.relations_for_id')345 @patch('charmhelpers.core.hookenv.relations_for_id')
332 def test_gets_relations_for_type(self, relations_for_id, relation_ids,346 def test_gets_relations_for_type(self, relations_for_id, relation_ids,
333 relation_type, in_relation_hook):347 relation_type, in_relation_hook):
334 reltype = 'foo-type'348 reltype = 'foo-type'
@@ -366,27 +380,66 @@
366 call(234),380 call(234),
367 ])381 ])
368382
369 @patch('charmhelpers.contrib.charmsupport.hookenv.config')383 @patch('charmhelpers.core.hookenv.relation_types')
370 @patch('charmhelpers.contrib.charmsupport.hookenv.local_unit')384 @patch('charmhelpers.core.hookenv.relation_ids')
371 @patch('charmhelpers.contrib.charmsupport.hookenv.relations_of_type')385 @patch('charmhelpers.core.hookenv.related_units')
372 @patch('charmhelpers.contrib.charmsupport.hookenv.os')386 @patch('charmhelpers.core.hookenv.relation_get')
373 def test_gets_execution_environment(self, os_, relations_of_type,387 def test_gets_relations(self, relation_get, related_units,
374 local_unit, config):388 relation_ids, relation_types):
389 relation_types.return_value = ['t1','t2']
390 relation_ids.return_value = ['i1']
391 related_units.return_value = ['u1','u2']
392 relation_get.return_value = {'key': 'val'}
393
394 result = hookenv.relations()
395
396 self.assertEqual(result, {
397 't1': {
398 'i1': {
399 'u1': {'key': 'val'},
400 'u2': {'key': 'val'},
401 },
402 },
403 't2': {
404 'i1': {
405 'u1': {'key': 'val'},
406 'u2': {'key': 'val'},
407 },
408 },
409 })
410
411
412 @patch('charmhelpers.core.hookenv.config')
413 @patch('charmhelpers.core.hookenv.relation_type')
414 @patch('charmhelpers.core.hookenv.local_unit')
415 @patch('charmhelpers.core.hookenv.relation_id')
416 @patch('charmhelpers.core.hookenv.relations')
417 @patch('charmhelpers.core.hookenv.relation_get')
418 @patch('charmhelpers.core.hookenv.os')
419 def test_gets_execution_environment(self, os_, relations_get,
420 relations, relation_id, local_unit,
421 relation_type, config):
375 config.return_value = 'some-config'422 config.return_value = 'some-config'
423 relation_type.return_value = 'some-type'
376 local_unit.return_value = 'some-unit'424 local_unit.return_value = 'some-unit'
377 relations_of_type.return_value = 'some-relations'425 relation_id.return_value = 'some-id'
426 relations.return_value = 'all-relations'
427 relations_get.return_value = 'some-relations'
378 os_.environ = 'some-environment'428 os_.environ = 'some-environment'
379429
380 result = hookenv.execution_environment()430 result = hookenv.execution_environment()
381431
382 self.assertEqual(result, {432 self.assertEqual(result, {
383 'conf': 'some-config',433 'conf': 'some-config',
434 'reltype': 'some-type',
384 'unit': 'some-unit',435 'unit': 'some-unit',
436 'relid': 'some-id',
385 'rel': 'some-relations',437 'rel': 'some-relations',
438 'rels': 'all-relations',
386 'env': 'some-environment',439 'env': 'some-environment',
387 })440 })
388441
389 @patch('charmhelpers.contrib.charmsupport.hookenv.os')442 @patch('charmhelpers.core.hookenv.os')
390 def test_gets_the_relation_id(self, os_):443 def test_gets_the_relation_id(self, os_):
391 os_.environ = {444 os_.environ = {
392 'JUJU_RELATION_ID': 'foo',445 'JUJU_RELATION_ID': 'foo',
@@ -394,7 +447,7 @@
394447
395 self.assertEqual(hookenv.relation_id(), 'foo')448 self.assertEqual(hookenv.relation_id(), 'foo')
396449
397 @patch('charmhelpers.contrib.charmsupport.hookenv.os')450 @patch('charmhelpers.core.hookenv.os')
398 def test_relation_id_none_if_no_env(self, os_):451 def test_relation_id_none_if_no_env(self, os_):
399 os_.environ = {}452 os_.environ = {}
400 self.assertEqual(hookenv.relation_id(), None)453 self.assertEqual(hookenv.relation_id(), None)
@@ -407,7 +460,7 @@
407 result = hookenv.relation_get()460 result = hookenv.relation_get()
408461
409 self.assertEqual(result['foo'], 'BAR')462 self.assertEqual(result['foo'], 'BAR')
410 check_output.assert_called_with(['relation-get', '--format=json'])463 check_output.assert_called_with(['relation-get', '--format=json', '-'])
411464
412 @patch('subprocess.check_output')465 @patch('subprocess.check_output')
413 def test_gets_relation_with_scope(self, check_output):466 def test_gets_relation_with_scope(self, check_output):
@@ -443,6 +496,57 @@
443 check_output.assert_called_with(['relation-get', '--format=json', '-r',496 check_output.assert_called_with(['relation-get', '--format=json', '-r',
444 123, 'baz-scope', 'baz-unit'])497 123, 'baz-scope', 'baz-unit'])
445498
499 @patch('subprocess.check_call')
500 def test_sets_relation(self, check_call_):
501 hookenv.relation_set(foo="bar")
502 check_call_.assert_called_with(['relation-set','foo=bar'])
503
504
505 @patch('subprocess.check_call')
506 def test_sets_relation_with_relation_id(self, check_call_):
507 hookenv.relation_set(relation_id="foo", bar="baz")
508 check_call_.assert_called_with(['relation-set', '-r', 'foo',
509 'bar=baz'])
510
511
512 def test_lists_relation_types(self):
513 open_ = mock_open()
514 open_.return_value = StringIO(CHARM_METADATA)
515 with patch('charmhelpers.core.hookenv.open', open_, create=True):
516 with patch.dict('os.environ', {'CHARM_DIR': '/var/empty'}):
517 reltypes = set(hookenv.relation_types())
518 open_.assert_called_once_with('/var/empty/metadata.yaml')
519 self.assertEqual(set(('testreqs','testprov','testpeer')), reltypes)
520
521
522 @patch('subprocess.check_call')
523 def test_opens_port(self, check_call_):
524 hookenv.open_port(443, "TCP")
525 hookenv.open_port(80)
526 hookenv.open_port(100, "UDP")
527 calls = [call(['open-port','443/TCP']),
528 call(['open-port','80/TCP']),
529 call(['open-port','100/UDP']),
530 ]
531 check_call_.assert_has_calls(calls)
532
533
534 @patch('subprocess.check_call')
535 def test_closes_port(self, check_call_):
536 hookenv.close_port(443, "TCP")
537 hookenv.close_port(80)
538 hookenv.close_port(100, "UDP")
539 calls = [call(['close-port','443/TCP']),
540 call(['close-port','80/TCP']),
541 call(['close-port','100/UDP']),
542 ]
543 check_call_.assert_has_calls(calls)
544
545 @patch('subprocess.check_output')
546 def test_gets_unit_attribute(self, check_output_):
547 hookenv.unit_get('foo')
548 check_output_.assert_called_with(['unit-get', 'foo'])
549
446550
447class HooksTest(TestCase):551class HooksTest(TestCase):
448 def test_runs_a_registered_function(self):552 def test_runs_a_registered_function(self):
@@ -488,3 +592,4 @@
488 self.assertRaises(hookenv.UnregisteredHookError, hooks.execute,592 self.assertRaises(hookenv.UnregisteredHookError, hooks.execute,
489 ['brew'])593 ['brew'])
490 self.assertEqual(execs, [True])594 self.assertEqual(execs, [True])
595
491596
=== renamed file 'tests/contrib/charmsupport/test_host.py' => 'tests/core/test_host.py'
--- tests/contrib/charmsupport/test_host.py 2013-05-11 20:24:29 +0000
+++ tests/core/test_host.py 2013-05-23 20:50:31 +0000
@@ -5,7 +5,7 @@
5from mock import patch, call, MagicMock5from mock import patch, call, MagicMock
6from testtools import TestCase6from testtools import TestCase
77
8from charmhelpers.contrib.charmsupport import host8from charmhelpers.core import host
99
1010
11MOUNT_LINES = ("""11MOUNT_LINES = ("""
@@ -52,7 +52,7 @@
52 self.assertTrue(result)52 self.assertTrue(result)
53 mock_call.assert_called_with(['initctl', action, service_name])53 mock_call.assert_called_with(['initctl', action, service_name])
54 exists.assert_has_calls([54 exists.assert_has_calls([
55 call(os.path.join('/etc/init', service_name)),55 call(os.path.join('/etc/init', '%s.conf' % service_name)),
56 ])56 ])
5757
58 @patch('subprocess.call')58 @patch('subprocess.call')
@@ -71,7 +71,6 @@
71 mock_call.assert_called_with([os.path.join('/etc/init.d',71 mock_call.assert_called_with([os.path.join('/etc/init.d',
72 service_name), action])72 service_name), action])
73 exists.assert_has_calls([73 exists.assert_has_calls([
74 call(os.path.join('/etc/init', service_name)),
75 call(os.path.join('/etc/init.d', service_name)),74 call(os.path.join('/etc/init.d', service_name)),
76 ])75 ])
7776
7877
=== added directory 'tests/payload'
=== added file 'tests/payload/__init__.py'
=== renamed file 'tests/contrib/charmsupport/test_execd.py' => 'tests/payload/test_execd.py'
--- tests/contrib/charmsupport/test_execd.py 2013-05-11 20:24:29 +0000
+++ tests/payload/test_execd.py 2013-05-23 20:50:31 +0000
@@ -2,10 +2,11 @@
2from mock import patch, call2from mock import patch, call
3import os3import os
4import shutil4import shutil
5from subprocess import CalledProcessError
56
6from tempfile import mkdtemp7from tempfile import mkdtemp
78
8from charmhelpers.contrib.charmsupport import execd9from charmhelpers.payload import execd
910
10class ExecDBaseTestCase(TestCase):11class ExecDBaseTestCase(TestCase):
11 def setUp(self):12 def setUp(self):
@@ -30,7 +31,7 @@
3031
31 self.assertEqual(expected, default_dir)32 self.assertEqual(expected, default_dir)
3233
33 @patch('charmhelpers.contrib.charmsupport.execd.execd_run')34 @patch('charmhelpers.payload.execd.execd_run')
34 def test_execd_preinstall_calls_charm_pre_install(self, mock_execd_run):35 def test_execd_preinstall_calls_charm_pre_install(self, mock_execd_run):
35 execd_dir = 'testdir'36 execd_dir = 'testdir'
36 execd.execd_preinstall(execd_dir)37 execd.execd_preinstall(execd_dir)
@@ -38,7 +39,7 @@
38 mock_execd_run.assert_called_with(execd_dir, 'charm-pre-install')39 mock_execd_run.assert_called_with(execd_dir, 'charm-pre-install')
3940
4041
41 @patch('charmhelpers.contrib.charmsupport.execd.default_execd_dir', return_value='foo')42 @patch('charmhelpers.payload.execd.default_execd_dir', return_value='foo')
42 @patch('os.listdir', return_value=['a','b','c'])43 @patch('os.listdir', return_value=['a','b','c'])
43 @patch('os.path.isdir', return_value=True)44 @patch('os.path.isdir', return_value=True)
44 def test_execd_module_list_from_env(self, mock_isdir, mock_listdir,45 def test_execd_module_list_from_env(self, mock_isdir, mock_listdir,
@@ -46,7 +47,7 @@
46 module_names = ['a','b','c']47 module_names = ['a','b','c']
47 mock_listdir.return_value = module_names48 mock_listdir.return_value = module_names
4849
49 modules = list(execd.execd_modules())50 modules = list(execd.execd_module_paths())
5051
51 expected = [os.path.join('foo', d) for d in module_names]52 expected = [os.path.join('foo', d) for d in module_names]
52 self.assertEqual(modules, expected)53 self.assertEqual(modules, expected)
@@ -55,7 +56,7 @@
55 mock_isdir.assert_has_calls([call(d) for d in expected])56 mock_isdir.assert_has_calls([call(d) for d in expected])
5657
5758
58 @patch('charmhelpers.contrib.charmsupport.execd.default_execd_dir')59 @patch('charmhelpers.payload.execd.default_execd_dir')
59 @patch('os.listdir', return_value=['a','b','c'])60 @patch('os.listdir', return_value=['a','b','c'])
60 @patch('os.path.isdir', return_value=True)61 @patch('os.path.isdir', return_value=True)
61 def test_execd_module_list_with_dir(self, mock_isdir, mock_listdir,62 def test_execd_module_list_with_dir(self, mock_isdir, mock_listdir,
@@ -63,7 +64,7 @@
63 module_names = ['a','b','c']64 module_names = ['a','b','c']
64 mock_listdir.return_value = module_names65 mock_listdir.return_value = module_names
6566
66 modules = list(execd.execd_modules('foo'))67 modules = list(execd.execd_module_paths('foo'))
6768
68 expected = [os.path.join('foo', d) for d in module_names]69 expected = [os.path.join('foo', d) for d in module_names]
69 self.assertEqual(modules, expected)70 self.assertEqual(modules, expected)
@@ -73,16 +74,52 @@
73 mock_isdir.assert_has_calls([call(d) for d in expected])74 mock_isdir.assert_has_calls([call(d) for d in expected])
7475
7576
76 @patch('subprocess.check_call')
77 @patch('charmhelpers.contrib.charmsupport.hookenv.log')
78 @patch('os.path.isfile', return_value=True)77 @patch('os.path.isfile', return_value=True)
79 @patch('os.access', return_value=True)78 @patch('os.access', return_value=True)
80 @patch('charmhelpers.contrib.charmsupport.execd.execd_modules', return_value=['a','b'])79 @patch('charmhelpers.payload.execd.execd_module_paths')
81 def test_execd_run(self, mock_modules, mock_access, mock_isfile,80 def test_execd_submodule_list(self, modlist_, access_, isfile_):
82 mock_log, mock_call):81 module_list = ['a','b','c']
83 submodule = 'charm-foo'82 modlist_.return_value = module_list
8483 submodules = [s for s in execd.execd_submodule_paths('sm')]
85 execd.execd_run(submodule)84
8685 expected = [os.path.join(d, 'sm') for d in module_list]
87 paths = [os.path.join(m, submodule) for m in ('a','b')]86 self.assertEqual(submodules, expected)
88 mock_call.assert_has_calls([call(d, shell=True) for d in paths])87
88
89 @patch('subprocess.check_call')
90 @patch('charmhelpers.payload.execd.execd_submodule_paths')
91 def test_execd_run(self, submods_, call_):
92 submod_list = ['a','b','c']
93 submods_.return_value = submod_list
94 execd.execd_run('foo')
95
96 submods_.assert_called_with('foo', None)
97 call_.assert_has_calls([call(d, shell=True) for d in submod_list])
98
99
100 @patch('subprocess.check_call')
101 @patch('charmhelpers.payload.execd.execd_submodule_paths')
102 @patch('charmhelpers.core.hookenv.log')
103 def test_execd_run_logs_exception(self, log_, submods_, check_call_):
104 submod_list = ['a','b','c']
105 submods_.return_value = submod_list
106 err_msg = 'darn'
107 check_call_.side_effect = CalledProcessError(1, 'cmd', err_msg)
108
109 execd.execd_run('foo')
110 log_.assert_called_with(err_msg)
111
112
113 @patch('subprocess.check_call')
114 @patch('charmhelpers.payload.execd.execd_submodule_paths')
115 @patch('charmhelpers.core.hookenv.log')
116 @patch('sys.exit')
117 def test_execd_run_dies_with_return_code(self, exit_, log_, submods_,
118 check_call_):
119 submod_list = ['a','b','c']
120 submods_.return_value = submod_list
121 retcode = 9
122 check_call_.side_effect = CalledProcessError(retcode, 'cmd')
123
124 execd.execd_run('foo', die_on_error=True)
125 exit_.assert_called_with(retcode)

Subscribers

People subscribed via source and target branches