From 64d2c2233cae7bb562d1aaa15b22b1b09126f8f0 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 29 Oct 2014 22:30:35 -0500 Subject: [PATCH 01/17] [bradm] initial nrpe checks --- config.yaml | 11 + files/nrpe-external-master/check_upstart_job | 72 ++++++ .../contrib/charmsupport/__init__.py | 0 .../charmhelpers/contrib/charmsupport/nrpe.py | 218 ++++++++++++++++++ .../contrib/charmsupport/volumes.py | 156 +++++++++++++ hooks/keystone_hooks.py | 17 ++ hooks/nrpe-external-master-relation-changed | 1 + hooks/nrpe-external-master-relation-joined | 1 + metadata.yaml | 3 + 9 files changed, 479 insertions(+) create mode 100755 files/nrpe-external-master/check_upstart_job create mode 100644 hooks/charmhelpers/contrib/charmsupport/__init__.py create mode 100644 hooks/charmhelpers/contrib/charmsupport/nrpe.py create mode 100644 hooks/charmhelpers/contrib/charmsupport/volumes.py create mode 120000 hooks/nrpe-external-master-relation-changed create mode 120000 hooks/nrpe-external-master-relation-joined diff --git a/config.yaml b/config.yaml index 59e634ec..7c79e890 100644 --- a/config.yaml +++ b/config.yaml @@ -218,3 +218,14 @@ options: The CPU core multiplier to use when configuring worker processes for Keystone. By default, the number of workers for each daemon is set to twice the number of CPU cores a service unit has. + nagios_context: + default: "juju" + type: string + description: | + Used by the nrpe-external-master subordinate charm. + A string that will be prepended to instance name to set the host name + in nagios. So for instance the hostname would be something like: + juju-myservice-0 + If you're running multiple environments with the same services in them + this allows you to differentiate between them. + diff --git a/files/nrpe-external-master/check_upstart_job b/files/nrpe-external-master/check_upstart_job new file mode 100755 index 00000000..94efb95e --- /dev/null +++ b/files/nrpe-external-master/check_upstart_job @@ -0,0 +1,72 @@ +#!/usr/bin/python + +# +# Copyright 2012, 2013 Canonical Ltd. +# +# Author: Paul Collins +# +# Based on http://www.eurion.net/python-snippets/snippet/Upstart%20service%20status.html +# + +import sys + +import dbus + + +class Upstart(object): + def __init__(self): + self._bus = dbus.SystemBus() + self._upstart = self._bus.get_object('com.ubuntu.Upstart', + '/com/ubuntu/Upstart') + def get_job(self, job_name): + path = self._upstart.GetJobByName(job_name, + dbus_interface='com.ubuntu.Upstart0_6') + return self._bus.get_object('com.ubuntu.Upstart', path) + + def get_properties(self, job): + path = job.GetInstance([], dbus_interface='com.ubuntu.Upstart0_6.Job') + instance = self._bus.get_object('com.ubuntu.Upstart', path) + return instance.GetAll('com.ubuntu.Upstart0_6.Instance', + dbus_interface=dbus.PROPERTIES_IFACE) + + def get_job_instances(self, job_name): + job = self.get_job(job_name) + paths = job.GetAllInstances([], dbus_interface='com.ubuntu.Upstart0_6.Job') + return [self._bus.get_object('com.ubuntu.Upstart', path) for path in paths] + + def get_job_instance_properties(self, job): + return job.GetAll('com.ubuntu.Upstart0_6.Instance', + dbus_interface=dbus.PROPERTIES_IFACE) + +try: + upstart = Upstart() + try: + job = upstart.get_job(sys.argv[1]) + props = upstart.get_properties(job) + + if props['state'] == 'running': + print 'OK: %s is running' % sys.argv[1] + sys.exit(0) + else: + print 'CRITICAL: %s is not running' % sys.argv[1] + sys.exit(2) + + except dbus.DBusException as e: + instances = upstart.get_job_instances(sys.argv[1]) + propses = [upstart.get_job_instance_properties(instance) for instance in instances] + states = dict([(props['name'], props['state']) for props in propses]) + if len(states) != states.values().count('running'): + not_running = [] + for name in states.keys(): + if states[name] != 'running': + not_running.append(name) + print 'CRITICAL: %d instances of %s not running: %s' % \ + (len(not_running), sys.argv[1], not_running.join(', ')) + sys.exit(2) + else: + print 'OK: %d instances of %s running' % (len(states), sys.argv[1]) + +except dbus.DBusException as e: + print 'CRITICAL: failed to get properties of \'%s\' from upstart' % sys.argv[1] + sys.exit(2) + diff --git a/hooks/charmhelpers/contrib/charmsupport/__init__.py b/hooks/charmhelpers/contrib/charmsupport/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/hooks/charmhelpers/contrib/charmsupport/nrpe.py new file mode 100644 index 00000000..f3bfe3f3 --- /dev/null +++ b/hooks/charmhelpers/contrib/charmsupport/nrpe.py @@ -0,0 +1,218 @@ +"""Compatibility with the nrpe-external-master charm""" +# Copyright 2012 Canonical Ltd. +# +# Authors: +# Matthew Wedgwood + +import subprocess +import pwd +import grp +import os +import re +import shlex +import yaml + +from charmhelpers.core.hookenv import ( + config, + local_unit, + log, + relation_ids, + relation_set, +) + +from charmhelpers.core.host import service + +# This module adds compatibility with the nrpe-external-master and plain nrpe +# subordinate charms. To use it in your charm: +# +# 1. Update metadata.yaml +# +# provides: +# (...) +# nrpe-external-master: +# interface: nrpe-external-master +# scope: container +# +# and/or +# +# provides: +# (...) +# local-monitors: +# interface: local-monitors +# scope: container + +# +# 2. Add the following to config.yaml +# +# nagios_context: +# default: "juju" +# type: string +# description: | +# Used by the nrpe subordinate charms. +# A string that will be prepended to instance name to set the host name +# in nagios. So for instance the hostname would be something like: +# juju-myservice-0 +# If you're running multiple environments with the same services in them +# this allows you to differentiate between them. +# +# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master +# +# 4. Update your hooks.py with something like this: +# +# from charmsupport.nrpe import NRPE +# (...) +# def update_nrpe_config(): +# nrpe_compat = NRPE() +# nrpe_compat.add_check( +# shortname = "myservice", +# description = "Check MyService", +# check_cmd = "check_http -w 2 -c 10 http://localhost" +# ) +# nrpe_compat.add_check( +# "myservice_other", +# "Check for widget failures", +# check_cmd = "/srv/myapp/scripts/widget_check" +# ) +# nrpe_compat.write() +# +# def config_changed(): +# (...) +# update_nrpe_config() +# +# def nrpe_external_master_relation_changed(): +# update_nrpe_config() +# +# def local_monitors_relation_changed(): +# update_nrpe_config() +# +# 5. ln -s hooks.py nrpe-external-master-relation-changed +# ln -s hooks.py local-monitors-relation-changed + + +class CheckException(Exception): + pass + + +class Check(object): + shortname_re = '[A-Za-z0-9-_]+$' + service_template = (""" +#--------------------------------------------------- +# This file is Juju managed +#--------------------------------------------------- +define service {{ + use active-service + host_name {nagios_hostname} + service_description {nagios_hostname}[{shortname}] """ + """{description} + check_command check_nrpe!{command} + servicegroups {nagios_servicegroup} +}} +""") + + def __init__(self, shortname, description, check_cmd): + super(Check, self).__init__() + # XXX: could be better to calculate this from the service name + if not re.match(self.shortname_re, shortname): + raise CheckException("shortname must match {}".format( + Check.shortname_re)) + self.shortname = shortname + self.command = "check_{}".format(shortname) + # Note: a set of invalid characters is defined by the + # Nagios server config + # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()= + self.description = description + self.check_cmd = self._locate_cmd(check_cmd) + + def _locate_cmd(self, check_cmd): + search_path = ( + '/', + os.path.join(os.environ['CHARM_DIR'], + 'files/nrpe-external-master'), + '/usr/lib/nagios/plugins', + ) + parts = shlex.split(check_cmd) + for path in search_path: + if os.path.exists(os.path.join(path, parts[0])): + command = os.path.join(path, parts[0]) + if len(parts) > 1: + command += " " + " ".join(parts[1:]) + return command + log('Check command not found: {}'.format(parts[0])) + return '' + + def write(self, nagios_context, hostname): + nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format( + self.command) + with open(nrpe_check_file, 'w') as nrpe_check_config: + nrpe_check_config.write("# check {}\n".format(self.shortname)) + nrpe_check_config.write("command[{}]={}\n".format( + self.command, self.check_cmd)) + + if not os.path.exists(NRPE.nagios_exportdir): + log('Not writing service config as {} is not accessible'.format( + NRPE.nagios_exportdir)) + else: + self.write_service_config(nagios_context, hostname) + + def write_service_config(self, nagios_context, hostname): + for f in os.listdir(NRPE.nagios_exportdir): + if re.search('.*{}.cfg'.format(self.command), f): + os.remove(os.path.join(NRPE.nagios_exportdir, f)) + + templ_vars = { + 'nagios_hostname': hostname, + 'nagios_servicegroup': nagios_context, + 'description': self.description, + 'shortname': self.shortname, + 'command': self.command, + } + nrpe_service_text = Check.service_template.format(**templ_vars) + nrpe_service_file = '{}/service__{}_{}.cfg'.format( + NRPE.nagios_exportdir, hostname, self.command) + with open(nrpe_service_file, 'w') as nrpe_service_config: + nrpe_service_config.write(str(nrpe_service_text)) + + def run(self): + subprocess.call(self.check_cmd) + + +class NRPE(object): + nagios_logdir = '/var/log/nagios' + nagios_exportdir = '/var/lib/nagios/export' + nrpe_confdir = '/etc/nagios/nrpe.d' + + def __init__(self): + super(NRPE, self).__init__() + self.config = config() + self.nagios_context = self.config['nagios_context'] + self.unit_name = local_unit().replace('/', '-') + self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) + self.checks = [] + + def add_check(self, *args, **kwargs): + self.checks.append(Check(*args, **kwargs)) + + def write(self): + try: + nagios_uid = pwd.getpwnam('nagios').pw_uid + nagios_gid = grp.getgrnam('nagios').gr_gid + except: + log("Nagios user not set up, nrpe checks not updated") + return + + if not os.path.exists(NRPE.nagios_logdir): + os.mkdir(NRPE.nagios_logdir) + os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid) + + nrpe_monitors = {} + monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}} + for nrpecheck in self.checks: + nrpecheck.write(self.nagios_context, self.hostname) + nrpe_monitors[nrpecheck.shortname] = { + "command": nrpecheck.command, + } + + service('restart', 'nagios-nrpe-server') + + for rid in relation_ids("local-monitors"): + relation_set(relation_id=rid, monitors=yaml.dump(monitors)) diff --git a/hooks/charmhelpers/contrib/charmsupport/volumes.py b/hooks/charmhelpers/contrib/charmsupport/volumes.py new file mode 100644 index 00000000..0f905dff --- /dev/null +++ b/hooks/charmhelpers/contrib/charmsupport/volumes.py @@ -0,0 +1,156 @@ +''' +Functions for managing volumes in juju units. One volume is supported per unit. +Subordinates may have their own storage, provided it is on its own partition. + +Configuration stanzas: + volume-ephemeral: + type: boolean + default: true + description: > + If false, a volume is mounted as sepecified in "volume-map" + If true, ephemeral storage will be used, meaning that log data + will only exist as long as the machine. YOU HAVE BEEN WARNED. + volume-map: + type: string + default: {} + description: > + YAML map of units to device names, e.g: + "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }" + Service units will raise a configure-error if volume-ephemeral + is 'true' and no volume-map value is set. Use 'juju set' to set a + value and 'juju resolved' to complete configuration. + +Usage: + from charmsupport.volumes import configure_volume, VolumeConfigurationError + from charmsupport.hookenv import log, ERROR + def post_mount_hook(): + stop_service('myservice') + def post_mount_hook(): + start_service('myservice') + + if __name__ == '__main__': + try: + configure_volume(before_change=pre_mount_hook, + after_change=post_mount_hook) + except VolumeConfigurationError: + log('Storage could not be configured', ERROR) +''' + +# XXX: Known limitations +# - fstab is neither consulted nor updated + +import os +from charmhelpers.core import hookenv +from charmhelpers.core import host +import yaml + + +MOUNT_BASE = '/srv/juju/volumes' + + +class VolumeConfigurationError(Exception): + '''Volume configuration data is missing or invalid''' + pass + + +def get_config(): + '''Gather and sanity-check volume configuration data''' + volume_config = {} + config = hookenv.config() + + errors = False + + if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'): + volume_config['ephemeral'] = True + else: + volume_config['ephemeral'] = False + + try: + volume_map = yaml.safe_load(config.get('volume-map', '{}')) + except yaml.YAMLError as e: + hookenv.log("Error parsing YAML volume-map: {}".format(e), + hookenv.ERROR) + errors = True + if volume_map is None: + # probably an empty string + volume_map = {} + elif not isinstance(volume_map, dict): + hookenv.log("Volume-map should be a dictionary, not {}".format( + type(volume_map))) + errors = True + + volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME']) + if volume_config['device'] and volume_config['ephemeral']: + # asked for ephemeral storage but also defined a volume ID + hookenv.log('A volume is defined for this unit, but ephemeral ' + 'storage was requested', hookenv.ERROR) + errors = True + elif not volume_config['device'] and not volume_config['ephemeral']: + # asked for permanent storage but did not define volume ID + hookenv.log('Ephemeral storage was requested, but there is no volume ' + 'defined for this unit.', hookenv.ERROR) + errors = True + + unit_mount_name = hookenv.local_unit().replace('/', '-') + volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name) + + if errors: + return None + return volume_config + + +def mount_volume(config): + if os.path.exists(config['mountpoint']): + if not os.path.isdir(config['mountpoint']): + hookenv.log('Not a directory: {}'.format(config['mountpoint'])) + raise VolumeConfigurationError() + else: + host.mkdir(config['mountpoint']) + if os.path.ismount(config['mountpoint']): + unmount_volume(config) + if not host.mount(config['device'], config['mountpoint'], persist=True): + raise VolumeConfigurationError() + + +def unmount_volume(config): + if os.path.ismount(config['mountpoint']): + if not host.umount(config['mountpoint'], persist=True): + raise VolumeConfigurationError() + + +def managed_mounts(): + '''List of all mounted managed volumes''' + return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts()) + + +def configure_volume(before_change=lambda: None, after_change=lambda: None): + '''Set up storage (or don't) according to the charm's volume configuration. + Returns the mount point or "ephemeral". before_change and after_change + are optional functions to be called if the volume configuration changes. + ''' + + config = get_config() + if not config: + hookenv.log('Failed to read volume configuration', hookenv.CRITICAL) + raise VolumeConfigurationError() + + if config['ephemeral']: + if os.path.ismount(config['mountpoint']): + before_change() + unmount_volume(config) + after_change() + return 'ephemeral' + else: + # persistent storage + if os.path.ismount(config['mountpoint']): + mounts = dict(managed_mounts()) + if mounts.get(config['mountpoint']) != config['device']: + before_change() + unmount_volume(config) + mount_volume(config) + after_change() + else: + before_change() + mount_volume(config) + after_change() + return config['mountpoint'] diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 788fc531..7769becc 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -77,6 +77,8 @@ from charmhelpers.contrib.network.ip import ( ) from charmhelpers.contrib.openstack.context import ADDRESS_TYPES +from charmhelpers.contrib.charmsupport.nrpe import NRPE + hooks = Hooks() CONFIGS = register_configs() @@ -109,6 +111,7 @@ def config_changed(): save_script_rc() configure_https() + update_nrpe_config() CONFIGS.write_all() if eligible_leader(CLUSTER_RES): migrate_database() @@ -355,6 +358,7 @@ def upgrade_charm(): group='keystone', peer_interface='cluster', ensure_local_user=True) + update_nrpe_config() synchronize_ca() if eligible_leader(CLUSTER_RES): log('Cluster leader - ensuring endpoint configuration' @@ -369,6 +373,19 @@ def upgrade_charm(): CONFIGS.write_all() +@hooks.hook('nrpe-external-master-relation-joined', 'nrpe-external-master-relation-changed') +def update_nrpe_config(): + nrpe = NRPE() + apt_install('python-dbus') + + nrpe.add_check( + shortname='keystone', + description='keystone process', + check_cmd = 'check_upstart_job keystone', + ) + + nrpe.write() + def main(): try: hooks.execute(sys.argv) diff --git a/hooks/nrpe-external-master-relation-changed b/hooks/nrpe-external-master-relation-changed new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/nrpe-external-master-relation-changed @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/hooks/nrpe-external-master-relation-joined b/hooks/nrpe-external-master-relation-joined new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/nrpe-external-master-relation-joined @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 7b4715a3..81e1bf27 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -7,6 +7,9 @@ description: | implements OpenStack’s Identity API. categories: ["misc"] provides: + nrpe-external-master: + interface: nrpe-external-master + scope: container identity-service: interface: keystone identity-admin: From ac36d51dbdccd07ea8f6194e20a8dce857adf9ed Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Thu, 30 Oct 2014 15:59:33 +1000 Subject: [PATCH 02/17] [bradm] Added charmsupport to charmhelpers --- charm-helpers-hooks.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml index e34e6ac8..f2e48204 100644 --- a/charm-helpers-hooks.yaml +++ b/charm-helpers-hooks.yaml @@ -12,3 +12,4 @@ include: - payload.execd - contrib.peerstorage - contrib.network.ip + - contrib.charmsupport From ebf0ccaf5c11401266e43b0052352005c1712896 Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Fri, 31 Oct 2014 14:51:08 +1000 Subject: [PATCH 03/17] [bradm] Added support to get nagios hostname from nrpe relation --- hooks/charmhelpers/contrib/charmsupport/nrpe.py | 8 ++++++-- hooks/keystone_hooks.py | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/hooks/charmhelpers/contrib/charmsupport/nrpe.py index f3bfe3f3..51b62d39 100644 --- a/hooks/charmhelpers/contrib/charmsupport/nrpe.py +++ b/hooks/charmhelpers/contrib/charmsupport/nrpe.py @@ -129,6 +129,7 @@ define service {{ os.path.join(os.environ['CHARM_DIR'], 'files/nrpe-external-master'), '/usr/lib/nagios/plugins', + '/usr/local/lib/nagios/plugins', ) parts = shlex.split(check_cmd) for path in search_path: @@ -181,12 +182,15 @@ class NRPE(object): nagios_exportdir = '/var/lib/nagios/export' nrpe_confdir = '/etc/nagios/nrpe.d' - def __init__(self): + def __init__(self, hostname=None): super(NRPE, self).__init__() self.config = config() self.nagios_context = self.config['nagios_context'] self.unit_name = local_unit().replace('/', '-') - self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) + if hostname: + self.hostname = hostname + else: + self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) self.checks = [] def add_check(self, *args, **kwargs): diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 7769becc..7696c371 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -19,6 +19,7 @@ from charmhelpers.core.hookenv import ( relation_get, relation_ids, relation_set, + relations_of_type, related_units, unit_get, ) @@ -375,7 +376,13 @@ def upgrade_charm(): @hooks.hook('nrpe-external-master-relation-joined', 'nrpe-external-master-relation-changed') def update_nrpe_config(): - nrpe = NRPE() + # Find out if nrpe set nagios_hostname + hostname = None + for rel in relations_of_type('nrpe-external-master'): + if 'nagios_hostname' in rel: + hostname = rel['nagios_hostname'] + break + nrpe = NRPE(hostname=hostname) apt_install('python-dbus') nrpe.add_check( From 3c0976a8943156bf55ef9fa186284ed9716b70fe Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Tue, 4 Nov 2014 17:09:28 +1000 Subject: [PATCH 04/17] [bradm] Tweaked check to include host context and unit name --- hooks/keystone_hooks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 7696c371..161f2f09 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -381,13 +381,16 @@ def update_nrpe_config(): for rel in relations_of_type('nrpe-external-master'): if 'nagios_hostname' in rel: hostname = rel['nagios_hostname'] + host_context = rel['nagios_host_context'] break nrpe = NRPE(hostname=hostname) apt_install('python-dbus') - + + current_unit = "%s:%s" % (host_context, local_unit()) + nrpe.add_check( shortname='keystone', - description='keystone process', + description='process check {%s}' % current_unit, check_cmd = 'check_upstart_job keystone', ) From 5c28abef9c951625df82baae7d97a6d3ab57f784 Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Thu, 6 Nov 2014 17:29:46 +1000 Subject: [PATCH 05/17] [bradm] Check if host_context is defined before using it --- hooks/keystone_hooks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 161f2f09..940608e9 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -378,6 +378,7 @@ def upgrade_charm(): def update_nrpe_config(): # Find out if nrpe set nagios_hostname hostname = None + host_context = None for rel in relations_of_type('nrpe-external-master'): if 'nagios_hostname' in rel: hostname = rel['nagios_hostname'] @@ -386,7 +387,10 @@ def update_nrpe_config(): nrpe = NRPE(hostname=hostname) apt_install('python-dbus') - current_unit = "%s:%s" % (host_context, local_unit()) + if host_context: + current_unit = "%s:%s" % (host_context, local_unit()) + else: + current_unit = local_unit() nrpe.add_check( shortname='keystone', From 0fb0cbd29ee671c12bf10aaea7634786d32ff7c0 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 12 Nov 2014 09:27:15 +0000 Subject: [PATCH 06/17] Provide fallback config options for HA VIP iface and cidr when it cannot be automatically determined --- config.yaml | 12 ++++++++++++ hooks/keystone_hooks.py | 8 ++++++-- unit_tests/test_keystone_hooks.py | 25 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index 59e634ec..c1534ebc 100644 --- a/config.yaml +++ b/config.yaml @@ -130,6 +130,18 @@ options: . If multiple networks are being used, a VIP should be provided for each network, separated by spaces. + vip_iface: + type: string + default: eth0 + description: | + Default network interface to use for HA vip when it cannot be automatically + determined. + vip_cidr: + type: int + default: 24 + description: | + Default CIDR netmask to use for HA vip when it cannot be automatically + determined. ha-bindiface: type: string default: eth0 diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 788fc531..828255ae 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -270,7 +270,11 @@ def ha_joined(): res_ks_vip = 'ocf:heartbeat:IPaddr2' vip_params = 'ip' - iface = get_iface_for_address(vip) + iface = (get_iface_for_address(vip) or + config('vip_iface')) + netmask = (get_netmask_for_address(vip) or + config('vip_cidr')) + if iface is not None: vip_key = 'res_ks_{}_vip'.format(iface) resources[vip_key] = res_ks_vip @@ -279,7 +283,7 @@ def ha_joined(): ' nic="{iface}"'.format(ip=vip_params, vip=vip, iface=iface, - netmask=get_netmask_for_address(vip)) + netmask=netmask) ) vip_group.append(vip_key) diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 49f75539..737b465a 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -381,6 +381,31 @@ class KeystoneRelationTests(CharmTestCase): } self.relation_set.assert_called_with(**args) + def test_ha_joined_no_bound_ip(self): + self.get_hacluster_config.return_value = { + 'vip': '10.10.10.10', + 'ha-bindiface': 'em0', + 'ha-mcastport': '8080' + } + self.test_config.set('vip_iface', 'eth120') + self.test_config.set('vip_cidr', '21') + self.get_iface_for_address.return_value = None + self.get_netmask_for_address.return_value = None + hooks.ha_joined() + args = { + 'corosync_bindiface': 'em0', + 'corosync_mcastport': '8080', + 'init_services': {'res_ks_haproxy': 'haproxy'}, + 'resources': {'res_ks_eth120_vip': 'ocf:heartbeat:IPaddr2', + 'res_ks_haproxy': 'lsb:haproxy'}, + 'resource_params': { + 'res_ks_eth120_vip': 'params ip="10.10.10.10"' + ' cidr_netmask="21" nic="eth120"', + 'res_ks_haproxy': 'op monitor interval="5s"'}, + 'clones': {'cl_ks_haproxy': 'res_ks_haproxy'} + } + self.relation_set.assert_called_with(**args) + def test_ha_joined_with_ipv6(self): self.test_config.set('prefer-ipv6', True) self.get_hacluster_config.return_value = { From d3ce3b37f223848746e608c7687fd593accd12de Mon Sep 17 00:00:00 2001 From: James Page Date: Sun, 16 Nov 2014 08:36:33 -0600 Subject: [PATCH 07/17] Resync new helper, fixup unit test --- charm-helpers-hooks.yaml | 2 +- hooks/charmhelpers/contrib/network/ip.py | 2 - .../charmhelpers/contrib/openstack/context.py | 192 +++++++++++++----- .../charmhelpers/contrib/openstack/neutron.py | 17 +- .../contrib/openstack/templates/haproxy.cfg | 3 +- hooks/charmhelpers/contrib/openstack/utils.py | 24 +++ .../contrib/storage/linux/ceph.py | 7 +- hooks/charmhelpers/core/hookenv.py | 6 + hooks/charmhelpers/core/host.py | 10 +- hooks/charmhelpers/core/services/__init__.py | 4 +- hooks/charmhelpers/fetch/__init__.py | 6 +- hooks/charmhelpers/fetch/giturl.py | 44 ++++ unit_tests/test_keystone_contexts.py | 3 +- 13 files changed, 258 insertions(+), 62 deletions(-) create mode 100644 hooks/charmhelpers/fetch/giturl.py diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml index e34e6ac8..63b611f7 100644 --- a/charm-helpers-hooks.yaml +++ b/charm-helpers-hooks.yaml @@ -1,4 +1,4 @@ -branch: lp:charm-helpers +branch: lp:~james-page/charm-helpers/lp.1391784 destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py index e62e5655..c4bfeadb 100644 --- a/hooks/charmhelpers/contrib/network/ip.py +++ b/hooks/charmhelpers/contrib/network/ip.py @@ -8,7 +8,6 @@ from functools import partial from charmhelpers.core.hookenv import unit_get from charmhelpers.fetch import apt_install from charmhelpers.core.hookenv import ( - WARNING, ERROR, log ) @@ -175,7 +174,6 @@ def format_ipv6_addr(address): if is_ipv6(address): address = "[%s]" % address else: - log("Not a valid ipv6 address: %s" % address, level=WARNING) address = None return address diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 538dc913..aaadb790 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -15,6 +15,7 @@ from charmhelpers.fetch import ( from charmhelpers.core.hookenv import ( config, + is_relation_made, local_unit, log, relation_get, @@ -24,7 +25,7 @@ from charmhelpers.core.hookenv import ( unit_get, unit_private_ip, ERROR, - INFO + DEBUG ) from charmhelpers.core.host import ( @@ -57,8 +58,9 @@ from charmhelpers.contrib.network.ip import ( is_address_in_network ) -from charmhelpers.contrib.openstack.utils import get_host_ip - +from charmhelpers.contrib.openstack.utils import ( + get_host_ip, +) CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' @@ -456,26 +458,27 @@ class HAProxyContext(OSContextGenerator): if _laddr: cluster_hosts[laddr]['backends'][_unit] = _laddr - # NOTE(jamespage) no split configurations found, just use - # private addresses - if not cluster_hosts: - cluster_hosts[addr] = {} - cluster_hosts[addr]['network'] = "{}/{}".format( - addr, - get_netmask_for_address(addr) - ) - cluster_hosts[addr]['backends'] = {} - cluster_hosts[addr]['backends'][l_unit] = addr - for rid in relation_ids('cluster'): - for unit in related_units(rid): - _unit = unit.replace('/', '-') - _laddr = relation_get('private-address', - rid=rid, unit=unit) - if _laddr: - cluster_hosts[addr]['backends'][_unit] = _laddr + # NOTE(jamespage) add backend based on private address - this + # with either be the only backend or the fallback if no acls + # match in the frontend + cluster_hosts[addr] = {} + cluster_hosts[addr]['network'] = "{}/{}".format( + addr, + get_netmask_for_address(addr) + ) + cluster_hosts[addr]['backends'] = {} + cluster_hosts[addr]['backends'][l_unit] = addr + for rid in relation_ids('cluster'): + for unit in related_units(rid): + _unit = unit.replace('/', '-') + _laddr = relation_get('private-address', + rid=rid, unit=unit) + if _laddr: + cluster_hosts[addr]['backends'][_unit] = _laddr ctxt = { 'frontends': cluster_hosts, + 'default_backend': addr } if config('haproxy-server-timeout'): @@ -584,6 +587,49 @@ class ApacheSSLContext(OSContextGenerator): cns.append(k.lstrip('ssl_key_')) return list(set(cns)) + def get_network_addresses(self): + """For each network configured, return corresponding address and vip + (if available). + + Returns a list of tuples of the form: + + [(address_in_net_a, vip_in_net_a), + (address_in_net_b, vip_in_net_b), + ...] + + or, if no vip(s) available: + + [(address_in_net_a, address_in_net_a), + (address_in_net_b, address_in_net_b), + ...] + """ + addresses = [] + vips = [] + if config('vip'): + vips = config('vip').split() + + for net_type in ['os-internal-network', 'os-admin-network', + 'os-public-network']: + addr = get_address_in_network(config(net_type), + unit_get('private-address')) + if len(vips) > 1 and is_clustered(): + if not config(net_type): + log("Multiple networks configured but net_type " + "is None (%s)." % net_type, level='WARNING') + continue + + for vip in vips: + if is_address_in_network(config(net_type), vip): + addresses.append((addr, vip)) + break + + elif is_clustered() and config('vip'): + addresses.append((addr, config('vip'))) + else: + addresses.append((addr, addr)) + + return addresses + def __call__(self): if isinstance(self.external_ports, basestring): self.external_ports = [self.external_ports] @@ -602,27 +648,7 @@ class ApacheSSLContext(OSContextGenerator): for cn in self.canonical_names(): self.configure_cert(cn) - addresses = [] - vips = [] - if config('vip'): - vips = config('vip').split() - - for network_type in ['os-internal-network', - 'os-admin-network', - 'os-public-network']: - address = get_address_in_network(config(network_type), - unit_get('private-address')) - if len(vips) > 0 and is_clustered(): - for vip in vips: - if is_address_in_network(config(network_type), - vip): - addresses.append((address, vip)) - break - elif is_clustered(): - addresses.append((address, config('vip'))) - else: - addresses.append((address, address)) - + addresses = self.get_network_addresses() for address, endpoint in set(addresses): for api_port in self.external_ports: ext_port = determine_apache_port(api_port) @@ -700,6 +726,7 @@ class NeutronContext(OSContextGenerator): self.network_manager) n1kv_config = neutron_plugin_attribute(self.plugin, 'config', self.network_manager) + n1kv_user_config_flags = config('n1kv-config-flags') n1kv_ctxt = { 'core_plugin': driver, 'neutron_plugin': 'n1kv', @@ -710,11 +737,29 @@ class NeutronContext(OSContextGenerator): 'vsm_username': config('n1kv-vsm-username'), 'vsm_password': config('n1kv-vsm-password'), 'restrict_policy_profiles': config( - 'n1kv_restrict_policy_profiles'), + 'n1kv-restrict-policy-profiles'), } + if n1kv_user_config_flags: + flags = config_flags_parser(n1kv_user_config_flags) + n1kv_ctxt['user_config_flags'] = flags return n1kv_ctxt + def calico_ctxt(self): + driver = neutron_plugin_attribute(self.plugin, 'driver', + self.network_manager) + config = neutron_plugin_attribute(self.plugin, 'config', + self.network_manager) + calico_ctxt = { + 'core_plugin': driver, + 'neutron_plugin': 'Calico', + 'neutron_security_groups': self.neutron_security_groups, + 'local_ip': unit_private_ip(), + 'config': config + } + + return calico_ctxt + def neutron_ctxt(self): if https(): proto = 'https' @@ -748,6 +793,8 @@ class NeutronContext(OSContextGenerator): ctxt.update(self.nvp_ctxt()) elif self.plugin == 'n1kv': ctxt.update(self.n1kv_ctxt()) + elif self.plugin == 'Calico': + ctxt.update(self.calico_ctxt()) alchemy_flags = config('neutron-alchemy-flags') if alchemy_flags: @@ -761,21 +808,39 @@ class NeutronContext(OSContextGenerator): class OSConfigFlagContext(OSContextGenerator): """ - Responsible for adding user-defined config-flags in charm config to a - template context. + Provides support for user-defined config flags. + + Users can define a comma-seperated list of key=value pairs + in the charm configuration and apply them at any point in + any file by using a template flag. + + Sometimes users might want config flags inserted within a + specific section so this class allows users to specify the + template flag name, allowing for multiple template flags + (sections) within the same context. NOTE: the value of config-flags may be a comma-separated list of key=value pairs and some Openstack config files support comma-separated lists as values. """ + def __init__(self, charm_flag='config-flags', + template_flag='user_config_flags'): + """ + charm_flag: config flags in charm configuration. + template_flag: insert point for user-defined flags template file. + """ + super(OSConfigFlagContext, self).__init__() + self._charm_flag = charm_flag + self._template_flag = template_flag + def __call__(self): - config_flags = config('config-flags') + config_flags = config(self._charm_flag) if not config_flags: return {} - flags = config_flags_parser(config_flags) - return {'user_config_flags': flags} + return {self._template_flag: + config_flags_parser(config_flags)} class SubordinateConfigContext(OSContextGenerator): @@ -867,7 +932,7 @@ class SubordinateConfigContext(OSContextGenerator): else: ctxt[k] = v - log("%d section(s) found" % (len(ctxt['sections'])), level=INFO) + log("%d section(s) found" % (len(ctxt['sections'])), level=DEBUG) return ctxt @@ -922,3 +987,34 @@ class WorkerConfigContext(OSContextGenerator): "workers": self.num_cpus * multiplier } return ctxt + + +class ZeroMQContext(OSContextGenerator): + interfaces = ['zeromq-configuration'] + + def __call__(self): + ctxt = {} + if is_relation_made('zeromq-configuration', 'host'): + for rid in relation_ids('zeromq-configuration'): + for unit in related_units(rid): + ctxt['zmq_nonce'] = relation_get('nonce', unit, rid) + ctxt['zmq_host'] = relation_get('host', unit, rid) + return ctxt + + +class NotificationDriverContext(OSContextGenerator): + + def __init__(self, zmq_relation='zeromq-configuration', amqp_relation='amqp'): + """ + :param zmq_relation : Name of Zeromq relation to check + """ + self.zmq_relation = zmq_relation + self.amqp_relation = amqp_relation + + def __call__(self): + ctxt = { + 'notifications': 'False', + } + if is_relation_made(self.amqp_relation): + ctxt['notifications'] = "True" + return ctxt diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 84d97bca..b2a2dfe0 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -138,10 +138,25 @@ def neutron_plugins(): relation_prefix='neutron', ssl_dir=NEUTRON_CONF_DIR)], 'services': [], - 'packages': [['neutron-plugin-cisco']], + 'packages': [[headers_package()] + determine_dkms_package(), + ['neutron-plugin-cisco']], 'server_packages': ['neutron-server', 'neutron-plugin-cisco'], 'server_services': ['neutron-server'] + }, + 'Calico': { + 'config': '/etc/neutron/plugins/ml2/ml2_conf.ini', + 'driver': 'neutron.plugins.ml2.plugin.Ml2Plugin', + 'contexts': [ + context.SharedDBContext(user=config('neutron-database-user'), + database=config('neutron-database'), + relation_prefix='neutron', + ssl_dir=NEUTRON_CONF_DIR)], + 'services': ['calico-compute', 'bird', 'neutron-dhcp-agent'], + 'packages': [[headers_package()] + determine_dkms_package(), + ['calico-compute', 'bird', 'neutron-dhcp-agent']], + 'server_packages': ['neutron-server', 'calico-control'], + 'server_services': ['neutron-server'] } } if release >= 'icehouse': diff --git a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg index 19c9b856..daaee011 100644 --- a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg +++ b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg @@ -42,7 +42,8 @@ frontend tcp-in_{{ service }} {% for frontend in frontends -%} acl net_{{ frontend }} dst {{ frontends[frontend]['network'] }} use_backend {{ service }}_{{ frontend }} if net_{{ frontend }} - {% endfor %} + {% endfor -%} + default_backend {{ service }}_{{ default_backend }} {% for frontend in frontends -%} backend {{ service }}_{{ frontend }} balance leastconn diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index b0d1b03a..ae24fb91 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -2,6 +2,7 @@ # Common python helper functions used for OpenStack charms. from collections import OrderedDict +from functools import wraps import subprocess import json @@ -468,6 +469,14 @@ def get_hostname(address, fqdn=True): return result.split('.')[0] +def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'): + mm_map = {} + if os.path.isfile(mm_file): + with open(mm_file, 'r') as f: + mm_map = json.load(f) + return mm_map + + def sync_db_with_multi_ipv6_addresses(database, database_user, relation_prefix=None): hosts = get_ipv6_addr(dynamic_only=False) @@ -484,3 +493,18 @@ def sync_db_with_multi_ipv6_addresses(database, database_user, for rid in relation_ids('shared-db'): relation_set(relation_id=rid, **kwargs) + + +def os_requires_version(ostack_release, pkg): + """ + Decorator for hook to specify minimum supported release + """ + def wrap(f): + @wraps(f) + def wrapped_f(*args): + if os_release(pkg) < ostack_release: + raise Exception("This hook is not supported on releases" + " before %s" % ostack_release) + f(*args) + return wrapped_f + return wrap diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 768438a4..598ec263 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -113,7 +113,7 @@ def get_osds(service): return None -def create_pool(service, name, replicas=2): +def create_pool(service, name, replicas=3): ''' Create a new RADOS pool ''' if pool_exists(service, name): log("Ceph pool {} already exists, skipping creation".format(name), @@ -300,7 +300,8 @@ def copy_files(src, dst, symlinks=False, ignore=None): def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point, - blk_device, fstype, system_services=[]): + blk_device, fstype, system_services=[], + replicas=3): """ NOTE: This function must only be called from a single service unit for the same rbd_img otherwise data loss will occur. @@ -317,7 +318,7 @@ def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point, # Ensure pool, RBD image, RBD mappings are in place. if not pool_exists(service, pool): log('ceph: Creating new pool {}.'.format(pool)) - create_pool(service, pool) + create_pool(service, pool, replicas=replicas) if not rbd_exists(service, pool, rbd_img): log('ceph: Creating RBD image ({}).'.format(rbd_img)) diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index af8fe2db..083a7090 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -214,6 +214,12 @@ class Config(dict): except KeyError: return (self._prev_dict or {})[key] + def keys(self): + prev_keys = [] + if self._prev_dict is not None: + prev_keys = self._prev_dict.keys() + return list(set(prev_keys + dict.keys(self))) + def load_previous(self, path=None): """Load previous copy of config from disk. diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index d7ce1e4c..0b8bdc50 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -6,13 +6,13 @@ # Matthew Wedgwood import os +import re import pwd import grp import random import string import subprocess import hashlib -import shutil from contextlib import contextmanager from collections import OrderedDict @@ -317,7 +317,13 @@ def list_nics(nic_type): ip_output = (line for line in ip_output if line) for line in ip_output: if line.split()[1].startswith(int_type): - interfaces.append(line.split()[1].replace(":", "")) + matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line) + if matched: + interface = matched.groups()[0] + else: + interface = line.split()[1].replace(":", "") + interfaces.append(interface) + return interfaces diff --git a/hooks/charmhelpers/core/services/__init__.py b/hooks/charmhelpers/core/services/__init__.py index e8039a84..69dde79a 100644 --- a/hooks/charmhelpers/core/services/__init__.py +++ b/hooks/charmhelpers/core/services/__init__.py @@ -1,2 +1,2 @@ -from .base import * -from .helpers import * +from .base import * # NOQA +from .helpers import * # NOQA diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 32a673d6..2398e8ed 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -72,6 +72,7 @@ CLOUD_ARCHIVE_POCKETS = { FETCH_HANDLERS = ( 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler', 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler', + 'charmhelpers.fetch.giturl.GitUrlFetchHandler', ) APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT. @@ -218,6 +219,7 @@ def add_source(source, key=None): pocket for the release. 'cloud:' may be used to activate official cloud archive pockets, such as 'cloud:icehouse' + 'distro' may be used as a noop @param key: A key to be added to the system's APT keyring and used to verify the signatures on packages. Ideally, this should be an @@ -251,8 +253,10 @@ def add_source(source, key=None): release = lsb_release()['DISTRIB_CODENAME'] with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt: apt.write(PROPOSED_POCKET.format(release)) + elif source == 'distro': + pass else: - raise SourceConfigError("Unknown source: {!r}".format(source)) + log("Unknown source: {!r}".format(source)) if key: if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key: diff --git a/hooks/charmhelpers/fetch/giturl.py b/hooks/charmhelpers/fetch/giturl.py new file mode 100644 index 00000000..7d672460 --- /dev/null +++ b/hooks/charmhelpers/fetch/giturl.py @@ -0,0 +1,44 @@ +import os +from charmhelpers.fetch import ( + BaseFetchHandler, + UnhandledSource +) +from charmhelpers.core.host import mkdir + +try: + from git import Repo +except ImportError: + from charmhelpers.fetch import apt_install + apt_install("python-git") + from git import Repo + + +class GitUrlFetchHandler(BaseFetchHandler): + """Handler for git branches via generic and github URLs""" + def can_handle(self, source): + url_parts = self.parse_url(source) + #TODO (mattyw) no support for ssh git@ yet + if url_parts.scheme not in ('http', 'https', 'git'): + return False + else: + return True + + def clone(self, source, dest, branch): + if not self.can_handle(source): + raise UnhandledSource("Cannot handle {}".format(source)) + + repo = Repo.clone_from(source, dest) + repo.git.checkout(branch) + + def install(self, source, branch="master"): + url_parts = self.parse_url(source) + branch_name = url_parts.path.strip("/").split("/")[-1] + dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", + branch_name) + if not os.path.exists(dest_dir): + mkdir(dest_dir, perms=0755) + try: + self.clone(source, dest_dir, branch) + except OSError as e: + raise UnhandledSource(e.strerror) + return dest_dir diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py index e55a1ab3..d38a6f2d 100644 --- a/unit_tests/test_keystone_contexts.py +++ b/unit_tests/test_keystone_contexts.py @@ -88,6 +88,7 @@ class TestKeystoneContexts(CharmTestCase): 'keystone': '1.2.3.4', 'unit-0': '10.0.0.0' } - }} + }}, + 'default_backend': '1.2.3.4' } ) From 206d69d3bc1e6b4910e79a9f627f1290f2e5fbdc Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Mon, 17 Nov 2014 13:39:29 +1000 Subject: [PATCH 08/17] [bradm] Added sysvinit daemon monitoring, switched to using services() instead of hard coded list, pep8 fixes --- .../nrpe-external-master/check_exit_status.pl | 189 ++++++++++++++++++ .../nrpe-external-master/check_status_file.py | 60 ++++++ files/nrpe-external-master/nagios_plugin.py | 78 ++++++++ hooks/keystone_hooks.py | 39 +++- hooks/keystone_utils.py | 8 + 5 files changed, 368 insertions(+), 6 deletions(-) create mode 100755 files/nrpe-external-master/check_exit_status.pl create mode 100755 files/nrpe-external-master/check_status_file.py create mode 100755 files/nrpe-external-master/nagios_plugin.py diff --git a/files/nrpe-external-master/check_exit_status.pl b/files/nrpe-external-master/check_exit_status.pl new file mode 100755 index 00000000..49df22d8 --- /dev/null +++ b/files/nrpe-external-master/check_exit_status.pl @@ -0,0 +1,189 @@ +#!/usr/bin/perl +################################################################################ +# # +# Copyright (C) 2011 Chad Columbus # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program 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 General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +################################################################################ + +use strict; +use Getopt::Std; +$| = 1; + +my %opts; +getopts('heronp:s:', \%opts); + +my $VERSION = "Version 1.0"; +my $AUTHOR = '(c) 2011 Chad Columbus '; + +# Default values: +my $script_to_check; +my $pattern = 'is running'; +my $cmd; +my $message; +my $error; + +# Exit codes +my $STATE_OK = 0; +my $STATE_WARNING = 1; +my $STATE_CRITICAL = 2; +my $STATE_UNKNOWN = 3; + +# Parse command line options +if ($opts{'h'} || scalar(%opts) == 0) { + &print_help(); + exit($STATE_OK); +} + +# Make sure scipt is provided: +if ($opts{'s'} eq '') { + # Script to run not provided + print "\nYou must provide a script to run. Example: -s /etc/init.d/httpd\n"; + exit($STATE_UNKNOWN); +} else { + $script_to_check = $opts{'s'}; +} + +# Make sure only a-z, 0-9, /, _, and - are used in the script. +if ($script_to_check =~ /[^a-z0-9\_\-\/\.]/) { + # Script contains illegal characters exit. + print "\nScript to check can only contain Letters, Numbers, Periods, Underscores, Hyphens, and/or Slashes\n"; + exit($STATE_UNKNOWN); +} + +# See if script is executable +if (! -x "$script_to_check") { + print "\nIt appears you can't execute $script_to_check, $!\n"; + exit($STATE_UNKNOWN); +} + +# If a pattern is provided use it: +if ($opts{'p'} ne '') { + $pattern = $opts{'p'}; +} + +# If -r run command via sudo as root: +if ($opts{'r'}) { + $cmd = "sudo -n $script_to_check status" . ' 2>&1'; +} else { + $cmd = "$script_to_check status" . ' 2>&1'; +} + +my $cmd_result = `$cmd`; +chomp($cmd_result); +if ($cmd_result =~ /sudo/i) { + # This means it could not run the sudo command + $message = "$script_to_check CRITICAL - Could not run: 'sudo -n $script_to_check status'. Result is $cmd_result"; + $error = $STATE_UNKNOWN; +} else { + # Check exitstatus instead of output: + if ($opts{'e'} == 1) { + if ($? != 0) { + # error + $message = "$script_to_check CRITICAL - Exit code: $?\."; + if ($opts{'o'} == 0) { + $message .= " $cmd_result"; + } + $error = $STATE_CRITICAL; + } else { + # success + $message = "$script_to_check OK - Exit code: $?\."; + if ($opts{'o'} == 0) { + $message .= " $cmd_result"; + } + $error = $STATE_OK; + } + } else { + my $not_check = 1; + if ($opts{'n'} == 1) { + $not_check = 0; + } + if (($cmd_result =~ /$pattern/i) == $not_check) { + $message = "$script_to_check OK"; + if ($opts{'o'} == 0) { + $message .= " - $cmd_result"; + } + $error = $STATE_OK; + } else { + $message = "$script_to_check CRITICAL"; + if ($opts{'o'} == 0) { + $message .= " - $cmd_result"; + } + $error = $STATE_CRITICAL; + } + } +} + +if ($message eq '') { + print "Error: program failed in an unknown way\n"; + exit($STATE_UNKNOWN); +} + +if ($error) { + print "$message\n"; + exit($error); +} else { + # If we get here we are OK + print "$message\n"; + exit($STATE_OK); +} + +#################################### +# Start Subs: +#################################### +sub print_help() { + print << "EOF"; +Check the output or exit status of a script. +$VERSION +$AUTHOR + +Options: +-h + Print detailed help screen + +-s + 'FULL PATH TO SCRIPT' (required) + This is the script to run, the script is designed to run scripts in the + /etc/init.d dir (but can run any script) and will call the script with + a 'status' argument. So if you use another script make sure it will + work with /path/script status, example: /etc/init.d/httpd status + +-e + This is the "exitstaus" flag, it means check the exit status + code instead of looking for a pattern in the output of the script. + +-p 'REGEX' + This is a pattern to look for in the output of the script to confirm it + is running, default is 'is running', but not all init.d scripts output + (iptables), so you can specify an arbitrary pattern. + All patterns are case insensitive. + +-n + This is the "NOT" flag, it means not the -p pattern, so if you want to + make sure the output of the script does NOT contain -p 'REGEX' + +-r + This is the "ROOT" flag, it means run as root via sudo. You will need a + line in your /etc/sudoers file like: + nagios ALL=(root) NOPASSWD: /etc/init.d/* status + +-o + This is the "SUPPRESS OUTPUT" flag. Some programs have a long output + (like iptables), this flag suppresses that output so it is not printed + as a part of the nagios message. +EOF +} + diff --git a/files/nrpe-external-master/check_status_file.py b/files/nrpe-external-master/check_status_file.py new file mode 100755 index 00000000..ba828087 --- /dev/null +++ b/files/nrpe-external-master/check_status_file.py @@ -0,0 +1,60 @@ +#!/usr/bin/python + +# m +# mmmm m m mmmm mmmm mmm mm#mm +# #" "# # # #" "# #" "# #" # # +# # # # # # # # # #"""" # +# ##m#" "mm"# ##m#" ##m#" "#mm" "mm +# # # # +# " " " +# This file is managed by puppet. Do not make local changes. + +# +# Copyright 2014 Canonical Ltd. +# +# Author: Jacek Nykis +# + +import re +import nagios_plugin + + +def parse_args(): + import argparse + + parser = argparse.ArgumentParser( + description='Read file and return nagios status based on its content', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-f', '--status-file', required=True, + help='Status file path') + parser.add_argument('-c', '--critical-text', default='CRITICAL', + help='String indicating critical status') + parser.add_argument('-w', '--warning-text', default='WARNING', + help='String indicating warning status') + parser.add_argument('-o', '--ok-text', default='OK', + help='String indicating OK status') + parser.add_argument('-u', '--unknown-text', default='UNKNOWN', + help='String indicating unknown status') + return parser.parse_args() + + +def check_status(args): + nagios_plugin.check_file_freshness(args.status_file, 43200) + + with open(args.status_file, "r") as f: + content = [l.strip() for l in f.readlines()] + + for line in content: + if re.search(args.critical_text, line): + raise nagios_plugin.CriticalError(line) + elif re.search(args.warning_text, line): + raise nagios_plugin.WarnError(line) + elif re.search(args.unknown_text, line): + raise nagios_plugin.UnknownError(line) + else: + print line + + +if __name__ == '__main__': + args = parse_args() + nagios_plugin.try_check(check_status, args) diff --git a/files/nrpe-external-master/nagios_plugin.py b/files/nrpe-external-master/nagios_plugin.py new file mode 100755 index 00000000..f0f8e7b5 --- /dev/null +++ b/files/nrpe-external-master/nagios_plugin.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# m +# mmmm m m mmmm mmmm mmm mm#mm +# #" "# # # #" "# #" "# #" # # +# # # # # # # # # #"""" # +# ##m#" "mm"# ##m#" ##m#" "#mm" "mm +# # # # +# " " " +# This file is managed by puppet. Do not make local changes. + +# Copyright (C) 2005, 2006, 2007, 2012 James Troup + +import os +import stat +import time +import traceback +import sys + + +################################################################################ + +class CriticalError(Exception): + """This indicates a critical error.""" + pass + + +class WarnError(Exception): + """This indicates a warning condition.""" + pass + + +class UnknownError(Exception): + """This indicates a unknown error was encountered.""" + pass + + +def try_check(function, *args, **kwargs): + """Perform a check with error/warn/unknown handling.""" + try: + function(*args, **kwargs) + except UnknownError, msg: + print msg + sys.exit(3) + except CriticalError, msg: + print msg + sys.exit(2) + except WarnError, msg: + print msg + sys.exit(1) + except: + print "%s raised unknown exception '%s'" % (function, sys.exc_info()[0]) + print '=' * 60 + traceback.print_exc(file=sys.stdout) + print '=' * 60 + sys.exit(3) + + +################################################################################ + +def check_file_freshness(filename, newer_than=600): + """Check a file exists, is readable and is newer than seconds (where defaults to 600).""" + # First check the file exists and is readable + if not os.path.exists(filename): + raise CriticalError("%s: does not exist." % (filename)) + if os.access(filename, os.R_OK) == 0: + raise CriticalError("%s: is not readable." % (filename)) + + # Then ensure the file is up-to-date enough + mtime = os.stat(filename)[stat.ST_MTIME] + last_modified = time.time() - mtime + if last_modified > newer_than: + raise CriticalError("%s: was last modified on %s and is too old (> %s seconds)." + % (filename, time.ctime(mtime), newer_than)) + if last_modified < 0: + raise CriticalError("%s: was last modified on %s which is in the future." + % (filename, time.ctime(mtime))) + +################################################################################ diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 940608e9..2926e1c7 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -51,6 +51,7 @@ from keystone_utils import ( register_configs, relation_list, restart_map, + services, CLUSTER_RES, KEYSTONE_CONF, SSH_USER, @@ -374,7 +375,8 @@ def upgrade_charm(): CONFIGS.write_all() -@hooks.hook('nrpe-external-master-relation-joined', 'nrpe-external-master-relation-changed') +@hooks.hook('nrpe-external-master-relation-joined', + 'nrpe-external-master-relation-changed') def update_nrpe_config(): # Find out if nrpe set nagios_hostname hostname = None @@ -392,14 +394,39 @@ def update_nrpe_config(): else: current_unit = local_unit() - nrpe.add_check( - shortname='keystone', - description='process check {%s}' % current_unit, - check_cmd = 'check_upstart_job keystone', - ) + services_to_monitor = services() + + for service in services_to_monitor: + upstart_init = '/etc/init/%s.conf' % service + sysv_init = '/etc/init.d/%s' % service + + if os.path.exists(upstart_init): + nrpe.add_check( + shortname=service, + description='process check {%s}' % current_unit, + check_cmd='check_upstart_job %s' % service, + ) + elif os.path.exists(sysv_init): + cronpath = '/etc/cron.d/nagios-service-check-%s' % service + checkpath = os.path.join(os.environ['CHARM_DIR'], + 'files/nrpe-external-master', + 'check_exit_status.pl'), + cron_template = '*/5 * * * * root %s \ +-s /etc/init.d/%s status > /var/lib/nagios/service-check-%s.txt\n' \ + % (checkpath[0], service, service) + f = open(cronpath, 'w') + f.write(cron_template) + f.close() + nrpe.add_check( + shortname=service, + description='process check {%s}' % current_unit, + check_cmd='check_status_file.py -f \ +/var/lib/nagios/service-check-%s.txt' % service, + ) nrpe.write() + def main(): try: hooks.execute(sys.argv) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 936cd2f8..c4c28707 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -220,6 +220,14 @@ def restart_map(): if v['services']]) +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 determine_ports(): '''Assemble a list of API ports for services we are managing''' ports = [config('admin-port'), config('service-port')] From 45d4a583e9b3fda9f35f5549446898323e087cca Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Mon, 17 Nov 2014 15:07:40 +1000 Subject: [PATCH 09/17] [bradm] Removed puppet header from nagios_plugin module --- files/nrpe-external-master/nagios_plugin.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/files/nrpe-external-master/nagios_plugin.py b/files/nrpe-external-master/nagios_plugin.py index f0f8e7b5..fc0d7b7b 100755 --- a/files/nrpe-external-master/nagios_plugin.py +++ b/files/nrpe-external-master/nagios_plugin.py @@ -1,13 +1,4 @@ #!/usr/bin/env python -# m -# mmmm m m mmmm mmmm mmm mm#mm -# #" "# # # #" "# #" "# #" # # -# # # # # # # # # #"""" # -# ##m#" "mm"# ##m#" ##m#" "#mm" "mm -# # # # -# " " " -# This file is managed by puppet. Do not make local changes. - # Copyright (C) 2005, 2006, 2007, 2012 James Troup import os From 1df62593c4bddcdca8736335713b00d66f533375 Mon Sep 17 00:00:00 2001 From: Brad Marshall Date: Tue, 18 Nov 2014 11:14:34 +1000 Subject: [PATCH 10/17] [bradm] Removed nagios check files that were moved to nrpe-external-master charm --- .../nrpe-external-master/check_exit_status.pl | 189 ------------------ .../nrpe-external-master/check_status_file.py | 60 ------ files/nrpe-external-master/check_upstart_job | 72 ------- files/nrpe-external-master/nagios_plugin.py | 69 ------- hooks/keystone_hooks.py | 6 +- 5 files changed, 3 insertions(+), 393 deletions(-) delete mode 100755 files/nrpe-external-master/check_exit_status.pl delete mode 100755 files/nrpe-external-master/check_status_file.py delete mode 100755 files/nrpe-external-master/check_upstart_job delete mode 100755 files/nrpe-external-master/nagios_plugin.py diff --git a/files/nrpe-external-master/check_exit_status.pl b/files/nrpe-external-master/check_exit_status.pl deleted file mode 100755 index 49df22d8..00000000 --- a/files/nrpe-external-master/check_exit_status.pl +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/perl -################################################################################ -# # -# Copyright (C) 2011 Chad Columbus # -# # -# This program is free software; you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation; either version 2 of the License, or # -# (at your option) any later version. # -# # -# This program 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 General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the Free Software # -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# # -################################################################################ - -use strict; -use Getopt::Std; -$| = 1; - -my %opts; -getopts('heronp:s:', \%opts); - -my $VERSION = "Version 1.0"; -my $AUTHOR = '(c) 2011 Chad Columbus '; - -# Default values: -my $script_to_check; -my $pattern = 'is running'; -my $cmd; -my $message; -my $error; - -# Exit codes -my $STATE_OK = 0; -my $STATE_WARNING = 1; -my $STATE_CRITICAL = 2; -my $STATE_UNKNOWN = 3; - -# Parse command line options -if ($opts{'h'} || scalar(%opts) == 0) { - &print_help(); - exit($STATE_OK); -} - -# Make sure scipt is provided: -if ($opts{'s'} eq '') { - # Script to run not provided - print "\nYou must provide a script to run. Example: -s /etc/init.d/httpd\n"; - exit($STATE_UNKNOWN); -} else { - $script_to_check = $opts{'s'}; -} - -# Make sure only a-z, 0-9, /, _, and - are used in the script. -if ($script_to_check =~ /[^a-z0-9\_\-\/\.]/) { - # Script contains illegal characters exit. - print "\nScript to check can only contain Letters, Numbers, Periods, Underscores, Hyphens, and/or Slashes\n"; - exit($STATE_UNKNOWN); -} - -# See if script is executable -if (! -x "$script_to_check") { - print "\nIt appears you can't execute $script_to_check, $!\n"; - exit($STATE_UNKNOWN); -} - -# If a pattern is provided use it: -if ($opts{'p'} ne '') { - $pattern = $opts{'p'}; -} - -# If -r run command via sudo as root: -if ($opts{'r'}) { - $cmd = "sudo -n $script_to_check status" . ' 2>&1'; -} else { - $cmd = "$script_to_check status" . ' 2>&1'; -} - -my $cmd_result = `$cmd`; -chomp($cmd_result); -if ($cmd_result =~ /sudo/i) { - # This means it could not run the sudo command - $message = "$script_to_check CRITICAL - Could not run: 'sudo -n $script_to_check status'. Result is $cmd_result"; - $error = $STATE_UNKNOWN; -} else { - # Check exitstatus instead of output: - if ($opts{'e'} == 1) { - if ($? != 0) { - # error - $message = "$script_to_check CRITICAL - Exit code: $?\."; - if ($opts{'o'} == 0) { - $message .= " $cmd_result"; - } - $error = $STATE_CRITICAL; - } else { - # success - $message = "$script_to_check OK - Exit code: $?\."; - if ($opts{'o'} == 0) { - $message .= " $cmd_result"; - } - $error = $STATE_OK; - } - } else { - my $not_check = 1; - if ($opts{'n'} == 1) { - $not_check = 0; - } - if (($cmd_result =~ /$pattern/i) == $not_check) { - $message = "$script_to_check OK"; - if ($opts{'o'} == 0) { - $message .= " - $cmd_result"; - } - $error = $STATE_OK; - } else { - $message = "$script_to_check CRITICAL"; - if ($opts{'o'} == 0) { - $message .= " - $cmd_result"; - } - $error = $STATE_CRITICAL; - } - } -} - -if ($message eq '') { - print "Error: program failed in an unknown way\n"; - exit($STATE_UNKNOWN); -} - -if ($error) { - print "$message\n"; - exit($error); -} else { - # If we get here we are OK - print "$message\n"; - exit($STATE_OK); -} - -#################################### -# Start Subs: -#################################### -sub print_help() { - print << "EOF"; -Check the output or exit status of a script. -$VERSION -$AUTHOR - -Options: --h - Print detailed help screen - --s - 'FULL PATH TO SCRIPT' (required) - This is the script to run, the script is designed to run scripts in the - /etc/init.d dir (but can run any script) and will call the script with - a 'status' argument. So if you use another script make sure it will - work with /path/script status, example: /etc/init.d/httpd status - --e - This is the "exitstaus" flag, it means check the exit status - code instead of looking for a pattern in the output of the script. - --p 'REGEX' - This is a pattern to look for in the output of the script to confirm it - is running, default is 'is running', but not all init.d scripts output - (iptables), so you can specify an arbitrary pattern. - All patterns are case insensitive. - --n - This is the "NOT" flag, it means not the -p pattern, so if you want to - make sure the output of the script does NOT contain -p 'REGEX' - --r - This is the "ROOT" flag, it means run as root via sudo. You will need a - line in your /etc/sudoers file like: - nagios ALL=(root) NOPASSWD: /etc/init.d/* status - --o - This is the "SUPPRESS OUTPUT" flag. Some programs have a long output - (like iptables), this flag suppresses that output so it is not printed - as a part of the nagios message. -EOF -} - diff --git a/files/nrpe-external-master/check_status_file.py b/files/nrpe-external-master/check_status_file.py deleted file mode 100755 index ba828087..00000000 --- a/files/nrpe-external-master/check_status_file.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/python - -# m -# mmmm m m mmmm mmmm mmm mm#mm -# #" "# # # #" "# #" "# #" # # -# # # # # # # # # #"""" # -# ##m#" "mm"# ##m#" ##m#" "#mm" "mm -# # # # -# " " " -# This file is managed by puppet. Do not make local changes. - -# -# Copyright 2014 Canonical Ltd. -# -# Author: Jacek Nykis -# - -import re -import nagios_plugin - - -def parse_args(): - import argparse - - parser = argparse.ArgumentParser( - description='Read file and return nagios status based on its content', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('-f', '--status-file', required=True, - help='Status file path') - parser.add_argument('-c', '--critical-text', default='CRITICAL', - help='String indicating critical status') - parser.add_argument('-w', '--warning-text', default='WARNING', - help='String indicating warning status') - parser.add_argument('-o', '--ok-text', default='OK', - help='String indicating OK status') - parser.add_argument('-u', '--unknown-text', default='UNKNOWN', - help='String indicating unknown status') - return parser.parse_args() - - -def check_status(args): - nagios_plugin.check_file_freshness(args.status_file, 43200) - - with open(args.status_file, "r") as f: - content = [l.strip() for l in f.readlines()] - - for line in content: - if re.search(args.critical_text, line): - raise nagios_plugin.CriticalError(line) - elif re.search(args.warning_text, line): - raise nagios_plugin.WarnError(line) - elif re.search(args.unknown_text, line): - raise nagios_plugin.UnknownError(line) - else: - print line - - -if __name__ == '__main__': - args = parse_args() - nagios_plugin.try_check(check_status, args) diff --git a/files/nrpe-external-master/check_upstart_job b/files/nrpe-external-master/check_upstart_job deleted file mode 100755 index 94efb95e..00000000 --- a/files/nrpe-external-master/check_upstart_job +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/python - -# -# Copyright 2012, 2013 Canonical Ltd. -# -# Author: Paul Collins -# -# Based on http://www.eurion.net/python-snippets/snippet/Upstart%20service%20status.html -# - -import sys - -import dbus - - -class Upstart(object): - def __init__(self): - self._bus = dbus.SystemBus() - self._upstart = self._bus.get_object('com.ubuntu.Upstart', - '/com/ubuntu/Upstart') - def get_job(self, job_name): - path = self._upstart.GetJobByName(job_name, - dbus_interface='com.ubuntu.Upstart0_6') - return self._bus.get_object('com.ubuntu.Upstart', path) - - def get_properties(self, job): - path = job.GetInstance([], dbus_interface='com.ubuntu.Upstart0_6.Job') - instance = self._bus.get_object('com.ubuntu.Upstart', path) - return instance.GetAll('com.ubuntu.Upstart0_6.Instance', - dbus_interface=dbus.PROPERTIES_IFACE) - - def get_job_instances(self, job_name): - job = self.get_job(job_name) - paths = job.GetAllInstances([], dbus_interface='com.ubuntu.Upstart0_6.Job') - return [self._bus.get_object('com.ubuntu.Upstart', path) for path in paths] - - def get_job_instance_properties(self, job): - return job.GetAll('com.ubuntu.Upstart0_6.Instance', - dbus_interface=dbus.PROPERTIES_IFACE) - -try: - upstart = Upstart() - try: - job = upstart.get_job(sys.argv[1]) - props = upstart.get_properties(job) - - if props['state'] == 'running': - print 'OK: %s is running' % sys.argv[1] - sys.exit(0) - else: - print 'CRITICAL: %s is not running' % sys.argv[1] - sys.exit(2) - - except dbus.DBusException as e: - instances = upstart.get_job_instances(sys.argv[1]) - propses = [upstart.get_job_instance_properties(instance) for instance in instances] - states = dict([(props['name'], props['state']) for props in propses]) - if len(states) != states.values().count('running'): - not_running = [] - for name in states.keys(): - if states[name] != 'running': - not_running.append(name) - print 'CRITICAL: %d instances of %s not running: %s' % \ - (len(not_running), sys.argv[1], not_running.join(', ')) - sys.exit(2) - else: - print 'OK: %d instances of %s running' % (len(states), sys.argv[1]) - -except dbus.DBusException as e: - print 'CRITICAL: failed to get properties of \'%s\' from upstart' % sys.argv[1] - sys.exit(2) - diff --git a/files/nrpe-external-master/nagios_plugin.py b/files/nrpe-external-master/nagios_plugin.py deleted file mode 100755 index fc0d7b7b..00000000 --- a/files/nrpe-external-master/nagios_plugin.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2005, 2006, 2007, 2012 James Troup - -import os -import stat -import time -import traceback -import sys - - -################################################################################ - -class CriticalError(Exception): - """This indicates a critical error.""" - pass - - -class WarnError(Exception): - """This indicates a warning condition.""" - pass - - -class UnknownError(Exception): - """This indicates a unknown error was encountered.""" - pass - - -def try_check(function, *args, **kwargs): - """Perform a check with error/warn/unknown handling.""" - try: - function(*args, **kwargs) - except UnknownError, msg: - print msg - sys.exit(3) - except CriticalError, msg: - print msg - sys.exit(2) - except WarnError, msg: - print msg - sys.exit(1) - except: - print "%s raised unknown exception '%s'" % (function, sys.exc_info()[0]) - print '=' * 60 - traceback.print_exc(file=sys.stdout) - print '=' * 60 - sys.exit(3) - - -################################################################################ - -def check_file_freshness(filename, newer_than=600): - """Check a file exists, is readable and is newer than seconds (where defaults to 600).""" - # First check the file exists and is readable - if not os.path.exists(filename): - raise CriticalError("%s: does not exist." % (filename)) - if os.access(filename, os.R_OK) == 0: - raise CriticalError("%s: is not readable." % (filename)) - - # Then ensure the file is up-to-date enough - mtime = os.stat(filename)[stat.ST_MTIME] - last_modified = time.time() - mtime - if last_modified > newer_than: - raise CriticalError("%s: was last modified on %s and is too old (> %s seconds)." - % (filename, time.ctime(mtime), newer_than)) - if last_modified < 0: - raise CriticalError("%s: was last modified on %s which is in the future." - % (filename, time.ctime(mtime))) - -################################################################################ diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 2926e1c7..e344206c 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -411,9 +411,9 @@ def update_nrpe_config(): checkpath = os.path.join(os.environ['CHARM_DIR'], 'files/nrpe-external-master', 'check_exit_status.pl'), - cron_template = '*/5 * * * * root %s \ --s /etc/init.d/%s status > /var/lib/nagios/service-check-%s.txt\n' \ - % (checkpath[0], service, service) + cron_template = '*/5 * * * * root \ +/usr/local/lib/nagios/plugins/check_exit_status.pl -s /etc/init.d/%s \ +status > /var/lib/nagios/service-check-%s.txt\n' % (service, service) f = open(cronpath, 'w') f.write(cron_template) f.close() From 58332453ed99e0f47430e92a8d0e129f631f9171 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 20 Nov 2014 10:24:19 -0600 Subject: [PATCH 11/17] Resync helpers --- hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg index daaee011..c6a00df2 100644 --- a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg +++ b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg @@ -44,6 +44,7 @@ frontend tcp-in_{{ service }} use_backend {{ service }}_{{ frontend }} if net_{{ frontend }} {% endfor -%} default_backend {{ service }}_{{ default_backend }} + {% for frontend in frontends -%} backend {{ service }}_{{ frontend }} balance leastconn From 13dc3c7017bb5c7d80c04047776c1e74aa0a995b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 9 Jan 2015 15:57:52 +0000 Subject: [PATCH 12/17] Fix lint --- hooks/keystone_hooks.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 424edf78..0fd95267 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -428,21 +428,21 @@ def update_nrpe_config(): ) elif os.path.exists(sysv_init): cronpath = '/etc/cron.d/nagios-service-check-%s' % service - checkpath = os.path.join(os.environ['CHARM_DIR'], - 'files/nrpe-external-master', - 'check_exit_status.pl'), - cron_template = '*/5 * * * * root \ -/usr/local/lib/nagios/plugins/check_exit_status.pl -s /etc/init.d/%s \ -status > /var/lib/nagios/service-check-%s.txt\n' % (service, service) + cron_entry = ('*/5 * * * * root ' + '/usr/local/lib/nagios/plugins/check_exit_status.pl ' + '-s /etc/init.d/%s status > ' + '/var/lib/nagios/service-check-%s.txt\n' % (service, + service) + ) f = open(cronpath, 'w') - f.write(cron_template) + f.write(cron_entry) f.close() nrpe.add_check( shortname=service, description='process check {%s}' % current_unit, - check_cmd='check_status_file.py -f \ -/var/lib/nagios/service-check-%s.txt' % service, - ) + check_cmd='check_status_file.py -f ' + '/var/lib/nagios/service-check-%s.txt' % service, + ) nrpe.write() From 70382b05c73d20db8038b9a3ca661f95342374cc Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 9 Jan 2015 15:58:43 +0000 Subject: [PATCH 13/17] Fix unit tests --- unit_tests/test_keystone_hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index b8cdee0d..07aa3e5d 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -56,6 +56,7 @@ TO_PATCH = [ 'ensure_initial_admin', 'add_service_to_keystone', 'synchronize_ca', + 'update_nrpe_config', # other 'check_call', 'execd_preinstall', From 931cea4b7ea5a212de00040faa558cd198242ae2 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 12 Jan 2015 12:04:00 +0000 Subject: [PATCH 14/17] Use rnpe functions from charmhelpers --- .../charmhelpers/contrib/charmsupport/nrpe.py | 102 ++++++++++++++++-- .../contrib/charmsupport/volumes.py | 7 +- hooks/charmhelpers/contrib/openstack/utils.py | 6 ++ hooks/charmhelpers/contrib/unison/__init__.py | 35 ++++-- hooks/charmhelpers/fetch/__init__.py | 9 +- hooks/keystone_hooks.py | 55 ++-------- 6 files changed, 145 insertions(+), 69 deletions(-) diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/hooks/charmhelpers/contrib/charmsupport/nrpe.py index 51b62d39..f3a936d0 100644 --- a/hooks/charmhelpers/contrib/charmsupport/nrpe.py +++ b/hooks/charmhelpers/contrib/charmsupport/nrpe.py @@ -18,6 +18,7 @@ from charmhelpers.core.hookenv import ( log, relation_ids, relation_set, + relations_of_type, ) from charmhelpers.core.host import service @@ -54,6 +55,12 @@ from charmhelpers.core.host import service # juju-myservice-0 # If you're running multiple environments with the same services in them # this allows you to differentiate between them. +# nagios_servicegroups: +# default: "" +# type: string +# description: | +# A comma-separated list of nagios servicegroups. +# If left empty, the nagios_context will be used as the servicegroup # # 3. Add custom checks (Nagios plugins) to files/nrpe-external-master # @@ -125,9 +132,6 @@ define service {{ def _locate_cmd(self, check_cmd): search_path = ( - '/', - os.path.join(os.environ['CHARM_DIR'], - 'files/nrpe-external-master'), '/usr/lib/nagios/plugins', '/usr/local/lib/nagios/plugins', ) @@ -141,7 +145,7 @@ define service {{ log('Check command not found: {}'.format(parts[0])) return '' - def write(self, nagios_context, hostname): + def write(self, nagios_context, hostname, nagios_servicegroups=None): nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format( self.command) with open(nrpe_check_file, 'w') as nrpe_check_config: @@ -153,16 +157,21 @@ define service {{ log('Not writing service config as {} is not accessible'.format( NRPE.nagios_exportdir)) else: - self.write_service_config(nagios_context, hostname) + self.write_service_config(nagios_context, hostname, + nagios_servicegroups) - def write_service_config(self, nagios_context, hostname): + def write_service_config(self, nagios_context, hostname, + nagios_servicegroups=None): for f in os.listdir(NRPE.nagios_exportdir): if re.search('.*{}.cfg'.format(self.command), f): os.remove(os.path.join(NRPE.nagios_exportdir, f)) + if not nagios_servicegroups: + nagios_servicegroups = nagios_context + templ_vars = { 'nagios_hostname': hostname, - 'nagios_servicegroup': nagios_context, + 'nagios_servicegroup': nagios_servicegroups, 'description': self.description, 'shortname': self.shortname, 'command': self.command, @@ -186,6 +195,10 @@ class NRPE(object): super(NRPE, self).__init__() self.config = config() self.nagios_context = self.config['nagios_context'] + if 'nagios_servicegroups' in self.config: + self.nagios_servicegroups = self.config['nagios_servicegroups'] + else: + self.nagios_servicegroups = 'juju' self.unit_name = local_unit().replace('/', '-') if hostname: self.hostname = hostname @@ -211,7 +224,8 @@ class NRPE(object): nrpe_monitors = {} monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}} for nrpecheck in self.checks: - nrpecheck.write(self.nagios_context, self.hostname) + nrpecheck.write(self.nagios_context, self.hostname, + self.nagios_servicegroups) nrpe_monitors[nrpecheck.shortname] = { "command": nrpecheck.command, } @@ -220,3 +234,75 @@ class NRPE(object): for rid in relation_ids("local-monitors"): relation_set(relation_id=rid, monitors=yaml.dump(monitors)) + + +def get_nagios_hostcontext(relation_name='nrpe-external-master'): + """ + Query relation with nrpe subordinate, return the nagios_host_context + + :param str relation_name: Name of relation nrpe sub joined to + """ + for rel in relations_of_type(relation_name): + if 'nagios_hostname' in rel: + return rel['nagios_host_context'] + + +def get_nagios_hostname(relation_name='nrpe-external-master'): + """ + Query relation with nrpe subordinate, return the nagios_hostname + + :param str relation_name: Name of relation nrpe sub joined to + """ + for rel in relations_of_type(relation_name): + if 'nagios_hostname' in rel: + return rel['nagios_hostname'] + + +def get_nagios_unit_name(relation_name='nrpe-external-master'): + """ + Return the nagios unit name prepended with host_context if needed + + :param str relation_name: Name of relation nrpe sub joined to + """ + host_context = get_nagios_hostcontext(relation_name) + if host_context: + unit = "%s:%s" % (host_context, local_unit()) + else: + unit = local_unit() + return unit + + +def add_init_service_checks(nrpe, services, unit_name): + """ + Add checks for each service in list + + :param NRPE nrpe: NRPE object to add check to + :param list services: List of services to check + :param str unit_name: Unit name to use in check description + """ + for svc in services: + upstart_init = '/etc/init/%s.conf' % svc + sysv_init = '/etc/init.d/%s' % svc + if os.path.exists(upstart_init): + nrpe.add_check( + shortname=svc, + description='process check {%s}' % unit_name, + check_cmd='check_upstart_job %s' % svc + ) + elif os.path.exists(sysv_init): + cronpath = '/etc/cron.d/nagios-service-check-%s' % svc + cron_file = ('*/5 * * * * root ' + '/usr/local/lib/nagios/plugins/check_exit_status.pl ' + '-s /etc/init.d/%s status > ' + '/var/lib/nagios/service-check-%s.txt\n' % (svc, + svc) + ) + f = open(cronpath, 'w') + f.write(cron_file) + f.close() + nrpe.add_check( + shortname=svc, + description='process check {%s}' % unit_name, + check_cmd='check_status_file.py -f ' + '/var/lib/nagios/service-check-%s.txt' % svc, + ) diff --git a/hooks/charmhelpers/contrib/charmsupport/volumes.py b/hooks/charmhelpers/contrib/charmsupport/volumes.py index 0f905dff..d61aa47f 100644 --- a/hooks/charmhelpers/contrib/charmsupport/volumes.py +++ b/hooks/charmhelpers/contrib/charmsupport/volumes.py @@ -2,7 +2,8 @@ Functions for managing volumes in juju units. One volume is supported per unit. Subordinates may have their own storage, provided it is on its own partition. -Configuration stanzas: +Configuration stanzas:: + volume-ephemeral: type: boolean default: true @@ -20,7 +21,8 @@ Configuration stanzas: is 'true' and no volume-map value is set. Use 'juju set' to set a value and 'juju resolved' to complete configuration. -Usage: +Usage:: + from charmsupport.volumes import configure_volume, VolumeConfigurationError from charmsupport.hookenv import log, ERROR def post_mount_hook(): @@ -34,6 +36,7 @@ Usage: after_change=post_mount_hook) except VolumeConfigurationError: log('Storage could not be configured', ERROR) + ''' # XXX: Known limitations diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 44179679..ddd40ce5 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -53,6 +53,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('saucy', 'havana'), ('trusty', 'icehouse'), ('utopic', 'juno'), + ('vivid', 'kilo'), ]) @@ -64,6 +65,7 @@ OPENSTACK_CODENAMES = OrderedDict([ ('2013.2', 'havana'), ('2014.1', 'icehouse'), ('2014.2', 'juno'), + ('2015.1', 'kilo'), ]) # The ugly duckling @@ -84,6 +86,7 @@ SWIFT_CODENAMES = OrderedDict([ ('2.0.0', 'juno'), ('2.1.0', 'juno'), ('2.2.0', 'juno'), + ('2.2.1', 'kilo'), ]) DEFAULT_LOOPBACK_SIZE = '5G' @@ -289,6 +292,9 @@ def configure_installation_source(rel): 'juno': 'trusty-updates/juno', 'juno/updates': 'trusty-updates/juno', 'juno/proposed': 'trusty-proposed/juno', + 'kilo': 'trusty-updates/kilo', + 'kilo/updates': 'trusty-updates/kilo', + 'kilo/proposed': 'trusty-proposed/kilo', } try: diff --git a/hooks/charmhelpers/contrib/unison/__init__.py b/hooks/charmhelpers/contrib/unison/__init__.py index f903ac03..261f7cd2 100644 --- a/hooks/charmhelpers/contrib/unison/__init__.py +++ b/hooks/charmhelpers/contrib/unison/__init__.py @@ -228,7 +228,12 @@ def collect_authed_hosts(peer_interface): return hosts -def sync_path_to_host(path, host, user, verbose=False, cmd=None, gid=None): +def sync_path_to_host(path, host, user, verbose=False, cmd=None, gid=None, + fatal=False): + """Sync path to an specific peer host + + Propagates exception if operation fails and fatal=True. + """ cmd = cmd or copy(BASE_CMD) if not verbose: cmd.append('-silent') @@ -245,20 +250,30 @@ def sync_path_to_host(path, host, user, verbose=False, cmd=None, gid=None): run_as_user(user, cmd, gid) except: log('Error syncing remote files') + if fatal: + raise -def sync_to_peer(host, user, paths=None, verbose=False, cmd=None, gid=None): - '''Sync paths to an specific host''' +def sync_to_peer(host, user, paths=None, verbose=False, cmd=None, gid=None, + fatal=False): + """Sync paths to an specific peer host + + Propagates exception if any operation fails and fatal=True. + """ if paths: for p in paths: - sync_path_to_host(p, host, user, verbose, cmd, gid) + sync_path_to_host(p, host, user, verbose, cmd, gid, fatal) -def sync_to_peers(peer_interface, user, paths=None, - verbose=False, cmd=None, gid=None): - '''Sync all hosts to an specific path''' - '''The type of group is integer, it allows user has permissions to ''' - '''operate a directory have a different group id with the user id.''' +def sync_to_peers(peer_interface, user, paths=None, verbose=False, cmd=None, + gid=None, fatal=False): + """Sync all hosts to an specific path + + The type of group is integer, it allows user has permissions to + operate a directory have a different group id with the user id. + + Propagates exception if any operation fails and fatal=True. + """ if paths: for host in collect_authed_hosts(peer_interface): - sync_to_peer(host, user, paths, verbose, cmd, gid) + sync_to_peer(host, user, paths, verbose, cmd, gid, fatal) diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py index 0a126fc3..aceadea4 100644 --- a/hooks/charmhelpers/fetch/__init__.py +++ b/hooks/charmhelpers/fetch/__init__.py @@ -64,9 +64,16 @@ CLOUD_ARCHIVE_POCKETS = { 'trusty-juno/updates': 'trusty-updates/juno', 'trusty-updates/juno': 'trusty-updates/juno', 'juno/proposed': 'trusty-proposed/juno', - 'juno/proposed': 'trusty-proposed/juno', 'trusty-juno/proposed': 'trusty-proposed/juno', 'trusty-proposed/juno': 'trusty-proposed/juno', + # Kilo + 'kilo': 'trusty-updates/kilo', + 'trusty-kilo': 'trusty-updates/kilo', + 'trusty-kilo/updates': 'trusty-updates/kilo', + 'trusty-updates/kilo': 'trusty-updates/kilo', + 'kilo/proposed': 'trusty-proposed/kilo', + 'trusty-kilo/proposed': 'trusty-proposed/kilo', + 'trusty-proposed/kilo': 'trusty-proposed/kilo', } # The order of this list is very important. Handlers should be listed in from diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 0fd95267..e5e2afd9 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -20,7 +20,6 @@ from charmhelpers.core.hookenv import ( relation_get, relation_ids, relation_set, - relations_of_type, related_units, unit_get, ) @@ -81,7 +80,7 @@ from charmhelpers.contrib.network.ip import ( ) from charmhelpers.contrib.openstack.context import ADDRESS_TYPES -from charmhelpers.contrib.charmsupport.nrpe import NRPE +from charmhelpers.contrib.charmsupport import nrpe hooks = Hooks() CONFIGS = register_configs() @@ -398,53 +397,13 @@ def upgrade_charm(): @hooks.hook('nrpe-external-master-relation-joined', 'nrpe-external-master-relation-changed') def update_nrpe_config(): - # Find out if nrpe set nagios_hostname - hostname = None - host_context = None - for rel in relations_of_type('nrpe-external-master'): - if 'nagios_hostname' in rel: - hostname = rel['nagios_hostname'] - host_context = rel['nagios_host_context'] - break - nrpe = NRPE(hostname=hostname) + # python-dbus is used by check_upstart_job apt_install('python-dbus') - - if host_context: - current_unit = "%s:%s" % (host_context, local_unit()) - else: - current_unit = local_unit() - - services_to_monitor = services() - - for service in services_to_monitor: - upstart_init = '/etc/init/%s.conf' % service - sysv_init = '/etc/init.d/%s' % service - - if os.path.exists(upstart_init): - nrpe.add_check( - shortname=service, - description='process check {%s}' % current_unit, - check_cmd='check_upstart_job %s' % service, - ) - elif os.path.exists(sysv_init): - cronpath = '/etc/cron.d/nagios-service-check-%s' % service - cron_entry = ('*/5 * * * * root ' - '/usr/local/lib/nagios/plugins/check_exit_status.pl ' - '-s /etc/init.d/%s status > ' - '/var/lib/nagios/service-check-%s.txt\n' % (service, - service) - ) - f = open(cronpath, 'w') - f.write(cron_entry) - f.close() - nrpe.add_check( - shortname=service, - description='process check {%s}' % current_unit, - check_cmd='check_status_file.py -f ' - '/var/lib/nagios/service-check-%s.txt' % service, - ) - - nrpe.write() + hostname = nrpe.get_nagios_hostname() + current_unit = nrpe.get_nagios_unit_name() + nrpe_setup = nrpe.NRPE(hostname=hostname) + nrpe.add_init_service_checks(nrpe_setup, services(), current_unit) + nrpe_setup.write() def main(): From 761d3df020db68245a26332b6542fbb07c5609dc Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 13 Jan 2015 12:06:14 +0000 Subject: [PATCH 15/17] [trivial] Resync helpers --- .../charmhelpers/contrib/openstack/context.py | 37 +++++++++++-------- .../contrib/openstack/templates/haproxy.cfg | 4 +- .../contrib/storage/linux/ceph.py | 11 ++++++ unit_tests/test_keystone_contexts.py | 1 + 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 180bfad2..eaa89a67 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -468,21 +468,25 @@ class HAProxyContext(OSContextGenerator): _unit = unit.replace('/', '-') cluster_hosts[laddr]['backends'][_unit] = _laddr - # NOTE(jamespage) no split configurations found, just use - # private addresses - if not cluster_hosts: - netmask = get_netmask_for_address(addr) - cluster_hosts[addr] = {'network': "{}/{}".format(addr, netmask), - 'backends': {l_unit: addr}} - for rid in relation_ids('cluster'): - for unit in related_units(rid): - _laddr = relation_get('private-address', - rid=rid, unit=unit) - if _laddr: - _unit = unit.replace('/', '-') - cluster_hosts[addr]['backends'][_unit] = _laddr + # NOTE(jamespage) add backend based on private address - this + # with either be the only backend or the fallback if no acls + # match in the frontend + cluster_hosts[addr] = {} + netmask = get_netmask_for_address(addr) + cluster_hosts[addr] = {'network': "{}/{}".format(addr, netmask), + 'backends': {l_unit: addr}} + for rid in relation_ids('cluster'): + for unit in related_units(rid): + _laddr = relation_get('private-address', + rid=rid, unit=unit) + if _laddr: + _unit = unit.replace('/', '-') + cluster_hosts[addr]['backends'][_unit] = _laddr - ctxt = {'frontends': cluster_hosts} + ctxt = { + 'frontends': cluster_hosts, + 'default_backend': addr + } if config('haproxy-server-timeout'): ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout') @@ -663,8 +667,9 @@ class ApacheSSLContext(OSContextGenerator): addresses = self.get_network_addresses() for address, endpoint in sorted(set(addresses)): for api_port in self.external_ports: - ext_port = determine_apache_port(api_port) - int_port = determine_api_port(api_port) + ext_port = determine_apache_port(api_port, + singlenode_mode=True) + int_port = determine_api_port(api_port, singlenode_mode=True) portmap = (address, endpoint, int(ext_port), int(int_port)) ctxt['endpoints'].append(portmap) ctxt['ext_ports'].append(int(ext_port)) diff --git a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg index 9ae1efb9..ad875f16 100644 --- a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg +++ b/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg @@ -44,7 +44,9 @@ frontend tcp-in_{{ service }} {% for frontend in frontends -%} acl net_{{ frontend }} dst {{ frontends[frontend]['network'] }} use_backend {{ service }}_{{ frontend }} if net_{{ frontend }} - {% endfor %} + {% endfor -%} + default_backend {{ service }}_{{ default_backend }} + {% for frontend in frontends -%} backend {{ service }}_{{ frontend }} balance leastconn diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 1479f4f3..6ebeab5c 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -157,6 +157,17 @@ def create_keyring(service, key): log('Created new ceph keyring at %s.' % keyring, level=DEBUG) +def delete_keyring(service): + """Delete an existing Ceph keyring.""" + keyring = _keyring_path(service) + if not os.path.exists(keyring): + log('Keyring does not exist at %s' % keyring, level=WARNING) + return + + os.remove(keyring) + log('Deleted ring at %s.' % keyring, level=INFO) + + def create_key_file(service, key): """Create a file containing key.""" keyfile = _keyfile_path(service) diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py index b27de1c2..70b44b34 100644 --- a/unit_tests/test_keystone_contexts.py +++ b/unit_tests/test_keystone_contexts.py @@ -82,6 +82,7 @@ class TestKeystoneContexts(CharmTestCase): 'stat_port': ':8888', 'service_ports': {'admin-port': ['keystone', '34'], 'public-port': ['keystone', '34']}, + 'default_backend': '1.2.3.4', 'frontends': {'1.2.3.4': { 'network': '1.2.3.4/255.255.255.0', 'backends': { From ea19d1edf2a8e6cc34fdab1159cde74608c5814d Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 13 Jan 2015 14:40:29 +0000 Subject: [PATCH 16/17] Switchback to trunk of charm-helpers --- charm-helpers-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml index 63b611f7..e34e6ac8 100644 --- a/charm-helpers-hooks.yaml +++ b/charm-helpers-hooks.yaml @@ -1,4 +1,4 @@ -branch: lp:~james-page/charm-helpers/lp.1391784 +branch: lp:charm-helpers destination: hooks/charmhelpers include: - core From b0ba16cf70690ee8ed7476653e104b4c272f6cd1 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 13 Jan 2015 14:41:56 +0000 Subject: [PATCH 17/17] Tweak tests --- unit_tests/test_keystone_contexts.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py index 5c8eaed7..70b44b34 100644 --- a/unit_tests/test_keystone_contexts.py +++ b/unit_tests/test_keystone_contexts.py @@ -89,8 +89,7 @@ class TestKeystoneContexts(CharmTestCase): 'keystone': '1.2.3.4', 'unit-0': '10.0.0.0' } - }}, - 'default_backend': '1.2.3.4' + }} } )