Merge lp:~1chb1n/charms/trusty/percona-cluster/liberty-prep into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/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
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+269210@code.launchpad.net

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 :

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/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #8757 percona-cluster-next for 1chb1n mp269210
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/8757/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6034 percona-cluster-next for 1chb1n mp269210
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6034/

Revision history for this message
Liam Young (gnuoy) wrote :

Approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2015-07-22 11:17:09 +0000
+++ Makefile 2015-08-26 13:24:25 +0000
@@ -3,18 +3,17 @@
3export PYTHONPATH := hooks3export PYTHONPATH := hooks
44
5lint:5lint:
6 @flake8 --exclude hooks/charmhelpers hooks6 @flake8 --exclude hooks/charmhelpers,tests/charmhelpers \
7 hooks unit_tests tests
7 @charm proof8 @charm proof
89
9unit_test:
10 @$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests
11
12test:10test:
11 @# Bundletester expects unit tests here.
12 @$(PYTHON) /usr/bin/nosetests -v --nologcapture --with-coverage unit_tests
13
14functional_test:
13 @echo Starting amulet tests...15 @echo Starting amulet tests...
14 #NOTE(beisner): can remove -v after bug 1320357 is fixed
15 # https://bugs.launchpad.net/amulet/+bug/1320357
16 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 270016 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
17 #echo "Tests disables; http://pad.lv/1446169"
1817
19bin/charm_helpers_sync.py:18bin/charm_helpers_sync.py:
20 @mkdir -p bin19 @mkdir -p bin
@@ -22,8 +21,8 @@
22 > bin/charm_helpers_sync.py21 > bin/charm_helpers_sync.py
2322
24sync: bin/charm_helpers_sync.py23sync: bin/charm_helpers_sync.py
25 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers.yaml24 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
26 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml25 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
2726
28publish: lint27publish: lint test
29 bzr push lp:charms/trusty/percona-cluster28 bzr push lp:charms/trusty/percona-cluster
3029
=== renamed file 'charm-helpers.yaml' => 'charm-helpers-hooks.yaml'
=== modified file 'hooks/charmhelpers/cli/__init__.py'
--- hooks/charmhelpers/cli/__init__.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/cli/__init__.py 2015-08-26 13:24:25 +0000
@@ -152,15 +152,11 @@
152 arguments = self.argument_parser.parse_args()152 arguments = self.argument_parser.parse_args()
153 argspec = inspect.getargspec(arguments.func)153 argspec = inspect.getargspec(arguments.func)
154 vargs = []154 vargs = []
155 kwargs = {}
156 for arg in argspec.args:155 for arg in argspec.args:
157 vargs.append(getattr(arguments, arg))156 vargs.append(getattr(arguments, arg))
158 if argspec.varargs:157 if argspec.varargs:
159 vargs.extend(getattr(arguments, argspec.varargs))158 vargs.extend(getattr(arguments, argspec.varargs))
160 if argspec.keywords:159 output = arguments.func(*vargs)
161 for kwarg in argspec.keywords.items():
162 kwargs[kwarg] = getattr(arguments, kwarg)
163 output = arguments.func(*vargs, **kwargs)
164 if getattr(arguments.func, '_cli_test_command', False):160 if getattr(arguments.func, '_cli_test_command', False):
165 self.exit_code = 0 if output else 1161 self.exit_code = 0 if output else 1
166 output = ''162 output = ''
167163
=== modified file 'hooks/charmhelpers/cli/commands.py'
--- hooks/charmhelpers/cli/commands.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/cli/commands.py 2015-08-26 13:24:25 +0000
@@ -26,7 +26,7 @@
26"""26"""
27Import the sub-modules which have decorated subcommands to register with chlp.27Import the sub-modules which have decorated subcommands to register with chlp.
28"""28"""
29import host # noqa29from . import host # noqa
30import benchmark # noqa30from . import benchmark # noqa
31import unitdata # noqa31from . import unitdata # noqa
32from charmhelpers.core import hookenv # noqa32from . import hookenv # noqa
3333
=== added file 'hooks/charmhelpers/cli/hookenv.py'
--- hooks/charmhelpers/cli/hookenv.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/cli/hookenv.py 2015-08-26 13:24:25 +0000
@@ -0,0 +1,23 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17from . import cmdline
18from charmhelpers.core import hookenv
19
20
21cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped)
22cmdline.subcommand('service-name')(hookenv.service_name)
23cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped)
024
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/core/hookenv.py 2015-08-26 13:24:25 +0000
@@ -34,23 +34,6 @@
34import tempfile34import tempfile
35from subprocess import CalledProcessError35from subprocess import CalledProcessError
3636
37try:
38 from charmhelpers.cli import cmdline
39except ImportError as e:
40 # due to the anti-pattern of partially synching charmhelpers directly
41 # into charms, it's possible that charmhelpers.cli is not available;
42 # if that's the case, they don't really care about using the cli anyway,
43 # so mock it out
44 if str(e) == 'No module named cli':
45 class cmdline(object):
46 @classmethod
47 def subcommand(cls, *args, **kwargs):
48 def _wrap(func):
49 return func
50 return _wrap
51 else:
52 raise
53
54import six37import six
55if not six.PY3:38if not six.PY3:
56 from UserDict import UserDict39 from UserDict import UserDict
@@ -91,6 +74,7 @@
91 res = func(*args, **kwargs)74 res = func(*args, **kwargs)
92 cache[key] = res75 cache[key] = res
93 return res76 return res
77 wrapper._wrapped = func
94 return wrapper78 return wrapper
9579
9680
@@ -190,7 +174,6 @@
190 return os.environ.get('JUJU_RELATION', None)174 return os.environ.get('JUJU_RELATION', None)
191175
192176
193@cmdline.subcommand()
194@cached177@cached
195def relation_id(relation_name=None, service_or_unit=None):178def relation_id(relation_name=None, service_or_unit=None):
196 """The relation ID for the current or a specified relation"""179 """The relation ID for the current or a specified relation"""
@@ -216,13 +199,11 @@
216 return os.environ.get('JUJU_REMOTE_UNIT', None)199 return os.environ.get('JUJU_REMOTE_UNIT', None)
217200
218201
219@cmdline.subcommand()
220def service_name():202def service_name():
221 """The name service group this unit belongs to"""203 """The name service group this unit belongs to"""
222 return local_unit().split('/')[0]204 return local_unit().split('/')[0]
223205
224206
225@cmdline.subcommand()
226@cached207@cached
227def remote_service_name(relid=None):208def remote_service_name(relid=None):
228 """The remote service name for a given relation-id (or the current relation)"""209 """The remote service name for a given relation-id (or the current relation)"""
229210
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/core/host.py 2015-08-26 13:24:25 +0000
@@ -72,7 +72,7 @@
72 stopped = service_stop(service_name)72 stopped = service_stop(service_name)
73 # XXX: Support systemd too73 # XXX: Support systemd too
74 override_path = os.path.join(74 override_path = os.path.join(
75 init_dir, '{}.conf.override'.format(service_name))75 init_dir, '{}.override'.format(service_name))
76 with open(override_path, 'w') as fh:76 with open(override_path, 'w') as fh:
77 fh.write("manual\n")77 fh.write("manual\n")
78 return stopped78 return stopped
@@ -86,7 +86,7 @@
86 if init_dir is None:86 if init_dir is None:
87 init_dir = "/etc/init"87 init_dir = "/etc/init"
88 override_path = os.path.join(88 override_path = os.path.join(
89 init_dir, '{}.conf.override'.format(service_name))89 init_dir, '{}.override'.format(service_name))
90 if os.path.exists(override_path):90 if os.path.exists(override_path):
91 os.unlink(override_path)91 os.unlink(override_path)
92 started = service_start(service_name)92 started = service_start(service_name)
@@ -148,6 +148,16 @@
148 return user_info148 return user_info
149149
150150
151def user_exists(username):
152 """Check if a user exists"""
153 try:
154 pwd.getpwnam(username)
155 user_exists = True
156 except KeyError:
157 user_exists = False
158 return user_exists
159
160
151def add_group(group_name, system_group=False):161def add_group(group_name, system_group=False):
152 """Add a group to the system"""162 """Add a group to the system"""
153 try:163 try:
@@ -280,6 +290,17 @@
280 return system_mounts290 return system_mounts
281291
282292
293def fstab_mount(mountpoint):
294 """Mount filesystem using fstab"""
295 cmd_args = ['mount', mountpoint]
296 try:
297 subprocess.check_output(cmd_args)
298 except subprocess.CalledProcessError as e:
299 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
300 return False
301 return True
302
303
283def file_hash(path, hash_type='md5'):304def file_hash(path, hash_type='md5'):
284 """305 """
285 Generate a hash checksum of the contents of 'path' or None if not found.306 Generate a hash checksum of the contents of 'path' or None if not found.
@@ -396,25 +417,80 @@
396 return(''.join(random_chars))417 return(''.join(random_chars))
397418
398419
399def list_nics(nic_type):420def is_phy_iface(interface):
421 """Returns True if interface is not virtual, otherwise False."""
422 if interface:
423 sys_net = '/sys/class/net'
424 if os.path.isdir(sys_net):
425 for iface in glob.glob(os.path.join(sys_net, '*')):
426 if '/virtual/' in os.path.realpath(iface):
427 continue
428
429 if interface == os.path.basename(iface):
430 return True
431
432 return False
433
434
435def get_bond_master(interface):
436 """Returns bond master if interface is bond slave otherwise None.
437
438 NOTE: the provided interface is expected to be physical
439 """
440 if interface:
441 iface_path = '/sys/class/net/%s' % (interface)
442 if os.path.exists(iface_path):
443 if '/virtual/' in os.path.realpath(iface_path):
444 return None
445
446 master = os.path.join(iface_path, 'master')
447 if os.path.exists(master):
448 master = os.path.realpath(master)
449 # make sure it is a bond master
450 if os.path.exists(os.path.join(master, 'bonding')):
451 return os.path.basename(master)
452
453 return None
454
455
456def list_nics(nic_type=None):
400 '''Return a list of nics of given type(s)'''457 '''Return a list of nics of given type(s)'''
401 if isinstance(nic_type, six.string_types):458 if isinstance(nic_type, six.string_types):
402 int_types = [nic_type]459 int_types = [nic_type]
403 else:460 else:
404 int_types = nic_type461 int_types = nic_type
462
405 interfaces = []463 interfaces = []
406 for int_type in int_types:464 if nic_type:
407 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']465 for int_type in int_types:
466 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
467 ip_output = subprocess.check_output(cmd).decode('UTF-8')
468 ip_output = ip_output.split('\n')
469 ip_output = (line for line in ip_output if line)
470 for line in ip_output:
471 if line.split()[1].startswith(int_type):
472 matched = re.search('.*: (' + int_type +
473 r'[0-9]+\.[0-9]+)@.*', line)
474 if matched:
475 iface = matched.groups()[0]
476 else:
477 iface = line.split()[1].replace(":", "")
478
479 if iface not in interfaces:
480 interfaces.append(iface)
481 else:
482 cmd = ['ip', 'a']
408 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')483 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
409 ip_output = (line for line in ip_output if line)484 ip_output = (line.strip() for line in ip_output if line)
485
486 key = re.compile('^[0-9]+:\s+(.+):')
410 for line in ip_output:487 for line in ip_output:
411 if line.split()[1].startswith(int_type):488 matched = re.search(key, line)
412 matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)489 if matched:
413 if matched:490 iface = matched.group(1)
414 interface = matched.groups()[0]491 iface = iface.partition("@")[0]
415 else:492 if iface not in interfaces:
416 interface = line.split()[1].replace(":", "")493 interfaces.append(iface)
417 interfaces.append(interface)
418494
419 return interfaces495 return interfaces
420496
421497
=== added file 'hooks/charmhelpers/core/hugepage.py'
--- hooks/charmhelpers/core/hugepage.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/hugepage.py 2015-08-26 13:24:25 +0000
@@ -0,0 +1,62 @@
1# -*- coding: utf-8 -*-
2
3# Copyright 2014-2015 Canonical Limited.
4#
5# This file is part of charm-helpers.
6#
7# charm-helpers is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License version 3 as
9# published by the Free Software Foundation.
10#
11# charm-helpers is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
18
19import yaml
20from charmhelpers.core import fstab
21from charmhelpers.core import sysctl
22from charmhelpers.core.host import (
23 add_group,
24 add_user_to_group,
25 fstab_mount,
26 mkdir,
27)
28
29
30def hugepage_support(user, group='hugetlb', nr_hugepages=256,
31 max_map_count=65536, mnt_point='/run/hugepages/kvm',
32 pagesize='2MB', mount=True):
33 """Enable hugepages on system.
34
35 Args:
36 user (str) -- Username to allow access to hugepages to
37 group (str) -- Group name to own hugepages
38 nr_hugepages (int) -- Number of pages to reserve
39 max_map_count (int) -- Number of Virtual Memory Areas a process can own
40 mnt_point (str) -- Directory to mount hugepages on
41 pagesize (str) -- Size of hugepages
42 mount (bool) -- Whether to Mount hugepages
43 """
44 group_info = add_group(group)
45 gid = group_info.gr_gid
46 add_user_to_group(user, group)
47 sysctl_settings = {
48 'vm.nr_hugepages': nr_hugepages,
49 'vm.max_map_count': max_map_count,
50 'vm.hugetlb_shm_group': gid,
51 }
52 sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')
53 mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)
54 lfstab = fstab.Fstab()
55 fstab_entry = lfstab.get_entry_by_attr('mountpoint', mnt_point)
56 if fstab_entry:
57 lfstab.remove_entry(fstab_entry)
58 entry = lfstab.Entry('nodev', mnt_point, 'hugetlbfs',
59 'mode=1770,gid={},pagesize={}'.format(gid, pagesize), 0, 0)
60 lfstab.add_entry(entry)
61 if mount:
62 fstab_mount(mnt_point)
063
=== modified file 'hooks/charmhelpers/core/services/helpers.py'
--- hooks/charmhelpers/core/services/helpers.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/core/services/helpers.py 2015-08-26 13:24:25 +0000
@@ -16,7 +16,9 @@
1616
17import os17import os
18import yaml18import yaml
19
19from charmhelpers.core import hookenv20from charmhelpers.core import hookenv
21from charmhelpers.core import host
20from charmhelpers.core import templating22from charmhelpers.core import templating
2123
22from charmhelpers.core.services.base import ManagerCallback24from charmhelpers.core.services.base import ManagerCallback
@@ -240,27 +242,41 @@
240242
241 :param str source: The template source file, relative to243 :param str source: The template source file, relative to
242 `$CHARM_DIR/templates`244 `$CHARM_DIR/templates`
245
243 :param str target: The target to write the rendered template to246 :param str target: The target to write the rendered template to
244 :param str owner: The owner of the rendered file247 :param str owner: The owner of the rendered file
245 :param str group: The group of the rendered file248 :param str group: The group of the rendered file
246 :param int perms: The permissions of the rendered file249 :param int perms: The permissions of the rendered file
247250 :param partial on_change_action: functools partial to be executed when
251 rendered file changes
248 """252 """
249 def __init__(self, source, target,253 def __init__(self, source, target,
250 owner='root', group='root', perms=0o444):254 owner='root', group='root', perms=0o444,
255 on_change_action=None):
251 self.source = source256 self.source = source
252 self.target = target257 self.target = target
253 self.owner = owner258 self.owner = owner
254 self.group = group259 self.group = group
255 self.perms = perms260 self.perms = perms
261 self.on_change_action = on_change_action
256262
257 def __call__(self, manager, service_name, event_name):263 def __call__(self, manager, service_name, event_name):
264 pre_checksum = ''
265 if self.on_change_action and os.path.isfile(self.target):
266 pre_checksum = host.file_hash(self.target)
258 service = manager.get_service(service_name)267 service = manager.get_service(service_name)
259 context = {}268 context = {}
260 for ctx in service.get('required_data', []):269 for ctx in service.get('required_data', []):
261 context.update(ctx)270 context.update(ctx)
262 templating.render(self.source, self.target, context,271 templating.render(self.source, self.target, context,
263 self.owner, self.group, self.perms)272 self.owner, self.group, self.perms)
273 if self.on_change_action:
274 if pre_checksum == host.file_hash(self.target):
275 hookenv.log(
276 'No change detected: {}'.format(self.target),
277 hookenv.DEBUG)
278 else:
279 self.on_change_action()
264280
265281
266# Convenience aliases for templates282# Convenience aliases for templates
267283
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2015-08-03 14:52:57 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2015-08-26 13:24:25 +0000
@@ -90,6 +90,14 @@
90 'kilo/proposed': 'trusty-proposed/kilo',90 'kilo/proposed': 'trusty-proposed/kilo',
91 'trusty-kilo/proposed': 'trusty-proposed/kilo',91 'trusty-kilo/proposed': 'trusty-proposed/kilo',
92 'trusty-proposed/kilo': 'trusty-proposed/kilo',92 'trusty-proposed/kilo': 'trusty-proposed/kilo',
93 # Liberty
94 'liberty': 'trusty-updates/liberty',
95 'trusty-liberty': 'trusty-updates/liberty',
96 'trusty-liberty/updates': 'trusty-updates/liberty',
97 'trusty-updates/liberty': 'trusty-updates/liberty',
98 'liberty/proposed': 'trusty-proposed/liberty',
99 'trusty-liberty/proposed': 'trusty-proposed/liberty',
100 'trusty-proposed/liberty': 'trusty-proposed/liberty',
93}101}
94102
95# The order of this list is very important. Handlers should be listed in from103# The order of this list is very important. Handlers should be listed in from
96104
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-09-19 15:58:28 +0000
+++ metadata.yaml 2015-08-26 13:24:25 +0000
@@ -5,7 +5,7 @@
5 Percona XtraDB Cluster provides an active/active MySQL5 Percona XtraDB Cluster provides an active/active MySQL
6 compatible alternative implemented using the Galera6 compatible alternative implemented using the Galera
7 synchronous replication extensions.7 synchronous replication extensions.
8categories:8tags:
9 - databases9 - databases
10provides:10provides:
11 db:11 db:
1212
=== renamed file 'tests/00-setup.sh' => 'tests/00-setup'
--- tests/00-setup.sh 2015-04-15 12:11:46 +0000
+++ tests/00-setup 2015-08-26 13:24:25 +0000
@@ -1,29 +1,16 @@
1#!/bin/bash -x1#!/bin/bash
2# The script installs amulet and other tools needed for the amulet tests.2
33set -ex
4# Get the status of the amulet package, this returns 0 of package is installed.4
5dpkg -s amulet5sudo add-apt-repository --yes ppa:juju/stable
6if [ $? -ne 0 ]; then6sudo apt-get update --yes
7 # Install the Amulet testing harness.7sudo apt-get install --yes amulet \
8 sudo add-apt-repository -y ppa:juju/stable8 python-cinderclient \
9 sudo apt-get update 9 python-distro-info \
10 sudo apt-get install -y -q amulet juju-core charm-tools10 python-glanceclient \
11fi11 python-heatclient \
1212 python-keystoneclient \
1313 python-neutronclient \
14PACKAGES="python3 python3-yaml"14 python-novaclient \
15for pkg in $PACKAGES; do15 python-pika \
16 dpkg -s python316 python-swiftclient
17 if [ $? -ne 0 ]; then
18 sudo apt-get install -y -q $pkg
19 fi
20done
21
22
23#if [ ! -f "$(dirname $0)/../local.yaml" ]; then
24# echo "To run these amulet tests a vip is needed, create a file called \
25#local.yaml in the charm dir, this file must contain a 'vip', if you're \
26#using the local provider with lxc you could use a free IP from the range \
27#10.0.3.0/24"
28# exit 1
29#fi
3017
=== modified file 'tests/basic_deployment.py'
--- tests/basic_deployment.py 2015-07-22 11:17:09 +0000
+++ tests/basic_deployment.py 2015-08-26 13:24:25 +0000
@@ -100,7 +100,7 @@
100100
101 resources = ['res_mysql_vip']101 resources = ['res_mysql_vip']
102 resources += ['res_mysql_monitor:%d' %102 resources += ['res_mysql_monitor:%d' %
103 i for i in range(self.units)]103 m for m in range(self.units)]
104104
105 assert sorted(self.get_pcmkr_resources()) == sorted(resources)105 assert sorted(self.get_pcmkr_resources()) == sorted(resources)
106 else:106 else:
107107
=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
--- tests/charmhelpers/contrib/amulet/utils.py 2015-08-03 14:52:57 +0000
+++ tests/charmhelpers/contrib/amulet/utils.py 2015-08-26 13:24:25 +0000
@@ -14,17 +14,23 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import amulet
18import ConfigParser
19import distro_info
20import io17import io
18import json
21import logging19import logging
22import os20import os
23import re21import re
24import six22import subprocess
25import sys23import sys
26import time24import time
27import urlparse25
26import amulet
27import distro_info
28import six
29from six.moves import configparser
30if six.PY3:
31 from urllib import parse as urlparse
32else:
33 import urlparse
2834
2935
30class AmuletUtils(object):36class AmuletUtils(object):
@@ -142,19 +148,23 @@
142148
143 for service_name in services_list:149 for service_name in services_list:
144 if (self.ubuntu_releases.index(release) >= systemd_switch or150 if (self.ubuntu_releases.index(release) >= systemd_switch or
145 service_name == "rabbitmq-server"):151 service_name in ['rabbitmq-server', 'apache2']):
146 # init is systemd152 # init is systemd (or regular sysv)
147 cmd = 'sudo service {} status'.format(service_name)153 cmd = 'sudo service {} status'.format(service_name)
154 output, code = sentry_unit.run(cmd)
155 service_running = code == 0
148 elif self.ubuntu_releases.index(release) < systemd_switch:156 elif self.ubuntu_releases.index(release) < systemd_switch:
149 # init is upstart157 # init is upstart
150 cmd = 'sudo status {}'.format(service_name)158 cmd = 'sudo status {}'.format(service_name)
159 output, code = sentry_unit.run(cmd)
160 service_running = code == 0 and "start/running" in output
151161
152 output, code = sentry_unit.run(cmd)
153 self.log.debug('{} `{}` returned '162 self.log.debug('{} `{}` returned '
154 '{}'.format(sentry_unit.info['unit_name'],163 '{}'.format(sentry_unit.info['unit_name'],
155 cmd, code))164 cmd, code))
156 if code != 0:165 if not service_running:
157 return "command `{}` returned {}".format(cmd, str(code))166 return u"command `{}` returned {} {}".format(
167 cmd, output, str(code))
158 return None168 return None
159169
160 def _get_config(self, unit, filename):170 def _get_config(self, unit, filename):
@@ -164,7 +174,7 @@
164 # NOTE(beisner): by default, ConfigParser does not handle options174 # NOTE(beisner): by default, ConfigParser does not handle options
165 # with no value, such as the flags used in the mysql my.cnf file.175 # with no value, such as the flags used in the mysql my.cnf file.
166 # https://bugs.python.org/issue7005176 # https://bugs.python.org/issue7005
167 config = ConfigParser.ConfigParser(allow_no_value=True)177 config = configparser.ConfigParser(allow_no_value=True)
168 config.readfp(io.StringIO(file_contents))178 config.readfp(io.StringIO(file_contents))
169 return config179 return config
170180
@@ -450,15 +460,20 @@
450 cmd, code, output))460 cmd, code, output))
451 return None461 return None
452462
453 def get_process_id_list(self, sentry_unit, process_name):463 def get_process_id_list(self, sentry_unit, process_name,
464 expect_success=True):
454 """Get a list of process ID(s) from a single sentry juju unit465 """Get a list of process ID(s) from a single sentry juju unit
455 for a single process name.466 for a single process name.
456467
457 :param sentry_unit: Pointer to amulet sentry instance (juju unit)468 :param sentry_unit: Amulet sentry instance (juju unit)
458 :param process_name: Process name469 :param process_name: Process name
470 :param expect_success: If False, expect the PID to be missing,
471 raise if it is present.
459 :returns: List of process IDs472 :returns: List of process IDs
460 """473 """
461 cmd = 'pidof {}'.format(process_name)474 cmd = 'pidof -x {}'.format(process_name)
475 if not expect_success:
476 cmd += " || exit 0 && exit 1"
462 output, code = sentry_unit.run(cmd)477 output, code = sentry_unit.run(cmd)
463 if code != 0:478 if code != 0:
464 msg = ('{} `{}` returned {} '479 msg = ('{} `{}` returned {} '
@@ -467,14 +482,23 @@
467 amulet.raise_status(amulet.FAIL, msg=msg)482 amulet.raise_status(amulet.FAIL, msg=msg)
468 return str(output).split()483 return str(output).split()
469484
470 def get_unit_process_ids(self, unit_processes):485 def get_unit_process_ids(self, unit_processes, expect_success=True):
471 """Construct a dict containing unit sentries, process names, and486 """Construct a dict containing unit sentries, process names, and
472 process IDs."""487 process IDs.
488
489 :param unit_processes: A dictionary of Amulet sentry instance
490 to list of process names.
491 :param expect_success: if False expect the processes to not be
492 running, raise if they are.
493 :returns: Dictionary of Amulet sentry instance to dictionary
494 of process names to PIDs.
495 """
473 pid_dict = {}496 pid_dict = {}
474 for sentry_unit, process_list in unit_processes.iteritems():497 for sentry_unit, process_list in six.iteritems(unit_processes):
475 pid_dict[sentry_unit] = {}498 pid_dict[sentry_unit] = {}
476 for process in process_list:499 for process in process_list:
477 pids = self.get_process_id_list(sentry_unit, process)500 pids = self.get_process_id_list(
501 sentry_unit, process, expect_success=expect_success)
478 pid_dict[sentry_unit].update({process: pids})502 pid_dict[sentry_unit].update({process: pids})
479 return pid_dict503 return pid_dict
480504
@@ -488,7 +512,7 @@
488 return ('Unit count mismatch. expected, actual: {}, '512 return ('Unit count mismatch. expected, actual: {}, '
489 '{} '.format(len(expected), len(actual)))513 '{} '.format(len(expected), len(actual)))
490514
491 for (e_sentry, e_proc_names) in expected.iteritems():515 for (e_sentry, e_proc_names) in six.iteritems(expected):
492 e_sentry_name = e_sentry.info['unit_name']516 e_sentry_name = e_sentry.info['unit_name']
493 if e_sentry in actual.keys():517 if e_sentry in actual.keys():
494 a_proc_names = actual[e_sentry]518 a_proc_names = actual[e_sentry]
@@ -507,11 +531,23 @@
507 '{}'.format(e_proc_name, a_proc_name))531 '{}'.format(e_proc_name, a_proc_name))
508532
509 a_pids_length = len(a_pids)533 a_pids_length = len(a_pids)
510 if e_pids_length != a_pids_length:534 fail_msg = ('PID count mismatch. {} ({}) expected, actual: '
511 return ('PID count mismatch. {} ({}) expected, actual: '
512 '{}, {} ({})'.format(e_sentry_name, e_proc_name,535 '{}, {} ({})'.format(e_sentry_name, e_proc_name,
513 e_pids_length, a_pids_length,536 e_pids_length, a_pids_length,
514 a_pids))537 a_pids))
538
539 # If expected is not bool, ensure PID quantities match
540 if not isinstance(e_pids_length, bool) and \
541 a_pids_length != e_pids_length:
542 return fail_msg
543 # If expected is bool True, ensure 1 or more PIDs exist
544 elif isinstance(e_pids_length, bool) and \
545 e_pids_length is True and a_pids_length < 1:
546 return fail_msg
547 # If expected is bool False, ensure 0 PIDs exist
548 elif isinstance(e_pids_length, bool) and \
549 e_pids_length is False and a_pids_length != 0:
550 return fail_msg
515 else:551 else:
516 self.log.debug('PID check OK: {} {} {}: '552 self.log.debug('PID check OK: {} {} {}: '
517 '{}'.format(e_sentry_name, e_proc_name,553 '{}'.format(e_sentry_name, e_proc_name,
@@ -531,3 +567,30 @@
531 return 'Dicts within list are not identical'567 return 'Dicts within list are not identical'
532568
533 return None569 return None
570
571 def run_action(self, unit_sentry, action,
572 _check_output=subprocess.check_output):
573 """Run the named action on a given unit sentry.
574
575 _check_output parameter is used for dependency injection.
576
577 @return action_id.
578 """
579 unit_id = unit_sentry.info["unit_name"]
580 command = ["juju", "action", "do", "--format=json", unit_id, action]
581 self.log.info("Running command: %s\n" % " ".join(command))
582 output = _check_output(command, universal_newlines=True)
583 data = json.loads(output)
584 action_id = data[u'Action queued with id']
585 return action_id
586
587 def wait_on_action(self, action_id, _check_output=subprocess.check_output):
588 """Wait for a given action, returning if it completed or not.
589
590 _check_output parameter is used for dependency injection.
591 """
592 command = ["juju", "action", "fetch", "--format=json", "--wait=0",
593 action_id]
594 output = _check_output(command, universal_newlines=True)
595 data = json.loads(output)
596 return data.get(u"status") == "completed"
534597
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-03 14:52:57 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-26 13:24:25 +0000
@@ -44,7 +44,7 @@
44 Determine if the local branch being tested is derived from its44 Determine if the local branch being tested is derived from its
45 stable or next (dev) branch, and based on this, use the corresonding45 stable or next (dev) branch, and based on this, use the corresonding
46 stable or next branches for the other_services."""46 stable or next branches for the other_services."""
47 base_charms = ['mysql', 'mongodb']47 base_charms = ['mysql', 'mongodb', 'nrpe']
4848
49 if self.series in ['precise', 'trusty']:49 if self.series in ['precise', 'trusty']:
50 base_series = self.series50 base_series = self.series
@@ -81,7 +81,7 @@
81 'ceph-osd', 'ceph-radosgw']81 'ceph-osd', 'ceph-radosgw']
82 # Most OpenStack subordinate charms do not expose an origin option82 # Most OpenStack subordinate charms do not expose an origin option
83 # as that is controlled by the principle.83 # as that is controlled by the principle.
84 ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch']84 ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']
8585
86 if self.openstack:86 if self.openstack:
87 for svc in services:87 for svc in services:
8888
=== added file 'tests/tests.yaml'
--- tests/tests.yaml 1970-01-01 00:00:00 +0000
+++ tests/tests.yaml 2015-08-26 13:24:25 +0000
@@ -0,0 +1,19 @@
1bootstrap: true
2reset: true
3virtualenv: true
4makefile:
5 - lint
6 - test
7sources:
8 - ppa:juju/stable
9packages:
10 - amulet
11 - python-cinderclient
12 - python-distro-info
13 - python-glanceclient
14 - python-heatclient
15 - python-keystoneclient
16 - python-neutronclient
17 - python-novaclient
18 - python-pika
19 - python-swiftclient
020
=== modified file 'unit_tests/test_percona_hooks.py'
--- unit_tests/test_percona_hooks.py 2015-03-17 14:37:44 +0000
+++ unit_tests/test_percona_hooks.py 2015-08-26 13:24:25 +0000
@@ -36,6 +36,7 @@
36 self.get_db_helper.return_value = helper36 self.get_db_helper.return_value = helper
37 self.test_config.set('vip', '10.0.3.3')37 self.test_config.set('vip', '10.0.3.3')
38 self.test_config.set('sst-password', password)38 self.test_config.set('sst-password', password)
39
39 def f(k):40 def f(k):
40 return self.test_config.get(k)41 return self.test_config.get(k)
4142
@@ -48,12 +49,12 @@
48 'cidr_netmask="24" '49 'cidr_netmask="24" '
49 'nic="eth0"'),50 'nic="eth0"'),
50 'res_mysql_monitor':51 'res_mysql_monitor':
51 hooks.RES_MONITOR_PARAMS % {'sstpass': 'ubuntu'}}52 hooks.RES_MONITOR_PARAMS % {'sstpass': 'ubuntu'}}
52 groups = {'grp_percona_cluster': 'res_mysql_vip'}53 groups = {'grp_percona_cluster': 'res_mysql_vip'}
5354
54 clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}55 clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
5556
56 colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'}57 colocations = {'vip_mysqld': 'inf: grp_percona_cluster cl_mysql_monitor'} # noqa
5758
58 locations = {'loc_percona_cluster':59 locations = {'loc_percona_cluster':
59 'grp_percona_cluster rule inf: writable eq 1'}60 'grp_percona_cluster rule inf: writable eq 1'}
6061
=== modified file 'unit_tests/test_percona_utils.py'
--- unit_tests/test_percona_utils.py 2015-07-29 10:21:16 +0000
+++ unit_tests/test_percona_utils.py 2015-08-26 13:24:25 +0000
@@ -111,8 +111,8 @@
111 mock_log, mock_get_ipv6_addr):111 mock_log, mock_get_ipv6_addr):
112 ipv6addr = '2001:db8:1:0:f816:3eff:fe79:cd'112 ipv6addr = '2001:db8:1:0:f816:3eff:fe79:cd'
113 mock_get_ipv6_addr.return_value = [ipv6addr]113 mock_get_ipv6_addr.return_value = [ipv6addr]
114 mock_rel_ids.return_value = [1,2]114 mock_rel_ids.return_value = [1, 2]
115 mock_rel_units.return_value = [3,4]115 mock_rel_units.return_value = [3, 4]
116 mock_get_host_ip.return_value = 'hostA'116 mock_get_host_ip.return_value = 'hostA'
117117
118 def _mock_rel_get(*args, **kwargs):118 def _mock_rel_get(*args, **kwargs):

Subscribers

People subscribed via source and target branches