Merge "Do not run client relation until clustered if HA"

This commit is contained in:
Jenkins 2017-02-16 16:36:07 +00:00 committed by Gerrit Code Review
commit ea3ea47d0a
11 changed files with 479 additions and 65 deletions

View File

@ -426,7 +426,7 @@ def ns_query(address):
try:
answers = dns.resolver.query(address, rtype)
except dns.resolver.NXDOMAIN as e:
except dns.resolver.NXDOMAIN:
return None
if answers:

View File

@ -20,6 +20,7 @@ import re
import six
import time
import urllib
import urlparse
import cinderclient.v1.client as cinder_client
import glanceclient.v1.client as glance_client
@ -37,6 +38,7 @@ import swiftclient
from charmhelpers.contrib.amulet.utils import (
AmuletUtils
)
from charmhelpers.core.decorators import retry_on_exception
DEBUG = logging.DEBUG
ERROR = logging.ERROR
@ -303,6 +305,46 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
return tenant in [t.name for t in keystone.tenants.list()]
@retry_on_exception(5, base_delay=10)
def keystone_wait_for_propagation(self, sentry_relation_pairs,
api_version):
"""Iterate over list of sentry and relation tuples and verify that
api_version has the expected value.
:param sentry_relation_pairs: list of sentry, relation name tuples used
for monitoring propagation of relation
data
:param api_version: api_version to expect in relation data
:returns: None if successful. Raise on error.
"""
for (sentry, relation_name) in sentry_relation_pairs:
rel = sentry.relation('identity-service',
relation_name)
self.log.debug('keystone relation data: {}'.format(rel))
if rel['api_version'] != str(api_version):
raise Exception("api_version not propagated through relation"
" data yet ('{}' != '{}')."
"".format(rel['api_version'], api_version))
def keystone_configure_api_version(self, sentry_relation_pairs, deployment,
api_version):
"""Configure preferred-api-version of keystone in deployment and
monitor provided list of relation objects for propagation
before returning to caller.
:param sentry_relation_pairs: list of sentry, relation tuples used for
monitoring propagation of relation data
:param deployment: deployment to configure
:param api_version: value preferred-api-version will be set to
:returns: None if successful. Raise on error.
"""
self.log.debug("Setting keystone preferred-api-version: '{}'"
"".format(api_version))
config = {'preferred-api-version': api_version}
deployment.d.configure('keystone', config)
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
def authenticate_cinder_admin(self, keystone_sentry, username,
password, tenant):
"""Authenticates admin user with cinder."""
@ -311,6 +353,37 @@ class OpenStackAmuletUtils(AmuletUtils):
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
return cinder_client.Client(username, password, tenant, ept)
def authenticate_keystone(self, keystone_ip, username, password,
api_version=False, admin_port=False,
user_domain_name=None, domain_name=None,
project_domain_name=None, project_name=None):
"""Authenticate with Keystone"""
self.log.debug('Authenticating with keystone...')
port = 5000
if admin_port:
port = 35357
base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'),
port)
if not api_version or api_version == 2:
ep = base_ep + "/v2.0"
return keystone_client.Client(username=username, password=password,
tenant_name=project_name,
auth_url=ep)
else:
ep = base_ep + "/v3"
auth = keystone_id_v3.Password(
user_domain_name=user_domain_name,
username=username,
password=password,
domain_name=domain_name,
project_domain_name=project_domain_name,
project_name=project_name,
auth_url=ep
)
return keystone_client_v3.Client(
session=keystone_session.Session(auth=auth)
)
def authenticate_keystone_admin(self, keystone_sentry, user, password,
tenant=None, api_version=None,
keystone_ip=None):
@ -319,30 +392,28 @@ class OpenStackAmuletUtils(AmuletUtils):
if not keystone_ip:
keystone_ip = keystone_sentry.info['public-address']
base_ep = "http://{}:35357".format(keystone_ip.strip().decode('utf-8'))
if not api_version or api_version == 2:
ep = base_ep + "/v2.0"
return keystone_client.Client(username=user, password=password,
tenant_name=tenant, auth_url=ep)
else:
ep = base_ep + "/v3"
auth = keystone_id_v3.Password(
user_domain_name='admin_domain',
username=user,
password=password,
domain_name='admin_domain',
auth_url=ep,
)
sess = keystone_session.Session(auth=auth)
return keystone_client_v3.Client(session=sess)
user_domain_name = None
domain_name = None
if api_version == 3:
user_domain_name = 'admin_domain'
domain_name = user_domain_name
return self.authenticate_keystone(keystone_ip, user, password,
project_name=tenant,
api_version=api_version,
user_domain_name=user_domain_name,
domain_name=domain_name,
admin_port=True)
def authenticate_keystone_user(self, keystone, user, password, tenant):
"""Authenticates a regular user with the keystone public endpoint."""
self.log.debug('Authenticating keystone user ({})...'.format(user))
ep = keystone.service_catalog.url_for(service_type='identity',
endpoint_type='publicURL')
return keystone_client.Client(username=user, password=password,
tenant_name=tenant, auth_url=ep)
keystone_ip = urlparse.urlparse(ep).hostname
return self.authenticate_keystone(keystone_ip, user, password,
project_name=tenant)
def authenticate_glance_admin(self, keystone):
"""Authenticates admin user with glance."""

