Merge lp:~1chb1n/charms/trusty/percona-cluster/liberty-prep into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next
- Trusty Tahr (14.04)
- liberty-prep
- Merge into next
Proposed by
Ryan Beisner
Status: | Merged |
---|---|
Merged at revision: | 72 |
Proposed branch: | lp:~1chb1n/charms/trusty/percona-cluster/liberty-prep |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next |
Diff against target: |
809 lines (+342/-111) 17 files modified
Makefile (+8/-9) hooks/charmhelpers/cli/__init__.py (+1/-5) hooks/charmhelpers/cli/commands.py (+4/-4) hooks/charmhelpers/cli/hookenv.py (+23/-0) hooks/charmhelpers/core/hookenv.py (+1/-20) hooks/charmhelpers/core/host.py (+89/-13) hooks/charmhelpers/core/hugepage.py (+62/-0) hooks/charmhelpers/core/services/helpers.py (+18/-2) hooks/charmhelpers/fetch/__init__.py (+8/-0) metadata.yaml (+1/-1) tests/00-setup (+16/-29) tests/basic_deployment.py (+1/-1) tests/charmhelpers/contrib/amulet/utils.py (+84/-21) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+2/-2) tests/tests.yaml (+19/-0) unit_tests/test_percona_hooks.py (+3/-2) unit_tests/test_percona_utils.py (+2/-2) |
To merge this branch: | bzr merge lp:~1chb1n/charms/trusty/percona-cluster/liberty-prep |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+269210@code.launchpad.net |
Commit message
Description of the change
Sync charm-helpers for liberty cloud archive support; Also update makefile, update c-h sync yaml, update tests dir, update amulet test dependencies - to be consistent with other os-charms. Clean up old existing lint in unit tests.
To post a comment you must log in.
Revision history for this message
Ryan Beisner (1chb1n) : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8757 percona-
LINT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6034 percona-
AMULET OK: passed
Build: http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2015-07-22 11:17:09 +0000 |
3 | +++ Makefile 2015-08-26 13:24:25 +0000 |
4 | @@ -3,18 +3,17 @@ |
5 | export PYTHONPATH := hooks |
6 | |
7 | lint: |
8 | - @flake8 --exclude hooks/charmhelpers hooks |
9 | + @flake8 --exclude hooks/charmhelpers,tests/charmhelpers \ |
10 | + hooks unit_tests tests |
11 | @charm proof |
12 | |
13 | -unit_test: |
14 | - @$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests |
15 | - |
16 | test: |
17 | + @# Bundletester expects unit tests here. |
18 | + @$(PYTHON) /usr/bin/nosetests -v --nologcapture --with-coverage unit_tests |
19 | + |
20 | +functional_test: |
21 | @echo Starting amulet tests... |
22 | - #NOTE(beisner): can remove -v after bug 1320357 is fixed |
23 | - # https://bugs.launchpad.net/amulet/+bug/1320357 |
24 | @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700 |
25 | - #echo "Tests disables; http://pad.lv/1446169" |
26 | |
27 | bin/charm_helpers_sync.py: |
28 | @mkdir -p bin |
29 | @@ -22,8 +21,8 @@ |
30 | > bin/charm_helpers_sync.py |
31 | |
32 | sync: bin/charm_helpers_sync.py |
33 | - @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers.yaml |
34 | + @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml |
35 | @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml |
36 | |
37 | -publish: lint |
38 | +publish: lint test |
39 | bzr push lp:charms/trusty/percona-cluster |
40 | |
41 | === renamed file 'charm-helpers.yaml' => 'charm-helpers-hooks.yaml' |
42 | === modified file 'hooks/charmhelpers/cli/__init__.py' |
43 | --- hooks/charmhelpers/cli/__init__.py 2015-08-03 14:52:57 +0000 |
44 | +++ hooks/charmhelpers/cli/__init__.py 2015-08-26 13:24:25 +0000 |
45 | @@ -152,15 +152,11 @@ |
46 | arguments = self.argument_parser.parse_args() |
47 | argspec = inspect.getargspec(arguments.func) |
48 | vargs = [] |
49 | - kwargs = {} |
50 | for arg in argspec.args: |
51 | vargs.append(getattr(arguments, arg)) |
52 | if argspec.varargs: |
53 | vargs.extend(getattr(arguments, argspec.varargs)) |
54 | - if argspec.keywords: |
55 | - for kwarg in argspec.keywords.items(): |
56 | - kwargs[kwarg] = getattr(arguments, kwarg) |
57 | - output = arguments.func(*vargs, **kwargs) |
58 | + output = arguments.func(*vargs) |
59 | if getattr(arguments.func, '_cli_test_command', False): |
60 | self.exit_code = 0 if output else 1 |
61 | output = '' |
62 | |
63 | === modified file 'hooks/charmhelpers/cli/commands.py' |
64 | --- hooks/charmhelpers/cli/commands.py 2015-08-03 14:52:57 +0000 |
65 | +++ hooks/charmhelpers/cli/commands.py 2015-08-26 13:24:25 +0000 |
66 | @@ -26,7 +26,7 @@ |
67 | """ |
68 | Import the sub-modules which have decorated subcommands to register with chlp. |
69 | """ |
70 | -import host # noqa |
71 | -import benchmark # noqa |
72 | -import unitdata # noqa |
73 | -from charmhelpers.core import hookenv # noqa |
74 | +from . import host # noqa |
75 | +from . import benchmark # noqa |
76 | +from . import unitdata # noqa |
77 | +from . import hookenv # noqa |
78 | |
79 | === added file 'hooks/charmhelpers/cli/hookenv.py' |
80 | --- hooks/charmhelpers/cli/hookenv.py 1970-01-01 00:00:00 +0000 |
81 | +++ hooks/charmhelpers/cli/hookenv.py 2015-08-26 13:24:25 +0000 |
82 | @@ -0,0 +1,23 @@ |
83 | +# Copyright 2014-2015 Canonical Limited. |
84 | +# |
85 | +# This file is part of charm-helpers. |
86 | +# |
87 | +# charm-helpers is free software: you can redistribute it and/or modify |
88 | +# it under the terms of the GNU Lesser General Public License version 3 as |
89 | +# published by the Free Software Foundation. |
90 | +# |
91 | +# charm-helpers is distributed in the hope that it will be useful, |
92 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
93 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
94 | +# GNU Lesser General Public License for more details. |
95 | +# |
96 | +# You should have received a copy of the GNU Lesser General Public License |
97 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
98 | + |
99 | +from . import cmdline |
100 | +from charmhelpers.core import hookenv |
101 | + |
102 | + |
103 | +cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped) |
104 | +cmdline.subcommand('service-name')(hookenv.service_name) |
105 | +cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped) |
106 | |
107 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
108 | --- hooks/charmhelpers/core/hookenv.py 2015-08-03 14:52:57 +0000 |
109 | +++ hooks/charmhelpers/core/hookenv.py 2015-08-26 13:24:25 +0000 |
110 | @@ -34,23 +34,6 @@ |
111 | import tempfile |
112 | from subprocess import CalledProcessError |
113 | |
114 | -try: |
115 | - from charmhelpers.cli import cmdline |
116 | -except ImportError as e: |
117 | - # due to the anti-pattern of partially synching charmhelpers directly |
118 | - # into charms, it's possible that charmhelpers.cli is not available; |
119 | - # if that's the case, they don't really care about using the cli anyway, |
120 | - # so mock it out |
121 | - if str(e) == 'No module named cli': |
122 | - class cmdline(object): |
123 | - @classmethod |
124 | - def subcommand(cls, *args, **kwargs): |
125 | - def _wrap(func): |
126 | - return func |
127 | - return _wrap |
128 | - else: |
129 | - raise |
130 | - |
131 | import six |
132 | if not six.PY3: |
133 | from UserDict import UserDict |
134 | @@ -91,6 +74,7 @@ |
135 | res = func(*args, **kwargs) |
136 | cache[key] = res |
137 | return res |
138 | + wrapper._wrapped = func |
139 | return wrapper |
140 | |
141 | |
142 | @@ -190,7 +174,6 @@ |
143 | return os.environ.get('JUJU_RELATION', None) |
144 | |
145 | |
146 | -@cmdline.subcommand() |
147 | @cached |
148 | def relation_id(relation_name=None, service_or_unit=None): |
149 | """The relation ID for the current or a specified relation""" |
150 | @@ -216,13 +199,11 @@ |
151 | return os.environ.get('JUJU_REMOTE_UNIT', None) |
152 | |
153 | |
154 | -@cmdline.subcommand() |
155 | def service_name(): |
156 | """The name service group this unit belongs to""" |
157 | return local_unit().split('/')[0] |
158 | |
159 | |
160 | -@cmdline.subcommand() |
161 | @cached |
162 | def remote_service_name(relid=None): |
163 | """The remote service name for a given relation-id (or the current relation)""" |
164 | |
165 | === modified file 'hooks/charmhelpers/core/host.py' |
166 | --- hooks/charmhelpers/core/host.py 2015-08-03 14:52:57 +0000 |
167 | +++ hooks/charmhelpers/core/host.py 2015-08-26 13:24:25 +0000 |
168 | @@ -72,7 +72,7 @@ |
169 | stopped = service_stop(service_name) |
170 | # XXX: Support systemd too |
171 | override_path = os.path.join( |
172 | - init_dir, '{}.conf.override'.format(service_name)) |
173 | + init_dir, '{}.override'.format(service_name)) |
174 | with open(override_path, 'w') as fh: |
175 | fh.write("manual\n") |
176 | return stopped |
177 | @@ -86,7 +86,7 @@ |
178 | if init_dir is None: |
179 | init_dir = "/etc/init" |
180 | override_path = os.path.join( |
181 | - init_dir, '{}.conf.override'.format(service_name)) |
182 | + init_dir, '{}.override'.format(service_name)) |
183 | if os.path.exists(override_path): |
184 | os.unlink(override_path) |
185 | started = service_start(service_name) |
186 | @@ -148,6 +148,16 @@ |
187 | return user_info |
188 | |
189 | |
190 | +def user_exists(username): |
191 | + """Check if a user exists""" |
192 | + try: |
193 | + pwd.getpwnam(username) |
194 | + user_exists = True |
195 | + except KeyError: |
196 | + user_exists = False |
197 | + return user_exists |
198 | + |
199 | + |
200 | def add_group(group_name, system_group=False): |
201 | """Add a group to the system""" |
202 | try: |
203 | @@ -280,6 +290,17 @@ |
204 | return system_mounts |
205 | |
206 | |
207 | +def fstab_mount(mountpoint): |
208 | + """Mount filesystem using fstab""" |
209 | + cmd_args = ['mount', mountpoint] |
210 | + try: |
211 | + subprocess.check_output(cmd_args) |
212 | + except subprocess.CalledProcessError as e: |
213 | + log('Error unmounting {}\n{}'.format(mountpoint, e.output)) |
214 | + return False |
215 | + return True |
216 | + |
217 | + |
218 | def file_hash(path, hash_type='md5'): |
219 | """ |
220 | Generate a hash checksum of the contents of 'path' or None if not found. |
221 | @@ -396,25 +417,80 @@ |
222 | return(''.join(random_chars)) |
223 | |
224 | |
225 | -def list_nics(nic_type): |
226 | +def is_phy_iface(interface): |
227 | + """Returns True if interface is not virtual, otherwise False.""" |
228 | + if interface: |
229 | + sys_net = '/sys/class/net' |
230 | + if os.path.isdir(sys_net): |
231 | + for iface in glob.glob(os.path.join(sys_net, '*')): |
232 | + if '/virtual/' in os.path.realpath(iface): |
233 | + continue |
234 | + |
235 | + if interface == os.path.basename(iface): |
236 | + return True |
237 | + |
238 | + return False |
239 | + |
240 | + |
241 | +def get_bond_master(interface): |
242 | + """Returns bond master if interface is bond slave otherwise None. |
243 | + |
244 | + NOTE: the provided interface is expected to be physical |
245 | + """ |
246 | + if interface: |
247 | + iface_path = '/sys/class/net/%s' % (interface) |
248 | + if os.path.exists(iface_path): |
249 | + if '/virtual/' in os.path.realpath(iface_path): |
250 | + return None |
251 | + |
252 | + master = os.path.join(iface_path, 'master') |
253 | + if os.path.exists(master): |
254 | + master = os.path.realpath(master) |
255 | + # make sure it is a bond master |
256 | + if os.path.exists(os.path.join(master, 'bonding')): |
257 | + return os.path.basename(master) |
258 | + |
259 | + return None |
260 | + |
261 | + |
262 | +def list_nics(nic_type=None): |
263 | '''Return a list of nics of given type(s)''' |
264 | if isinstance(nic_type, six.string_types): |
265 | int_types = [nic_type] |
266 | else: |
267 | int_types = nic_type |
268 | + |
269 | interfaces = [] |
270 | - for int_type in int_types: |
271 | - cmd = ['ip', 'addr', 'show', 'label', int_type + '*'] |
272 | + if nic_type: |
273 | + for int_type in int_types: |
274 | + cmd = ['ip', 'addr', 'show', 'label', int_type + '*'] |
275 | + ip_output = subprocess.check_output(cmd).decode('UTF-8') |
276 | + ip_output = ip_output.split('\n') |
277 | + ip_output = (line for line in ip_output if line) |
278 | + for line in ip_output: |
279 | + if line.split()[1].startswith(int_type): |
280 | + matched = re.search('.*: (' + int_type + |
281 | + r'[0-9]+\.[0-9]+)@.*', line) |
282 | + if matched: |
283 | + iface = matched.groups()[0] |
284 | + else: |
285 | + iface = line.split()[1].replace(":", "") |
286 | + |
287 | + if iface not in interfaces: |
288 | + interfaces.append(iface) |
289 | + else: |
290 | + cmd = ['ip', 'a'] |
291 | ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n') |
292 | - ip_output = (line for line in ip_output if line) |
293 | + ip_output = (line.strip() for line in ip_output if line) |
294 | + |
295 | + key = re.compile('^[0-9]+:\s+(.+):') |
296 | for line in ip_output: |
297 | - if line.split()[1].startswith(int_type): |
298 | - matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line) |
299 | - if matched: |
300 | - interface = matched.groups()[0] |
301 | - else: |
302 | - interface = line.split()[1].replace(":", "") |
303 | - interfaces.append(interface) |
304 | + matched = re.search(key, line) |
305 | + if matched: |
306 | + iface = matched.group(1) |
307 | + iface = iface.partition("@")[0] |
308 | + if iface not in interfaces: |
309 | + interfaces.append(iface) |
310 | |
311 | return interfaces |
312 | |
313 | |
314 | === added file 'hooks/charmhelpers/core/hugepage.py' |
315 | --- hooks/charmhelpers/core/hugepage.py 1970-01-01 00:00:00 +0000 |
316 | +++ hooks/charmhelpers/core/hugepage.py 2015-08-26 13:24:25 +0000 |
317 | @@ -0,0 +1,62 @@ |
318 | +# -*- coding: utf-8 -*- |
319 | + |
320 | +# Copyright 2014-2015 Canonical Limited. |
321 | +# |
322 | +# This file is part of charm-helpers. |
323 | +# |
324 | +# charm-helpers is free software: you can redistribute it and/or modify |
325 | +# it under the terms of the GNU Lesser General Public License version 3 as |
326 | +# published by the Free Software Foundation. |
327 | +# |
328 | +# charm-helpers is distributed in the hope that it will be useful, |
329 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
330 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
331 | +# GNU Lesser General Public License for more details. |
332 | +# |
333 | +# You should have received a copy of the GNU Lesser General Public License |
334 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
335 | + |
336 | +import yaml |
337 | +from charmhelpers.core import fstab |
338 | +from charmhelpers.core import sysctl |
339 | +from charmhelpers.core.host import ( |
340 | + add_group, |
341 | + add_user_to_group, |
342 | + fstab_mount, |
343 | + mkdir, |
344 | +) |
345 | + |
346 | + |
347 | +def hugepage_support(user, group='hugetlb', nr_hugepages=256, |
348 | + max_map_count=65536, mnt_point='/run/hugepages/kvm', |
349 | + pagesize='2MB', mount=True): |
350 | + """Enable hugepages on system. |
351 | + |
352 | + Args: |
353 | + user (str) -- Username to allow access to hugepages to |
354 | + group (str) -- Group name to own hugepages |
355 | + nr_hugepages (int) -- Number of pages to reserve |
356 | + max_map_count (int) -- Number of Virtual Memory Areas a process can own |
357 | + mnt_point (str) -- Directory to mount hugepages on |
358 | + pagesize (str) -- Size of hugepages |
359 | + mount (bool) -- Whether to Mount hugepages |
360 | + """ |
361 | + group_info = add_group(group) |
362 | + gid = group_info.gr_gid |
363 | + add_user_to_group(user, group) |
364 | + sysctl_settings = { |
365 | + 'vm.nr_hugepages': nr_hugepages, |
366 | + 'vm.max_map_count': max_map_count, |
367 | + 'vm.hugetlb_shm_group': gid, |
368 | + } |
369 | + sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf') |
370 | + mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) |
371 | + lfstab = fstab.Fstab() |
372 | + fstab_entry = lfstab.get_entry_by_attr('mountpoint', mnt_point) |
373 | + if fstab_entry: |
374 | + lfstab.remove_entry(fstab_entry) |
375 | + entry = lfstab.Entry('nodev', mnt_point, 'hugetlbfs', |
376 | + 'mode=1770,gid={},pagesize={}'.format(gid, pagesize), 0, 0) |
377 | + lfstab.add_entry(entry) |
378 | + if mount: |
379 | + fstab_mount(mnt_point) |
380 | |
381 | === modified file 'hooks/charmhelpers/core/services/helpers.py' |
382 | --- hooks/charmhelpers/core/services/helpers.py 2015-08-03 14:52:57 +0000 |
383 | +++ hooks/charmhelpers/core/services/helpers.py 2015-08-26 13:24:25 +0000 |
384 | @@ -16,7 +16,9 @@ |
385 | |
386 | import os |
387 | import yaml |
388 | + |
389 | from charmhelpers.core import hookenv |
390 | +from charmhelpers.core import host |
391 | from charmhelpers.core import templating |
392 | |
393 | from charmhelpers.core.services.base import ManagerCallback |
394 | @@ -240,27 +242,41 @@ |
395 | |
396 | :param str source: The template source file, relative to |
397 | `$CHARM_DIR/templates` |
398 | + |
399 | :param str target: The target to write the rendered template to |
400 | :param str owner: The owner of the rendered file |
401 | :param str group: The group of the rendered file |
402 | :param int perms: The permissions of the rendered file |
403 | - |
404 | + :param partial on_change_action: functools partial to be executed when |
405 | + rendered file changes |
406 | """ |
407 | def __init__(self, source, target, |
408 | - owner='root', group='root', perms=0o444): |
409 | + owner='root', group='root', perms=0o444, |
410 | + on_change_action=None): |
411 | self.source = source |
412 | self.target = target |
413 | self.owner = owner |
414 | self.group = group |
415 | self.perms = perms |
416 | + self.on_change_action = on_change_action |
417 | |
418 | def __call__(self, manager, service_name, event_name): |
419 | + pre_checksum = '' |
420 | + if self.on_change_action and os.path.isfile(self.target): |
421 | + pre_checksum = host.file_hash(self.target) |
422 | service = manager.get_service(service_name) |
423 | context = {} |
424 | for ctx in service.get('required_data', []): |
425 | context.update(ctx) |
426 | templating.render(self.source, self.target, context, |
427 | self.owner, self.group, self.perms) |
428 | + if self.on_change_action: |
429 | + if pre_checksum == host.file_hash(self.target): |
430 | + hookenv.log( |
431 | + 'No change detected: {}'.format(self.target), |
432 | + hookenv.DEBUG) |
433 | + else: |
434 | + self.on_change_action() |
435 | |
436 | |
437 | # Convenience aliases for templates |
438 | |
439 | === modified file 'hooks/charmhelpers/fetch/__init__.py' |
440 | --- hooks/charmhelpers/fetch/__init__.py 2015-08-03 14:52:57 +0000 |
441 | +++ hooks/charmhelpers/fetch/__init__.py 2015-08-26 13:24:25 +0000 |
442 | @@ -90,6 +90,14 @@ |
443 | 'kilo/proposed': 'trusty-proposed/kilo', |
444 | 'trusty-kilo/proposed': 'trusty-proposed/kilo', |
445 | 'trusty-proposed/kilo': 'trusty-proposed/kilo', |
446 | + # Liberty |
447 | + 'liberty': 'trusty-updates/liberty', |
448 | + 'trusty-liberty': 'trusty-updates/liberty', |
449 | + 'trusty-liberty/updates': 'trusty-updates/liberty', |
450 | + 'trusty-updates/liberty': 'trusty-updates/liberty', |
451 | + 'liberty/proposed': 'trusty-proposed/liberty', |
452 | + 'trusty-liberty/proposed': 'trusty-proposed/liberty', |
453 | + 'trusty-proposed/liberty': 'trusty-proposed/liberty', |
454 | } |
455 | |
456 | # The order of this list is very important. Handlers should be listed in from |
457 | |
458 | === modified file 'metadata.yaml' |
459 | --- metadata.yaml 2013-09-19 15:58:28 +0000 |
460 | +++ metadata.yaml 2015-08-26 13:24:25 +0000 |
461 | @@ -5,7 +5,7 @@ |
462 | Percona XtraDB Cluster provides an active/active MySQL |
463 | compatible alternative implemented using the Galera |
464 | synchronous replication extensions. |
465 | -categories: |
466 | +tags: |
467 | - databases |
468 | provides: |
469 | db: |
470 | |
471 | === renamed file 'tests/00-setup.sh' => 'tests/00-setup' |
472 | --- tests/00-setup.sh 2015-04-15 12:11:46 +0000 |
473 | +++ tests/00-setup 2015-08-26 13:24:25 +0000 |
474 | @@ -1,29 +1,16 @@ |
475 | -#!/bin/bash -x |
476 | -# The script installs amulet and other tools needed for the amulet tests. |
477 | - |
478 | -# Get the status of the amulet package, this returns 0 of package is installed. |
479 | -dpkg -s amulet |
480 | -if [ $? -ne 0 ]; then |
481 | - # Install the Amulet testing harness. |
482 | - sudo add-apt-repository -y ppa:juju/stable |
483 | - sudo apt-get update |
484 | - sudo apt-get install -y -q amulet juju-core charm-tools |
485 | -fi |
486 | - |
487 | - |
488 | -PACKAGES="python3 python3-yaml" |
489 | -for pkg in $PACKAGES; do |
490 | - dpkg -s python3 |
491 | - if [ $? -ne 0 ]; then |
492 | - sudo apt-get install -y -q $pkg |
493 | - fi |
494 | -done |
495 | - |
496 | - |
497 | -#if [ ! -f "$(dirname $0)/../local.yaml" ]; then |
498 | -# echo "To run these amulet tests a vip is needed, create a file called \ |
499 | -#local.yaml in the charm dir, this file must contain a 'vip', if you're \ |
500 | -#using the local provider with lxc you could use a free IP from the range \ |
501 | -#10.0.3.0/24" |
502 | -# exit 1 |
503 | -#fi |
504 | +#!/bin/bash |
505 | + |
506 | +set -ex |
507 | + |
508 | +sudo add-apt-repository --yes ppa:juju/stable |
509 | +sudo apt-get update --yes |
510 | +sudo apt-get install --yes amulet \ |
511 | + python-cinderclient \ |
512 | + python-distro-info \ |
513 | + python-glanceclient \ |
514 | + python-heatclient \ |
515 | + python-keystoneclient \ |
516 | + python-neutronclient \ |
517 | + python-novaclient \ |
518 | + python-pika \ |
519 | + python-swiftclient |
520 | |
521 | === modified file 'tests/basic_deployment.py' |
522 | --- tests/basic_deployment.py 2015-07-22 11:17:09 +0000 |
523 | +++ tests/basic_deployment.py 2015-08-26 13:24:25 +0000 |
524 | @@ -100,7 +100,7 @@ |
525 | |
526 | resources = ['res_mysql_vip'] |
527 | resources += ['res_mysql_monitor:%d' % |
528 | - i for i in range(self.units)] |
529 | + m for m in range(self.units)] |
530 | |
531 | assert sorted(self.get_pcmkr_resources()) == sorted(resources) |
532 | else: |
533 | |
534 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' |
535 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-08-03 14:52:57 +0000 |
536 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-08-26 13:24:25 +0000 |
537 | @@ -14,17 +14,23 @@ |
538 | # You should have received a copy of the GNU Lesser General Public License |
539 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
540 | |
541 | -import amulet |
542 | -import ConfigParser |
543 | -import distro_info |
544 | import io |
545 | +import json |
546 | import logging |
547 | import os |
548 | import re |
549 | -import six |
550 | +import subprocess |
551 | import sys |
552 | import time |
553 | -import urlparse |
554 | + |
555 | +import amulet |
556 | +import distro_info |
557 | +import six |
558 | +from six.moves import configparser |
559 | +if six.PY3: |
560 | + from urllib import parse as urlparse |
561 | +else: |
562 | + import urlparse |
563 | |
564 | |
565 | class AmuletUtils(object): |
566 | @@ -142,19 +148,23 @@ |
567 | |
568 | for service_name in services_list: |
569 | if (self.ubuntu_releases.index(release) >= systemd_switch or |
570 | - service_name == "rabbitmq-server"): |
571 | - # init is systemd |
572 | + service_name in ['rabbitmq-server', 'apache2']): |
573 | + # init is systemd (or regular sysv) |
574 | cmd = 'sudo service {} status'.format(service_name) |
575 | + output, code = sentry_unit.run(cmd) |
576 | + service_running = code == 0 |
577 | elif self.ubuntu_releases.index(release) < systemd_switch: |
578 | # init is upstart |
579 | cmd = 'sudo status {}'.format(service_name) |
580 | + output, code = sentry_unit.run(cmd) |
581 | + service_running = code == 0 and "start/running" in output |
582 | |
583 | - output, code = sentry_unit.run(cmd) |
584 | self.log.debug('{} `{}` returned ' |
585 | '{}'.format(sentry_unit.info['unit_name'], |
586 | cmd, code)) |
587 | - if code != 0: |
588 | - return "command `{}` returned {}".format(cmd, str(code)) |
589 | + if not service_running: |
590 | + return u"command `{}` returned {} {}".format( |
591 | + cmd, output, str(code)) |
592 | return None |
593 | |
594 | def _get_config(self, unit, filename): |
595 | @@ -164,7 +174,7 @@ |
596 | # NOTE(beisner): by default, ConfigParser does not handle options |
597 | # with no value, such as the flags used in the mysql my.cnf file. |
598 | # https://bugs.python.org/issue7005 |
599 | - config = ConfigParser.ConfigParser(allow_no_value=True) |
600 | + config = configparser.ConfigParser(allow_no_value=True) |
601 | config.readfp(io.StringIO(file_contents)) |
602 | return config |
603 | |
604 | @@ -450,15 +460,20 @@ |
605 | cmd, code, output)) |
606 | return None |
607 | |
608 | - def get_process_id_list(self, sentry_unit, process_name): |
609 | + def get_process_id_list(self, sentry_unit, process_name, |
610 | + expect_success=True): |
611 | """Get a list of process ID(s) from a single sentry juju unit |
612 | for a single process name. |
613 | |
614 | - :param sentry_unit: Pointer to amulet sentry instance (juju unit) |
615 | + :param sentry_unit: Amulet sentry instance (juju unit) |
616 | :param process_name: Process name |
617 | + :param expect_success: If False, expect the PID to be missing, |
618 | + raise if it is present. |
619 | :returns: List of process IDs |
620 | """ |
621 | - cmd = 'pidof {}'.format(process_name) |
622 | + cmd = 'pidof -x {}'.format(process_name) |
623 | + if not expect_success: |
624 | + cmd += " || exit 0 && exit 1" |
625 | output, code = sentry_unit.run(cmd) |
626 | if code != 0: |
627 | msg = ('{} `{}` returned {} ' |
628 | @@ -467,14 +482,23 @@ |
629 | amulet.raise_status(amulet.FAIL, msg=msg) |
630 | return str(output).split() |
631 | |
632 | - def get_unit_process_ids(self, unit_processes): |
633 | + def get_unit_process_ids(self, unit_processes, expect_success=True): |
634 | """Construct a dict containing unit sentries, process names, and |
635 | - process IDs.""" |
636 | + process IDs. |
637 | + |
638 | + :param unit_processes: A dictionary of Amulet sentry instance |
639 | + to list of process names. |
640 | + :param expect_success: if False expect the processes to not be |
641 | + running, raise if they are. |
642 | + :returns: Dictionary of Amulet sentry instance to dictionary |
643 | + of process names to PIDs. |
644 | + """ |
645 | pid_dict = {} |
646 | - for sentry_unit, process_list in unit_processes.iteritems(): |
647 | + for sentry_unit, process_list in six.iteritems(unit_processes): |
648 | pid_dict[sentry_unit] = {} |
649 | for process in process_list: |
650 | - pids = self.get_process_id_list(sentry_unit, process) |
651 | + pids = self.get_process_id_list( |
652 | + sentry_unit, process, expect_success=expect_success) |
653 | pid_dict[sentry_unit].update({process: pids}) |
654 | return pid_dict |
655 | |
656 | @@ -488,7 +512,7 @@ |
657 | return ('Unit count mismatch. expected, actual: {}, ' |
658 | '{} '.format(len(expected), len(actual))) |
659 | |
660 | - for (e_sentry, e_proc_names) in expected.iteritems(): |
661 | + for (e_sentry, e_proc_names) in six.iteritems(expected): |
662 | e_sentry_name = e_sentry.info['unit_name'] |
663 | if e_sentry in actual.keys(): |
664 | a_proc_names = actual[e_sentry] |
665 | @@ -507,11 +531,23 @@ |
666 | '{}'.format(e_proc_name, a_proc_name)) |
667 | |
668 | a_pids_length = len(a_pids) |
669 | - if e_pids_length != a_pids_length: |
670 | - return ('PID count mismatch. {} ({}) expected, actual: ' |
671 | + fail_msg = ('PID count mismatch. {} ({}) expected, actual: ' |
672 | '{}, {} ({})'.format(e_sentry_name, e_proc_name, |
673 | e_pids_length, a_pids_length, |
674 | a_pids)) |
675 | + |
676 | + # If expected is not bool, ensure PID quantities match |
677 | + if not isinstance(e_pids_length, bool) and \ |
678 | + a_pids_length != e_pids_length: |
679 | + return fail_msg |
680 | + # If expected is bool True, ensure 1 or more PIDs exist |
681 | + elif isinstance(e_pids_length, bool) and \ |
682 | + e_pids_length is True and a_pids_length < 1: |
683 | + return fail_msg |
684 | + # If expected is bool False, ensure 0 PIDs exist |
685 | + elif isinstance(e_pids_length, bool) and \ |
686 | + e_pids_length is False and a_pids_length != 0: |
687 | + return fail_msg |
688 | else: |
689 | self.log.debug('PID check OK: {} {} {}: ' |
690 | '{}'.format(e_sentry_name, e_proc_name, |
691 | @@ -531,3 +567,30 @@ |
692 | return 'Dicts within list are not identical' |
693 | |
694 | return None |
695 | + |
696 | + def run_action(self, unit_sentry, action, |
697 | + _check_output=subprocess.check_output): |
698 | + """Run the named action on a given unit sentry. |
699 | + |
700 | + _check_output parameter is used for dependency injection. |
701 | + |
702 | + @return action_id. |
703 | + """ |
704 | + unit_id = unit_sentry.info["unit_name"] |
705 | + command = ["juju", "action", "do", "--format=json", unit_id, action] |
706 | + self.log.info("Running command: %s\n" % " ".join(command)) |
707 | + output = _check_output(command, universal_newlines=True) |
708 | + data = json.loads(output) |
709 | + action_id = data[u'Action queued with id'] |
710 | + return action_id |
711 | + |
712 | + def wait_on_action(self, action_id, _check_output=subprocess.check_output): |
713 | + """Wait for a given action, returning if it completed or not. |
714 | + |
715 | + _check_output parameter is used for dependency injection. |
716 | + """ |
717 | + command = ["juju", "action", "fetch", "--format=json", "--wait=0", |
718 | + action_id] |
719 | + output = _check_output(command, universal_newlines=True) |
720 | + data = json.loads(output) |
721 | + return data.get(u"status") == "completed" |
722 | |
723 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
724 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-03 14:52:57 +0000 |
725 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-26 13:24:25 +0000 |
726 | @@ -44,7 +44,7 @@ |
727 | Determine if the local branch being tested is derived from its |
728 | stable or next (dev) branch, and based on this, use the corresonding |
729 | stable or next branches for the other_services.""" |
730 | - base_charms = ['mysql', 'mongodb'] |
731 | + base_charms = ['mysql', 'mongodb', 'nrpe'] |
732 | |
733 | if self.series in ['precise', 'trusty']: |
734 | base_series = self.series |
735 | @@ -81,7 +81,7 @@ |
736 | 'ceph-osd', 'ceph-radosgw'] |
737 | # Most OpenStack subordinate charms do not expose an origin option |
738 | # as that is controlled by the principle. |
739 | - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] |
740 | + ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] |
741 | |
742 | if self.openstack: |
743 | for svc in services: |
744 | |
745 | === added file 'tests/tests.yaml' |
746 | --- tests/tests.yaml 1970-01-01 00:00:00 +0000 |
747 | +++ tests/tests.yaml 2015-08-26 13:24:25 +0000 |
748 | @@ -0,0 +1,19 @@ |
749 | +bootstrap: true |
750 | +reset: true |
751 | +virtualenv: true |
752 | +makefile: |
753 | + - lint |
754 | + - test |
755 | +sources: |
756 | + - ppa:juju/stable |
757 | +packages: |
758 | + - amulet |
759 | + - python-cinderclient |
760 | + - python-distro-info |
761 | + - python-glanceclient |
762 | + - python-heatclient |
763 | + - python-keystoneclient |
764 | + - python-neutronclient |
765 | + - python-novaclient |
766 | + - python-pika |
767 | + - python-swiftclient |
768 | |
769 | === modified file 'unit_tests/test_percona_hooks.py' |
770 | --- unit_tests/test_percona_hooks.py 2015-03-17 14:37:44 +0000 |
771 | +++ unit_tests/test_percona_hooks.py 2015-08-26 13:24:25 +0000 |
772 | @@ -36,6 +36,7 @@ |
773 | self.get_db_helper.return_value = helper |
774 | self.test_config.set('vip', '10.0.3.3') |
775 | self.test_config.set('sst-password', password) |
776 | + |
777 | def f(k): |
778 | return self.test_config.get(k) |
779 | |
780 | @@ -48,12 +49,12 @@ |
781 | 'cidr_netmask="24" ' |
782 | 'nic="eth0"'), |
783 | 'res_mysql_monitor': |
784 | - hooks.RES_MONITOR_PARAMS % {'sstpass': 'ubuntu'}} |
785 | + hooks.RES_MONITOR_PARAMS % {'sstpass': 'ubuntu'}} |
786 | groups = {'grp_percona_cluster': 'res_mysql_vip'} |
787 | |
788 | clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'} |
789 | |
790 | - colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'} |
791 | + colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'} # noqa |
792 | |
793 | locations = {'loc_percona_cluster': |
794 | 'grp_percona_cluster rule inf: writable eq 1'} |
795 | |
796 | === modified file 'unit_tests/test_percona_utils.py' |
797 | --- unit_tests/test_percona_utils.py 2015-07-29 10:21:16 +0000 |
798 | +++ unit_tests/test_percona_utils.py 2015-08-26 13:24:25 +0000 |
799 | @@ -111,8 +111,8 @@ |
800 | mock_log, mock_get_ipv6_addr): |
801 | ipv6addr = '2001:db8:1:0:f816:3eff:fe79:cd' |
802 | mock_get_ipv6_addr.return_value = [ipv6addr] |
803 | - mock_rel_ids.return_value = [1,2] |
804 | - mock_rel_units.return_value = [3,4] |
805 | + mock_rel_ids.return_value = [1, 2] |
806 | + mock_rel_units.return_value = [3, 4] |
807 | mock_get_host_ip.return_value = 'hostA' |
808 | |
809 | def _mock_rel_get(*args, **kwargs): |
charm_unit_test #8088 percona- cluster- next for 1chb1n mp269210
UNIT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_unit_ test/8088/