From 7c2fc52176eb74deb055d249e234e885a9cbe740 Mon Sep 17 00:00:00 2001 From: Jorge Niedbalski Date: Thu, 9 Jul 2015 18:29:27 -0300 Subject: [PATCH 01/20] Adds the api_workers option --- config.yaml | 5 +++ hooks/ceilometer_contexts.py | 1 + templates/kilo/ceilometer.conf | 43 ++++++++++++++++++++++++++ unit_tests/test_ceilometer_contexts.py | 7 +++-- 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 templates/kilo/ceilometer.conf diff --git a/config.yaml b/config.yaml index bb5ee77..8751941 100644 --- a/config.yaml +++ b/config.yaml @@ -133,3 +133,8 @@ options: description: | Default multicast port number that will be used to communicate between HA Cluster nodes. + api-workers: + type: int + default: 1 + description: | + Number of workers for Ceilometer API server. (>= Kilo). diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py index 1cf8a0a..72aea39 100644 --- a/hooks/ceilometer_contexts.py +++ b/hooks/ceilometer_contexts.py @@ -78,6 +78,7 @@ class CeilometerContext(OSContextGenerator): from ceilometer_utils import get_shared_secret ctxt = { + 'api_workers': config('api-workers'), 'port': CEILOMETER_PORT, 'metering_secret': get_shared_secret() } diff --git a/templates/kilo/ceilometer.conf b/templates/kilo/ceilometer.conf new file mode 100644 index 0000000..3c41bd4 --- /dev/null +++ b/templates/kilo/ceilometer.conf @@ -0,0 +1,43 @@ +# kilo +############################################################################### +# [ WARNING ] +# ceilometer configuration file maintained by Juju +# local changes may be overwritten. +############################################################################### +[DEFAULT] +debug = {{ debug }} +verbose = {{ verbose }} +use_syslog = {{ use_syslog }} + +{% include "parts/rabbitmq" -%} + +[api] +port = {{ port }} +api_workers = {{ api_workers }} + +[service_credentials] +os_auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0 +os_tenant_name = {{ admin_tenant_name }} +os_username = {{ admin_user }} +os_password = {{ admin_password }} + +[database] +{% if db_replset: -%} +connection = mongodb://{{ db_mongo_servers }}/{{ db_name }}?readPreference=primaryPreferred&replicaSet={{ db_replset }} +mongodb_replica_set = {{ db_replset }} +{% else -%} +connection = mongodb://{{ db_host }}:{{ db_port }}/{{ db_name }} +{% endif %} + +[publisher_rpc] +metering_secret = {{ metering_secret }} + +[keystone_authtoken] +auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/ +auth_host = {{ auth_host }} +auth_port = {{ auth_port }} +auth_protocol = {{ auth_protocol }} +admin_tenant_name = {{ admin_tenant_name }} +admin_user = {{ admin_user }} +admin_password = {{ admin_password }} +signing_dir = {{ signing_dir }} diff --git a/unit_tests/test_ceilometer_contexts.py b/unit_tests/test_ceilometer_contexts.py index 5d63575..038dfd0 100644 --- a/unit_tests/test_ceilometer_contexts.py +++ b/unit_tests/test_ceilometer_contexts.py @@ -111,8 +111,11 @@ class CeilometerContextsTest(CharmTestCase): @patch.object(utils, 'get_shared_secret') def test_ceilometer_context(self, secret): secret.return_value = 'mysecret' - self.assertEquals(contexts.CeilometerContext()(), - {'port': 8777, 'metering_secret': 'mysecret'}) + self.assertEquals(contexts.CeilometerContext()(), { + 'port': 8777, + 'metering_secret': 'mysecret', + 'api_workers': 1, + }) def test_ceilometer_service_context(self): self.relation_ids.return_value = ['ceilometer-service:0'] From a71ac4fd1257df4cebdd465f3c1f2251245fe5dc Mon Sep 17 00:00:00 2001 From: Jorge Niedbalski Date: Thu, 9 Jul 2015 18:31:30 -0300 Subject: [PATCH 02/20] Adds the api_workers option --- templates/kilo/ceilometer.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/kilo/ceilometer.conf b/templates/kilo/ceilometer.conf index 3c41bd4..da44948 100644 --- a/templates/kilo/ceilometer.conf +++ b/templates/kilo/ceilometer.conf @@ -8,12 +8,12 @@ debug = {{ debug }} verbose = {{ verbose }} use_syslog = {{ use_syslog }} +api_workers = {{ api_workers }} {% include "parts/rabbitmq" -%} [api] port = {{ port }} -api_workers = {{ api_workers }} [service_credentials] os_auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0 From 31975df29c3a03b437854c50019b9820106c5eb9 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Mon, 17 Aug 2015 16:37:46 +0200 Subject: [PATCH 03/20] Sync charmhelpers --- hooks/charmhelpers/cli/__init__.py | 6 +- hooks/charmhelpers/cli/commands.py | 8 +- hooks/charmhelpers/cli/hookenv.py | 23 ++++ .../contrib/openstack/amulet/deployment.py | 4 +- hooks/charmhelpers/contrib/openstack/utils.py | 65 ++++++++--- .../contrib/storage/linux/utils.py | 5 +- hooks/charmhelpers/core/hookenv.py | 21 +--- hooks/charmhelpers/core/host.py | 25 ++++- hooks/charmhelpers/core/services/helpers.py | 20 +++- hooks/charmhelpers/fetch/__init__.py | 8 ++ tests/basic_deployment.py | 46 ++++++++ tests/charmhelpers/contrib/amulet/utils.py | 105 ++++++++++++++---- .../contrib/openstack/amulet/deployment.py | 4 +- 13 files changed, 266 insertions(+), 74 deletions(-) create mode 100644 hooks/charmhelpers/cli/hookenv.py diff --git a/hooks/charmhelpers/cli/__init__.py b/hooks/charmhelpers/cli/__init__.py index 7118daf..16d52cc 100644 --- a/hooks/charmhelpers/cli/__init__.py +++ b/hooks/charmhelpers/cli/__init__.py @@ -152,15 +152,11 @@ class CommandLine(object): arguments = self.argument_parser.parse_args() argspec = inspect.getargspec(arguments.func) vargs = [] - kwargs = {} for arg in argspec.args: vargs.append(getattr(arguments, arg)) if argspec.varargs: vargs.extend(getattr(arguments, argspec.varargs)) - if argspec.keywords: - for kwarg in argspec.keywords.items(): - kwargs[kwarg] = getattr(arguments, kwarg) - output = arguments.func(*vargs, **kwargs) + output = arguments.func(*vargs) if getattr(arguments.func, '_cli_test_command', False): self.exit_code = 0 if output else 1 output = '' diff --git a/hooks/charmhelpers/cli/commands.py b/hooks/charmhelpers/cli/commands.py index 443ff05..7e91db0 100644 --- a/hooks/charmhelpers/cli/commands.py +++ b/hooks/charmhelpers/cli/commands.py @@ -26,7 +26,7 @@ from . import CommandLine # noqa """ Import the sub-modules which have decorated subcommands to register with chlp. """ -import host # noqa -import benchmark # noqa -import unitdata # noqa -from charmhelpers.core import hookenv # noqa +from . import host # noqa +from . import benchmark # noqa +from . import unitdata # noqa +from . import hookenv # noqa diff --git a/hooks/charmhelpers/cli/hookenv.py b/hooks/charmhelpers/cli/hookenv.py new file mode 100644 index 0000000..265c816 --- /dev/null +++ b/hooks/charmhelpers/cli/hookenv.py @@ -0,0 +1,23 @@ +# Copyright 2014-2015 Canonical Limited. +# +# This file is part of charm-helpers. +# +# charm-helpers is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 as +# published by the Free Software Foundation. +# +# charm-helpers is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with charm-helpers. If not, see . + +from . import cmdline +from charmhelpers.core import hookenv + + +cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped) +cmdline.subcommand('service-name')(hookenv.service_name) +cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped) diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py index b01e6cb..07ee2ef 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py @@ -44,7 +44,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): Determine if the local branch being tested is derived from its stable or next (dev) branch, and based on this, use the corresonding stable or next branches for the other_services.""" - base_charms = ['mysql', 'mongodb'] + base_charms = ['mysql', 'mongodb', 'nrpe'] if self.series in ['precise', 'trusty']: base_series = self.series @@ -81,7 +81,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): 'ceph-osd', 'ceph-radosgw'] # Most OpenStack subordinate charms do not expose an origin option # as that is controlled by the principle. - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] + ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] if self.openstack: for svc in services: diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 4dd000c..c9fd68f 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -24,6 +24,7 @@ import subprocess import json import os import sys +import re import six import yaml @@ -69,7 +70,6 @@ CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' 'restricted main multiverse universe') - UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('oneiric', 'diablo'), ('precise', 'essex'), @@ -118,6 +118,34 @@ SWIFT_CODENAMES = OrderedDict([ ('2.3.0', 'liberty'), ]) +# >= Liberty version->codename mapping +PACKAGE_CODENAMES = { + 'nova-common': OrderedDict([ + ('12.0.0', 'liberty'), + ]), + 'neutron-common': OrderedDict([ + ('7.0.0', 'liberty'), + ]), + 'cinder-common': OrderedDict([ + ('7.0.0', 'liberty'), + ]), + 'keystone': OrderedDict([ + ('8.0.0', 'liberty'), + ]), + 'horizon-common': OrderedDict([ + ('8.0.0', 'liberty'), + ]), + 'ceilometer-common': OrderedDict([ + ('5.0.0', 'liberty'), + ]), + 'heat-common': OrderedDict([ + ('5.0.0', 'liberty'), + ]), + 'glance-common': OrderedDict([ + ('11.0.0', 'liberty'), + ]), +} + DEFAULT_LOOPBACK_SIZE = '5G' @@ -201,20 +229,29 @@ def get_os_codename_package(package, fatal=True): error_out(e) vers = apt.upstream_version(pkg.current_ver.ver_str) + match = re.match('^(\d)\.(\d)\.(\d)', vers) + if match: + vers = match.group(0) - try: - if 'swift' in pkg.name: - swift_vers = vers[:5] - if swift_vers not in SWIFT_CODENAMES: - # Deal with 1.10.0 upward - swift_vers = vers[:6] - return SWIFT_CODENAMES[swift_vers] - else: - vers = vers[:6] - return OPENSTACK_CODENAMES[vers] - except KeyError: - e = 'Could not determine OpenStack codename for version %s' % vers - error_out(e) + # >= Liberty independent project versions + if (package in PACKAGE_CODENAMES and + vers in PACKAGE_CODENAMES[package]): + return PACKAGE_CODENAMES[package][vers] + else: + # < Liberty co-ordinated project versions + try: + if 'swift' in pkg.name: + swift_vers = vers[:5] + if swift_vers not in SWIFT_CODENAMES: + # Deal with 1.10.0 upward + swift_vers = vers[:6] + return SWIFT_CODENAMES[swift_vers] + else: + vers = vers[:6] + return OPENSTACK_CODENAMES[vers] + except KeyError: + e = 'Could not determine OpenStack codename for version %s' % vers + error_out(e) def get_os_version_package(pkg, fatal=True): diff --git a/hooks/charmhelpers/contrib/storage/linux/utils.py b/hooks/charmhelpers/contrib/storage/linux/utils.py index e2769e4..1e57941 100644 --- a/hooks/charmhelpers/contrib/storage/linux/utils.py +++ b/hooks/charmhelpers/contrib/storage/linux/utils.py @@ -43,9 +43,10 @@ def zap_disk(block_device): :param block_device: str: Full path of block device to clean. ''' + # https://github.com/ceph/ceph/commit/fdd7f8d83afa25c4e09aaedd90ab93f3b64a677b # sometimes sgdisk exits non-zero; this is OK, dd will clean up - call(['sgdisk', '--zap-all', '--mbrtogpt', - '--clear', block_device]) + call(['sgdisk', '--zap-all', '--', block_device]) + call(['sgdisk', '--clear', '--mbrtogpt', '--', block_device]) dev_end = check_output(['blockdev', '--getsz', block_device]).decode('UTF-8') gpt_end = int(dev_end.split()[0]) - 100 diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index 18860f5..a35d006 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -34,23 +34,6 @@ import errno import tempfile from subprocess import CalledProcessError -try: - from charmhelpers.cli import cmdline -except ImportError as e: - # due to the anti-pattern of partially synching charmhelpers directly - # into charms, it's possible that charmhelpers.cli is not available; - # if that's the case, they don't really care about using the cli anyway, - # so mock it out - if str(e) == 'No module named cli': - class cmdline(object): - @classmethod - def subcommand(cls, *args, **kwargs): - def _wrap(func): - return func - return _wrap - else: - raise - import six if not six.PY3: from UserDict import UserDict @@ -91,6 +74,7 @@ def cached(func): res = func(*args, **kwargs) cache[key] = res return res + wrapper._wrapped = func return wrapper @@ -190,7 +174,6 @@ def relation_type(): return os.environ.get('JUJU_RELATION', None) -@cmdline.subcommand() @cached def relation_id(relation_name=None, service_or_unit=None): """The relation ID for the current or a specified relation""" @@ -216,13 +199,11 @@ def remote_unit(): return os.environ.get('JUJU_REMOTE_UNIT', None) -@cmdline.subcommand() def service_name(): """The name service group this unit belongs to""" return local_unit().split('/')[0] -@cmdline.subcommand() @cached def remote_service_name(relid=None): """The remote service name for a given relation-id (or the current relation)""" diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index 8ae8ef8..ec659ee 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -72,7 +72,7 @@ def service_pause(service_name, init_dir=None): stopped = service_stop(service_name) # XXX: Support systemd too override_path = os.path.join( - init_dir, '{}.conf.override'.format(service_name)) + init_dir, '{}.override'.format(service_name)) with open(override_path, 'w') as fh: fh.write("manual\n") return stopped @@ -86,7 +86,7 @@ def service_resume(service_name, init_dir=None): if init_dir is None: init_dir = "/etc/init" override_path = os.path.join( - init_dir, '{}.conf.override'.format(service_name)) + init_dir, '{}.override'.format(service_name)) if os.path.exists(override_path): os.unlink(override_path) started = service_start(service_name) @@ -148,6 +148,16 @@ def adduser(username, password=None, shell='/bin/bash', system_user=False): return user_info +def user_exists(username): + """Check if a user exists""" + try: + pwd.getpwnam(username) + user_exists = True + except KeyError: + user_exists = False + return user_exists + + def add_group(group_name, system_group=False): """Add a group to the system""" try: @@ -280,6 +290,17 @@ def mounts(): return system_mounts +def fstab_mount(mountpoint): + """Mount filesystem using fstab""" + cmd_args = ['mount', mountpoint] + try: + subprocess.check_output(cmd_args) + except subprocess.CalledProcessError as e: + log('Error unmounting {}\n{}'.format(mountpoint, e.output)) + return False + return True + + def file_hash(path, hash_type='md5'): """ Generate a hash checksum of the contents of 'path' or None if not found. diff --git a/hooks/charmhelpers/core/services/helpers.py b/hooks/charmhelpers/core/services/helpers.py index 8005c41..3f67783 100644 --- a/hooks/charmhelpers/core/services/helpers.py +++ b/hooks/charmhelpers/core/services/helpers.py @@ -16,7 +16,9 @@ import os import yaml + from charmhelpers.core import hookenv +from charmhelpers.core import host from charmhelpers.core import templating from charmhelpers.core.services.base import ManagerCallback @@ -240,27 +242,41 @@ class TemplateCallback(ManagerCallback): :param str source: The template source file, relative to `$CHARM_DIR/templates` + :param str target: The target to write the rendered template to :param str owner: The owner of the rendered file :param str group: The group of the rendered file :param int perms: The permissions of the rendered file - + :param partial on_change_action: functools partial to be executed when + rendered file changes """ def __init__(self, source, target, - owner='root', group='root', perms=0o444): + owner='root', group='root', perms=0o444, + on_change_action=None): self.source = source self.target = target self.owner = owner self.group = group self.perms = perms + self.on_change_action = on_change_action def __call__(self, manager, service_name, event_name): + pre_checksum = '' + if self.on_change_action and os.path.isfile(self.target): + pre_checksum = host.file_hash(self.target) service = manager.get_service(service_name) context = {} for ctx in service.get('required_data', []): context.update(ctx) templating.render(self.source, self.target, context, self.owner, self.group, self.perms) + if self.on_change_action: + if pre_checksum == host.file_hash(self.target): + hookenv.log( + 'No change detected: {}'.format(self.target), + hookenv.DEBUG) + else: + self.on_change_action() # Convenience aliases for templates diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 0a3bb96..cd0b783 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -90,6 +90,14 @@ CLOUD_ARCHIVE_POCKETS = { 'kilo/proposed': 'trusty-proposed/kilo', 'trusty-kilo/proposed': 'trusty-proposed/kilo', 'trusty-proposed/kilo': 'trusty-proposed/kilo', + # Liberty + 'liberty': 'trusty-updates/liberty', + 'trusty-liberty': 'trusty-updates/liberty', + 'trusty-liberty/updates': 'trusty-updates/liberty', + 'trusty-updates/liberty': 'trusty-updates/liberty', + 'liberty/proposed': 'trusty-proposed/liberty', + 'trusty-liberty/proposed': 'trusty-proposed/liberty', + 'trusty-proposed/liberty': 'trusty-proposed/liberty', } # The order of this list is very important. Handlers should be listed in from diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 7ec449b..95739be 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -107,6 +107,33 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): endpoint_type='publicURL') self.ceil = ceilclient.Client(endpoint=ep, token=self._get_token) + def _run_action(self, unit_id, action, *args): + command = ["juju", "action", "do", "--format=json", unit_id, action] + command.extend(args) + print("Running command: %s\n" % " ".join(command)) + output = subprocess.check_output(command) + output_json = output.decode(encoding="UTF-8") + data = json.loads(output_json) + action_id = data[u'Action queued with id'] + return action_id + + def _wait_on_action(self, action_id): + command = ["juju", "action", "fetch", "--format=json", action_id] + while True: + try: + output = subprocess.check_output(command) + except Exception as e: + print(e) + return False + output_json = output.decode(encoding="UTF-8") + data = json.loads(output_json) + if data[u"status"] == "completed": + return True + elif data[u"status"] == "failed": + return False + time.sleep(2) + + def test_100_services(self): """Verify the expected services are running on the corresponding service units.""" @@ -569,3 +596,22 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): sleep_time = 0 self.d.configure(juju_service, set_default) + + def test_1000_pause_and_resume(self): + """The services can be paused and resumed. """ + sentry = self.ceil_sentry + juju_service = 'ceilometer' + + services = [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-alarm-evaluator', + 'ceilometer-alarm-notifier', + 'ceilometer-agent-notification', + ] + unit_name = "ceilometer/0" + # unit = self.d.sentry.unit[unit_name] + + action_id = self._run_action(unit_name, "pause") + assert self._wait_on_action(action_id), "Pause action failed." diff --git a/tests/charmhelpers/contrib/amulet/utils.py b/tests/charmhelpers/contrib/amulet/utils.py index 3de26af..7816c93 100644 --- a/tests/charmhelpers/contrib/amulet/utils.py +++ b/tests/charmhelpers/contrib/amulet/utils.py @@ -14,17 +14,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with charm-helpers. If not, see . -import amulet -import ConfigParser -import distro_info import io +import json import logging import os import re -import six +import subprocess import sys import time -import urlparse + +import amulet +import distro_info +import six +from six.moves import configparser +if six.PY3: + from urllib import parse as urlparse +else: + import urlparse class AmuletUtils(object): @@ -142,19 +148,23 @@ class AmuletUtils(object): for service_name in services_list: if (self.ubuntu_releases.index(release) >= systemd_switch or - service_name == "rabbitmq-server"): - # init is systemd + service_name in ['rabbitmq-server', 'apache2']): + # init is systemd (or regular sysv) cmd = 'sudo service {} status'.format(service_name) + output, code = sentry_unit.run(cmd) + service_running = code == 0 elif self.ubuntu_releases.index(release) < systemd_switch: # init is upstart cmd = 'sudo status {}'.format(service_name) + output, code = sentry_unit.run(cmd) + service_running = code == 0 and "start/running" in output - output, code = sentry_unit.run(cmd) self.log.debug('{} `{}` returned ' '{}'.format(sentry_unit.info['unit_name'], cmd, code)) - if code != 0: - return "command `{}` returned {}".format(cmd, str(code)) + if not service_running: + return u"command `{}` returned {} {}".format( + cmd, output, str(code)) return None def _get_config(self, unit, filename): @@ -164,7 +174,7 @@ class AmuletUtils(object): # NOTE(beisner): by default, ConfigParser does not handle options # with no value, such as the flags used in the mysql my.cnf file. # https://bugs.python.org/issue7005 - config = ConfigParser.ConfigParser(allow_no_value=True) + config = configparser.ConfigParser(allow_no_value=True) config.readfp(io.StringIO(file_contents)) return config @@ -450,15 +460,20 @@ class AmuletUtils(object): cmd, code, output)) return None - def get_process_id_list(self, sentry_unit, process_name): + def get_process_id_list(self, sentry_unit, process_name, + expect_success=True): """Get a list of process ID(s) from a single sentry juju unit for a single process name. - :param sentry_unit: Pointer to amulet sentry instance (juju unit) + :param sentry_unit: Amulet sentry instance (juju unit) :param process_name: Process name + :param expect_success: If False, expect the PID to be missing, + raise if it is present. :returns: List of process IDs """ - cmd = 'pidof {}'.format(process_name) + cmd = 'pidof -x {}'.format(process_name) + if not expect_success: + cmd += " || exit 0 && exit 1" output, code = sentry_unit.run(cmd) if code != 0: msg = ('{} `{}` returned {} ' @@ -467,14 +482,23 @@ class AmuletUtils(object): amulet.raise_status(amulet.FAIL, msg=msg) return str(output).split() - def get_unit_process_ids(self, unit_processes): + def get_unit_process_ids(self, unit_processes, expect_success=True): """Construct a dict containing unit sentries, process names, and - process IDs.""" + process IDs. + + :param unit_processes: A dictionary of Amulet sentry instance + to list of process names. + :param expect_success: if False expect the processes to not be + running, raise if they are. + :returns: Dictionary of Amulet sentry instance to dictionary + of process names to PIDs. + """ pid_dict = {} - for sentry_unit, process_list in unit_processes.iteritems(): + for sentry_unit, process_list in six.iteritems(unit_processes): pid_dict[sentry_unit] = {} for process in process_list: - pids = self.get_process_id_list(sentry_unit, process) + pids = self.get_process_id_list( + sentry_unit, process, expect_success=expect_success) pid_dict[sentry_unit].update({process: pids}) return pid_dict @@ -488,7 +512,7 @@ class AmuletUtils(object): return ('Unit count mismatch. expected, actual: {}, ' '{} '.format(len(expected), len(actual))) - for (e_sentry, e_proc_names) in expected.iteritems(): + for (e_sentry, e_proc_names) in six.iteritems(expected): e_sentry_name = e_sentry.info['unit_name'] if e_sentry in actual.keys(): a_proc_names = actual[e_sentry] @@ -507,11 +531,23 @@ class AmuletUtils(object): '{}'.format(e_proc_name, a_proc_name)) a_pids_length = len(a_pids) - if e_pids_length != a_pids_length: - return ('PID count mismatch. {} ({}) expected, actual: ' + fail_msg = ('PID count mismatch. {} ({}) expected, actual: ' '{}, {} ({})'.format(e_sentry_name, e_proc_name, e_pids_length, a_pids_length, a_pids)) + + # If expected is not bool, ensure PID quantities match + if not isinstance(e_pids_length, bool) and \ + a_pids_length != e_pids_length: + return fail_msg + # If expected is bool True, ensure 1 or more PIDs exist + elif isinstance(e_pids_length, bool) and \ + e_pids_length is True and a_pids_length < 1: + return fail_msg + # If expected is bool False, ensure 0 PIDs exist + elif isinstance(e_pids_length, bool) and \ + e_pids_length is False and a_pids_length != 0: + return fail_msg else: self.log.debug('PID check OK: {} {} {}: ' '{}'.format(e_sentry_name, e_proc_name, @@ -531,3 +567,30 @@ class AmuletUtils(object): return 'Dicts within list are not identical' return None + + def run_action(self, unit_sentry, action, + _check_output=subprocess.check_output): + """Run the named action on a given unit sentry. + + _check_output parameter is used for dependency injection. + + @return action_id. + """ + unit_id = unit_sentry.info["unit_name"] + command = ["juju", "action", "do", "--format=json", unit_id, action] + self.log.info("Running command: %s\n" % " ".join(command)) + output = _check_output(command, universal_newlines=True) + data = json.loads(output) + action_id = data[u'Action queued with id'] + return action_id + + def wait_on_action(self, action_id, _check_output=subprocess.check_output): + """Wait for a given action, returning if it completed or not. + + _check_output parameter is used for dependency injection. + """ + command = ["juju", "action", "fetch", "--format=json", "--wait=0", + action_id] + output = _check_output(command, universal_newlines=True) + data = json.loads(output) + return data.get(u"status") == "completed" diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py index b01e6cb..07ee2ef 100644 --- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py @@ -44,7 +44,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): Determine if the local branch being tested is derived from its stable or next (dev) branch, and based on this, use the corresonding stable or next branches for the other_services.""" - base_charms = ['mysql', 'mongodb'] + base_charms = ['mysql', 'mongodb', 'nrpe'] if self.series in ['precise', 'trusty']: base_series = self.series @@ -81,7 +81,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): 'ceph-osd', 'ceph-radosgw'] # Most OpenStack subordinate charms do not expose an origin option # as that is controlled by the principle. - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] + ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] if self.openstack: for svc in services: From fecf0425478d58792be4a84c59c55035657c38dc Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Fri, 21 Aug 2015 09:55:53 +0200 Subject: [PATCH 04/20] Test resume action --- tests/basic_deployment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 95739be..cdb4b90 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -615,3 +615,6 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): action_id = self._run_action(unit_name, "pause") assert self._wait_on_action(action_id), "Pause action failed." + + action_id = self.run_action(unit_name, "resume") + assert self._wait_on_action(action_id), "Resume action failed." From c2f19374565952623626cfbf32cbeefe4c4e6977 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Mon, 31 Aug 2015 15:56:40 +0200 Subject: [PATCH 05/20] Commit current state to rebase.O --- hooks/charmhelpers/contrib/network/ip.py | 6 +- .../charmhelpers/contrib/openstack/context.py | 27 +++++-- .../charmhelpers/contrib/openstack/neutron.py | 43 +++++++---- hooks/charmhelpers/contrib/openstack/utils.py | 12 +-- hooks/charmhelpers/core/hookenv.py | 18 +++-- hooks/charmhelpers/core/host.py | 77 ++++++++++++++++--- 6 files changed, 138 insertions(+), 45 deletions(-) diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index fff6d5c..67b4dcc 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -435,8 +435,12 @@ def get_hostname(address, fqdn=True): rev = dns.reversename.from_address(address) result = ns_query(rev) + if not result: - return None + try: + result = socket.gethostbyaddr(address)[0] + except: + return None else: result = address diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index ab2ebac..9a33a03 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -50,6 +50,8 @@ from charmhelpers.core.sysctl import create as sysctl_create from charmhelpers.core.strutils import bool_from_string from charmhelpers.core.host import ( + get_bond_master, + is_phy_iface, list_nics, get_nic_hwaddr, mkdir, @@ -923,7 +925,6 @@ class NeutronContext(OSContextGenerator): class NeutronPortContext(OSContextGenerator): - NIC_PREFIXES = ['eth', 'bond'] def resolve_ports(self, ports): """Resolve NICs not yet bound to bridge(s) @@ -935,7 +936,18 @@ class NeutronPortContext(OSContextGenerator): hwaddr_to_nic = {} hwaddr_to_ip = {} - for nic in list_nics(self.NIC_PREFIXES): + for nic in list_nics(): + # Ignore virtual interfaces (bond masters will be identified from + # their slaves) + if not is_phy_iface(nic): + continue + + _nic = get_bond_master(nic) + if _nic: + log("Replacing iface '%s' with bond master '%s'" % (nic, _nic), + level=DEBUG) + nic = _nic + hwaddr = get_nic_hwaddr(nic) hwaddr_to_nic[hwaddr] = nic addresses = get_ipv4_addr(nic, fatal=False) @@ -961,7 +973,8 @@ class NeutronPortContext(OSContextGenerator): # trust it to be the real external network). resolved.append(entry) - return resolved + # Ensure no duplicates + return list(set(resolved)) class OSConfigFlagContext(OSContextGenerator): @@ -1280,15 +1293,19 @@ class DataPortContext(NeutronPortContext): def __call__(self): ports = config('data-port') if ports: + # Map of {port/mac:bridge} portmap = parse_data_port_mappings(ports) - ports = portmap.values() + ports = portmap.keys() + # Resolve provided ports or mac addresses and filter out those + # already attached to a bridge. resolved = self.resolve_ports(ports) + # FIXME: is this necessary? normalized = {get_nic_hwaddr(port): port for port in resolved if port not in ports} normalized.update({port: port for port in resolved if port in ports}) if resolved: - return {bridge: normalized[port] for bridge, port in + return {bridge: normalized[port] for port, bridge in six.iteritems(portmap) if port in normalized.keys()} return None diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index f7b7235..c3d5c28 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -255,17 +255,30 @@ def network_manager(): return 'neutron' -def parse_mappings(mappings): +def parse_mappings(mappings, key_rvalue=False): + """By default mappings are lvalue keyed. + + If key_rvalue is True, the mapping will be reversed to allow multiple + configs for the same lvalue. + """ parsed = {} if mappings: mappings = mappings.split() for m in mappings: p = m.partition(':') - key = p[0].strip() - if p[1]: - parsed[key] = p[2].strip() + + if key_rvalue: + key_index = 2 + val_index = 0 + # if there is no rvalue skip to next + if not p[1]: + continue else: - parsed[key] = '' + key_index = 0 + val_index = 2 + + key = p[key_index].strip() + parsed[key] = p[val_index].strip() return parsed @@ -283,25 +296,25 @@ def parse_bridge_mappings(mappings): def parse_data_port_mappings(mappings, default_bridge='br-data'): """Parse data port mappings. - Mappings must be a space-delimited list of bridge:port mappings. + Mappings must be a space-delimited list of port:bridge mappings. - Returns dict of the form {bridge:port}. + Returns dict of the form {port:bridge} where port may be an mac address or + interface name. """ - _mappings = parse_mappings(mappings) + + # NOTE(dosaboy): we use rvalue for key to allow multiple values to be + # proposed for since it may be a mac address which will differ + # across units this allowing first-known-good to be chosen. + _mappings = parse_mappings(mappings, key_rvalue=True) if not _mappings or list(_mappings.values()) == ['']: if not mappings: return {} # For backwards-compatibility we need to support port-only provided in # config. - _mappings = {default_bridge: mappings.split()[0]} - - bridges = _mappings.keys() - ports = _mappings.values() - if len(set(bridges)) != len(bridges): - raise Exception("It is not allowed to have more than one port " - "configured on the same bridge") + _mappings = {mappings.split()[0]: default_bridge} + ports = _mappings.keys() if len(set(ports)) != len(ports): raise Exception("It is not allowed to have the same port configured " "on more than one bridge") diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index c9fd68f..66fcb58 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - # Copyright 2014-2015 Canonical Limited. # # This file is part of charm-helpers. @@ -195,9 +193,9 @@ def get_os_codename_version(vers): error_out(e) -def get_os_version_codename(codename): +def get_os_version_codename(codename, version_map=OPENSTACK_CODENAMES): '''Determine OpenStack version number from codename.''' - for k, v in six.iteritems(OPENSTACK_CODENAMES): + for k, v in six.iteritems(version_map): if v == codename: return k e = 'Could not derive OpenStack version for '\ @@ -429,7 +427,11 @@ def openstack_upgrade_available(package): import apt_pkg as apt src = config('openstack-origin') cur_vers = get_os_version_package(package) - available_vers = get_os_version_install_source(src) + if "swift" in package: + codename = get_os_codename_install_source(src) + available_vers = get_os_version_codename(codename, SWIFT_CODENAMES) + else: + available_vers = get_os_version_install_source(src) apt.init() return apt.version_compare(available_vers, cur_vers) == 1 diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index a35d006..ab53a78 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -767,21 +767,23 @@ def status_set(workload_state, message): def status_get(): - """Retrieve the previously set juju workload state + """Retrieve the previously set juju workload state and message + + If the status-get command is not found then assume this is juju < 1.23 and + return 'unknown', "" - If the status-set command is not found then assume this is juju < 1.23 and - return 'unknown' """ - cmd = ['status-get'] + cmd = ['status-get', "--format=json", "--include-data"] try: - raw_status = subprocess.check_output(cmd, universal_newlines=True) - status = raw_status.rstrip() - return status + raw_status = subprocess.check_output(cmd) except OSError as e: if e.errno == errno.ENOENT: - return 'unknown' + return ('unknown', "") else: raise + else: + status = json.loads(raw_status.decode("UTF-8")) + return (status["status"], status["message"]) def translate_exc(from_exc, to_exc): diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index ec659ee..29e8fee 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -417,25 +417,80 @@ def pwgen(length=None): return(''.join(random_chars)) -def list_nics(nic_type): +def is_phy_iface(interface): + """Returns True if interface is not virtual, otherwise False.""" + if interface: + sys_net = '/sys/class/net' + if os.path.isdir(sys_net): + for iface in glob.glob(os.path.join(sys_net, '*')): + if '/virtual/' in os.path.realpath(iface): + continue + + if interface == os.path.basename(iface): + return True + + return False + + +def get_bond_master(interface): + """Returns bond master if interface is bond slave otherwise None. + + NOTE: the provided interface is expected to be physical + """ + if interface: + iface_path = '/sys/class/net/%s' % (interface) + if os.path.exists(iface_path): + if '/virtual/' in os.path.realpath(iface_path): + return None + + master = os.path.join(iface_path, 'master') + if os.path.exists(master): + master = os.path.realpath(master) + # make sure it is a bond master + if os.path.exists(os.path.join(master, 'bonding')): + return os.path.basename(master) + + return None + + +def list_nics(nic_type=None): '''Return a list of nics of given type(s)''' if isinstance(nic_type, six.string_types): int_types = [nic_type] else: int_types = nic_type + interfaces = [] - for int_type in int_types: - cmd = ['ip', 'addr', 'show', 'label', int_type + '*'] + if nic_type: + for int_type in int_types: + cmd = ['ip', 'addr', 'show', 'label', int_type + '*'] + ip_output = subprocess.check_output(cmd).decode('UTF-8') + ip_output = ip_output.split('\n') + ip_output = (line for line in ip_output if line) + for line in ip_output: + if line.split()[1].startswith(int_type): + matched = re.search('.*: (' + int_type + + r'[0-9]+\.[0-9]+)@.*', line) + if matched: + iface = matched.groups()[0] + else: + iface = line.split()[1].replace(":", "") + + if iface not in interfaces: + interfaces.append(iface) + else: + cmd = ['ip', 'a'] ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n') - ip_output = (line for line in ip_output if line) + ip_output = (line.strip() for line in ip_output if line) + + key = re.compile('^[0-9]+:\s+(.+):') for line in ip_output: - if line.split()[1].startswith(int_type): - matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line) - if matched: - interface = matched.groups()[0] - else: - interface = line.split()[1].replace(":", "") - interfaces.append(interface) + matched = re.search(key, line) + if matched: + iface = matched.group(1) + iface = iface.partition("@")[0] + if iface not in interfaces: + interfaces.append(iface) return interfaces From 8b0f1ae295450129f751b17dc51bcacdf7dda979 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Tue, 1 Sep 2015 16:51:17 +0200 Subject: [PATCH 06/20] Import subprocess --- actions.yaml | 4 ++++ tests/basic_deployment.py | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 actions.yaml diff --git a/actions.yaml b/actions.yaml new file mode 100644 index 0000000..9315c88 --- /dev/null +++ b/actions.yaml @@ -0,0 +1,4 @@ +pause: + Pause the apache service providing ceilometer functions. +resume: + Resume the apache service providing ceilometer functions. \ No newline at end of file diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index cdb4b90..7de241a 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -1,5 +1,7 @@ #!/usr/bin/python +import subprocess + """ Basic ceilometer functional tests. """ From 36a850816c81fd0953eae71697be27772e38cbd4 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Tue, 1 Sep 2015 17:06:16 +0200 Subject: [PATCH 07/20] Move charmhelpers to root directory and symlink to hooks and actions. --- actions/charmhelpers | 1 + {hooks/charmhelpers => charmhelpers}/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/cli/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/cli/benchmark.py | 0 {hooks/charmhelpers => charmhelpers}/cli/commands.py | 0 {hooks/charmhelpers => charmhelpers}/cli/hookenv.py | 0 {hooks/charmhelpers => charmhelpers}/cli/host.py | 0 {hooks/charmhelpers => charmhelpers}/cli/unitdata.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/__init__.py | 0 .../contrib/charmsupport/__init__.py | 0 .../charmhelpers => charmhelpers}/contrib/charmsupport/nrpe.py | 0 .../contrib/charmsupport/volumes.py | 0 .../charmhelpers => charmhelpers}/contrib/hahelpers/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/apache.py | 0 .../charmhelpers => charmhelpers}/contrib/hahelpers/cluster.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/network/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/network/ip.py | 0 .../charmhelpers => charmhelpers}/contrib/openstack/__init__.py | 0 .../contrib/openstack/alternatives.py | 0 .../contrib/openstack/amulet/__init__.py | 0 .../contrib/openstack/amulet/deployment.py | 0 .../contrib/openstack/amulet/utils.py | 0 .../charmhelpers => charmhelpers}/contrib/openstack/context.py | 0 .../contrib/openstack/files/__init__.py | 0 .../contrib/openstack/files/check_haproxy.sh | 0 .../contrib/openstack/files/check_haproxy_queue_depth.sh | 0 {hooks/charmhelpers => charmhelpers}/contrib/openstack/ip.py | 0 .../charmhelpers => charmhelpers}/contrib/openstack/neutron.py | 0 .../contrib/openstack/templates/__init__.py | 0 .../contrib/openstack/templates/ceph.conf | 0 .../contrib/openstack/templates/git.upstart | 0 .../contrib/openstack/templates/haproxy.cfg | 0 .../contrib/openstack/templates/openstack_https_frontend | 0 .../contrib/openstack/templates/openstack_https_frontend.conf | 0 .../contrib/openstack/templates/section-keystone-authtoken | 0 .../contrib/openstack/templates/section-rabbitmq-oslo | 0 .../contrib/openstack/templates/section-zeromq | 0 .../contrib/openstack/templating.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/openstack/utils.py | 0 .../contrib/peerstorage/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/python/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/python/packages.py | 0 {hooks/charmhelpers => charmhelpers}/contrib/storage/__init__.py | 0 .../contrib/storage/linux/__init__.py | 0 .../charmhelpers => charmhelpers}/contrib/storage/linux/ceph.py | 0 .../contrib/storage/linux/loopback.py | 0 .../charmhelpers => charmhelpers}/contrib/storage/linux/lvm.py | 0 .../charmhelpers => charmhelpers}/contrib/storage/linux/utils.py | 0 {hooks/charmhelpers => charmhelpers}/core/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/core/decorators.py | 0 {hooks/charmhelpers => charmhelpers}/core/files.py | 0 {hooks/charmhelpers => charmhelpers}/core/fstab.py | 0 {hooks/charmhelpers => charmhelpers}/core/hookenv.py | 0 {hooks/charmhelpers => charmhelpers}/core/host.py | 0 {hooks/charmhelpers => charmhelpers}/core/hugepage.py | 0 {hooks/charmhelpers => charmhelpers}/core/services/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/core/services/base.py | 0 {hooks/charmhelpers => charmhelpers}/core/services/helpers.py | 0 {hooks/charmhelpers => charmhelpers}/core/strutils.py | 0 {hooks/charmhelpers => charmhelpers}/core/sysctl.py | 0 {hooks/charmhelpers => charmhelpers}/core/templating.py | 0 {hooks/charmhelpers => charmhelpers}/core/unitdata.py | 0 {hooks/charmhelpers => charmhelpers}/fetch/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/fetch/archiveurl.py | 0 {hooks/charmhelpers => charmhelpers}/fetch/bzrurl.py | 0 {hooks/charmhelpers => charmhelpers}/fetch/giturl.py | 0 {hooks/charmhelpers => charmhelpers}/payload/__init__.py | 0 {hooks/charmhelpers => charmhelpers}/payload/execd.py | 0 hooks/charmhelpers | 1 + 69 files changed, 2 insertions(+) create mode 120000 actions/charmhelpers rename {hooks/charmhelpers => charmhelpers}/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/benchmark.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/commands.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/hookenv.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/host.py (100%) rename {hooks/charmhelpers => charmhelpers}/cli/unitdata.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/nrpe.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/volumes.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/apache.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/cluster.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/network/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/network/ip.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/alternatives.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/deployment.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/utils.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/context.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/check_haproxy.sh (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/check_haproxy_queue_depth.sh (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/ip.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/neutron.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/ceph.conf (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/git.upstart (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/haproxy.cfg (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/openstack_https_frontend (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/openstack_https_frontend.conf (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-keystone-authtoken (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-rabbitmq-oslo (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-zeromq (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templating.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/utils.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/peerstorage/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/python/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/python/packages.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/ceph.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/loopback.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/lvm.py (100%) rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/utils.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/decorators.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/files.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/fstab.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/hookenv.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/host.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/hugepage.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/services/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/services/base.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/services/helpers.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/strutils.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/sysctl.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/templating.py (100%) rename {hooks/charmhelpers => charmhelpers}/core/unitdata.py (100%) rename {hooks/charmhelpers => charmhelpers}/fetch/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/fetch/archiveurl.py (100%) rename {hooks/charmhelpers => charmhelpers}/fetch/bzrurl.py (100%) rename {hooks/charmhelpers => charmhelpers}/fetch/giturl.py (100%) rename {hooks/charmhelpers => charmhelpers}/payload/__init__.py (100%) rename {hooks/charmhelpers => charmhelpers}/payload/execd.py (100%) create mode 120000 hooks/charmhelpers diff --git a/actions/charmhelpers b/actions/charmhelpers new file mode 120000 index 0000000..702de73 --- /dev/null +++ b/actions/charmhelpers @@ -0,0 +1 @@ +../charmhelpers \ No newline at end of file diff --git a/hooks/charmhelpers/__init__.py b/charmhelpers/__init__.py similarity index 100% rename from hooks/charmhelpers/__init__.py rename to charmhelpers/__init__.py diff --git a/hooks/charmhelpers/cli/__init__.py b/charmhelpers/cli/__init__.py similarity index 100% rename from hooks/charmhelpers/cli/__init__.py rename to charmhelpers/cli/__init__.py diff --git a/hooks/charmhelpers/cli/benchmark.py b/charmhelpers/cli/benchmark.py similarity index 100% rename from hooks/charmhelpers/cli/benchmark.py rename to charmhelpers/cli/benchmark.py diff --git a/hooks/charmhelpers/cli/commands.py b/charmhelpers/cli/commands.py similarity index 100% rename from hooks/charmhelpers/cli/commands.py rename to charmhelpers/cli/commands.py diff --git a/hooks/charmhelpers/cli/hookenv.py b/charmhelpers/cli/hookenv.py similarity index 100% rename from hooks/charmhelpers/cli/hookenv.py rename to charmhelpers/cli/hookenv.py diff --git a/hooks/charmhelpers/cli/host.py b/charmhelpers/cli/host.py similarity index 100% rename from hooks/charmhelpers/cli/host.py rename to charmhelpers/cli/host.py diff --git a/hooks/charmhelpers/cli/unitdata.py b/charmhelpers/cli/unitdata.py similarity index 100% rename from hooks/charmhelpers/cli/unitdata.py rename to charmhelpers/cli/unitdata.py diff --git a/hooks/charmhelpers/contrib/__init__.py b/charmhelpers/contrib/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/__init__.py rename to charmhelpers/contrib/__init__.py diff --git a/hooks/charmhelpers/contrib/charmsupport/__init__.py b/charmhelpers/contrib/charmsupport/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/__init__.py rename to charmhelpers/contrib/charmsupport/__init__.py diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/charmhelpers/contrib/charmsupport/nrpe.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/nrpe.py rename to charmhelpers/contrib/charmsupport/nrpe.py diff --git a/hooks/charmhelpers/contrib/charmsupport/volumes.py b/charmhelpers/contrib/charmsupport/volumes.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/volumes.py rename to charmhelpers/contrib/charmsupport/volumes.py diff --git a/hooks/charmhelpers/contrib/hahelpers/__init__.py b/charmhelpers/contrib/hahelpers/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hahelpers/__init__.py rename to charmhelpers/contrib/hahelpers/__init__.py diff --git a/hooks/charmhelpers/contrib/hahelpers/apache.py b/charmhelpers/contrib/hahelpers/apache.py similarity index 100% rename from hooks/charmhelpers/contrib/hahelpers/apache.py rename to charmhelpers/contrib/hahelpers/apache.py diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/charmhelpers/contrib/hahelpers/cluster.py similarity index 100% rename from hooks/charmhelpers/contrib/hahelpers/cluster.py rename to charmhelpers/contrib/hahelpers/cluster.py diff --git a/hooks/charmhelpers/contrib/network/__init__.py b/charmhelpers/contrib/network/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/network/__init__.py rename to charmhelpers/contrib/network/__init__.py diff --git a/hooks/charmhelpers/contrib/network/ip.py b/charmhelpers/contrib/network/ip.py similarity index 100% rename from hooks/charmhelpers/contrib/network/ip.py rename to charmhelpers/contrib/network/ip.py diff --git a/hooks/charmhelpers/contrib/openstack/__init__.py b/charmhelpers/contrib/openstack/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/__init__.py rename to charmhelpers/contrib/openstack/__init__.py diff --git a/hooks/charmhelpers/contrib/openstack/alternatives.py b/charmhelpers/contrib/openstack/alternatives.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/alternatives.py rename to charmhelpers/contrib/openstack/alternatives.py diff --git a/hooks/charmhelpers/contrib/openstack/amulet/__init__.py b/charmhelpers/contrib/openstack/amulet/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/amulet/__init__.py rename to charmhelpers/contrib/openstack/amulet/__init__.py diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/charmhelpers/contrib/openstack/amulet/deployment.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/amulet/deployment.py rename to charmhelpers/contrib/openstack/amulet/deployment.py diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/charmhelpers/contrib/openstack/amulet/utils.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/amulet/utils.py rename to charmhelpers/contrib/openstack/amulet/utils.py diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/charmhelpers/contrib/openstack/context.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/context.py rename to charmhelpers/contrib/openstack/context.py diff --git a/hooks/charmhelpers/contrib/openstack/files/__init__.py b/charmhelpers/contrib/openstack/files/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/files/__init__.py rename to charmhelpers/contrib/openstack/files/__init__.py diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh b/charmhelpers/contrib/openstack/files/check_haproxy.sh similarity index 100% rename from hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh rename to charmhelpers/contrib/openstack/files/check_haproxy.sh diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh b/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh similarity index 100% rename from hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh rename to charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/charmhelpers/contrib/openstack/ip.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/ip.py rename to charmhelpers/contrib/openstack/ip.py diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/charmhelpers/contrib/openstack/neutron.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/neutron.py rename to charmhelpers/contrib/openstack/neutron.py diff --git a/hooks/charmhelpers/contrib/openstack/templates/__init__.py b/charmhelpers/contrib/openstack/templates/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/__init__.py rename to charmhelpers/contrib/openstack/templates/__init__.py diff --git a/hooks/charmhelpers/contrib/openstack/templates/ceph.conf b/charmhelpers/contrib/openstack/templates/ceph.conf similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/ceph.conf rename to charmhelpers/contrib/openstack/templates/ceph.conf diff --git a/hooks/charmhelpers/contrib/openstack/templates/git.upstart b/charmhelpers/contrib/openstack/templates/git.upstart similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/git.upstart rename to charmhelpers/contrib/openstack/templates/git.upstart diff --git a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg b/charmhelpers/contrib/openstack/templates/haproxy.cfg similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg rename to charmhelpers/contrib/openstack/templates/haproxy.cfg diff --git a/hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend b/charmhelpers/contrib/openstack/templates/openstack_https_frontend similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend rename to charmhelpers/contrib/openstack/templates/openstack_https_frontend diff --git a/hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf b/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf rename to charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken rename to charmhelpers/contrib/openstack/templates/section-keystone-authtoken diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo b/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo rename to charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-zeromq b/charmhelpers/contrib/openstack/templates/section-zeromq similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templates/section-zeromq rename to charmhelpers/contrib/openstack/templates/section-zeromq diff --git a/hooks/charmhelpers/contrib/openstack/templating.py b/charmhelpers/contrib/openstack/templating.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/templating.py rename to charmhelpers/contrib/openstack/templating.py diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/utils.py rename to charmhelpers/contrib/openstack/utils.py diff --git a/hooks/charmhelpers/contrib/peerstorage/__init__.py b/charmhelpers/contrib/peerstorage/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/peerstorage/__init__.py rename to charmhelpers/contrib/peerstorage/__init__.py diff --git a/hooks/charmhelpers/contrib/python/__init__.py b/charmhelpers/contrib/python/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/python/__init__.py rename to charmhelpers/contrib/python/__init__.py diff --git a/hooks/charmhelpers/contrib/python/packages.py b/charmhelpers/contrib/python/packages.py similarity index 100% rename from hooks/charmhelpers/contrib/python/packages.py rename to charmhelpers/contrib/python/packages.py diff --git a/hooks/charmhelpers/contrib/storage/__init__.py b/charmhelpers/contrib/storage/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/__init__.py rename to charmhelpers/contrib/storage/__init__.py diff --git a/hooks/charmhelpers/contrib/storage/linux/__init__.py b/charmhelpers/contrib/storage/linux/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/__init__.py rename to charmhelpers/contrib/storage/linux/__init__.py diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/ceph.py rename to charmhelpers/contrib/storage/linux/ceph.py diff --git a/hooks/charmhelpers/contrib/storage/linux/loopback.py b/charmhelpers/contrib/storage/linux/loopback.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/loopback.py rename to charmhelpers/contrib/storage/linux/loopback.py diff --git a/hooks/charmhelpers/contrib/storage/linux/lvm.py b/charmhelpers/contrib/storage/linux/lvm.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/lvm.py rename to charmhelpers/contrib/storage/linux/lvm.py diff --git a/hooks/charmhelpers/contrib/storage/linux/utils.py b/charmhelpers/contrib/storage/linux/utils.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/utils.py rename to charmhelpers/contrib/storage/linux/utils.py diff --git a/hooks/charmhelpers/core/__init__.py b/charmhelpers/core/__init__.py similarity index 100% rename from hooks/charmhelpers/core/__init__.py rename to charmhelpers/core/__init__.py diff --git a/hooks/charmhelpers/core/decorators.py b/charmhelpers/core/decorators.py similarity index 100% rename from hooks/charmhelpers/core/decorators.py rename to charmhelpers/core/decorators.py diff --git a/hooks/charmhelpers/core/files.py b/charmhelpers/core/files.py similarity index 100% rename from hooks/charmhelpers/core/files.py rename to charmhelpers/core/files.py diff --git a/hooks/charmhelpers/core/fstab.py b/charmhelpers/core/fstab.py similarity index 100% rename from hooks/charmhelpers/core/fstab.py rename to charmhelpers/core/fstab.py diff --git a/hooks/charmhelpers/core/hookenv.py b/charmhelpers/core/hookenv.py similarity index 100% rename from hooks/charmhelpers/core/hookenv.py rename to charmhelpers/core/hookenv.py diff --git a/hooks/charmhelpers/core/host.py b/charmhelpers/core/host.py similarity index 100% rename from hooks/charmhelpers/core/host.py rename to charmhelpers/core/host.py diff --git a/hooks/charmhelpers/core/hugepage.py b/charmhelpers/core/hugepage.py similarity index 100% rename from hooks/charmhelpers/core/hugepage.py rename to charmhelpers/core/hugepage.py diff --git a/hooks/charmhelpers/core/services/__init__.py b/charmhelpers/core/services/__init__.py similarity index 100% rename from hooks/charmhelpers/core/services/__init__.py rename to charmhelpers/core/services/__init__.py diff --git a/hooks/charmhelpers/core/services/base.py b/charmhelpers/core/services/base.py similarity index 100% rename from hooks/charmhelpers/core/services/base.py rename to charmhelpers/core/services/base.py diff --git a/hooks/charmhelpers/core/services/helpers.py b/charmhelpers/core/services/helpers.py similarity index 100% rename from hooks/charmhelpers/core/services/helpers.py rename to charmhelpers/core/services/helpers.py diff --git a/hooks/charmhelpers/core/strutils.py b/charmhelpers/core/strutils.py similarity index 100% rename from hooks/charmhelpers/core/strutils.py rename to charmhelpers/core/strutils.py diff --git a/hooks/charmhelpers/core/sysctl.py b/charmhelpers/core/sysctl.py similarity index 100% rename from hooks/charmhelpers/core/sysctl.py rename to charmhelpers/core/sysctl.py diff --git a/hooks/charmhelpers/core/templating.py b/charmhelpers/core/templating.py similarity index 100% rename from hooks/charmhelpers/core/templating.py rename to charmhelpers/core/templating.py diff --git a/hooks/charmhelpers/core/unitdata.py b/charmhelpers/core/unitdata.py similarity index 100% rename from hooks/charmhelpers/core/unitdata.py rename to charmhelpers/core/unitdata.py diff --git a/hooks/charmhelpers/fetch/__init__.py b/charmhelpers/fetch/__init__.py similarity index 100% rename from hooks/charmhelpers/fetch/__init__.py rename to charmhelpers/fetch/__init__.py diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/charmhelpers/fetch/archiveurl.py similarity index 100% rename from hooks/charmhelpers/fetch/archiveurl.py rename to charmhelpers/fetch/archiveurl.py diff --git a/hooks/charmhelpers/fetch/bzrurl.py b/charmhelpers/fetch/bzrurl.py similarity index 100% rename from hooks/charmhelpers/fetch/bzrurl.py rename to charmhelpers/fetch/bzrurl.py diff --git a/hooks/charmhelpers/fetch/giturl.py b/charmhelpers/fetch/giturl.py similarity index 100% rename from hooks/charmhelpers/fetch/giturl.py rename to charmhelpers/fetch/giturl.py diff --git a/hooks/charmhelpers/payload/__init__.py b/charmhelpers/payload/__init__.py similarity index 100% rename from hooks/charmhelpers/payload/__init__.py rename to charmhelpers/payload/__init__.py diff --git a/hooks/charmhelpers/payload/execd.py b/charmhelpers/payload/execd.py similarity index 100% rename from hooks/charmhelpers/payload/execd.py rename to charmhelpers/payload/execd.py diff --git a/hooks/charmhelpers b/hooks/charmhelpers new file mode 120000 index 0000000..702de73 --- /dev/null +++ b/hooks/charmhelpers @@ -0,0 +1 @@ +../charmhelpers \ No newline at end of file From be76d9fa59697d63c4edd6ca8e4e1537ff70d147 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Tue, 1 Sep 2015 17:37:13 +0200 Subject: [PATCH 08/20] Setup actions. --- actions/actions.py | 51 ++++++++ actions/ceilometer_utils.py | 1 + actions/pause | 1 + actions/resume | 1 + ceilometer_utils.py | 225 +++++++++++++++++++++++++++++++++++ hooks/ceilometer_utils.py | 226 +----------------------------------- 6 files changed, 280 insertions(+), 225 deletions(-) create mode 100755 actions/actions.py create mode 120000 actions/ceilometer_utils.py create mode 120000 actions/pause create mode 120000 actions/resume create mode 100644 ceilometer_utils.py mode change 100644 => 120000 hooks/ceilometer_utils.py diff --git a/actions/actions.py b/actions/actions.py new file mode 100755 index 0000000..5c91f1d --- /dev/null +++ b/actions/actions.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + +import os +import sys + +from charmhelpers.core.host import service_pause, service_resume +from charmhelpers.core.hookenv import action_fail, status_set +from ceilometer_utils import CEILOMETER_SERVICES + +def pause(args): + """Pause the Ceilometer services. + + @raises Exception should the service fail to stop. + """ + for service in CEILOMETER_SERVICES: + if not service_pause(service): + raise Exception("Failed to %s." % service) + status_set( + "maintenance", "Paused. Use 'resume' action to resume normal service.") + +def resume(args): + """Resume the Ceilometer services. + + @raises Exception should the service fail to start.""" + for service in CEILOMETER_SERVICES: + if not service_resume(service): + raise Exception("Failed to resume %s." % service) + status_set("active", "") + + +# A dictionary of all the defined actions to callables (which take +# parsed arguments). +ACTIONS = {"pause": pause, "resume": resume} + + +def main(args): + action_name = os.path.basename(args[0]) + try: + action = ACTIONS[action_name] + except KeyError: + return "Action %s undefined" % action_name + else: + try: + action(args) + except Exception as e: + action_fail(str(e)) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) + diff --git a/actions/ceilometer_utils.py b/actions/ceilometer_utils.py new file mode 120000 index 0000000..5920356 --- /dev/null +++ b/actions/ceilometer_utils.py @@ -0,0 +1 @@ +../ceilometer_utils.py \ No newline at end of file diff --git a/actions/pause b/actions/pause new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/actions/pause @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/actions/resume b/actions/resume new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/actions/resume @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/ceilometer_utils.py b/ceilometer_utils.py new file mode 100644 index 0000000..6ced227 --- /dev/null +++ b/ceilometer_utils.py @@ -0,0 +1,225 @@ +import os +import uuid + +from collections import OrderedDict + +from charmhelpers.contrib.openstack import ( + templating, + context, +) +from ceilometer_contexts import ( + ApacheSSLContext, + LoggingConfigContext, + MongoDBContext, + CeilometerContext, + HAProxyContext +) +from charmhelpers.contrib.openstack.utils import ( + get_os_codename_package, + get_os_codename_install_source, + configure_installation_source +) +from charmhelpers.core.hookenv import config, log +from charmhelpers.fetch import apt_update, apt_install, apt_upgrade +from copy import deepcopy + +HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' +CEILOMETER_CONF_DIR = "/etc/ceilometer" +CEILOMETER_CONF = "%s/ceilometer.conf" % CEILOMETER_CONF_DIR +HTTPS_APACHE_CONF = "/etc/apache2/sites-available/openstack_https_frontend" +HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \ + "openstack_https_frontend.conf" +CLUSTER_RES = 'grp_ceilometer_vips' + +CEILOMETER_SERVICES = [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-alarm-evaluator', + 'ceilometer-alarm-notifier', + 'ceilometer-agent-notification', +] + +CEILOMETER_DB = "ceilometer" +CEILOMETER_SERVICE = "ceilometer" + +CEILOMETER_PACKAGES = [ + 'haproxy', + 'apache2', + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'python-pymongo', +] + +ICEHOUSE_PACKAGES = [ + 'ceilometer-alarm-notifier', + 'ceilometer-alarm-evaluator', + 'ceilometer-agent-notification' +] + +ICEHOUSE_SERVICES = [ + 'ceilometer-alarm-notifier', + 'ceilometer-alarm-evaluator', + 'ceilometer-agent-notification' +] + +CEILOMETER_ROLE = "ResellerAdmin" +SVC = 'ceilometer' + +CONFIG_FILES = OrderedDict([ + (CEILOMETER_CONF, { + 'hook_contexts': [context.IdentityServiceContext(service=SVC, + service_user=SVC), + context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), + LoggingConfigContext(), + MongoDBContext(), + CeilometerContext(), + context.SyslogContext(), + HAProxyContext()], + 'services': CEILOMETER_SERVICES + }), + (HAPROXY_CONF, { + 'hook_contexts': [context.HAProxyContext(singlenode_mode=True), + HAProxyContext()], + 'services': ['haproxy'], + }), + (HTTPS_APACHE_CONF, { + 'hook_contexts': [ApacheSSLContext()], + 'services': ['apache2'], + }), + (HTTPS_APACHE_24_CONF, { + 'hook_contexts': [ApacheSSLContext()], + 'services': ['apache2'], + }) +]) + +TEMPLATES = 'templates' + +SHARED_SECRET = "/etc/ceilometer/secret.txt" + + +def register_configs(): + """ + Register config files with their respective contexts. + Regstration of some configs may not be required depending on + existing of certain relations. + """ + # if called without anything installed (eg during install hook) + # just default to earliest supported release. configs dont get touched + # till post-install, anyway. + release = get_os_codename_package('ceilometer-common', fatal=False) \ + or 'grizzly' + configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, + openstack_release=release) + + if (get_os_codename_install_source(config('openstack-origin')) + >= 'icehouse'): + CONFIG_FILES[CEILOMETER_CONF]['services'] = \ + CONFIG_FILES[CEILOMETER_CONF]['services'] + ICEHOUSE_SERVICES + + for conf in CONFIG_FILES: + configs.register(conf, CONFIG_FILES[conf]['hook_contexts']) + + if os.path.exists('/etc/apache2/conf-available'): + configs.register(HTTPS_APACHE_24_CONF, + CONFIG_FILES[HTTPS_APACHE_24_CONF]['hook_contexts']) + else: + configs.register(HTTPS_APACHE_CONF, + CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts']) + return configs + + +def restart_map(): + ''' + Determine the correct resource map to be passed to + charmhelpers.core.restart_on_change() based on the services configured. + + :returns: dict: A dictionary mapping config file to lists of services + that should be restarted when file changes. + ''' + _map = {} + for f, ctxt in CONFIG_FILES.iteritems(): + svcs = [] + for svc in ctxt['services']: + svcs.append(svc) + if svcs: + _map[f] = svcs + return _map + + +def services(): + ''' Returns a list of services associate with this charm ''' + _services = [] + for v in restart_map().values(): + _services = _services + v + return list(set(_services)) + + +def get_ceilometer_context(): + ''' Retrieve a map of all current relation data for agent configuration ''' + ctxt = {} + for hcontext in CONFIG_FILES[CEILOMETER_CONF]['hook_contexts']: + ctxt.update(hcontext()) + return ctxt + + +def do_openstack_upgrade(configs): + """ + Perform an upgrade. Takes care of upgrading packages, rewriting + configs, database migrations and potentially any other post-upgrade + actions. + + :param configs: The charms main OSConfigRenderer object. + """ + new_src = config('openstack-origin') + new_os_rel = get_os_codename_install_source(new_src) + + log('Performing OpenStack upgrade to %s.' % (new_os_rel)) + + configure_installation_source(new_src) + dpkg_opts = [ + '--option', 'Dpkg::Options::=--force-confnew', + '--option', 'Dpkg::Options::=--force-confdef', + ] + apt_update(fatal=True) + apt_upgrade(options=dpkg_opts, fatal=True, dist=True) + apt_install(packages=get_packages(), + options=dpkg_opts, + fatal=True) + + # set CONFIGS to load templates from new release + configs.set_release(openstack_release=new_os_rel) + + +def get_packages(): + packages = deepcopy(CEILOMETER_PACKAGES) + if (get_os_codename_install_source(config('openstack-origin')) + >= 'icehouse'): + packages = packages + ICEHOUSE_PACKAGES + return packages + + +def get_shared_secret(): + """ + Returns the current shared secret for the ceilometer node. If the shared + secret does not exist, this method will generate one. + """ + secret = None + if not os.path.exists(SHARED_SECRET): + secret = str(uuid.uuid4()) + set_shared_secret(secret) + else: + with open(SHARED_SECRET, 'r') as secret_file: + secret = secret_file.read().strip() + return secret + + +def set_shared_secret(secret): + """ + Sets the shared secret which is used to sign ceilometer messages. + + :param secret: the secret to set + """ + with open(SHARED_SECRET, 'w') as secret_file: + secret_file.write(secret) diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py deleted file mode 100644 index 6ced227..0000000 --- a/hooks/ceilometer_utils.py +++ /dev/null @@ -1,225 +0,0 @@ -import os -import uuid - -from collections import OrderedDict - -from charmhelpers.contrib.openstack import ( - templating, - context, -) -from ceilometer_contexts import ( - ApacheSSLContext, - LoggingConfigContext, - MongoDBContext, - CeilometerContext, - HAProxyContext -) -from charmhelpers.contrib.openstack.utils import ( - get_os_codename_package, - get_os_codename_install_source, - configure_installation_source -) -from charmhelpers.core.hookenv import config, log -from charmhelpers.fetch import apt_update, apt_install, apt_upgrade -from copy import deepcopy - -HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' -CEILOMETER_CONF_DIR = "/etc/ceilometer" -CEILOMETER_CONF = "%s/ceilometer.conf" % CEILOMETER_CONF_DIR -HTTPS_APACHE_CONF = "/etc/apache2/sites-available/openstack_https_frontend" -HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \ - "openstack_https_frontend.conf" -CLUSTER_RES = 'grp_ceilometer_vips' - -CEILOMETER_SERVICES = [ - 'ceilometer-agent-central', - 'ceilometer-collector', - 'ceilometer-api', - 'ceilometer-alarm-evaluator', - 'ceilometer-alarm-notifier', - 'ceilometer-agent-notification', -] - -CEILOMETER_DB = "ceilometer" -CEILOMETER_SERVICE = "ceilometer" - -CEILOMETER_PACKAGES = [ - 'haproxy', - 'apache2', - 'ceilometer-agent-central', - 'ceilometer-collector', - 'ceilometer-api', - 'python-pymongo', -] - -ICEHOUSE_PACKAGES = [ - 'ceilometer-alarm-notifier', - 'ceilometer-alarm-evaluator', - 'ceilometer-agent-notification' -] - -ICEHOUSE_SERVICES = [ - 'ceilometer-alarm-notifier', - 'ceilometer-alarm-evaluator', - 'ceilometer-agent-notification' -] - -CEILOMETER_ROLE = "ResellerAdmin" -SVC = 'ceilometer' - -CONFIG_FILES = OrderedDict([ - (CEILOMETER_CONF, { - 'hook_contexts': [context.IdentityServiceContext(service=SVC, - service_user=SVC), - context.AMQPContext(ssl_dir=CEILOMETER_CONF_DIR), - LoggingConfigContext(), - MongoDBContext(), - CeilometerContext(), - context.SyslogContext(), - HAProxyContext()], - 'services': CEILOMETER_SERVICES - }), - (HAPROXY_CONF, { - 'hook_contexts': [context.HAProxyContext(singlenode_mode=True), - HAProxyContext()], - 'services': ['haproxy'], - }), - (HTTPS_APACHE_CONF, { - 'hook_contexts': [ApacheSSLContext()], - 'services': ['apache2'], - }), - (HTTPS_APACHE_24_CONF, { - 'hook_contexts': [ApacheSSLContext()], - 'services': ['apache2'], - }) -]) - -TEMPLATES = 'templates' - -SHARED_SECRET = "/etc/ceilometer/secret.txt" - - -def register_configs(): - """ - Register config files with their respective contexts. - Regstration of some configs may not be required depending on - existing of certain relations. - """ - # if called without anything installed (eg during install hook) - # just default to earliest supported release. configs dont get touched - # till post-install, anyway. - release = get_os_codename_package('ceilometer-common', fatal=False) \ - or 'grizzly' - configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, - openstack_release=release) - - if (get_os_codename_install_source(config('openstack-origin')) - >= 'icehouse'): - CONFIG_FILES[CEILOMETER_CONF]['services'] = \ - CONFIG_FILES[CEILOMETER_CONF]['services'] + ICEHOUSE_SERVICES - - for conf in CONFIG_FILES: - configs.register(conf, CONFIG_FILES[conf]['hook_contexts']) - - if os.path.exists('/etc/apache2/conf-available'): - configs.register(HTTPS_APACHE_24_CONF, - CONFIG_FILES[HTTPS_APACHE_24_CONF]['hook_contexts']) - else: - configs.register(HTTPS_APACHE_CONF, - CONFIG_FILES[HTTPS_APACHE_CONF]['hook_contexts']) - return configs - - -def restart_map(): - ''' - Determine the correct resource map to be passed to - charmhelpers.core.restart_on_change() based on the services configured. - - :returns: dict: A dictionary mapping config file to lists of services - that should be restarted when file changes. - ''' - _map = {} - for f, ctxt in CONFIG_FILES.iteritems(): - svcs = [] - for svc in ctxt['services']: - svcs.append(svc) - if svcs: - _map[f] = svcs - return _map - - -def services(): - ''' Returns a list of services associate with this charm ''' - _services = [] - for v in restart_map().values(): - _services = _services + v - return list(set(_services)) - - -def get_ceilometer_context(): - ''' Retrieve a map of all current relation data for agent configuration ''' - ctxt = {} - for hcontext in CONFIG_FILES[CEILOMETER_CONF]['hook_contexts']: - ctxt.update(hcontext()) - return ctxt - - -def do_openstack_upgrade(configs): - """ - Perform an upgrade. Takes care of upgrading packages, rewriting - configs, database migrations and potentially any other post-upgrade - actions. - - :param configs: The charms main OSConfigRenderer object. - """ - new_src = config('openstack-origin') - new_os_rel = get_os_codename_install_source(new_src) - - log('Performing OpenStack upgrade to %s.' % (new_os_rel)) - - configure_installation_source(new_src) - dpkg_opts = [ - '--option', 'Dpkg::Options::=--force-confnew', - '--option', 'Dpkg::Options::=--force-confdef', - ] - apt_update(fatal=True) - apt_upgrade(options=dpkg_opts, fatal=True, dist=True) - apt_install(packages=get_packages(), - options=dpkg_opts, - fatal=True) - - # set CONFIGS to load templates from new release - configs.set_release(openstack_release=new_os_rel) - - -def get_packages(): - packages = deepcopy(CEILOMETER_PACKAGES) - if (get_os_codename_install_source(config('openstack-origin')) - >= 'icehouse'): - packages = packages + ICEHOUSE_PACKAGES - return packages - - -def get_shared_secret(): - """ - Returns the current shared secret for the ceilometer node. If the shared - secret does not exist, this method will generate one. - """ - secret = None - if not os.path.exists(SHARED_SECRET): - secret = str(uuid.uuid4()) - set_shared_secret(secret) - else: - with open(SHARED_SECRET, 'r') as secret_file: - secret = secret_file.read().strip() - return secret - - -def set_shared_secret(secret): - """ - Sets the shared secret which is used to sign ceilometer messages. - - :param secret: the secret to set - """ - with open(SHARED_SECRET, 'w') as secret_file: - secret_file.write(secret) diff --git a/hooks/ceilometer_utils.py b/hooks/ceilometer_utils.py new file mode 120000 index 0000000..5920356 --- /dev/null +++ b/hooks/ceilometer_utils.py @@ -0,0 +1 @@ +../ceilometer_utils.py \ No newline at end of file From 01fcf44d6f9e678c2699f6ed2faf5dfb02335793 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 12:20:55 +0200 Subject: [PATCH 09/20] Add description keys to actions.yaml --- actions.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions.yaml b/actions.yaml index 9315c88..36ef29c 100644 --- a/actions.yaml +++ b/actions.yaml @@ -1,4 +1,4 @@ pause: - Pause the apache service providing ceilometer functions. + description: Pause the apache service providing ceilometer functions. resume: - Resume the apache service providing ceilometer functions. \ No newline at end of file + descrpition: Resume the apache service providing ceilometer functions. \ No newline at end of file From 5270c6319508ab4a74fcf44fc5f5e5ab1624cfb7 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 14:47:09 +0200 Subject: [PATCH 10/20] Import JSON --- tests/basic_deployment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 7de241a..da7723d 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -6,6 +6,7 @@ import subprocess Basic ceilometer functional tests. """ import amulet +import json import time from ceilometerclient.v2 import client as ceilclient From 96f936878dec9cdc418b50286ec6551f0ba0d452 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 17:36:10 +0200 Subject: [PATCH 11/20] Don't import things that aren't present. --- actions/actions.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/actions/actions.py b/actions/actions.py index 5c91f1d..78d016a 100755 --- a/actions/actions.py +++ b/actions/actions.py @@ -5,7 +5,16 @@ import sys from charmhelpers.core.host import service_pause, service_resume from charmhelpers.core.hookenv import action_fail, status_set -from ceilometer_utils import CEILOMETER_SERVICES + +CEILOMETER_SERVICES = [ + 'ceilometer-agent-central', + 'ceilometer-collector', + 'ceilometer-api', + 'ceilometer-alarm-evaluator', + 'ceilometer-alarm-notifier', + 'ceilometer-agent-notification', +] + def pause(args): """Pause the Ceilometer services. From 07f7d66afae2cae75eb2d8c7d1599b2f38df407e Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 17:51:15 +0200 Subject: [PATCH 12/20] self._run_action not self.run_action. --- tests/basic_deployment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index da7723d..104496c 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -619,5 +619,5 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): action_id = self._run_action(unit_name, "pause") assert self._wait_on_action(action_id), "Pause action failed." - action_id = self.run_action(unit_name, "resume") + action_id = self._run_action(unit_name, "resume") assert self._wait_on_action(action_id), "Resume action failed." From a96e842a350a74904f0c4e639f322afd3f0115cb Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 21:11:10 +0200 Subject: [PATCH 13/20] Tidy linting issues --- ceilometer_utils.py | 8 ++++---- hooks/ceilometer_hooks.py | 3 +-- tests/basic_deployment.py | 14 -------------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/ceilometer_utils.py b/ceilometer_utils.py index 6ced227..bc470c7 100644 --- a/ceilometer_utils.py +++ b/ceilometer_utils.py @@ -113,8 +113,8 @@ def register_configs(): configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, openstack_release=release) - if (get_os_codename_install_source(config('openstack-origin')) - >= 'icehouse'): + if (get_os_codename_install_source( + config('openstack-origin')) >= 'icehouse'): CONFIG_FILES[CEILOMETER_CONF]['services'] = \ CONFIG_FILES[CEILOMETER_CONF]['services'] + ICEHOUSE_SERVICES @@ -194,8 +194,8 @@ def do_openstack_upgrade(configs): def get_packages(): packages = deepcopy(CEILOMETER_PACKAGES) - if (get_os_codename_install_source(config('openstack-origin')) - >= 'icehouse'): + if (get_os_codename_install_source( + config('openstack-origin')) >= 'icehouse'): packages = packages + ICEHOUSE_PACKAGES return packages diff --git a/hooks/ceilometer_hooks.py b/hooks/ceilometer_hooks.py index 381d80c..9428b10 100755 --- a/hooks/ceilometer_hooks.py +++ b/hooks/ceilometer_hooks.py @@ -66,8 +66,7 @@ CONFIGS = register_configs() def install(): execd_preinstall() origin = config('openstack-origin') - if (lsb_release()['DISTRIB_CODENAME'] == 'precise' - and origin == 'distro'): + if (lsb_release()['DISTRIB_CODENAME'] == 'precise' and origin == 'distro'): origin = 'cloud:precise-grizzly' configure_installation_source(origin) apt_update(fatal=True) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 104496c..de1cdc6 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -136,7 +136,6 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): return False time.sleep(2) - def test_100_services(self): """Verify the expected services are running on the corresponding service units.""" @@ -602,20 +601,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_1000_pause_and_resume(self): """The services can be paused and resumed. """ - sentry = self.ceil_sentry - juju_service = 'ceilometer' - - services = [ - 'ceilometer-agent-central', - 'ceilometer-collector', - 'ceilometer-api', - 'ceilometer-alarm-evaluator', - 'ceilometer-alarm-notifier', - 'ceilometer-agent-notification', - ] unit_name = "ceilometer/0" - # unit = self.d.sentry.unit[unit_name] - action_id = self._run_action(unit_name, "pause") assert self._wait_on_action(action_id), "Pause action failed." From 4b612e01d0b460d174bbdb85e68d09beff2c04c0 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 21:20:06 +0200 Subject: [PATCH 14/20] Sync charm-helpers from branch with status-get. --- .../contrib/openstack/amulet/deployment.py | 25 +- .../contrib/openstack/amulet/utils.py | 359 ++++++++++++++++++ charmhelpers/contrib/openstack/context.py | 14 + charmhelpers/contrib/openstack/neutron.py | 14 + charmhelpers/contrib/openstack/utils.py | 8 +- charmhelpers/contrib/storage/linux/ceph.py | 13 +- charmhelpers/core/host.py | 48 ++- tests/charmhelpers/contrib/amulet/utils.py | 9 + 8 files changed, 457 insertions(+), 33 deletions(-) diff --git a/charmhelpers/contrib/openstack/amulet/deployment.py b/charmhelpers/contrib/openstack/amulet/deployment.py index 07ee2ef..63155d8 100644 --- a/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/charmhelpers/contrib/openstack/amulet/deployment.py @@ -44,8 +44,15 @@ class OpenStackAmuletDeployment(AmuletDeployment): Determine if the local branch being tested is derived from its stable or next (dev) branch, and based on this, use the corresonding stable or next branches for the other_services.""" + + # Charms outside the lp:~openstack-charmers namespace base_charms = ['mysql', 'mongodb', 'nrpe'] + # Force these charms to current series even when using an older series. + # ie. Use trusty/nrpe even when series is precise, as the P charm + # does not possess the necessary external master config and hooks. + force_series_current = ['nrpe'] + if self.series in ['precise', 'trusty']: base_series = self.series else: @@ -53,11 +60,17 @@ class OpenStackAmuletDeployment(AmuletDeployment): if self.stable: for svc in other_services: + if svc['name'] in force_series_current: + base_series = self.current_next + temp = 'lp:charms/{}/{}' svc['location'] = temp.format(base_series, svc['name']) else: for svc in other_services: + if svc['name'] in force_series_current: + base_series = self.current_next + if svc['name'] in base_charms: temp = 'lp:charms/{}/{}' svc['location'] = temp.format(base_series, @@ -77,21 +90,23 @@ class OpenStackAmuletDeployment(AmuletDeployment): services = other_services services.append(this_service) + + # Charms which should use the source config option use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', 'ceph-osd', 'ceph-radosgw'] - # Most OpenStack subordinate charms do not expose an origin option - # as that is controlled by the principle. - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] + + # Charms which can not use openstack-origin, ie. many subordinates + no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] if self.openstack: for svc in services: - if svc['name'] not in use_source + ignore: + if svc['name'] not in use_source + no_origin: config = {'openstack-origin': self.openstack} self.d.configure(svc['name'], config) if self.source: for svc in services: - if svc['name'] in use_source and svc['name'] not in ignore: + if svc['name'] in use_source and svc['name'] not in no_origin: config = {'source': self.source} self.d.configure(svc['name'], config) diff --git a/charmhelpers/contrib/openstack/amulet/utils.py b/charmhelpers/contrib/openstack/amulet/utils.py index 03f7927..b139741 100644 --- a/charmhelpers/contrib/openstack/amulet/utils.py +++ b/charmhelpers/contrib/openstack/amulet/utils.py @@ -27,6 +27,7 @@ import glanceclient.v1.client as glance_client import heatclient.v1.client as heat_client import keystoneclient.v2_0 as keystone_client import novaclient.v1_1.client as nova_client +import pika import swiftclient from charmhelpers.contrib.amulet.utils import ( @@ -602,3 +603,361 @@ class OpenStackAmuletUtils(AmuletUtils): self.log.debug('Ceph {} samples (OK): ' '{}'.format(sample_type, samples)) return None + +# rabbitmq/amqp specific helpers: + def add_rmq_test_user(self, sentry_units, + username="testuser1", password="changeme"): + """Add a test user via the first rmq juju unit, check connection as + the new user against all sentry units. + + :param sentry_units: list of sentry unit pointers + :param username: amqp user name, default to testuser1 + :param password: amqp user password + :returns: None if successful. Raise on error. + """ + self.log.debug('Adding rmq user ({})...'.format(username)) + + # Check that user does not already exist + cmd_user_list = 'rabbitmqctl list_users' + output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) + if username in output: + self.log.warning('User ({}) already exists, returning ' + 'gracefully.'.format(username)) + return + + perms = '".*" ".*" ".*"' + cmds = ['rabbitmqctl add_user {} {}'.format(username, password), + 'rabbitmqctl set_permissions {} {}'.format(username, perms)] + + # Add user via first unit + for cmd in cmds: + output, _ = self.run_cmd_unit(sentry_units[0], cmd) + + # Check connection against the other sentry_units + self.log.debug('Checking user connect against units...') + for sentry_unit in sentry_units: + connection = self.connect_amqp_by_unit(sentry_unit, ssl=False, + username=username, + password=password) + connection.close() + + def delete_rmq_test_user(self, sentry_units, username="testuser1"): + """Delete a rabbitmq user via the first rmq juju unit. + + :param sentry_units: list of sentry unit pointers + :param username: amqp user name, default to testuser1 + :param password: amqp user password + :returns: None if successful or no such user. + """ + self.log.debug('Deleting rmq user ({})...'.format(username)) + + # Check that the user exists + cmd_user_list = 'rabbitmqctl list_users' + output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) + + if username not in output: + self.log.warning('User ({}) does not exist, returning ' + 'gracefully.'.format(username)) + return + + # Delete the user + cmd_user_del = 'rabbitmqctl delete_user {}'.format(username) + output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del) + + def get_rmq_cluster_status(self, sentry_unit): + """Execute rabbitmq cluster status command on a unit and return + the full output. + + :param unit: sentry unit + :returns: String containing console output of cluster status command + """ + cmd = 'rabbitmqctl cluster_status' + output, _ = self.run_cmd_unit(sentry_unit, cmd) + self.log.debug('{} cluster_status:\n{}'.format( + sentry_unit.info['unit_name'], output)) + return str(output) + + def get_rmq_cluster_running_nodes(self, sentry_unit): + """Parse rabbitmqctl cluster_status output string, return list of + running rabbitmq cluster nodes. + + :param unit: sentry unit + :returns: List containing node names of running nodes + """ + # NOTE(beisner): rabbitmqctl cluster_status output is not + # json-parsable, do string chop foo, then json.loads that. + str_stat = self.get_rmq_cluster_status(sentry_unit) + if 'running_nodes' in str_stat: + pos_start = str_stat.find("{running_nodes,") + 15 + pos_end = str_stat.find("]},", pos_start) + 1 + str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"') + run_nodes = json.loads(str_run_nodes) + return run_nodes + else: + return [] + + def validate_rmq_cluster_running_nodes(self, sentry_units): + """Check that all rmq unit hostnames are represented in the + cluster_status output of all units. + + :param host_names: dict of juju unit names to host names + :param units: list of sentry unit pointers (all rmq units) + :returns: None if successful, otherwise return error message + """ + host_names = self.get_unit_hostnames(sentry_units) + errors = [] + + # Query every unit for cluster_status running nodes + for query_unit in sentry_units: + query_unit_name = query_unit.info['unit_name'] + running_nodes = self.get_rmq_cluster_running_nodes(query_unit) + + # Confirm that every unit is represented in the queried unit's + # cluster_status running nodes output. + for validate_unit in sentry_units: + val_host_name = host_names[validate_unit.info['unit_name']] + val_node_name = 'rabbit@{}'.format(val_host_name) + + if val_node_name not in running_nodes: + errors.append('Cluster member check failed on {}: {} not ' + 'in {}\n'.format(query_unit_name, + val_node_name, + running_nodes)) + if errors: + return ''.join(errors) + + def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None): + """Check a single juju rmq unit for ssl and port in the config file.""" + host = sentry_unit.info['public-address'] + unit_name = sentry_unit.info['unit_name'] + + conf_file = '/etc/rabbitmq/rabbitmq.config' + conf_contents = str(self.file_contents_safe(sentry_unit, + conf_file, max_wait=16)) + # Checks + conf_ssl = 'ssl' in conf_contents + conf_port = str(port) in conf_contents + + # Port explicitly checked in config + if port and conf_port and conf_ssl: + self.log.debug('SSL is enabled @{}:{} ' + '({})'.format(host, port, unit_name)) + return True + elif port and not conf_port and conf_ssl: + self.log.debug('SSL is enabled @{} but not on port {} ' + '({})'.format(host, port, unit_name)) + return False + # Port not checked (useful when checking that ssl is disabled) + elif not port and conf_ssl: + self.log.debug('SSL is enabled @{}:{} ' + '({})'.format(host, port, unit_name)) + return True + elif not port and not conf_ssl: + self.log.debug('SSL not enabled @{}:{} ' + '({})'.format(host, port, unit_name)) + return False + else: + msg = ('Unknown condition when checking SSL status @{}:{} ' + '({})'.format(host, port, unit_name)) + amulet.raise_status(amulet.FAIL, msg) + + def validate_rmq_ssl_enabled_units(self, sentry_units, port=None): + """Check that ssl is enabled on rmq juju sentry units. + + :param sentry_units: list of all rmq sentry units + :param port: optional ssl port override to validate + :returns: None if successful, otherwise return error message + """ + for sentry_unit in sentry_units: + if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port): + return ('Unexpected condition: ssl is disabled on unit ' + '({})'.format(sentry_unit.info['unit_name'])) + return None + + def validate_rmq_ssl_disabled_units(self, sentry_units): + """Check that ssl is enabled on listed rmq juju sentry units. + + :param sentry_units: list of all rmq sentry units + :returns: True if successful. Raise on error. + """ + for sentry_unit in sentry_units: + if self.rmq_ssl_is_enabled_on_unit(sentry_unit): + return ('Unexpected condition: ssl is enabled on unit ' + '({})'.format(sentry_unit.info['unit_name'])) + return None + + def configure_rmq_ssl_on(self, sentry_units, deployment, + port=None, max_wait=60): + """Turn ssl charm config option on, with optional non-default + ssl port specification. Confirm that it is enabled on every + unit. + + :param sentry_units: list of sentry units + :param deployment: amulet deployment object pointer + :param port: amqp port, use defaults if None + :param max_wait: maximum time to wait in seconds to confirm + :returns: None if successful. Raise on error. + """ + self.log.debug('Setting ssl charm config option: on') + + # Enable RMQ SSL + config = {'ssl': 'on'} + if port: + config['ssl_port'] = port + + deployment.configure('rabbitmq-server', config) + + # Confirm + tries = 0 + ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) + while ret and tries < (max_wait / 4): + time.sleep(4) + self.log.debug('Attempt {}: {}'.format(tries, ret)) + ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) + tries += 1 + + if ret: + amulet.raise_status(amulet.FAIL, ret) + + def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60): + """Turn ssl charm config option off, confirm that it is disabled + on every unit. + + :param sentry_units: list of sentry units + :param deployment: amulet deployment object pointer + :param max_wait: maximum time to wait in seconds to confirm + :returns: None if successful. Raise on error. + """ + self.log.debug('Setting ssl charm config option: off') + + # Disable RMQ SSL + config = {'ssl': 'off'} + deployment.configure('rabbitmq-server', config) + + # Confirm + tries = 0 + ret = self.validate_rmq_ssl_disabled_units(sentry_units) + while ret and tries < (max_wait / 4): + time.sleep(4) + self.log.debug('Attempt {}: {}'.format(tries, ret)) + ret = self.validate_rmq_ssl_disabled_units(sentry_units) + tries += 1 + + if ret: + amulet.raise_status(amulet.FAIL, ret) + + def connect_amqp_by_unit(self, sentry_unit, ssl=False, + port=None, fatal=True, + username="testuser1", password="changeme"): + """Establish and return a pika amqp connection to the rabbitmq service + running on a rmq juju unit. + + :param sentry_unit: sentry unit pointer + :param ssl: boolean, default to False + :param port: amqp port, use defaults if None + :param fatal: boolean, default to True (raises on connect error) + :param username: amqp user name, default to testuser1 + :param password: amqp user password + :returns: pika amqp connection pointer or None if failed and non-fatal + """ + host = sentry_unit.info['public-address'] + unit_name = sentry_unit.info['unit_name'] + + # Default port logic if port is not specified + if ssl and not port: + port = 5671 + elif not ssl and not port: + port = 5672 + + self.log.debug('Connecting to amqp on {}:{} ({}) as ' + '{}...'.format(host, port, unit_name, username)) + + try: + credentials = pika.PlainCredentials(username, password) + parameters = pika.ConnectionParameters(host=host, port=port, + credentials=credentials, + ssl=ssl, + connection_attempts=3, + retry_delay=5, + socket_timeout=1) + connection = pika.BlockingConnection(parameters) + assert connection.server_properties['product'] == 'RabbitMQ' + self.log.debug('Connect OK') + return connection + except Exception as e: + msg = ('amqp connection failed to {}:{} as ' + '{} ({})'.format(host, port, username, str(e))) + if fatal: + amulet.raise_status(amulet.FAIL, msg) + else: + self.log.warn(msg) + return None + + def publish_amqp_message_by_unit(self, sentry_unit, message, + queue="test", ssl=False, + username="testuser1", + password="changeme", + port=None): + """Publish an amqp message to a rmq juju unit. + + :param sentry_unit: sentry unit pointer + :param message: amqp message string + :param queue: message queue, default to test + :param username: amqp user name, default to testuser1 + :param password: amqp user password + :param ssl: boolean, default to False + :param port: amqp port, use defaults if None + :returns: None. Raises exception if publish failed. + """ + self.log.debug('Publishing message to {} queue:\n{}'.format(queue, + message)) + connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, + port=port, + username=username, + password=password) + + # NOTE(beisner): extra debug here re: pika hang potential: + # https://github.com/pika/pika/issues/297 + # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw + self.log.debug('Defining channel...') + channel = connection.channel() + self.log.debug('Declaring queue...') + channel.queue_declare(queue=queue, auto_delete=False, durable=True) + self.log.debug('Publishing message...') + channel.basic_publish(exchange='', routing_key=queue, body=message) + self.log.debug('Closing channel...') + channel.close() + self.log.debug('Closing connection...') + connection.close() + + def get_amqp_message_by_unit(self, sentry_unit, queue="test", + username="testuser1", + password="changeme", + ssl=False, port=None): + """Get an amqp message from a rmq juju unit. + + :param sentry_unit: sentry unit pointer + :param queue: message queue, default to test + :param username: amqp user name, default to testuser1 + :param password: amqp user password + :param ssl: boolean, default to False + :param port: amqp port, use defaults if None + :returns: amqp message body as string. Raise if get fails. + """ + connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, + port=port, + username=username, + password=password) + channel = connection.channel() + method_frame, _, body = channel.basic_get(queue) + + if method_frame: + self.log.debug('Retreived message from {} queue:\n{}'.format(queue, + body)) + channel.basic_ack(method_frame.delivery_tag) + channel.close() + connection.close() + return body + else: + msg = 'No message retrieved.' + amulet.raise_status(amulet.FAIL, msg) diff --git a/charmhelpers/contrib/openstack/context.py b/charmhelpers/contrib/openstack/context.py index 9a33a03..9ae4582 100644 --- a/charmhelpers/contrib/openstack/context.py +++ b/charmhelpers/contrib/openstack/context.py @@ -895,6 +895,18 @@ class NeutronContext(OSContextGenerator): 'neutron_url': '%s://%s:%s' % (proto, host, '9696')} return ctxt + def pg_ctxt(self): + driver = neutron_plugin_attribute(self.plugin, 'driver', + self.network_manager) + config = neutron_plugin_attribute(self.plugin, 'config', + self.network_manager) + ovs_ctxt = {'core_plugin': driver, + 'neutron_plugin': 'plumgrid', + 'neutron_security_groups': self.neutron_security_groups, + 'local_ip': unit_private_ip(), + 'config': config} + return ovs_ctxt + def __call__(self): if self.network_manager not in ['quantum', 'neutron']: return {} @@ -914,6 +926,8 @@ class NeutronContext(OSContextGenerator): ctxt.update(self.calico_ctxt()) elif self.plugin == 'vsp': ctxt.update(self.nuage_ctxt()) + elif self.plugin == 'plumgrid': + ctxt.update(self.pg_ctxt()) alchemy_flags = config('neutron-alchemy-flags') if alchemy_flags: diff --git a/charmhelpers/contrib/openstack/neutron.py b/charmhelpers/contrib/openstack/neutron.py index c3d5c28..55b2037 100644 --- a/charmhelpers/contrib/openstack/neutron.py +++ b/charmhelpers/contrib/openstack/neutron.py @@ -195,6 +195,20 @@ def neutron_plugins(): 'packages': [], 'server_packages': ['neutron-server', 'neutron-plugin-nuage'], 'server_services': ['neutron-server'] + }, + 'plumgrid': { + 'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini', + 'driver': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.NeutronPluginPLUMgridV2', + 'contexts': [ + context.SharedDBContext(user=config('database-user'), + database=config('database'), + ssl_dir=NEUTRON_CONF_DIR)], + 'services': [], + 'packages': [['plumgrid-lxc'], + ['iovisor-dkms']], + 'server_packages': ['neutron-server', + 'neutron-plugin-plumgrid'], + 'server_services': ['neutron-server'] } } if release >= 'icehouse': diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index 66fcb58..17a9379 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -114,6 +114,7 @@ SWIFT_CODENAMES = OrderedDict([ ('2.2.1', 'kilo'), ('2.2.2', 'kilo'), ('2.3.0', 'liberty'), + ('2.4.0', 'liberty'), ]) # >= Liberty version->codename mapping @@ -142,6 +143,9 @@ PACKAGE_CODENAMES = { 'glance-common': OrderedDict([ ('11.0.0', 'liberty'), ]), + 'openstack-dashboard': OrderedDict([ + ('8.0.0', 'liberty'), + ]), } DEFAULT_LOOPBACK_SIZE = '5G' @@ -227,7 +231,7 @@ def get_os_codename_package(package, fatal=True): error_out(e) vers = apt.upstream_version(pkg.current_ver.ver_str) - match = re.match('^(\d)\.(\d)\.(\d)', vers) + match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) if match: vers = match.group(0) @@ -248,6 +252,8 @@ def get_os_codename_package(package, fatal=True): vers = vers[:6] return OPENSTACK_CODENAMES[vers] except KeyError: + if not fatal: + return None e = 'Could not determine OpenStack codename for version %s' % vers error_out(e) diff --git a/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py index 00dbffb..6e03669 100644 --- a/charmhelpers/contrib/storage/linux/ceph.py +++ b/charmhelpers/contrib/storage/linux/ceph.py @@ -56,6 +56,8 @@ from charmhelpers.fetch import ( apt_install, ) +from charmhelpers.core.kernel import modprobe + KEYRING = '/etc/ceph/ceph.client.{}.keyring' KEYFILE = '/etc/ceph/ceph.client.{}.key' @@ -288,17 +290,6 @@ def place_data_on_block_device(blk_device, data_src_dst): os.chown(data_src_dst, uid, gid) -# TODO: re-use -def modprobe(module): - """Load a kernel module and configure for auto-load on reboot.""" - log('Loading kernel module', level=INFO) - cmd = ['modprobe', module] - check_call(cmd) - with open('/etc/modules', 'r+') as modules: - if module not in modules.read(): - modules.write(module) - - def copy_files(src, dst, symlinks=False, ignore=None): """Copy files from src to dst.""" for item in os.listdir(src): diff --git a/charmhelpers/core/host.py b/charmhelpers/core/host.py index 29e8fee..cb3c527 100644 --- a/charmhelpers/core/host.py +++ b/charmhelpers/core/host.py @@ -63,32 +63,48 @@ def service_reload(service_name, restart_on_failure=False): return service_result -def service_pause(service_name, init_dir=None): +def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"): """Pause a system service. Stop it, and prevent it from starting again at boot.""" - if init_dir is None: - init_dir = "/etc/init" stopped = service_stop(service_name) - # XXX: Support systemd too - override_path = os.path.join( - init_dir, '{}.override'.format(service_name)) - with open(override_path, 'w') as fh: - fh.write("manual\n") + upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) + sysv_file = os.path.join(initd_dir, service_name) + if os.path.exists(upstart_file): + override_path = os.path.join( + init_dir, '{}.override'.format(service_name)) + with open(override_path, 'w') as fh: + fh.write("manual\n") + elif os.path.exists(sysv_file): + subprocess.check_call(["update-rc.d", service_name, "disable"]) + else: + # XXX: Support SystemD too + raise ValueError( + "Unable to detect {0} as either Upstart {1} or SysV {2}".format( + service_name, upstart_file, sysv_file)) return stopped -def service_resume(service_name, init_dir=None): +def service_resume(service_name, init_dir="/etc/init", + initd_dir="/etc/init.d"): """Resume a system service. Reenable starting again at boot. Start the service""" - # XXX: Support systemd too - if init_dir is None: - init_dir = "/etc/init" - override_path = os.path.join( - init_dir, '{}.override'.format(service_name)) - if os.path.exists(override_path): - os.unlink(override_path) + upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) + sysv_file = os.path.join(initd_dir, service_name) + if os.path.exists(upstart_file): + override_path = os.path.join( + init_dir, '{}.override'.format(service_name)) + if os.path.exists(override_path): + os.unlink(override_path) + elif os.path.exists(sysv_file): + subprocess.check_call(["update-rc.d", service_name, "enable"]) + else: + # XXX: Support SystemD too + raise ValueError( + "Unable to detect {0} as either Upstart {1} or SysV {2}".format( + service_name, upstart_file, sysv_file)) + started = service_start(service_name) return started diff --git a/tests/charmhelpers/contrib/amulet/utils.py b/tests/charmhelpers/contrib/amulet/utils.py index 7816c93..1179997 100644 --- a/tests/charmhelpers/contrib/amulet/utils.py +++ b/tests/charmhelpers/contrib/amulet/utils.py @@ -594,3 +594,12 @@ class AmuletUtils(object): output = _check_output(command, universal_newlines=True) data = json.loads(output) return data.get(u"status") == "completed" + + def status_get(self, unit): + """Return the current service status of this unit.""" + raw_status, return_code = unit.run( + "status-get --format=json --include-data") + if return_code != 0: + return ("unknown", "") + status = json.loads(raw_status) + return (status["status"], status["message"]) From 43b36b9277d8241b351c0598d2123153e090638e Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 21:23:14 +0200 Subject: [PATCH 15/20] Test status, initially, post pause and post resume. --- tests/basic_deployment.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index de1cdc6..9dfdfeb 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -602,8 +602,14 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): def test_1000_pause_and_resume(self): """The services can be paused and resumed. """ unit_name = "ceilometer/0" + unit = self.d.sentry.unit[unit_name] + + assert u.status_get(unit)[0] == "unknown" + action_id = self._run_action(unit_name, "pause") assert self._wait_on_action(action_id), "Pause action failed." + assert u.status_get(unit)[0] == "maintenance" action_id = self._run_action(unit_name, "resume") assert self._wait_on_action(action_id), "Resume action failed." + assert u.status_get(unit)[0] == "active" From 212c98ce971132ac1d4f73ae95e50647dd54a376 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 10 Sep 2015 22:54:44 +0200 Subject: [PATCH 16/20] lint --- charmhelpers/core/kernel.py | 68 +++++++++++++++++++++++++++++++++++++ tests/basic_deployment.py | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 charmhelpers/core/kernel.py diff --git a/charmhelpers/core/kernel.py b/charmhelpers/core/kernel.py new file mode 100644 index 0000000..5dc6495 --- /dev/null +++ b/charmhelpers/core/kernel.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2014-2015 Canonical Limited. +# +# This file is part of charm-helpers. +# +# charm-helpers is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 as +# published by the Free Software Foundation. +# +# charm-helpers is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with charm-helpers. If not, see . + +__author__ = "Jorge Niedbalski " + +from charmhelpers.core.hookenv import ( + log, + INFO +) + +from subprocess import check_call, check_output +import re + + +def modprobe(module, persist=True): + """Load a kernel module and configure for auto-load on reboot.""" + cmd = ['modprobe', module] + + log('Loading kernel module %s' % module, level=INFO) + + check_call(cmd) + if persist: + with open('/etc/modules', 'r+') as modules: + if module not in modules.read(): + modules.write(module) + + +def rmmod(module, force=False): + """Remove a module from the linux kernel""" + cmd = ['rmmod'] + if force: + cmd.append('-f') + cmd.append(module) + log('Removing kernel module %s' % module, level=INFO) + return check_call(cmd) + + +def lsmod(): + """Shows what kernel modules are currently loaded""" + return check_output(['lsmod'], + universal_newlines=True) + + +def is_module_loaded(module): + """Checks if a kernel module is already loaded""" + matches = re.findall('^%s[ ]+' % module, lsmod(), re.M) + return len(matches) > 0 + + +def update_initramfs(version='all'): + """Updates an initramfs image""" + return check_call(["update-initramfs", "-k", version, "-u"]) diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py index 9dfdfeb..1caff7a 100644 --- a/tests/basic_deployment.py +++ b/tests/basic_deployment.py @@ -605,7 +605,7 @@ class CeilometerBasicDeployment(OpenStackAmuletDeployment): unit = self.d.sentry.unit[unit_name] assert u.status_get(unit)[0] == "unknown" - + action_id = self._run_action(unit_name, "pause") assert self._wait_on_action(action_id), "Pause action failed." assert u.status_get(unit)[0] == "maintenance" From 8420dfec88fa531c77bbc461236c82e8568e5794 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Tue, 15 Sep 2015 16:32:17 +0200 Subject: [PATCH 17/20] Update the descriptions of the pause and resume actions. --- actions.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions.yaml b/actions.yaml index 36ef29c..e8f7c3a 100644 --- a/actions.yaml +++ b/actions.yaml @@ -1,4 +1,4 @@ pause: - description: Pause the apache service providing ceilometer functions. + description: Pause the Ceilometer unit. This action will stop Ceilometer services. resume: - descrpition: Resume the apache service providing ceilometer functions. \ No newline at end of file + descrpition: Resume the Ceilometer unit. This action will start Ceilometer services. \ No newline at end of file From 690306b117d23c910c1973c1fea6428f78b12db7 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Tue, 15 Sep 2015 16:44:30 +0200 Subject: [PATCH 18/20] Import CEILOMETER_SERVICES. --- actions/actions.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/actions/actions.py b/actions/actions.py index 78d016a..7205368 100755 --- a/actions/actions.py +++ b/actions/actions.py @@ -5,15 +5,7 @@ import sys from charmhelpers.core.host import service_pause, service_resume from charmhelpers.core.hookenv import action_fail, status_set - -CEILOMETER_SERVICES = [ - 'ceilometer-agent-central', - 'ceilometer-collector', - 'ceilometer-api', - 'ceilometer-alarm-evaluator', - 'ceilometer-alarm-notifier', - 'ceilometer-agent-notification', -] +from hooks.ceilometer_utils import CEILOMETER_SERVICES def pause(args): From 53941543039170c8d2c7bdcf5bb1ab61697fd307 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Wed, 16 Sep 2015 10:26:05 +0200 Subject: [PATCH 19/20] Use local linked ceilometer_utils.py in actions. --- actions/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/actions.py b/actions/actions.py index 7205368..9b8b6aa 100755 --- a/actions/actions.py +++ b/actions/actions.py @@ -5,7 +5,7 @@ import sys from charmhelpers.core.host import service_pause, service_resume from charmhelpers.core.hookenv import action_fail, status_set -from hooks.ceilometer_utils import CEILOMETER_SERVICES +from ceilometer_utils import CEILOMETER_SERVICES def pause(args): From d208449e0b3f4cd4c8dff4d4f76722d3231c4f68 Mon Sep 17 00:00:00 2001 From: "Geoffrey J. Teale" Date: Thu, 17 Sep 2015 10:21:52 +0200 Subject: [PATCH 20/20] Move ceilometer_contexts to root directory of charm and symlink to both hooks and actions. --- actions/ceilometer_contexts.py | 1 + ceilometer_contexts.py | 121 ++++++++++++++++++++++++++++++++ hooks/ceilometer_contexts.py | 122 +-------------------------------- 3 files changed, 123 insertions(+), 121 deletions(-) create mode 120000 actions/ceilometer_contexts.py create mode 100644 ceilometer_contexts.py mode change 100644 => 120000 hooks/ceilometer_contexts.py diff --git a/actions/ceilometer_contexts.py b/actions/ceilometer_contexts.py new file mode 120000 index 0000000..ca556f6 --- /dev/null +++ b/actions/ceilometer_contexts.py @@ -0,0 +1 @@ +../ceilometer_contexts.py \ No newline at end of file diff --git a/ceilometer_contexts.py b/ceilometer_contexts.py new file mode 100644 index 0000000..1cf8a0a --- /dev/null +++ b/ceilometer_contexts.py @@ -0,0 +1,121 @@ +from charmhelpers.core.hookenv import ( + relation_ids, + relation_get, + related_units, + config +) + +from charmhelpers.contrib.openstack.utils import os_release + +from charmhelpers.contrib.openstack.context import ( + OSContextGenerator, + context_complete, + ApacheSSLContext as SSLContext, +) + +from charmhelpers.contrib.hahelpers.cluster import ( + determine_apache_port, + determine_api_port +) + +CEILOMETER_DB = 'ceilometer' + + +class LoggingConfigContext(OSContextGenerator): + def __call__(self): + return {'debug': config('debug'), 'verbose': config('verbose')} + + +class MongoDBContext(OSContextGenerator): + interfaces = ['mongodb'] + + def __call__(self): + mongo_servers = [] + replset = None + use_replset = os_release('ceilometer-api') >= 'icehouse' + + for relid in relation_ids('shared-db'): + rel_units = related_units(relid) + use_replset = use_replset and (len(rel_units) > 1) + + for unit in rel_units: + host = relation_get('hostname', unit, relid) + port = relation_get('port', unit, relid) + + conf = { + "db_host": host, + "db_port": port, + "db_name": CEILOMETER_DB + } + + if not context_complete(conf): + continue + + if not use_replset: + return conf + + if replset is None: + replset = relation_get('replset', unit, relid) + + mongo_servers.append('{}:{}'.format(host, port)) + + if mongo_servers: + return { + 'db_mongo_servers': ','.join(mongo_servers), + 'db_name': CEILOMETER_DB, + 'db_replset': replset + } + + return {} + + +CEILOMETER_PORT = 8777 + + +class CeilometerContext(OSContextGenerator): + def __call__(self): + # Lazy-import to avoid a circular dependency in the imports + from ceilometer_utils import get_shared_secret + + ctxt = { + 'port': CEILOMETER_PORT, + 'metering_secret': get_shared_secret() + } + return ctxt + + +class CeilometerServiceContext(OSContextGenerator): + interfaces = ['ceilometer-service'] + + def __call__(self): + for relid in relation_ids('ceilometer-service'): + for unit in related_units(relid): + conf = relation_get(unit=unit, rid=relid) + if context_complete(conf): + return conf + return {} + + +class HAProxyContext(OSContextGenerator): + interfaces = ['ceilometer-haproxy'] + + def __call__(self): + '''Extends the main charmhelpers HAProxyContext with a port mapping + specific to this charm. + ''' + haproxy_port = CEILOMETER_PORT + api_port = determine_api_port(CEILOMETER_PORT, singlenode_mode=True) + apache_port = determine_apache_port(CEILOMETER_PORT, + singlenode_mode=True) + + ctxt = { + 'service_ports': {'ceilometer_api': [haproxy_port, apache_port]}, + 'port': api_port + } + return ctxt + + +class ApacheSSLContext(SSLContext): + + external_ports = [CEILOMETER_PORT] + service_namespace = "ceilometer" diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py deleted file mode 100644 index 1cf8a0a..0000000 --- a/hooks/ceilometer_contexts.py +++ /dev/null @@ -1,121 +0,0 @@ -from charmhelpers.core.hookenv import ( - relation_ids, - relation_get, - related_units, - config -) - -from charmhelpers.contrib.openstack.utils import os_release - -from charmhelpers.contrib.openstack.context import ( - OSContextGenerator, - context_complete, - ApacheSSLContext as SSLContext, -) - -from charmhelpers.contrib.hahelpers.cluster import ( - determine_apache_port, - determine_api_port -) - -CEILOMETER_DB = 'ceilometer' - - -class LoggingConfigContext(OSContextGenerator): - def __call__(self): - return {'debug': config('debug'), 'verbose': config('verbose')} - - -class MongoDBContext(OSContextGenerator): - interfaces = ['mongodb'] - - def __call__(self): - mongo_servers = [] - replset = None - use_replset = os_release('ceilometer-api') >= 'icehouse' - - for relid in relation_ids('shared-db'): - rel_units = related_units(relid) - use_replset = use_replset and (len(rel_units) > 1) - - for unit in rel_units: - host = relation_get('hostname', unit, relid) - port = relation_get('port', unit, relid) - - conf = { - "db_host": host, - "db_port": port, - "db_name": CEILOMETER_DB - } - - if not context_complete(conf): - continue - - if not use_replset: - return conf - - if replset is None: - replset = relation_get('replset', unit, relid) - - mongo_servers.append('{}:{}'.format(host, port)) - - if mongo_servers: - return { - 'db_mongo_servers': ','.join(mongo_servers), - 'db_name': CEILOMETER_DB, - 'db_replset': replset - } - - return {} - - -CEILOMETER_PORT = 8777 - - -class CeilometerContext(OSContextGenerator): - def __call__(self): - # Lazy-import to avoid a circular dependency in the imports - from ceilometer_utils import get_shared_secret - - ctxt = { - 'port': CEILOMETER_PORT, - 'metering_secret': get_shared_secret() - } - return ctxt - - -class CeilometerServiceContext(OSContextGenerator): - interfaces = ['ceilometer-service'] - - def __call__(self): - for relid in relation_ids('ceilometer-service'): - for unit in related_units(relid): - conf = relation_get(unit=unit, rid=relid) - if context_complete(conf): - return conf - return {} - - -class HAProxyContext(OSContextGenerator): - interfaces = ['ceilometer-haproxy'] - - def __call__(self): - '''Extends the main charmhelpers HAProxyContext with a port mapping - specific to this charm. - ''' - haproxy_port = CEILOMETER_PORT - api_port = determine_api_port(CEILOMETER_PORT, singlenode_mode=True) - apache_port = determine_apache_port(CEILOMETER_PORT, - singlenode_mode=True) - - ctxt = { - 'service_ports': {'ceilometer_api': [haproxy_port, apache_port]}, - 'port': api_port - } - return ctxt - - -class ApacheSSLContext(SSLContext): - - external_ports = [CEILOMETER_PORT] - service_namespace = "ceilometer" diff --git a/hooks/ceilometer_contexts.py b/hooks/ceilometer_contexts.py new file mode 120000 index 0000000..ca556f6 --- /dev/null +++ b/hooks/ceilometer_contexts.py @@ -0,0 +1 @@ +../ceilometer_contexts.py \ No newline at end of file