View File

@ -126,3 +126,14 @@ def assert_charm_supports_dns_ha():
status_set('blocked', msg)
raise DNSHAException(msg)
return True
def expect_ha():
""" Determine if the unit expects to be in HA
Check for VIP or dns-ha settings which indicate the unit should expect to
be related to hacluster.
@returns boolean
"""
return config('vip') or config('dns-ha')

View File

@ -40,6 +40,7 @@ from subprocess import (
)
from charmhelpers.core.hookenv import (
config,
service_name,
local_unit,
relation_get,
relation_ids,
@ -1043,8 +1044,18 @@ class CephBrokerRq(object):
self.request_id = str(uuid.uuid1())
self.ops = []
def add_op_request_access_to_group(self, name, namespace=None,
permission=None, key_name=None):
"""
Adds the requested permissions to the current service's Ceph key,
allowing the key to access only the specified pools
"""
self.ops.append({'op': 'add-permissions-to-key', 'group': name,
'namespace': namespace, 'name': key_name or service_name(),
'group-permission': permission})
def add_op_create_pool(self, name, replica_count=3, pg_num=None,
weight=None):
weight=None, group=None, namespace=None):
"""Adds an operation to create a pool.
@param pg_num setting: optional setting. If not provided, this value
@ -1058,7 +1069,8 @@ class CephBrokerRq(object):
self.ops.append({'op': 'create-pool', 'name': name,
'replicas': replica_count, 'pg_num': pg_num,
'weight': weight})
'weight': weight, 'group': group,
'group-namespace': namespace})
def set_ops(self, ops):
"""Set request ops to provided value.

View File

