Resync helpers

This commit is contained in:
James Page
2014-07-02 09:19:37 +01:00
parent c6a05222a3
commit 8b3f3a5cb1
11 changed files with 130 additions and 71 deletions

2
.bzrignore Normal file
View File

@@ -0,0 +1,2 @@
bin
.coverage

View File

@@ -16,9 +16,14 @@ test:
# https://bugs.launchpad.net/amulet/+bug/1320357 # https://bugs.launchpad.net/amulet/+bug/1320357
@juju test -v -p AMULET_HTTP_PROXY @juju test -v -p AMULET_HTTP_PROXY
sync: bin/charm_helpers_sync.py:
@charm-helper-sync -c charm-helpers-hooks.yaml @mkdir -p bin
@charm-helper-sync -c charm-helpers-tests.yaml @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \
> bin/charm_helpers_sync.py
sync: bin/charm_helpers_sync.py
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
publish: lint test publish: lint test
bzr push lp:charms/keystone bzr push lp:charms/keystone

View File

@@ -1,4 +1,4 @@
branch: lp:charm-helpers branch: lp:~james-page/charm-helpers/network-splits
destination: hooks/charmhelpers destination: hooks/charmhelpers
include: include:
- core - core

View File

@@ -11,8 +11,6 @@ import os
from socket import gethostname as get_unit_hostname from socket import gethostname as get_unit_hostname
from charmhelpers.fetch import apt_install
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, log,
relation_ids, relation_ids,
@@ -165,7 +163,7 @@ def get_hacluster_config():
return conf return conf
def canonical_url(configs, vip_setting='vip'): def canonical_url(configs, vip_setting='vip', address=None):
''' '''
Returns the correct HTTP URL to this host given the state of HTTPS Returns the correct HTTP URL to this host given the state of HTTPS
configuration and hacluster. configuration and hacluster.
@@ -181,35 +179,6 @@ def canonical_url(configs, vip_setting='vip'):
scheme = 'https' scheme = 'https'
if is_clustered(): if is_clustered():
addr = config_get(vip_setting) addr = config_get(vip_setting)
elif config_get('use-ipv6'):
addr = '[%s]' % get_ipv6_addr()
else: else:
addr = unit_get('private-address') addr = address or unit_get('private-address')
return '%s://%s' % (scheme, addr) return '%s://%s' % (scheme, addr)
def get_ipv6_addr(iface="eth0"):
# TODO Is there a scenario that the ipv6 will be gone in the middle
# phrase, which caused ipv6 address async.
try:
try:
import netifaces
except ImportError:
apt_install('python-netifaces')
import netifaces
iface_addrs = netifaces.ifaddresses(iface)
if netifaces.AF_INET6 not in iface_addrs:
raise Exception("Interface '%s' doesn't have an ipv6 address.")
addresses = netifaces.ifaddresses(iface)[netifaces.AF_INET6]
ipv6_addr = [addr['addr'] for addr in addresses \
if 'fe80' not in addr['addr']]
if not ipv6_addr:
raise Exception("Interface '%s' doesn't have global ipv6 address.")
return ipv6_addr[0]
except ValueError:
raise Exception("Invalid interface '%s'" % iface)

View File

@@ -67,3 +67,29 @@ def get_address_in_network(network, fallback=None, fatal=False):
not_found_error_out() not_found_error_out()
return None return None
def is_address_in_network(network, address):
"""
Determine whether the provided address is within a network range.
:param network (str): CIDR presentation format. For example,
'192.168.1.0/24'.
:param address: An individual IPv4 or IPv6 address without a net
mask or subnet prefix. For example, '192.168.1.1'.
:returns boolean: Flag indicating whether address is in network.
"""
try:
network = netaddr.IPNetwork(network)
except (netaddr.core.AddrFormatError, ValueError):
raise ValueError("Network (%s) is not in CIDR presentation format" %
network)
try:
address = netaddr.IPAddress(address)
except (netaddr.core.AddrFormatError, ValueError):
raise ValueError("Address (%s) is not in correct presentation format" %
address)
if address in network:
return True
else:
return False

View File

@@ -7,19 +7,36 @@ class OpenStackAmuletDeployment(AmuletDeployment):
"""This class inherits from AmuletDeployment and has additional support """This class inherits from AmuletDeployment and has additional support
that is specifically for use by OpenStack charms.""" that is specifically for use by OpenStack charms."""
def __init__(self, series=None, openstack=None): def __init__(self, series=None, openstack=None, source=None):
"""Initialize the deployment environment.""" """Initialize the deployment environment."""
self.openstack = None
super(OpenStackAmuletDeployment, self).__init__(series) super(OpenStackAmuletDeployment, self).__init__(series)
self.openstack = openstack
self.source = source
if openstack: def _add_services(self, this_service, other_services):
self.openstack = openstack """Add services to the deployment and set openstack-origin."""
super(OpenStackAmuletDeployment, self)._add_services(this_service,
other_services)
name = 0
services = other_services
services.append(this_service)
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph']
if self.openstack:
for svc in services:
if svc[name] not in use_source:
config = {'openstack-origin': self.openstack}
self.d.configure(svc[name], config)
if self.source:
for svc in services:
if svc[name] in use_source:
config = {'source': self.source}
self.d.configure(svc[name], config)
def _configure_services(self, configs): def _configure_services(self, configs):
"""Configure all of the services.""" """Configure all of the services."""
for service, config in configs.iteritems(): for service, config in configs.iteritems():
if service == self.this_service:
config['openstack-origin'] = self.openstack
self.d.configure(service, config) self.d.configure(service, config)
def _get_openstack_release(self): def _get_openstack_release(self):

