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
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):

Subscribers

People subscribed via source and target branches