@ -1035,3 +1035,34 @@ def network_get_primary_address(binding):
'''
cmd = ['network-get', '--primary-address', binding]
return subprocess.check_output(cmd).decode('UTF-8').strip()
def add_metric(*args, **kwargs):
"""Add metric values. Values may be expressed with keyword arguments. For
metric names containing dashes, these may be expressed as one or more
'key=value' positional arguments. May only be called from the collect-metrics
hook."""
_args = ['add-metric']
_kvpairs = []
_kvpairs.extend(args)
_kvpairs.extend(['{}={}'.format(k, v) for k, v in kwargs.items()])
_args.extend(sorted(_kvpairs))
try:
subprocess.check_call(_args)
return
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
log_message = 'add-metric failed: {}'.format(' '.join(_kvpairs))
log(log_message, level='INFO')
def meter_status():
"""Get the meter status, if running in the meter-status-changed hook."""
return os.environ.get('JUJU_METER_STATUS')
def meter_info():
"""Get the meter status information, if running in the meter-status-changed
hook."""
return os.environ.get('JUJU_METER_INFO')

View File

@ -54,38 +54,138 @@ elif __platform__ == "centos":
cmp_pkgrevno,
) # flake8: noqa -- ignore F401 for this import
UPDATEDB_PATH = '/etc/updatedb.conf'
def service_start(service_name):
"""Start a system service"""
return service('start', service_name)
def service_start(service_name, **kwargs):
"""Start a system service.
The specified service name is managed via the system level init system.
Some init systems (e.g. upstart) require that additional arguments be
provided in order to directly control service instances whereas other init
systems allow for addressing instances of a service directly by name (e.g.
systemd).
The kwargs allow for the additional parameters to be passed to underlying
init systems for those systems which require/allow for them. For example,
the ceph-osd upstart script requires the id parameter to be passed along
in order to identify which running daemon should be reloaded. The follow-
ing example stops the ceph-osd service for instance id=4:
service_stop('ceph-osd', id=4)
:param service_name: the name of the service to stop
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for systemd enabled systems.
"""
return service('start', service_name, **kwargs)
def service_stop(service_name):
"""Stop a system service"""
return service('stop', service_name)
def service_stop(service_name, **kwargs):
"""Stop a system service.
The specified service name is managed via the system level init system.
Some init systems (e.g. upstart) require that additional arguments be
provided in order to directly control service instances whereas other init
systems allow for addressing instances of a service directly by name (e.g.
systemd).
The kwargs allow for the additional parameters to be passed to underlying
init systems for those systems which require/allow for them. For example,
the ceph-osd upstart script requires the id parameter to be passed along
in order to identify which running daemon should be reloaded. The follow-
ing example stops the ceph-osd service for instance id=4:
service_stop('ceph-osd', id=4)
:param service_name: the name of the service to stop
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for systemd enabled systems.
"""
return service('stop', service_name, **kwargs)
def service_restart(service_name):
"""Restart a system service"""
def service_restart(service_name, **kwargs):
"""Restart a system service.
The specified service name is managed via the system level init system.
Some init systems (e.g. upstart) require that additional arguments be
provided in order to directly control service instances whereas other init
systems allow for addressing instances of a service directly by name (e.g.
systemd).
The kwargs allow for the additional parameters to be passed to underlying
init systems for those systems which require/allow for them. For example,
the ceph-osd upstart script requires the id parameter to be passed along
in order to identify which running daemon should be restarted. The follow-
ing example restarts the ceph-osd service for instance id=4:
service_restart('ceph-osd', id=4)
:param service_name: the name of the service to restart
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for init systems not allowing additional
parameters via the commandline (systemd).
"""
return service('restart', service_name)
def service_reload(service_name, restart_on_failure=False):
def service_reload(service_name, restart_on_failure=False, **kwargs):
"""Reload a system service, optionally falling back to restart if
reload fails"""
service_result = service('reload', service_name)
reload fails.
The specified service name is managed via the system level init system.
Some init systems (e.g. upstart) require that additional arguments be
provided in order to directly control service instances whereas other init
systems allow for addressing instances of a service directly by name (e.g.
systemd).
The kwargs allow for the additional parameters to be passed to underlying
init systems for those systems which require/allow for them. For example,
the ceph-osd upstart script requires the id parameter to be passed along
in order to identify which running daemon should be reloaded. The follow-
ing example restarts the ceph-osd service for instance id=4:
service_reload('ceph-osd', id=4)
:param service_name: the name of the service to reload
:param restart_on_failure: boolean indicating whether to fallback to a
restart if the reload fails.
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for init systems not allowing additional
parameters via the commandline (systemd).
"""
service_result = service('reload', service_name, **kwargs)
if not service_result and restart_on_failure:
service_result = service('restart', service_name)
service_result = service('restart', service_name, **kwargs)
return service_result
def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
**kwargs):
"""Pause a system service.
Stop it, and prevent it from starting again at boot."""
Stop it, and prevent it from starting again at boot.
:param service_name: the name of the service to pause
:param init_dir: path to the upstart init directory
:param initd_dir: path to the sysv init directory
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for init systems which do not support
key=value arguments via the commandline.
"""
stopped = True
if service_running(service_name):
stopped = service_stop(service_name)
if service_running(service_name, **kwargs):
stopped = service_stop(service_name, **kwargs)
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd():
@ -106,10 +206,19 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
def service_resume(service_name, init_dir="/etc/init",
initd_dir="/etc/init.d"):
initd_dir="/etc/init.d", **kwargs):
"""Resume a system service.
Reenable starting again at boot. Start the service"""
Reenable starting again at boot. Start the service.
:param service_name: the name of the service to resume
:param init_dir: the path to the init dir
:param initd dir: the path to the initd dir
:param **kwargs: additional parameters to pass to the init system when
managing services. These will be passed as key=value
parameters to the init system's commandline. kwargs
are ignored for systemd enabled systems.
"""
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd():
@ -126,19 +235,28 @@ def service_resume(service_name, init_dir="/etc/init",
"Unable to detect {0} as SystemD, Upstart {1} or"
" SysV {2}".format(
service_name, upstart_file, sysv_file))
started = service_running(service_name, **kwargs)
started = service_running(service_name)
if not started:
started = service_start(service_name)
started = service_start(service_name, **kwargs)
return started
def service(action, service_name):
"""Control a system service"""
def service(action, service_name, **kwargs):
"""Control a system service.
:param action: the action to take on the service
:param service_name: the name of the service to perform th action on
:param **kwargs: additional params to be passed to the service command in
the form of key=value.
"""
if init_is_systemd():
cmd = ['systemctl', action, service_name]
else:
cmd = ['service', service_name, action]
for key, value in six.iteritems(kwargs):
parameter = '%s=%s' % (key, value)
cmd.append(parameter)
return subprocess.call(cmd) == 0
@ -146,15 +264,26 @@ _UPSTART_CONF = "/etc/init/{}.conf"
_INIT_D_CONF = "/etc/init.d/{}"
def service_running(service_name):
"""Determine whether a system service is running"""
def service_running(service_name, **kwargs):
"""Determine whether a system service is running.
:param service_name: the name of the service
:param **kwargs: additional args to pass to the service command. This is
used to pass additional key=value arguments to the
service command line for managing specific instance
units (e.g. service ceph-osd status id=2). The kwargs
are ignored in systemd services.
"""
if init_is_systemd():
return service('is-active', service_name)
else:
if os.path.exists(_UPSTART_CONF.format(service_name)):
try:
output = subprocess.check_output(
['status', service_name],
cmd = ['status', service_name]
for key, value in six.iteritems(kwargs):
parameter = '%s=%s' % (key, value)
cmd.append(parameter)
output = subprocess.check_output(cmd,
stderr=subprocess.STDOUT).decode('UTF-8')
except subprocess.CalledProcessError:
return False
@ -720,6 +849,20 @@ def lchownr(path, owner, group):
chownr(path, owner, group, follow_links=False)
def owner(path):
"""Returns a tuple containing the username & groupname owning the path.
:param str path: the string path to retrieve the ownership
:return tuple(str, str): A (username, groupname) tuple containing the
name of the user and group owning the path.
:raises OSError: if the specified path does not exist
"""
stat = os.stat(path)
username = pwd.getpwuid(stat.st_uid)[0]
groupname = grp.getgrgid(stat.st_gid)[0]
return username, groupname
def get_total_ram():
"""The total amount of system RAM in bytes.
@ -751,3 +894,25 @@ def is_container():
else:
# Detect using upstart container file marker
return os.path.exists(UPSTART_CONTAINER_TYPE)
def add_to_updatedb_prunepath(path, updatedb_path=UPDATEDB_PATH):
with open(updatedb_path, 'r+') as f_id:
updatedb_text = f_id.read()
output = updatedb(updatedb_text, path)
f_id.seek(0)
f_id.write(output)
f_id.truncate()
def updatedb(updatedb_text, new_path):
lines = [line for line in updatedb_text.split("\n")]
for i, line in enumerate(lines):
if line.startswith("PRUNEPATHS="):
paths_line = line.split("=")[1].replace('"', '')
paths = paths_line.split(" ")
if new_path not in paths:
paths.append(new_path)
lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths))
output = "\n".join(lines)
return output

View File

@ -8,12 +8,18 @@ def get_platform():
will be returned (which is the name of the module).
This string is used to decide which platform module should be imported.
"""
# linux_distribution is deprecated and will be removed in Python 3.7
# Warings *not* disabled, as we certainly need to fix this.
tuple_platform = platform.linux_distribution()
current_platform = tuple_platform[0]
if "Ubuntu" in current_platform:
return "ubuntu"
elif "CentOS" in current_platform:
return "centos"
elif "debian" in current_platform:
# Stock Python does not detect Ubuntu and instead returns debian.
# Or at least it does in some build environments like Travis CI
return "ubuntu"
else:
raise RuntimeError("This module is not supported on {}."
.format(current_platform))

View File

@ -127,10 +127,12 @@ from charmhelpers.contrib.hahelpers.cluster import (
get_hacluster_config,
peer_units,
https,
is_clustered,
)
from charmhelpers.contrib.openstack.ha.utils import (
update_dns_ha_resource_params,
expect_ha,
)
from charmhelpers.payload.execd import execd_preinstall
@ -431,6 +433,10 @@ def identity_changed(relation_id=None, remote_unit=None):
"updates", level=INFO)
return
if expect_ha() and not is_clustered():
log("Expected to be HA but no hacluster relation yet", level=INFO)
return
add_service_to_keystone(relation_id, remote_unit)
if is_service_present('neutron', 'network'):
delete_service_entry('quantum', 'network')
@ -474,6 +480,9 @@ def identity_credentials_changed(relation_id=None, remote_unit=None):
:param remote_unit: Related unit on the relation
"""
if is_elected_leader(CLUSTER_RES):
if expect_ha() and not is_clustered():
log("Expected to be HA but no hacluster relation yet", level=INFO)
return
if not is_db_ready():
log("identity-credentials-relation-changed hook fired before db "
"ready - deferring until db ready", level=WARNING)
@ -701,6 +710,9 @@ def ha_changed():
@hooks.hook('identity-admin-relation-changed')
def admin_relation_changed(relation_id=None):
# TODO: fixup
if expect_ha() and not is_clustered():
log("Expected to be HA but no hacluster relation yet", level=INFO)
return
relation_data = {
'service_hostname': resolve_address(ADMIN),
'service_port': config('service-port'),

View File

@ -20,6 +20,7 @@ import re
import six
import time
import urllib
import urlparse
import cinderclient.v1.client as cinder_client
import glanceclient.v1.client as glance_client
@ -37,6 +38,7 @@ import swiftclient
from charmhelpers.contrib.amulet.utils import (
AmuletUtils
)
from charmhelpers.core.decorators import retry_on_exception
DEBUG = logging.DEBUG
ERROR = logging.ERROR
@ -303,6 +305,46 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
return tenant in [t.name for t in keystone.tenants.list()]
@retry_on_exception(5, base_delay=10)
def keystone_wait_for_propagation(self, sentry_relation_pairs,
api_version):
"""Iterate over list of sentry and relation tuples and verify that
api_version has the expected value.
:param sentry_relation_pairs: list of sentry, relation name tuples used
for monitoring propagation of relation
data
:param api_version: api_version to expect in relation data
:returns: None if successful. Raise on error.
"""
for (sentry, relation_name) in sentry_relation_pairs:
rel = sentry.relation('identity-service',
relation_name)
self.log.debug('keystone relation data: {}'.format(rel))
if rel['api_version'] != str(api_version):
raise Exception("api_version not propagated through relation"
" data yet ('{}' != '{}')."
"".format(rel['api_version'], api_version))
def keystone_configure_api_version(self, sentry_relation_pairs, deployment,
api_version):
"""Configure preferred-api-version of keystone in deployment and
monitor provided list of relation objects for propagation
before returning to caller.
:param sentry_relation_pairs: list of sentry, relation tuples used for
monitoring propagation of relation data
:param deployment: deployment to configure
:param api_version: value preferred-api-version will be set to
:returns: None if successful. Raise on error.
"""
self.log.debug("Setting keystone preferred-api-version: '{}'"
"".format(api_version))
config = {'preferred-api-version': api_version}
deployment.d.configure('keystone', config)
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
def authenticate_cinder_admin(self, keystone_sentry, username,
password, tenant):
"""Authenticates admin user with cinder."""
@ -311,6 +353,37 @@ class OpenStackAmuletUtils(AmuletUtils):
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
return cinder_client.Client(username, password, tenant, ept)
def authenticate_keystone(self, keystone_ip, username, password,
api_version=False, admin_port=False,
user_domain_name=None, domain_name=None,
project_domain_name=None, project_name=None):
"""Authenticate with Keystone"""
self.log.debug('Authenticating with keystone...')
port = 5000
if admin_port:
port = 35357
base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'),
port)
if not api_version or api_version == 2:
ep = base_ep + "/v2.0"
return keystone_client.Client(username=username, password=password,
tenant_name=project_name,
auth_url=ep)
else:
ep = base_ep + "/v3"
auth = keystone_id_v3.Password(
user_domain_name=user_domain_name,
username=username,
password=password,
domain_name=domain_name,
project_domain_name=project_domain_name,
project_name=project_name,
auth_url=ep
)
return keystone_client_v3.Client(
session=keystone_session.Session(auth=auth)
)
def authenticate_keystone_admin(self, keystone_sentry, user, password,
tenant=None, api_version=None,
keystone_ip=None):
@ -319,30 +392,28 @@ class OpenStackAmuletUtils(AmuletUtils):
if not keystone_ip:
keystone_ip = keystone_sentry.info['public-address']
base_ep = "http://{}:35357".format(keystone_ip.strip().decode('utf-8'))
if not api_version or api_version == 2:
ep = base_ep + "/v2.0"
return keystone_client.Client(username=user, password=password,
tenant_name=tenant, auth_url=ep)
else:
ep = base_ep + "/v3"
auth = keystone_id_v3.Password(
user_domain_name='admin_domain',
username=user,
password=password,
domain_name='admin_domain',
auth_url=ep,
)
sess = keystone_session.Session(auth=auth)
return keystone_client_v3.Client(session=sess)
user_domain_name = None
domain_name = None
if api_version == 3:
user_domain_name = 'admin_domain'
domain_name = user_domain_name
return self.authenticate_keystone(keystone_ip, user, password,
project_name=tenant,
api_version=api_version,
user_domain_name=user_domain_name,
domain_name=domain_name,
admin_port=True)
def authenticate_keystone_user(self, keystone, user, password, tenant):
"""Authenticates a regular user with the keystone public endpoint."""
self.log.debug('Authenticating keystone user ({})...'.format(user))
ep = keystone.service_catalog.url_for(service_type='identity',
endpoint_type='publicURL')
return keystone_client.Client(username=user, password=password,
tenant_name=tenant, auth_url=ep)
keystone_ip = urlparse.urlparse(ep).hostname
return self.authenticate_keystone(keystone_ip, user, password,
project_name=tenant)
def authenticate_glance_admin(self, keystone):
"""Authenticates admin user with glance."""

View File

@ -1035,3 +1035,34 @@ def network_get_primary_address(binding):
'''
cmd = ['network-get', '--primary-address', binding]
return subprocess.check_output(cmd).decode('UTF-8').strip()
def add_metric(*args, **kwargs):
"""Add metric values. Values may be expressed with keyword arguments. For
metric names containing dashes, these may be expressed as one or more
'key=value' positional arguments. May only be called from the collect-metrics
hook."""
_args = ['add-metric']
_kvpairs = []
_kvpairs.extend(args)
_kvpairs.extend(['{}={}'.format(k, v) for k, v in kwargs.items()])
_args.extend(sorted(_kvpairs))
try:
subprocess.check_call(_args)
return
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
log_message = 'add-metric failed: {}'.format(' '.join(_kvpairs))
log(log_message, level='INFO')
def meter_status():
"""Get the meter status, if running in the meter-status-changed hook."""
return os.environ.get('JUJU_METER_STATUS')
def meter_info():
"""Get the meter status information, if running in the meter-status-changed
hook."""
return os.environ.get('JUJU_METER_INFO')

View File

@ -75,9 +75,11 @@ TO_PATCH = [
'resolve_address',
# charmhelpers.contrib.openstack.ha.utils
'update_dns_ha_resource_params',
'expect_ha',
# charmhelpers.contrib.hahelpers.cluster_utils
'is_elected_leader',
'get_hacluster_config',
'is_clustered',
# keystone_utils
'restart_map',
'register_configs',
@ -647,6 +649,7 @@ class KeystoneRelationTests(CharmTestCase):
def test_identity_changed_leader(self, mock_send_notifications,
mock_hashlib, mock_ensure_ssl_cert_master,
mock_log, mock_is_db_initialised):
self.expect_ha.return_value = False
mock_is_db_initialised.return_value = True
self.is_db_ready.return_value = True
self.is_service_present.return_value = True
@ -671,6 +674,7 @@ class KeystoneRelationTests(CharmTestCase):
mock_ensure_ssl_cert_master,
mock_log,
mock_is_db_initialised):
self.expect_ha.return_value = False
mock_is_db_initialised.return_value = True
self.is_db_ready.return_value = True
self.is_service_present.return_value = False