Resync helpers
This commit is contained in:
2
.bzrignore
Normal file
2
.bzrignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bin
|
||||||
|
.coverage
|
11
Makefile
11
Makefile
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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):
|
||||||
|
@@ -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))
|
||||||
|
@@ -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),
|
||||||
|
@@ -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:
|
||||||
|
@@ -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):
|
||||||
|
@@ -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))
|
||||||
|
Reference in New Issue
Block a user