charmhelper sync for vivid-kilo bug 1423680
This commit is contained in:
		| @@ -24,6 +24,8 @@ import subprocess | ||||
| import pwd | ||||
| import grp | ||||
| import os | ||||
| import glob | ||||
| import shutil | ||||
| import re | ||||
| import shlex | ||||
| import yaml | ||||
| @@ -161,7 +163,7 @@ define service {{ | ||||
|         log('Check command not found: {}'.format(parts[0])) | ||||
|         return '' | ||||
|  | ||||
|     def write(self, nagios_context, hostname, nagios_servicegroups=None): | ||||
|     def write(self, nagios_context, hostname, nagios_servicegroups): | ||||
|         nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format( | ||||
|             self.command) | ||||
|         with open(nrpe_check_file, 'w') as nrpe_check_config: | ||||
| @@ -177,14 +179,11 @@ define service {{ | ||||
|                                       nagios_servicegroups) | ||||
|  | ||||
|     def write_service_config(self, nagios_context, hostname, | ||||
|                              nagios_servicegroups=None): | ||||
|                              nagios_servicegroups): | ||||
|         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_servicegroups, | ||||
| @@ -211,10 +210,10 @@ class NRPE(object): | ||||
|         super(NRPE, self).__init__() | ||||
|         self.config = config() | ||||
|         self.nagios_context = self.config['nagios_context'] | ||||
|         if 'nagios_servicegroups' in self.config: | ||||
|         if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']: | ||||
|             self.nagios_servicegroups = self.config['nagios_servicegroups'] | ||||
|         else: | ||||
|             self.nagios_servicegroups = 'juju' | ||||
|             self.nagios_servicegroups = self.nagios_context | ||||
|         self.unit_name = local_unit().replace('/', '-') | ||||
|         if hostname: | ||||
|             self.hostname = hostname | ||||
| @@ -322,3 +321,38 @@ def add_init_service_checks(nrpe, services, unit_name): | ||||
|                 check_cmd='check_status_file.py -f ' | ||||
|                           '/var/lib/nagios/service-check-%s.txt' % svc, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| def copy_nrpe_checks(): | ||||
|     """ | ||||
|     Copy the nrpe checks into place | ||||
|  | ||||
|     """ | ||||
|     NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins' | ||||
|     nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks', | ||||
|                                   'charmhelpers', 'contrib', 'openstack', | ||||
|                                   'files') | ||||
|  | ||||
|     if not os.path.exists(NAGIOS_PLUGINS): | ||||
|         os.makedirs(NAGIOS_PLUGINS) | ||||
|     for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")): | ||||
|         if os.path.isfile(fname): | ||||
|             shutil.copy2(fname, | ||||
|                          os.path.join(NAGIOS_PLUGINS, os.path.basename(fname))) | ||||
|  | ||||
|  | ||||
| def add_haproxy_checks(nrpe, unit_name): | ||||
|     """ | ||||
|     Add checks for each service in list | ||||
|  | ||||
|     :param NRPE nrpe: NRPE object to add check to | ||||
|     :param str unit_name: Unit name to use in check description | ||||
|     """ | ||||
|     nrpe.add_check( | ||||
|         shortname='haproxy_servers', | ||||
|         description='Check HAProxy {%s}' % unit_name, | ||||
|         check_cmd='check_haproxy.sh') | ||||
|     nrpe.add_check( | ||||
|         shortname='haproxy_queue', | ||||
|         description='Check HAProxy queue depth {%s}' % unit_name, | ||||
|         check_cmd='check_haproxy_queue_depth.sh') | ||||
|   | ||||
| @@ -48,6 +48,9 @@ from charmhelpers.core.hookenv import ( | ||||
| from charmhelpers.core.decorators import ( | ||||
|     retry_on_exception, | ||||
| ) | ||||
| from charmhelpers.core.strutils import ( | ||||
|     bool_from_string, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class HAIncompleteConfig(Exception): | ||||
| @@ -164,7 +167,8 @@ def https(): | ||||
|     . | ||||
|     returns: boolean | ||||
|     ''' | ||||
|     if config_get('use-https') == "yes": | ||||
|     use_https = config_get('use-https') | ||||
|     if use_https and bool_from_string(use_https): | ||||
|         return True | ||||
|     if config_get('ssl_cert') and config_get('ssl_key'): | ||||
|         return True | ||||
|   | ||||
| @@ -71,16 +71,19 @@ class OpenStackAmuletDeployment(AmuletDeployment): | ||||
|         services.append(this_service) | ||||
|         use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | ||||
|                       'ceph-osd', 'ceph-radosgw'] | ||||
|         # Openstack subordinate charms do not expose an origin option as that | ||||
|         # is controlled by the principle | ||||
|         ignore = ['neutron-openvswitch'] | ||||
|  | ||||
|         if self.openstack: | ||||
|             for svc in services: | ||||
|                 if svc['name'] not in use_source: | ||||
|                 if svc['name'] not in use_source + ignore: | ||||
|                     config = {'openstack-origin': self.openstack} | ||||
|                     self.d.configure(svc['name'], config) | ||||
|  | ||||
|         if self.source: | ||||
|             for svc in services: | ||||
|                 if svc['name'] in use_source: | ||||
|                 if svc['name'] in use_source and svc['name'] not in ignore: | ||||
|                     config = {'source': self.source} | ||||
|                     self.d.configure(svc['name'], config) | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								hooks/charmhelpers/contrib/openstack/files/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								hooks/charmhelpers/contrib/openstack/files/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # Copyright 2014-2015 Canonical Limited. | ||||
| # | ||||
| # This file is part of charm-helpers. | ||||
| # | ||||
| # charm-helpers is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Lesser General Public License version 3 as | ||||
| # published by the Free Software Foundation. | ||||
| # | ||||
| # charm-helpers is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Lesser General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| # dummy __init__.py to fool syncer into thinking this is a syncable python | ||||
| # module | ||||
							
								
								
									
										32
									
								
								hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #!/bin/bash | ||||
| #-------------------------------------------- | ||||
| # This file is managed by Juju | ||||
| #-------------------------------------------- | ||||
| # | ||||
| # Copyright 2009,2012 Canonical Ltd. | ||||
| # Author: Tom Haddon | ||||
|  | ||||
| CRITICAL=0 | ||||
| NOTACTIVE='' | ||||
| LOGFILE=/var/log/nagios/check_haproxy.log | ||||
| AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') | ||||
|  | ||||
| for appserver in $(grep '    server' /etc/haproxy/haproxy.cfg | awk '{print $2'}); | ||||
| do | ||||
|     output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK') | ||||
|     if [ $? != 0 ]; then | ||||
|         date >> $LOGFILE | ||||
|         echo $output >> $LOGFILE | ||||
|         /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1 | ||||
|         CRITICAL=1 | ||||
|         NOTACTIVE="${NOTACTIVE} $appserver" | ||||
|     fi | ||||
| done | ||||
|  | ||||
| if [ $CRITICAL = 1 ]; then | ||||
|     echo "CRITICAL:${NOTACTIVE}" | ||||
|     exit 2 | ||||
| fi | ||||
|  | ||||
| echo "OK: All haproxy instances looking good" | ||||
| exit 0 | ||||
							
								
								
									
										30
									
								
								hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #!/bin/bash | ||||
| #-------------------------------------------- | ||||
| # This file is managed by Juju | ||||
| #-------------------------------------------- | ||||
| #                                        | ||||
| # Copyright 2009,2012 Canonical Ltd. | ||||
| # Author: Tom Haddon | ||||
|  | ||||
| # These should be config options at some stage | ||||
| CURRQthrsh=0 | ||||
| MAXQthrsh=100 | ||||
|  | ||||
| AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') | ||||
|  | ||||
| HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v) | ||||
|  | ||||
| for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}') | ||||
| do | ||||
|     CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3) | ||||
|     MAXQ=$(echo "$HAPROXYSTATS"  | grep $BACKEND | grep BACKEND | cut -d , -f 4) | ||||
|  | ||||
|     if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then | ||||
|         echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ" | ||||
|         exit 2 | ||||
|     fi | ||||
| done | ||||
|  | ||||
| echo "OK: All haproxy queue depths looking good" | ||||
| exit 0 | ||||
|  | ||||
| @@ -26,6 +26,8 @@ from charmhelpers.contrib.network.ip import ( | ||||
| ) | ||||
| from charmhelpers.contrib.hahelpers.cluster import is_clustered | ||||
|  | ||||
| from functools import partial | ||||
|  | ||||
| PUBLIC = 'public' | ||||
| INTERNAL = 'int' | ||||
| ADMIN = 'admin' | ||||
| @@ -107,3 +109,38 @@ def resolve_address(endpoint_type=PUBLIC): | ||||
|                          "clustered=%s)" % (net_type, clustered)) | ||||
|  | ||||
|     return resolved_address | ||||
|  | ||||
|  | ||||
| def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC, | ||||
|                  override=None): | ||||
|     """Returns the correct endpoint URL to advertise to Keystone. | ||||
|  | ||||
|     This method provides the correct endpoint URL which should be advertised to | ||||
|     the keystone charm for endpoint creation. This method allows for the url to | ||||
|     be overridden to force a keystone endpoint to have specific URL for any of | ||||
|     the defined scopes (admin, internal, public). | ||||
|  | ||||
|     :param configs: OSTemplateRenderer config templating object to inspect | ||||
|                     for a complete https context. | ||||
|     :param url_template: str format string for creating the url template. Only | ||||
|                          two values will be passed - the scheme+hostname | ||||
|                         returned by the canonical_url and the port. | ||||
|     :param endpoint_type: str endpoint type to resolve. | ||||
|     :param override: str the name of the config option which overrides the | ||||
|                      endpoint URL defined by the charm itself. None will | ||||
|                      disable any overrides (default). | ||||
|     """ | ||||
|     if override: | ||||
|         # Return any user-defined overrides for the keystone endpoint URL. | ||||
|         user_value = config(override) | ||||
|         if user_value: | ||||
|             return user_value.strip() | ||||
|  | ||||
|     return url_template % (canonical_url(configs, endpoint_type), port) | ||||
|  | ||||
|  | ||||
| public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC) | ||||
|  | ||||
| internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL) | ||||
|  | ||||
| admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN) | ||||
|   | ||||
| @@ -103,6 +103,7 @@ SWIFT_CODENAMES = OrderedDict([ | ||||
|     ('2.1.0', 'juno'), | ||||
|     ('2.2.0', 'juno'), | ||||
|     ('2.2.1', 'kilo'), | ||||
|     ('2.2.2', 'kilo'), | ||||
| ]) | ||||
|  | ||||
| DEFAULT_LOOPBACK_SIZE = '5G' | ||||
|   | ||||
| @@ -17,8 +17,6 @@ | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" | ||||
|  | ||||
| from charmhelpers.fetch import apt_install, apt_update | ||||
| from charmhelpers.core.hookenv import log | ||||
|  | ||||
| @@ -29,6 +27,8 @@ except ImportError: | ||||
|     apt_install('python-pip') | ||||
|     from pip import main as pip_execute | ||||
|  | ||||
| __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" | ||||
|  | ||||
|  | ||||
| def parse_options(given, available): | ||||
|     """Given a set of options, check if available""" | ||||
|   | ||||
| @@ -17,11 +17,11 @@ | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | ||||
|  | ||||
| import io | ||||
| import os | ||||
|  | ||||
| __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | ||||
|  | ||||
|  | ||||
| class Fstab(io.FileIO): | ||||
|     """This class extends file in order to implement a file reader/writer | ||||
| @@ -77,7 +77,7 @@ class Fstab(io.FileIO): | ||||
|         for line in self.readlines(): | ||||
|             line = line.decode('us-ascii') | ||||
|             try: | ||||
|                 if line.strip() and not line.startswith("#"): | ||||
|                 if line.strip() and not line.strip().startswith("#"): | ||||
|                     yield self._hydrate_entry(line) | ||||
|             except ValueError: | ||||
|                 pass | ||||
| @@ -104,7 +104,7 @@ class Fstab(io.FileIO): | ||||
|  | ||||
|         found = False | ||||
|         for index, line in enumerate(lines): | ||||
|             if not line.startswith("#"): | ||||
|             if line.strip() and not line.strip().startswith("#"): | ||||
|                 if self._hydrate_entry(line) == entry: | ||||
|                     found = True | ||||
|                     break | ||||
|   | ||||
| @@ -191,11 +191,11 @@ def mkdir(path, owner='root', group='root', perms=0o555, force=False): | ||||
|  | ||||
|  | ||||
| def write_file(path, content, owner='root', group='root', perms=0o444): | ||||
|     """Create or overwrite a file with the contents of a string""" | ||||
|     """Create or overwrite a file with the contents of a byte string.""" | ||||
|     log("Writing file {} {}:{} {:o}".format(path, owner, group, perms)) | ||||
|     uid = pwd.getpwnam(owner).pw_uid | ||||
|     gid = grp.getgrnam(group).gr_gid | ||||
|     with open(path, 'w') as target: | ||||
|     with open(path, 'wb') as target: | ||||
|         os.fchown(target.fileno(), uid, gid) | ||||
|         os.fchmod(target.fileno(), perms) | ||||
|         target.write(content) | ||||
| @@ -305,11 +305,11 @@ def restart_on_change(restart_map, stopstart=False): | ||||
|     ceph_client_changed function. | ||||
|     """ | ||||
|     def wrap(f): | ||||
|         def wrapped_f(*args): | ||||
|         def wrapped_f(*args, **kwargs): | ||||
|             checksums = {} | ||||
|             for path in restart_map: | ||||
|                 checksums[path] = file_hash(path) | ||||
|             f(*args) | ||||
|             f(*args, **kwargs) | ||||
|             restarts = [] | ||||
|             for path in restart_map: | ||||
|                 if checksums[path] != file_hash(path): | ||||
| @@ -361,7 +361,7 @@ 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): | ||||
|                 matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line) | ||||
|                 matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line) | ||||
|                 if matched: | ||||
|                     interface = matched.groups()[0] | ||||
|                 else: | ||||
|   | ||||
							
								
								
									
										42
									
								
								hooks/charmhelpers/core/strutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								hooks/charmhelpers/core/strutils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| # Copyright 2014-2015 Canonical Limited. | ||||
| # | ||||
| # This file is part of charm-helpers. | ||||
| # | ||||
| # charm-helpers is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Lesser General Public License version 3 as | ||||
| # published by the Free Software Foundation. | ||||
| # | ||||
| # charm-helpers is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Lesser General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| import six | ||||
|  | ||||
|  | ||||
| def bool_from_string(value): | ||||
|     """Interpret string value as boolean. | ||||
|  | ||||
|     Returns True if value translates to True otherwise False. | ||||
|     """ | ||||
|     if isinstance(value, six.string_types): | ||||
|         value = six.text_type(value) | ||||
|     else: | ||||
|         msg = "Unable to interpret non-string value '%s' as boolean" % (value) | ||||
|         raise ValueError(msg) | ||||
|  | ||||
|     value = value.strip().lower() | ||||
|  | ||||
|     if value in ['y', 'yes', 'true', 't']: | ||||
|         return True | ||||
|     elif value in ['n', 'no', 'false', 'f']: | ||||
|         return False | ||||
|  | ||||
|     msg = "Unable to interpret string value '%s' as boolean" % (value) | ||||
|     raise ValueError(msg) | ||||
| @@ -17,8 +17,6 @@ | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | ||||
|  | ||||
| import yaml | ||||
|  | ||||
| from subprocess import check_call | ||||
| @@ -26,25 +24,33 @@ from subprocess import check_call | ||||
| from charmhelpers.core.hookenv import ( | ||||
|     log, | ||||
|     DEBUG, | ||||
|     ERROR, | ||||
| ) | ||||
|  | ||||
| __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | ||||
|  | ||||
|  | ||||
| def create(sysctl_dict, sysctl_file): | ||||
|     """Creates a sysctl.conf file from a YAML associative array | ||||
|  | ||||
|     :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 } | ||||
|     :type sysctl_dict: dict | ||||
|     :param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }" | ||||
|     :type sysctl_dict: str | ||||
|     :param sysctl_file: path to the sysctl file to be saved | ||||
|     :type sysctl_file: str or unicode | ||||
|     :returns: None | ||||
|     """ | ||||
|     sysctl_dict = yaml.load(sysctl_dict) | ||||
|     try: | ||||
|         sysctl_dict_parsed = yaml.safe_load(sysctl_dict) | ||||
|     except yaml.YAMLError: | ||||
|         log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict), | ||||
|             level=ERROR) | ||||
|         return | ||||
|  | ||||
|     with open(sysctl_file, "w") as fd: | ||||
|         for key, value in sysctl_dict.items(): | ||||
|         for key, value in sysctl_dict_parsed.items(): | ||||
|             fd.write("{}={}\n".format(key, value)) | ||||
|  | ||||
|     log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict), | ||||
|     log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed), | ||||
|         level=DEBUG) | ||||
|  | ||||
|     check_call(["sysctl", "-p", sysctl_file]) | ||||
|   | ||||
| @@ -21,7 +21,7 @@ from charmhelpers.core import hookenv | ||||
|  | ||||
|  | ||||
| def render(source, target, context, owner='root', group='root', | ||||
|            perms=0o444, templates_dir=None): | ||||
|            perms=0o444, templates_dir=None, encoding='UTF-8'): | ||||
|     """ | ||||
|     Render a template. | ||||
|  | ||||
| @@ -64,5 +64,5 @@ def render(source, target, context, owner='root', group='root', | ||||
|                     level=hookenv.ERROR) | ||||
|         raise e | ||||
|     content = template.render(context) | ||||
|     host.mkdir(os.path.dirname(target), owner, group) | ||||
|     host.write_file(target, content, owner, group, perms) | ||||
|     host.mkdir(os.path.dirname(target), owner, group, perms=0o755) | ||||
|     host.write_file(target, content.encode(encoding), owner, group, perms) | ||||
|   | ||||
							
								
								
									
										477
									
								
								hooks/charmhelpers/core/unitdata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								hooks/charmhelpers/core/unitdata.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,477 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright 2014-2015 Canonical Limited. | ||||
| # | ||||
| # This file is part of charm-helpers. | ||||
| # | ||||
| # charm-helpers is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Lesser General Public License version 3 as | ||||
| # published by the Free Software Foundation. | ||||
| # | ||||
| # charm-helpers is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Lesser General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Lesser General Public License | ||||
| # along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| # | ||||
| # Authors: | ||||
| #  Kapil Thangavelu <kapil.foss@gmail.com> | ||||
| # | ||||
| """ | ||||
| Intro | ||||
| ----- | ||||
|  | ||||
| A simple way to store state in units. This provides a key value | ||||
| storage with support for versioned, transactional operation, | ||||
| and can calculate deltas from previous values to simplify unit logic | ||||
| when processing changes. | ||||
|  | ||||
|  | ||||
| Hook Integration | ||||
| ---------------- | ||||
|  | ||||
| There are several extant frameworks for hook execution, including | ||||
|  | ||||
|  - charmhelpers.core.hookenv.Hooks | ||||
|  - charmhelpers.core.services.ServiceManager | ||||
|  | ||||
| The storage classes are framework agnostic, one simple integration is | ||||
| via the HookData contextmanager. It will record the current hook | ||||
| execution environment (including relation data, config data, etc.), | ||||
| setup a transaction and allow easy access to the changes from | ||||
| previously seen values. One consequence of the integration is the | ||||
| reservation of particular keys ('rels', 'unit', 'env', 'config', | ||||
| 'charm_revisions') for their respective values. | ||||
|  | ||||
| Here's a fully worked integration example using hookenv.Hooks:: | ||||
|  | ||||
|        from charmhelper.core import hookenv, unitdata | ||||
|  | ||||
|        hook_data = unitdata.HookData() | ||||
|        db = unitdata.kv() | ||||
|        hooks = hookenv.Hooks() | ||||
|  | ||||
|        @hooks.hook | ||||
|        def config_changed(): | ||||
|            # Print all changes to configuration from previously seen | ||||
|            # values. | ||||
|            for changed, (prev, cur) in hook_data.conf.items(): | ||||
|                print('config changed', changed, | ||||
|                      'previous value', prev, | ||||
|                      'current value',  cur) | ||||
|  | ||||
|            # Get some unit specific bookeeping | ||||
|            if not db.get('pkg_key'): | ||||
|                key = urllib.urlopen('https://example.com/pkg_key').read() | ||||
|                db.set('pkg_key', key) | ||||
|  | ||||
|            # Directly access all charm config as a mapping. | ||||
|            conf = db.getrange('config', True) | ||||
|  | ||||
|            # Directly access all relation data as a mapping | ||||
|            rels = db.getrange('rels', True) | ||||
|  | ||||
|        if __name__ == '__main__': | ||||
|            with hook_data(): | ||||
|                hook.execute() | ||||
|  | ||||
|  | ||||
| A more basic integration is via the hook_scope context manager which simply | ||||
| manages transaction scope (and records hook name, and timestamp):: | ||||
|  | ||||
|   >>> from unitdata import kv | ||||
|   >>> db = kv() | ||||
|   >>> with db.hook_scope('install'): | ||||
|   ...    # do work, in transactional scope. | ||||
|   ...    db.set('x', 1) | ||||
|   >>> db.get('x') | ||||
|   1 | ||||
|  | ||||
|  | ||||
| Usage | ||||
| ----- | ||||
|  | ||||
| Values are automatically json de/serialized to preserve basic typing | ||||
| and complex data struct capabilities (dicts, lists, ints, booleans, etc). | ||||
|  | ||||
| Individual values can be manipulated via get/set:: | ||||
|  | ||||
|    >>> kv.set('y', True) | ||||
|    >>> kv.get('y') | ||||
|    True | ||||
|  | ||||
|    # We can set complex values (dicts, lists) as a single key. | ||||
|    >>> kv.set('config', {'a': 1, 'b': True'}) | ||||
|  | ||||
|    # Also supports returning dictionaries as a record which | ||||
|    # provides attribute access. | ||||
|    >>> config = kv.get('config', record=True) | ||||
|    >>> config.b | ||||
|    True | ||||
|  | ||||
|  | ||||
| Groups of keys can be manipulated with update/getrange:: | ||||
|  | ||||
|    >>> kv.update({'z': 1, 'y': 2}, prefix="gui.") | ||||
|    >>> kv.getrange('gui.', strip=True) | ||||
|    {'z': 1, 'y': 2} | ||||
|  | ||||
| When updating values, its very helpful to understand which values | ||||
| have actually changed and how have they changed. The storage | ||||
| provides a delta method to provide for this:: | ||||
|  | ||||
|    >>> data = {'debug': True, 'option': 2} | ||||
|    >>> delta = kv.delta(data, 'config.') | ||||
|    >>> delta.debug.previous | ||||
|    None | ||||
|    >>> delta.debug.current | ||||
|    True | ||||
|    >>> delta | ||||
|    {'debug': (None, True), 'option': (None, 2)} | ||||
|  | ||||
| Note the delta method does not persist the actual change, it needs to | ||||
| be explicitly saved via 'update' method:: | ||||
|  | ||||
|    >>> kv.update(data, 'config.') | ||||
|  | ||||
| Values modified in the context of a hook scope retain historical values | ||||
| associated to the hookname. | ||||
|  | ||||
|    >>> with db.hook_scope('config-changed'): | ||||
|    ...      db.set('x', 42) | ||||
|    >>> db.gethistory('x') | ||||
|    [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'), | ||||
|     (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')] | ||||
|  | ||||
| """ | ||||
|  | ||||
| import collections | ||||
| import contextlib | ||||
| import datetime | ||||
| import json | ||||
| import os | ||||
| import pprint | ||||
| import sqlite3 | ||||
| import sys | ||||
|  | ||||
| __author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>' | ||||
|  | ||||
|  | ||||
| class Storage(object): | ||||
|     """Simple key value database for local unit state within charms. | ||||
|  | ||||
|     Modifications are automatically committed at hook exit. That's | ||||
|     currently regardless of exit code. | ||||
|  | ||||
|     To support dicts, lists, integer, floats, and booleans values | ||||
|     are automatically json encoded/decoded. | ||||
|     """ | ||||
|     def __init__(self, path=None): | ||||
|         self.db_path = path | ||||
|         if path is None: | ||||
|             self.db_path = os.path.join( | ||||
|                 os.environ.get('CHARM_DIR', ''), '.unit-state.db') | ||||
|         self.conn = sqlite3.connect('%s' % self.db_path) | ||||
|         self.cursor = self.conn.cursor() | ||||
|         self.revision = None | ||||
|         self._closed = False | ||||
|         self._init() | ||||
|  | ||||
|     def close(self): | ||||
|         if self._closed: | ||||
|             return | ||||
|         self.flush(False) | ||||
|         self.cursor.close() | ||||
|         self.conn.close() | ||||
|         self._closed = True | ||||
|  | ||||
|     def _scoped_query(self, stmt, params=None): | ||||
|         if params is None: | ||||
|             params = [] | ||||
|         return stmt, params | ||||
|  | ||||
|     def get(self, key, default=None, record=False): | ||||
|         self.cursor.execute( | ||||
|             *self._scoped_query( | ||||
|                 'select data from kv where key=?', [key])) | ||||
|         result = self.cursor.fetchone() | ||||
|         if not result: | ||||
|             return default | ||||
|         if record: | ||||
|             return Record(json.loads(result[0])) | ||||
|         return json.loads(result[0]) | ||||
|  | ||||
|     def getrange(self, key_prefix, strip=False): | ||||
|         stmt = "select key, data from kv where key like '%s%%'" % key_prefix | ||||
|         self.cursor.execute(*self._scoped_query(stmt)) | ||||
|         result = self.cursor.fetchall() | ||||
|  | ||||
|         if not result: | ||||
|             return None | ||||
|         if not strip: | ||||
|             key_prefix = '' | ||||
|         return dict([ | ||||
|             (k[len(key_prefix):], json.loads(v)) for k, v in result]) | ||||
|  | ||||
|     def update(self, mapping, prefix=""): | ||||
|         for k, v in mapping.items(): | ||||
|             self.set("%s%s" % (prefix, k), v) | ||||
|  | ||||
|     def unset(self, key): | ||||
|         self.cursor.execute('delete from kv where key=?', [key]) | ||||
|         if self.revision and self.cursor.rowcount: | ||||
|             self.cursor.execute( | ||||
|                 'insert into kv_revisions values (?, ?, ?)', | ||||
|                 [key, self.revision, json.dumps('DELETED')]) | ||||
|  | ||||
|     def set(self, key, value): | ||||
|         serialized = json.dumps(value) | ||||
|  | ||||
|         self.cursor.execute( | ||||
|             'select data from kv where key=?', [key]) | ||||
|         exists = self.cursor.fetchone() | ||||
|  | ||||
|         # Skip mutations to the same value | ||||
|         if exists: | ||||
|             if exists[0] == serialized: | ||||
|                 return value | ||||
|  | ||||
|         if not exists: | ||||
|             self.cursor.execute( | ||||
|                 'insert into kv (key, data) values (?, ?)', | ||||
|                 (key, serialized)) | ||||
|         else: | ||||
|             self.cursor.execute(''' | ||||
|             update kv | ||||
|             set data = ? | ||||
|             where key = ?''', [serialized, key]) | ||||
|  | ||||
|         # Save | ||||
|         if not self.revision: | ||||
|             return value | ||||
|  | ||||
|         self.cursor.execute( | ||||
|             'select 1 from kv_revisions where key=? and revision=?', | ||||
|             [key, self.revision]) | ||||
|         exists = self.cursor.fetchone() | ||||
|  | ||||
|         if not exists: | ||||
|             self.cursor.execute( | ||||
|                 '''insert into kv_revisions ( | ||||
|                 revision, key, data) values (?, ?, ?)''', | ||||
|                 (self.revision, key, serialized)) | ||||
|         else: | ||||
|             self.cursor.execute( | ||||
|                 ''' | ||||
|                 update kv_revisions | ||||
|                 set data = ? | ||||
|                 where key = ? | ||||
|                 and   revision = ?''', | ||||
|                 [serialized, key, self.revision]) | ||||
|  | ||||
|         return value | ||||
|  | ||||
|     def delta(self, mapping, prefix): | ||||
|         """ | ||||
|         return a delta containing values that have changed. | ||||
|         """ | ||||
|         previous = self.getrange(prefix, strip=True) | ||||
|         if not previous: | ||||
|             pk = set() | ||||
|         else: | ||||
|             pk = set(previous.keys()) | ||||
|         ck = set(mapping.keys()) | ||||
|         delta = DeltaSet() | ||||
|  | ||||
|         # added | ||||
|         for k in ck.difference(pk): | ||||
|             delta[k] = Delta(None, mapping[k]) | ||||
|  | ||||
|         # removed | ||||
|         for k in pk.difference(ck): | ||||
|             delta[k] = Delta(previous[k], None) | ||||
|  | ||||
|         # changed | ||||
|         for k in pk.intersection(ck): | ||||
|             c = mapping[k] | ||||
|             p = previous[k] | ||||
|             if c != p: | ||||
|                 delta[k] = Delta(p, c) | ||||
|  | ||||
|         return delta | ||||
|  | ||||
|     @contextlib.contextmanager | ||||
|     def hook_scope(self, name=""): | ||||
|         """Scope all future interactions to the current hook execution | ||||
|         revision.""" | ||||
|         assert not self.revision | ||||
|         self.cursor.execute( | ||||
|             'insert into hooks (hook, date) values (?, ?)', | ||||
|             (name or sys.argv[0], | ||||
|              datetime.datetime.utcnow().isoformat())) | ||||
|         self.revision = self.cursor.lastrowid | ||||
|         try: | ||||
|             yield self.revision | ||||
|             self.revision = None | ||||
|         except: | ||||
|             self.flush(False) | ||||
|             self.revision = None | ||||
|             raise | ||||
|         else: | ||||
|             self.flush() | ||||
|  | ||||
|     def flush(self, save=True): | ||||
|         if save: | ||||
|             self.conn.commit() | ||||
|         elif self._closed: | ||||
|             return | ||||
|         else: | ||||
|             self.conn.rollback() | ||||
|  | ||||
|     def _init(self): | ||||
|         self.cursor.execute(''' | ||||
|             create table if not exists kv ( | ||||
|                key text, | ||||
|                data text, | ||||
|                primary key (key) | ||||
|                )''') | ||||
|         self.cursor.execute(''' | ||||
|             create table if not exists kv_revisions ( | ||||
|                key text, | ||||
|                revision integer, | ||||
|                data text, | ||||
|                primary key (key, revision) | ||||
|                )''') | ||||
|         self.cursor.execute(''' | ||||
|             create table if not exists hooks ( | ||||
|                version integer primary key autoincrement, | ||||
|                hook text, | ||||
|                date text | ||||
|                )''') | ||||
|         self.conn.commit() | ||||
|  | ||||
|     def gethistory(self, key, deserialize=False): | ||||
|         self.cursor.execute( | ||||
|             ''' | ||||
|             select kv.revision, kv.key, kv.data, h.hook, h.date | ||||
|             from kv_revisions kv, | ||||
|                  hooks h | ||||
|             where kv.key=? | ||||
|              and kv.revision = h.version | ||||
|             ''', [key]) | ||||
|         if deserialize is False: | ||||
|             return self.cursor.fetchall() | ||||
|         return map(_parse_history, self.cursor.fetchall()) | ||||
|  | ||||
|     def debug(self, fh=sys.stderr): | ||||
|         self.cursor.execute('select * from kv') | ||||
|         pprint.pprint(self.cursor.fetchall(), stream=fh) | ||||
|         self.cursor.execute('select * from kv_revisions') | ||||
|         pprint.pprint(self.cursor.fetchall(), stream=fh) | ||||
|  | ||||
|  | ||||
| def _parse_history(d): | ||||
|     return (d[0], d[1], json.loads(d[2]), d[3], | ||||
|             datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f")) | ||||
|  | ||||
|  | ||||
| class HookData(object): | ||||
|     """Simple integration for existing hook exec frameworks. | ||||
|  | ||||
|     Records all unit information, and stores deltas for processing | ||||
|     by the hook. | ||||
|  | ||||
|     Sample:: | ||||
|  | ||||
|        from charmhelper.core import hookenv, unitdata | ||||
|  | ||||
|        changes = unitdata.HookData() | ||||
|        db = unitdata.kv() | ||||
|        hooks = hookenv.Hooks() | ||||
|  | ||||
|        @hooks.hook | ||||
|        def config_changed(): | ||||
|            # View all changes to configuration | ||||
|            for changed, (prev, cur) in changes.conf.items(): | ||||
|                print('config changed', changed, | ||||
|                      'previous value', prev, | ||||
|                      'current value',  cur) | ||||
|  | ||||
|            # Get some unit specific bookeeping | ||||
|            if not db.get('pkg_key'): | ||||
|                key = urllib.urlopen('https://example.com/pkg_key').read() | ||||
|                db.set('pkg_key', key) | ||||
|  | ||||
|        if __name__ == '__main__': | ||||
|            with changes(): | ||||
|                hook.execute() | ||||
|  | ||||
|     """ | ||||
|     def __init__(self): | ||||
|         self.kv = kv() | ||||
|         self.conf = None | ||||
|         self.rels = None | ||||
|  | ||||
|     @contextlib.contextmanager | ||||
|     def __call__(self): | ||||
|         from charmhelpers.core import hookenv | ||||
|         hook_name = hookenv.hook_name() | ||||
|  | ||||
|         with self.kv.hook_scope(hook_name): | ||||
|             self._record_charm_version(hookenv.charm_dir()) | ||||
|             delta_config, delta_relation = self._record_hook(hookenv) | ||||
|             yield self.kv, delta_config, delta_relation | ||||
|  | ||||
|     def _record_charm_version(self, charm_dir): | ||||
|         # Record revisions.. charm revisions are meaningless | ||||
|         # to charm authors as they don't control the revision. | ||||
|         # so logic dependnent on revision is not particularly | ||||
|         # useful, however it is useful for debugging analysis. | ||||
|         charm_rev = open( | ||||
|             os.path.join(charm_dir, 'revision')).read().strip() | ||||
|         charm_rev = charm_rev or '0' | ||||
|         revs = self.kv.get('charm_revisions', []) | ||||
|         if charm_rev not in revs: | ||||
|             revs.append(charm_rev.strip() or '0') | ||||
|             self.kv.set('charm_revisions', revs) | ||||
|  | ||||
|     def _record_hook(self, hookenv): | ||||
|         data = hookenv.execution_environment() | ||||
|         self.conf = conf_delta = self.kv.delta(data['conf'], 'config') | ||||
|         self.rels = rels_delta = self.kv.delta(data['rels'], 'rels') | ||||
|         self.kv.set('env', data['env']) | ||||
|         self.kv.set('unit', data['unit']) | ||||
|         self.kv.set('relid', data.get('relid')) | ||||
|         return conf_delta, rels_delta | ||||
|  | ||||
|  | ||||
| class Record(dict): | ||||
|  | ||||
|     __slots__ = () | ||||
|  | ||||
|     def __getattr__(self, k): | ||||
|         if k in self: | ||||
|             return self[k] | ||||
|         raise AttributeError(k) | ||||
|  | ||||
|  | ||||
| class DeltaSet(Record): | ||||
|  | ||||
|     __slots__ = () | ||||
|  | ||||
|  | ||||
| Delta = collections.namedtuple('Delta', ['previous', 'current']) | ||||
|  | ||||
|  | ||||
| _KV = None | ||||
|  | ||||
|  | ||||
| def kv(): | ||||
|     global _KV | ||||
|     if _KV is None: | ||||
|         _KV = Storage() | ||||
|     return _KV | ||||
| @@ -18,6 +18,16 @@ import os | ||||
| import hashlib | ||||
| import re | ||||
|  | ||||
| from charmhelpers.fetch import ( | ||||
|     BaseFetchHandler, | ||||
|     UnhandledSource | ||||
| ) | ||||
| from charmhelpers.payload.archive import ( | ||||
|     get_archive_handler, | ||||
|     extract, | ||||
| ) | ||||
| from charmhelpers.core.host import mkdir, check_hash | ||||
|  | ||||
| import six | ||||
| if six.PY3: | ||||
|     from urllib.request import ( | ||||
| @@ -35,16 +45,6 @@ else: | ||||
|     ) | ||||
|     from urlparse import urlparse, urlunparse, parse_qs | ||||
|  | ||||
| from charmhelpers.fetch import ( | ||||
|     BaseFetchHandler, | ||||
|     UnhandledSource | ||||
| ) | ||||
| from charmhelpers.payload.archive import ( | ||||
|     get_archive_handler, | ||||
|     extract, | ||||
| ) | ||||
| from charmhelpers.core.host import mkdir, check_hash | ||||
|  | ||||
|  | ||||
| def splituser(host): | ||||
|     '''urllib.splituser(), but six's support of this seems broken''' | ||||
|   | ||||
| @@ -32,7 +32,7 @@ except ImportError: | ||||
|     apt_install("python-git") | ||||
|     from git import Repo | ||||
|  | ||||
| from git.exc import GitCommandError | ||||
| from git.exc import GitCommandError  # noqa E402 | ||||
|  | ||||
|  | ||||
| class GitUrlFetchHandler(BaseFetchHandler): | ||||
|   | ||||
| @@ -169,7 +169,12 @@ class AmuletUtils(object): | ||||
|             cmd = 'pgrep -o -f {}'.format(service) | ||||
|         else: | ||||
|             cmd = 'pgrep -o {}'.format(service) | ||||
|         proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip()) | ||||
|         cmd = cmd + '  | grep  -v pgrep || exit 0' | ||||
|         cmd_out = sentry_unit.run(cmd) | ||||
|         self.log.debug('CMDout: ' + str(cmd_out)) | ||||
|         if cmd_out[0]: | ||||
|             self.log.debug('Pid for %s %s' % (service, str(cmd_out[0]))) | ||||
|             proc_dir = '/proc/{}'.format(cmd_out[0].strip()) | ||||
|             return self._get_dir_mtime(sentry_unit, proc_dir) | ||||
|  | ||||
|     def service_restarted(self, sentry_unit, service, filename, | ||||
| @@ -187,6 +192,121 @@ class AmuletUtils(object): | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|     def service_restarted_since(self, sentry_unit, mtime, service, | ||||
|                                 pgrep_full=False, sleep_time=20, | ||||
|                                 retry_count=2): | ||||
|         """Check if service was been started after a given time. | ||||
|  | ||||
|         Args: | ||||
|           sentry_unit (sentry): The sentry unit to check for the service on | ||||
|           mtime (float): The epoch time to check against | ||||
|           service (string): service name to look for in process table | ||||
|           pgrep_full (boolean): Use full command line search mode with pgrep | ||||
|           sleep_time (int): Seconds to sleep before looking for process | ||||
|           retry_count (int): If service is not found, how many times to retry | ||||
|  | ||||
|         Returns: | ||||
|           bool: True if service found and its start time it newer than mtime, | ||||
|                 False if service is older than mtime or if service was | ||||
|                 not found. | ||||
|         """ | ||||
|         self.log.debug('Checking %s restarted since %s' % (service, mtime)) | ||||
|         time.sleep(sleep_time) | ||||
|         proc_start_time = self._get_proc_start_time(sentry_unit, service, | ||||
|                                                     pgrep_full) | ||||
|         while retry_count > 0 and not proc_start_time: | ||||
|             self.log.debug('No pid file found for service %s, will retry %i ' | ||||
|                            'more times' % (service, retry_count)) | ||||
|             time.sleep(30) | ||||
|             proc_start_time = self._get_proc_start_time(sentry_unit, service, | ||||
|                                                         pgrep_full) | ||||
|             retry_count = retry_count - 1 | ||||
|  | ||||
|         if not proc_start_time: | ||||
|             self.log.warn('No proc start time found, assuming service did ' | ||||
|                           'not start') | ||||
|             return False | ||||
|         if proc_start_time >= mtime: | ||||
|             self.log.debug('proc start time is newer than provided mtime' | ||||
|                            '(%s >= %s)' % (proc_start_time, mtime)) | ||||
|             return True | ||||
|         else: | ||||
|             self.log.warn('proc start time (%s) is older than provided mtime ' | ||||
|                           '(%s), service did not restart' % (proc_start_time, | ||||
|                                                              mtime)) | ||||
|             return False | ||||
|  | ||||
|     def config_updated_since(self, sentry_unit, filename, mtime, | ||||
|                              sleep_time=20): | ||||
|         """Check if file was modified after a given time. | ||||
|  | ||||
|         Args: | ||||
|           sentry_unit (sentry): The sentry unit to check the file mtime on | ||||
|           filename (string): The file to check mtime of | ||||
|           mtime (float): The epoch time to check against | ||||
|           sleep_time (int): Seconds to sleep before looking for process | ||||
|  | ||||
|         Returns: | ||||
|           bool: True if file was modified more recently than mtime, False if | ||||
|                 file was modified before mtime, | ||||
|         """ | ||||
|         self.log.debug('Checking %s updated since %s' % (filename, mtime)) | ||||
|         time.sleep(sleep_time) | ||||
|         file_mtime = self._get_file_mtime(sentry_unit, filename) | ||||
|         if file_mtime >= mtime: | ||||
|             self.log.debug('File mtime is newer than provided mtime ' | ||||
|                            '(%s >= %s)' % (file_mtime, mtime)) | ||||
|             return True | ||||
|         else: | ||||
|             self.log.warn('File mtime %s is older than provided mtime %s' | ||||
|                           % (file_mtime, mtime)) | ||||
|             return False | ||||
|  | ||||
|     def validate_service_config_changed(self, sentry_unit, mtime, service, | ||||
|                                         filename, pgrep_full=False, | ||||
|                                         sleep_time=20, retry_count=2): | ||||
|         """Check service and file were updated after mtime | ||||
|  | ||||
|         Args: | ||||
|           sentry_unit (sentry): The sentry unit to check for the service on | ||||
|           mtime (float): The epoch time to check against | ||||
|           service (string): service name to look for in process table | ||||
|           filename (string): The file to check mtime of | ||||
|           pgrep_full (boolean): Use full command line search mode with pgrep | ||||
|           sleep_time (int): Seconds to sleep before looking for process | ||||
|           retry_count (int): If service is not found, how many times to retry | ||||
|  | ||||
|         Typical Usage: | ||||
|             u = OpenStackAmuletUtils(ERROR) | ||||
|             ... | ||||
|             mtime = u.get_sentry_time(self.cinder_sentry) | ||||
|             self.d.configure('cinder', {'verbose': 'True', 'debug': 'True'}) | ||||
|             if not u.validate_service_config_changed(self.cinder_sentry, | ||||
|                                                      mtime, | ||||
|                                                      'cinder-api', | ||||
|                                                      '/etc/cinder/cinder.conf') | ||||
|                 amulet.raise_status(amulet.FAIL, msg='update failed') | ||||
|         Returns: | ||||
|           bool: True if both service and file where updated/restarted after | ||||
|                 mtime, False if service is older than mtime or if service was | ||||
|                 not found or if filename was modified before mtime. | ||||
|         """ | ||||
|         self.log.debug('Checking %s restarted since %s' % (service, mtime)) | ||||
|         time.sleep(sleep_time) | ||||
|         service_restart = self.service_restarted_since(sentry_unit, mtime, | ||||
|                                                        service, | ||||
|                                                        pgrep_full=pgrep_full, | ||||
|                                                        sleep_time=0, | ||||
|                                                        retry_count=retry_count) | ||||
|         config_update = self.config_updated_since(sentry_unit, filename, mtime, | ||||
|                                                   sleep_time=0) | ||||
|         return service_restart and config_update | ||||
|  | ||||
|     def get_sentry_time(self, sentry_unit): | ||||
|         """Return current epoch time on a sentry""" | ||||
|         cmd = "date +'%s'" | ||||
|         return float(sentry_unit.run(cmd)[0]) | ||||
|  | ||||
|     def relation_error(self, name, data): | ||||
|         return 'unexpected relation data in {} - {}'.format(name, data) | ||||
|  | ||||
|   | ||||
| @@ -71,16 +71,19 @@ class OpenStackAmuletDeployment(AmuletDeployment): | ||||
|         services.append(this_service) | ||||
|         use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | ||||
|                       'ceph-osd', 'ceph-radosgw'] | ||||
|         # Openstack subordinate charms do not expose an origin option as that | ||||
|         # is controlled by the principle | ||||
|         ignore = ['neutron-openvswitch'] | ||||
|  | ||||
|         if self.openstack: | ||||
|             for svc in services: | ||||
|                 if svc['name'] not in use_source: | ||||
|                 if svc['name'] not in use_source + ignore: | ||||
|                     config = {'openstack-origin': self.openstack} | ||||
|                     self.d.configure(svc['name'], config) | ||||
|  | ||||
|         if self.source: | ||||
|             for svc in services: | ||||
|                 if svc['name'] in use_source: | ||||
|                 if svc['name'] in use_source and svc['name'] not in ignore: | ||||
|                     config = {'source': self.source} | ||||
|                     self.d.configure(svc['name'], config) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ryan Beisner
					Ryan Beisner