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
on 2015-08-26
| 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 | 2015-08-26 | Approve on 2015-08-26 | |
|
Review via email:
|
|||
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.
charm_lint_check #8757 percona-
LINT OK: passed
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/