View File

@@ -74,7 +74,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected tenant data - {}".format(ret) return "unexpected tenant data - {}".format(ret)
if not found: if not found:
return "tenant {} does not exist".format(e.name) return "tenant {} does not exist".format(e['name'])
return ret return ret
def validate_role_data(self, expected, actual): def validate_role_data(self, expected, actual):
@@ -91,7 +91,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected role data - {}".format(ret) return "unexpected role data - {}".format(ret)
if not found: if not found:
return "role {} does not exist".format(e.name) return "role {} does not exist".format(e['name'])
return ret return ret
def validate_user_data(self, expected, actual): def validate_user_data(self, expected, actual):
@@ -110,7 +110,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected user data - {}".format(ret) return "unexpected user data - {}".format(ret)
if not found: if not found:
return "user {} does not exist".format(e.name) return "user {} does not exist".format(e['name'])
return ret return ret
def validate_flavor_data(self, expected, actual): def validate_flavor_data(self, expected, actual):
@@ -192,8 +192,8 @@ class OpenStackAmuletUtils(AmuletUtils):
count = 1 count = 1
status = instance.status status = instance.status
while status == 'BUILD' and count < 10: while status != 'ACTIVE' and count < 60:
time.sleep(5) time.sleep(3)
instance = nova.servers.get(instance.id) instance = nova.servers.get(instance.id)
status = instance.status status = instance.status
self.log.debug('instance status: {}'.format(status)) self.log.debug('instance status: {}'.format(status))

View File

@@ -21,6 +21,7 @@ from charmhelpers.core.hookenv import (
relation_get, relation_get,
relation_ids, relation_ids,
related_units, related_units,
relation_set,
unit_get, unit_get,
unit_private_ip, unit_private_ip,
ERROR, ERROR,
@@ -42,6 +43,8 @@ from charmhelpers.contrib.openstack.neutron import (
neutron_plugin_attribute, neutron_plugin_attribute,
) )
from charmhelpers.contrib.network.ip import get_address_in_network
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
@@ -134,8 +137,22 @@ class SharedDBContext(OSContextGenerator):
'Missing required charm config options. ' 'Missing required charm config options. '
'(database name and user)') '(database name and user)')
raise OSContextError raise OSContextError
ctxt = {} ctxt = {}
# NOTE(jamespage) if mysql charm provides a network upon which
# access to the database should be made, reconfigure relation
# with the service units local address and defer execution
access_network = relation_get('access-network')
if access_network is not None:
access_hostname = get_address_in_network(access_network,
unit_get('private-address'))
set_hostname = relation_get(attribute='hostname',
unit=local_unit())
if set_hostname != access_hostname:
relation_set(hostname=access_hostname)
return ctxt # Defer any further hook execution for now....
password_setting = 'password' password_setting = 'password'
if self.relation_prefix: if self.relation_prefix:
password_setting = self.relation_prefix + '_password' password_setting = self.relation_prefix + '_password'
@@ -340,10 +357,12 @@ class CephContext(OSContextGenerator):
use_syslog = str(config('use-syslog')).lower() use_syslog = str(config('use-syslog')).lower()
for rid in relation_ids('ceph'): for rid in relation_ids('ceph'):
for unit in related_units(rid): for unit in related_units(rid):
mon_hosts.append(relation_get('private-address', rid=rid,
unit=unit))
auth = relation_get('auth', rid=rid, unit=unit) auth = relation_get('auth', rid=rid, unit=unit)
key = relation_get('key', rid=rid, unit=unit) key = relation_get('key', rid=rid, unit=unit)
ceph_addr = \
relation_get('ceph-public-address', rid=rid, unit=unit) or \
relation_get('private-address', rid=rid, unit=unit)
mon_hosts.append(ceph_addr)
ctxt = { ctxt = {
'mon_hosts': ' '.join(mon_hosts), 'mon_hosts': ' '.join(mon_hosts),

View File

@@ -53,9 +53,10 @@ class AmuletUtils(object):
"""Verify the specified services are running on the corresponding """Verify the specified services are running on the corresponding
service units.""" service units."""
for k, v in commands.iteritems(): for k, v in commands.iteritems():
output, code = k.run(v) for cmd in v:
if code != 0: output, code = k.run(cmd)
return "command `{}` returned {}".format(v, str(code)) if code != 0:
return "command `{}` returned {}".format(cmd, str(code))
return None return None
def _get_config(self, unit, filename): def _get_config(self, unit, filename):
@@ -126,21 +127,24 @@ class AmuletUtils(object):
"""Get last modification time of directory.""" """Get last modification time of directory."""
return sentry_unit.directory_stat(directory)['mtime'] return sentry_unit.directory_stat(directory)['mtime']
def _get_proc_start_time(self, sentry_unit, service): def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False):
"""Determine start time of the process based on the last modification """Determine start time of the process based on the last modification
time of the /proc/pid directory. The servie string will be matched time of the /proc/pid directory. If pgrep_full is True, the process
against any substring in the full command line, choosing the oldest name is matched against the full command line."""
process.""" if pgrep_full:
cmd = 'pgrep -f -o {}'.format(service) cmd = 'pgrep -o -f {}'.format(service)
else:
cmd = 'pgrep -o {}'.format(service)
proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip()) proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip())
return self._get_dir_mtime(sentry_unit, proc_dir) return self._get_dir_mtime(sentry_unit, proc_dir)
def service_restarted(self, sentry_unit, service, filename): def service_restarted(self, sentry_unit, service, filename,
pgrep_full=False):
"""Compare a service's start time vs a file's last modification time """Compare a service's start time vs a file's last modification time
(such as a config file for that service) to determine if the service (such as a config file for that service) to determine if the service
has been restarted.""" has been restarted."""
sleep(10) sleep(10)
if self._get_proc_start_time(sentry_unit, service) >= \ if self._get_proc_start_time(sentry_unit, service, pgrep_full) >= \
self._get_file_mtime(sentry_unit, filename): self._get_file_mtime(sentry_unit, filename):
return True return True
else: else:

View File

@@ -7,19 +7,36 @@ class OpenStackAmuletDeployment(AmuletDeployment):
"""This class inherits from AmuletDeployment and has additional support """This class inherits from AmuletDeployment and has additional support
that is specifically for use by OpenStack charms.""" that is specifically for use by OpenStack charms."""
def __init__(self, series=None, openstack=None): def __init__(self, series=None, openstack=None, source=None):
"""Initialize the deployment environment.""" """Initialize the deployment environment."""
self.openstack = None
super(OpenStackAmuletDeployment, self).__init__(series) super(OpenStackAmuletDeployment, self).__init__(series)
self.openstack = openstack
self.source = source
if openstack: def _add_services(self, this_service, other_services):
self.openstack = openstack """Add services to the deployment and set openstack-origin."""
super(OpenStackAmuletDeployment, self)._add_services(this_service,
other_services)
name = 0
services = other_services
services.append(this_service)
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph']
if self.openstack:
for svc in services:
if svc[name] not in use_source:
config = {'openstack-origin': self.openstack}
self.d.configure(svc[name], config)
if self.source:
for svc in services:
if svc[name] in use_source:
config = {'source': self.source}
self.d.configure(svc[name], config)
def _configure_services(self, configs): def _configure_services(self, configs):
"""Configure all of the services.""" """Configure all of the services."""
for service, config in configs.iteritems(): for service, config in configs.iteritems():
if service == self.this_service:
config['openstack-origin'] = self.openstack
self.d.configure(service, config) self.d.configure(service, config)
def _get_openstack_release(self): def _get_openstack_release(self):

View File

@@ -74,7 +74,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected tenant data - {}".format(ret) return "unexpected tenant data - {}".format(ret)
if not found: if not found:
return "tenant {} does not exist".format(e.name) return "tenant {} does not exist".format(e['name'])
return ret return ret
def validate_role_data(self, expected, actual): def validate_role_data(self, expected, actual):
@@ -91,7 +91,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected role data - {}".format(ret) return "unexpected role data - {}".format(ret)
if not found: if not found:
return "role {} does not exist".format(e.name) return "role {} does not exist".format(e['name'])
return ret return ret
def validate_user_data(self, expected, actual): def validate_user_data(self, expected, actual):
@@ -110,7 +110,7 @@ class OpenStackAmuletUtils(AmuletUtils):
if ret: if ret:
return "unexpected user data - {}".format(ret) return "unexpected user data - {}".format(ret)
if not found: if not found:
return "user {} does not exist".format(e.name) return "user {} does not exist".format(e['name'])
return ret return ret
def validate_flavor_data(self, expected, actual): def validate_flavor_data(self, expected, actual):
@@ -192,8 +192,8 @@ class OpenStackAmuletUtils(AmuletUtils):
count = 1 count = 1
status = instance.status status = instance.status
while status == 'BUILD' and count < 10: while status != 'ACTIVE' and count < 60:
time.sleep(5) time.sleep(3)
instance = nova.servers.get(instance.id) instance = nova.servers.get(instance.id)
status = instance.status status = instance.status
self.log.debug('instance status: {}'.format(status)) self.log.debug('instance status: {}'.format(status))