Rebase and resync

This commit is contained in:
James Page 2014-10-01 23:07:44 +01:00
commit d3b6038871
17 changed files with 377 additions and 72 deletions

View File

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

View File

@ -161,4 +161,15 @@ options:
192.168.0.0/24)
.
This network will be used for public endpoints.
prefer-ipv6:
type: boolean
default: False
description: |
If True enables IPv6 support. The charm will expect network interfaces
to be configured with an IPv6 address. If set to False (default) IPv4
is expected.
.
NOTE: these charms do not currently support IPv6 privacy extension. In
order for this charm to function correctly, the privacy extension must be
disabled and a non-temporary address must be configured/available on
your network interface.

View File

@ -1,11 +1,16 @@
import glob
import re
import subprocess
import sys
from functools import partial
from charmhelpers.core.hookenv import unit_get
from charmhelpers.fetch import apt_install
from charmhelpers.core.hookenv import (
ERROR, log,
WARNING,
ERROR,
log
)
try:
@ -164,13 +169,14 @@ def format_ipv6_addr(address):
if is_ipv6(address):
address = "[%s]" % address
else:
log("Not an valid ipv6 address: %s" % address,
level=ERROR)
log("Not a valid ipv6 address: %s" % address, level=WARNING)
address = None
return address
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=True, exc_list=None):
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
fatal=True, exc_list=None):
"""
Return the assigned IP address for a given interface, if any, or [].
"""
@ -210,26 +216,105 @@ def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=T
if 'addr' in entry and entry['addr'] not in exc_list:
addresses.append(entry['addr'])
if fatal and not addresses:
raise Exception("Interface '%s' doesn't have any %s addresses." % (iface, inet_type))
raise Exception("Interface '%s' doesn't have any %s addresses." %
(iface, inet_type))
return addresses
get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
def get_ipv6_addr(iface='eth0', inc_aliases=False, fatal=True, exc_list=None):
def get_iface_from_addr(addr):
"""Work out on which interface the provided address is configured."""
for iface in netifaces.interfaces():
addresses = netifaces.ifaddresses(iface)
for inet_type in addresses:
for _addr in addresses[inet_type]:
_addr = _addr['addr']
# link local
ll_key = re.compile("(.+)%.*")
raw = re.match(ll_key, _addr)
if raw:
_addr = raw.group(1)
if _addr == addr:
log("Address '%s' is configured on iface '%s'" %
(addr, iface))
return iface
msg = "Unable to infer net iface on which '%s' is configured" % (addr)
raise Exception(msg)
def sniff_iface(f):
"""If no iface provided, inject net iface inferred from unit private
address.
"""
Return the assigned IPv6 address for a given interface, if any, or [].
def iface_sniffer(*args, **kwargs):
if not kwargs.get('iface', None):
kwargs['iface'] = get_iface_from_addr(unit_get('private-address'))
return f(*args, **kwargs)
return iface_sniffer
@sniff_iface
def get_ipv6_addr(iface=None, inc_aliases=False, fatal=True, exc_list=None,
dynamic_only=True):
"""Get assigned IPv6 address for a given interface.
Returns list of addresses found. If no address found, returns empty list.
If iface is None, we infer the current primary interface by doing a reverse
lookup on the unit private-address.
We currently only support scope global IPv6 addresses i.e. non-temporary
addresses. If no global IPv6 address is found, return the first one found
in the ipv6 address list.
"""
addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
inc_aliases=inc_aliases, fatal=fatal,
exc_list=exc_list)
remotly_addressable = []
for address in addresses:
if not address.startswith('fe80'):
remotly_addressable.append(address)
if fatal and not remotly_addressable:
raise Exception("Interface '%s' doesn't have global ipv6 address." % iface)
return remotly_addressable
if addresses:
global_addrs = []
for addr in addresses:
key_scope_link_local = re.compile("^fe80::..(.+)%(.+)")
m = re.match(key_scope_link_local, addr)
if m:
eui_64_mac = m.group(1)
iface = m.group(2)
else:
global_addrs.append(addr)
if global_addrs:
# Make sure any found global addresses are not temporary
cmd = ['ip', 'addr', 'show', iface]
out = subprocess.check_output(cmd)
if dynamic_only:
key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
else:
key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
addrs = []
for line in out.split('\n'):
line = line.strip()
m = re.match(key, line)
if m and 'temporary' not in line:
# Return the first valid address we find
for addr in global_addrs:
if m.group(1) == addr:
if not dynamic_only or \
m.group(1).endswith(eui_64_mac):
addrs.append(addr)
if addrs:
return addrs
if fatal:
raise Exception("Interface '%s' doesn't have a scope global "
"non-temporary ipv6 address." % iface)
return []
def get_bridges(vnic_dir='/sys/devices/virtual/net'):

View File

@ -10,32 +10,62 @@ class OpenStackAmuletDeployment(AmuletDeployment):
that is specifically for use by OpenStack charms.
"""
def __init__(self, series=None, openstack=None, source=None):
def __init__(self, series=None, openstack=None, source=None, stable=True):
"""Initialize the deployment environment."""
super(OpenStackAmuletDeployment, self).__init__(series)
self.openstack = openstack
self.source = source
self.stable = stable
# Note(coreycb): this needs to be changed when new next branches come
# out.
self.current_next = "trusty"
def _determine_branch_locations(self, other_services):
"""Determine the branch locations for the other services.
Determine if the local branch being tested is derived from its
stable or next (dev) branch, and based on this, use the corresonding
stable or next branches for the other_services."""
base_charms = ['mysql', 'mongodb', 'rabbitmq-server']
if self.stable:
for svc in other_services:
temp = 'lp:charms/{}'
svc['location'] = temp.format(svc['name'])
else:
for svc in other_services:
if svc['name'] in base_charms:
temp = 'lp:charms/{}'
svc['location'] = temp.format(svc['name'])
else:
temp = 'lp:~openstack-charmers/charms/{}/{}/next'
svc['location'] = temp.format(self.current_next,
svc['name'])
return other_services
def _add_services(self, this_service, other_services):
"""Add services to the deployment and set openstack-origin."""
"""Add services to the deployment and set openstack-origin/source."""
other_services = self._determine_branch_locations(other_services)
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']
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
'ceph-osd', 'ceph-radosgw']
if self.openstack:
for svc in services:
if svc[name] not in use_source:
if svc['name'] not in use_source:
config = {'openstack-origin': self.openstack}
self.d.configure(svc[name], config)
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:
config = {'source': self.source}
self.d.configure(svc[name], config)
self.d.configure(svc['name'], config)
def _configure_services(self, configs):
"""Configure all of the services."""

View File

@ -187,15 +187,16 @@ class OpenStackAmuletUtils(AmuletUtils):
f = opener.open("http://download.cirros-cloud.net/version/released")
version = f.read().strip()
cirros_img = "tests/cirros-{}-x86_64-disk.img".format(version)
cirros_img = "cirros-{}-x86_64-disk.img".format(version)
local_path = os.path.join('tests', cirros_img)
if not os.path.exists(cirros_img):
if not os.path.exists(local_path):
cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",
version, cirros_img)
opener.retrieve(cirros_url, cirros_img)
opener.retrieve(cirros_url, local_path)
f.close()
with open(cirros_img) as f:
with open(local_path) as f:
image = glance.images.create(name=image_name, is_public=True,
disk_format='qcow2',
container_format='bare', data=f)

View File

@ -52,8 +52,9 @@ from charmhelpers.contrib.openstack.neutron import (
from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_ipv6_addr,
is_address_in_network,
get_netmask_for_address
get_netmask_for_address,
format_ipv6_addr,
is_address_in_network
)
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
@ -175,8 +176,10 @@ class SharedDBContext(OSContextGenerator):
for rid in relation_ids('shared-db'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
host = rdata.get('db_host')
host = format_ipv6_addr(host) or host
ctxt = {
'database_host': rdata.get('db_host'),
'database_host': host,
'database': self.database,
'database_user': self.user,
'database_password': rdata.get(password_setting),
@ -252,10 +255,15 @@ class IdentityServiceContext(OSContextGenerator):
for rid in relation_ids('identity-service'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
serv_host = rdata.get('service_host')
serv_host = format_ipv6_addr(serv_host) or serv_host
auth_host = rdata.get('auth_host')
auth_host = format_ipv6_addr(auth_host) or auth_host
ctxt = {
'service_port': rdata.get('service_port'),
'service_host': rdata.get('service_host'),
'auth_host': rdata.get('auth_host'),
'service_host': serv_host,
'auth_host': auth_host,
'auth_port': rdata.get('auth_port'),
'admin_tenant_name': rdata.get('service_tenant'),
'admin_user': rdata.get('service_username'),
@ -304,11 +312,13 @@ class AMQPContext(OSContextGenerator):
for unit in related_units(rid):
if relation_get('clustered', rid=rid, unit=unit):
ctxt['clustered'] = True
ctxt['rabbitmq_host'] = relation_get('vip', rid=rid,
unit=unit)
vip = relation_get('vip', rid=rid, unit=unit)
vip = format_ipv6_addr(vip) or vip
ctxt['rabbitmq_host'] = vip
else:
ctxt['rabbitmq_host'] = relation_get('private-address',
rid=rid, unit=unit)
host = relation_get('private-address', rid=rid, unit=unit)
host = format_ipv6_addr(host) or host
ctxt['rabbitmq_host'] = host
ctxt.update({
'rabbitmq_user': username,
'rabbitmq_password': relation_get('password', rid=rid,
@ -347,8 +357,9 @@ class AMQPContext(OSContextGenerator):
and len(related_units(rid)) > 1:
rabbitmq_hosts = []
for unit in related_units(rid):
rabbitmq_hosts.append(relation_get('private-address',
rid=rid, unit=unit))
host = relation_get('private-address', rid=rid, unit=unit)
host = format_ipv6_addr(host) or host
rabbitmq_hosts.append(host)
ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
if not context_complete(ctxt):
return {}
@ -377,6 +388,7 @@ class CephContext(OSContextGenerator):
ceph_addr = \
relation_get('ceph-public-address', rid=rid, unit=unit) or \
relation_get('private-address', rid=rid, unit=unit)
ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
mon_hosts.append(ceph_addr)
ctxt = {
@ -413,8 +425,9 @@ class HAProxyContext(OSContextGenerator):
return {}
l_unit = local_unit().replace('/', '-')
if config('prefer-ipv6'):
addr = get_ipv6_addr()
addr = get_ipv6_addr(exc_list=[config('vip')])[0]
else:
addr = unit_get('private-address')
@ -443,7 +456,7 @@ class HAProxyContext(OSContextGenerator):
# NOTE(jamespage) no split configurations found, just use
# private addresses
if len(cluster_hosts) < 1:
if not cluster_hosts:
cluster_hosts[addr] = {}
cluster_hosts[addr]['network'] = "{}/{}".format(
addr,
@ -463,6 +476,11 @@ class HAProxyContext(OSContextGenerator):
'frontends': cluster_hosts,
}
if config('haproxy-server-timeout'):
ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
if config('haproxy-client-timeout'):
ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
if config('prefer-ipv6'):
ctxt['local_host'] = 'ip6-localhost'
ctxt['haproxy_host'] = '::'
@ -870,3 +888,16 @@ class SyslogContext(OSContextGenerator):
'use_syslog': config('use-syslog')
}
return ctxt
class BindHostContext(OSContextGenerator):
def __call__(self):
if config('prefer-ipv6'):
return {
'bind_host': '::'
}
else:
return {
'bind_host': '0.0.0.0'
}

View File

@ -66,7 +66,7 @@ def resolve_address(endpoint_type=PUBLIC):
resolved_address = vip
else:
if config('prefer-ipv6'):
fallback_addr = get_ipv6_addr()
fallback_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
else:
fallback_addr = unit_get(_address_map[endpoint_type]['fallback'])
resolved_address = get_address_in_network(

View File

@ -14,8 +14,17 @@ defaults
retries 3
timeout queue 1000
timeout connect 1000
{% if haproxy_client_timeout -%}
timeout client {{ haproxy_client_timeout }}
{% else -%}
timeout client 30000
{% endif -%}
{% if haproxy_server_timeout -%}
timeout server {{ haproxy_server_timeout }}
{% else -%}
timeout server 30000
{% endif -%}
listen stats {{ stat_port }}
mode http

View File

@ -4,6 +4,7 @@
from collections import OrderedDict
import subprocess
import json
import os
import socket
import sys
@ -13,7 +14,9 @@ from charmhelpers.core.hookenv import (
log as juju_log,
charm_dir,
ERROR,
INFO
INFO,
relation_ids,
relation_set
)
from charmhelpers.contrib.storage.linux.lvm import (
@ -22,6 +25,10 @@ from charmhelpers.contrib.storage.linux.lvm import (
remove_lvm_physical_volume,
)
from charmhelpers.contrib.network.ip import (
get_ipv6_addr
)
from charmhelpers.core.host import lsb_release, mounts, umount
from charmhelpers.fetch import apt_install, apt_cache
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
@ -457,3 +464,21 @@ def get_hostname(address, fqdn=True):
return result
else:
return result.split('.')[0]
def sync_db_with_multi_ipv6_addresses(database, database_user,
relation_prefix=None):
hosts = get_ipv6_addr(dynamic_only=False)
kwargs = {'database': database,
'username': database_user,
'hostname': json.dumps(hosts)}
if relation_prefix:
keys = kwargs.keys()
for key in keys:
kwargs["%s_%s" % (relation_prefix, key)] = kwargs[key]
del kwargs[key]
for rid in relation_ids('shared-db'):
relation_set(relation_id=rid, **kwargs)

View File

@ -3,7 +3,7 @@ from charmhelpers.core.hookenv import (
relation_ids,
service_name,
related_units,
relation_get,
relation_get
)
from charmhelpers.contrib.openstack.context import (

View File

@ -20,7 +20,8 @@ from cinder_utils import (
CLUSTER_RES,
CINDER_CONF,
CINDER_API_CONF,
ceph_config_file
ceph_config_file,
setup_ipv6
)
from charmhelpers.core.hookenv import (
@ -38,11 +39,17 @@ from charmhelpers.core.hookenv import (
ERROR,
)
from charmhelpers.fetch import apt_install, apt_update
from charmhelpers.fetch import (
apt_install,
apt_update
)
from charmhelpers.core.host import lsb_release, restart_on_change
from charmhelpers.contrib.openstack.utils import (
configure_installation_source, openstack_upgrade_available)
configure_installation_source,
openstack_upgrade_available,
sync_db_with_multi_ipv6_addresses)
from charmhelpers.contrib.storage.linux.ceph import ensure_ceph_keyring
@ -55,7 +62,9 @@ from charmhelpers.payload.execd import execd_preinstall
from charmhelpers.contrib.network.ip import (
get_iface_for_address,
get_netmask_for_address,
get_address_in_network
get_address_in_network,
get_ipv6_addr,
is_ipv6
)
from charmhelpers.contrib.openstack.ip import (
canonical_url,
@ -85,6 +94,12 @@ def install():
@restart_on_change(restart_map(), stopstart=True)
def config_changed():
conf = config()
if conf['prefer-ipv6']:
setup_ipv6()
sync_db_with_multi_ipv6_addresses(config('database'),
config('database-user'))
if (service_enabled('volume') and
conf['block-device'] not in [None, 'None', 'none']):
block_devices = conf['block-device'].split()
@ -115,9 +130,15 @@ def db_joined():
log(e, level=ERROR)
raise Exception(e)
conf = config()
relation_set(database=conf['database'], username=conf['database-user'],
hostname=unit_get('private-address'))
if config('prefer-ipv6'):
sync_db_with_multi_ipv6_addresses(config('database'),
config('database-user'))
else:
host = unit_get('private-address')
conf = config()
relation_set(database=conf['database'],
username=conf['database-user'],
hostname=host)
@hooks.hook('pgsql-db-relation-joined')
@ -262,6 +283,10 @@ def cluster_joined(relation_id=None):
relation_id=relation_id,
relation_settings={'{}-address'.format(addr_type): address}
)
if config('prefer-ipv6'):
private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
relation_set(relation_id=relation_id,
relation_settings={'private-address': address})
@hooks.hook('cluster-relation-changed',
@ -273,7 +298,7 @@ def cluster_changed():
@hooks.hook('ha-relation-joined')
def ha_joined():
config = get_hacluster_config()
cluster_config = get_hacluster_config()
resources = {
'res_cinder_haproxy': 'lsb:haproxy'
@ -284,14 +309,22 @@ def ha_joined():
}
vip_group = []
for vip in config['vip'].split():
for vip in cluster_config['vip'].split():
if is_ipv6(vip):
res_cinder_vip = 'ocf:heartbeat:IPv6addr'
vip_params = 'ipv6addr'
else:
res_cinder_vip = 'ocf:heartbeat:IPaddr2'
vip_params = 'ip'
iface = get_iface_for_address(vip)
if iface is not None:
vip_key = 'res_cinder_{}_vip'.format(iface)
resources[vip_key] = 'ocf:heartbeat:IPaddr2'
resources[vip_key] = res_cinder_vip
resource_params[vip_key] = (
'params ip="{vip}" cidr_netmask="{netmask}"'
' nic="{iface}"'.format(vip=vip,
'params {ip}="{vip}" cidr_netmask="{netmask}"'
' nic="{iface}"'.format(ip=vip_params,
vip=vip,
iface=iface,
netmask=get_netmask_for_address(vip))
)
@ -307,8 +340,8 @@ def ha_joined():
'cl_cinder_haproxy': 'res_cinder_haproxy'
}
relation_set(init_services=init_services,
corosync_bindiface=config['ha-bindiface'],
corosync_mcastport=config['ha-mcastport'],
corosync_bindiface=cluster_config['ha-bindiface'],
corosync_mcastport=cluster_config['ha-mcastport'],
resources=resources,
resource_params=resource_params,
clones=clones)

View File

@ -14,7 +14,8 @@ from charmhelpers.core.hookenv import (
from charmhelpers.fetch import (
apt_upgrade,
apt_update,
apt_install
apt_install,
add_source
)
from charmhelpers.core.host import (
@ -22,7 +23,8 @@ from charmhelpers.core.host import (
umount,
service_stop,
service_start,
mkdir
mkdir,
lsb_release
)
from charmhelpers.contrib.storage.linux.ceph import (
@ -128,7 +130,8 @@ CONFIG_FILES = OrderedDict([
config_file=CINDER_CONF),
cinder_contexts.StorageBackendContext(),
cinder_contexts.LoggingConfigContext(),
context.IdentityServiceContext()],
context.IdentityServiceContext(),
context.BindHostContext()],
'services': ['cinder-api', 'cinder-volume',
'cinder-scheduler', 'haproxy']
}),
@ -425,3 +428,19 @@ def do_openstack_upgrade(configs):
if eligible_leader(CLUSTER_RES):
migrate_database()
[service_start(s) for s in services()]
def setup_ipv6():
ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
if ubuntu_rel < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04")
# NOTE(xianghui): Need to install haproxy(1.5.3) from trusty-backports
# to support ipv6 address, so check is required to make sure not
# breaking other versions, IPv6 only support for >= Trusty
if ubuntu_rel == 'trusty':
add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports'
' main')
apt_update()
apt_install('haproxy/trusty-backports', fatal=True)

View File

@ -34,6 +34,7 @@ host = {{ host }}
rbd_user = {{ rbd_user }}
{% endif -%}
osapi_volume_listen = {{ bind_host }}
{% if osapi_volume_listen_port -%}
osapi_volume_listen_port = {{ osapi_volume_listen_port }}
{% endif -%}

View File

@ -95,19 +95,25 @@ class TestCinderContext(CharmTestCase):
self.assertEquals(contexts.StorageBackendContext()(),
{'backends': 'cinder-ceph,cinder-vmware'})
@patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.is_clustered')
@patch('charmhelpers.contrib.openstack.context.determine_apache_port')
@patch('charmhelpers.contrib.openstack.context.determine_api_port')
@patch('charmhelpers.contrib.openstack.context.unit_get')
@patch('charmhelpers.contrib.openstack.context.https')
mod_ch_context = 'charmhelpers.contrib.openstack.context'
@patch('%s.ApacheSSLContext.canonical_names' % (mod_ch_context))
@patch('%s.ApacheSSLContext.configure_ca' % (mod_ch_context))
@patch('%s.config' % (mod_ch_context))
@patch('%s.is_clustered' % (mod_ch_context))
@patch('%s.determine_apache_port' % (mod_ch_context))
@patch('%s.determine_api_port' % (mod_ch_context))
@patch('%s.unit_get' % (mod_ch_context))
@patch('%s.https' % (mod_ch_context))
@patch.object(utils, 'service_enabled')
def test_apache_ssl_context_service_enabled(self, service_enabled,
mock_https, mock_unit_get,
mock_determine_api_port,
mock_determine_apache_port,
mock_is_clustered,
mock_config):
mock_hookenv,
mock_configure_ca,
mock_cfg_canonical_names):
mock_https.return_value = True
mock_unit_get.return_value = '1.2.3.4'
mock_determine_api_port.return_value = '12'
@ -122,7 +128,6 @@ class TestCinderContext(CharmTestCase):
service_enabled.return_value = False
self.assertEquals(ctxt(), {})
self.assertFalse(mock_https.called)
service_enabled.return_value = True
self.assertEquals(ctxt(), {'endpoints': [('1.2.3.4', '1.2.3.4',
34, 12)],

View File

@ -16,6 +16,7 @@ utils.restart_map.return_value = RESTART_MAP
utils.register_configs = MagicMock()
import cinder_hooks as hooks
hooks.hooks._config_save = False
hooks.hooks._config_save = False
@ -62,6 +63,8 @@ TO_PATCH = [
'eligible_leader',
'get_hacluster_config',
'execd_preinstall',
'get_ipv6_addr',
'sync_db_with_multi_ipv6_addresses'
]
@ -261,7 +264,7 @@ class TestJoinedHooks(CharmTestCase):
def setUp(self):
super(TestJoinedHooks, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get_all
self.config.side_effect = self.test_config.get
def test_db_joined(self):
'It properly requests access to a shared-db service'
@ -272,6 +275,18 @@ class TestJoinedHooks(CharmTestCase):
'hostname': 'cindernode1', 'database': 'cinder'}
self.relation_set.assert_called_with(**expected)
def test_db_joined_with_ipv6(self):
'It properly requests access to a shared-db service'
self.unit_get.return_value = 'cindernode1'
self.get_ipv6_addr.return_value = ['2001:db8:1::1']
self.sync_db_with_multi_ipv6_addresses.return_value = MagicMock()
self.is_relation_made.return_value = False
self.test_config.set('prefer-ipv6', True)
self.test_config.set('vip', 'dummy_vip')
hooks.hooks.execute(['hooks/shared-db-relation-joined'])
self.sync_db_with_multi_ipv6_addresses.assert_called_with_once(
'cinder', 'cinder')
def test_db_joined_with_postgresql(self):
self.is_relation_made.return_value = True

View File

@ -12,6 +12,7 @@ utils.register_configs = MagicMock()
utils.service_enabled = MagicMock()
import cinder_hooks as hooks
hooks.hooks._config_save = False
# Unpatch it now that its loaded.
utils.register_configs = _register_configs
@ -62,7 +63,7 @@ class TestClusterHooks(CharmTestCase):
def setUp(self):
super(TestClusterHooks, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get_all
self.config.side_effect = self.test_config.get
@patch('charmhelpers.core.host.service')
@patch('charmhelpers.core.host.file_hash')
@ -121,6 +122,8 @@ class TestClusterHooks(CharmTestCase):
'vip_iface': 'eth101',
'vip_cidr': '19',
}
self.test_config.set('prefer-ipv6', 'False')
self.get_hacluster_config.return_value = conf
self.get_iface_for_address.return_value = 'eth101'
self.get_netmask_for_address.return_value = '255.255.224.0'
@ -146,6 +149,40 @@ class TestClusterHooks(CharmTestCase):
call(**ex_args)
])
def test_ha_joined_complete_config_with_ipv6(self):
'Ensure hacluster subordinate receives all relevant config'
conf = {
'ha-bindiface': 'eth100',
'ha-mcastport': '37373',
'vip': '2001:db8:1::1',
'vip_iface': 'eth101',
'vip_cidr': '64',
}
self.test_config.set('prefer-ipv6', 'True')
self.get_hacluster_config.return_value = conf
self.get_iface_for_address.return_value = 'eth101'
self.get_netmask_for_address.return_value = 'ffff.ffff.ffff.ffff'
hooks.hooks.execute(['hooks/ha-relation-joined'])
ex_args = {
'corosync_mcastport': '37373',
'init_services': {'res_cinder_haproxy': 'haproxy'},
'resource_params': {
'res_cinder_eth101_vip':
'params ipv6addr="2001:db8:1::1" '
'cidr_netmask="ffff.ffff.ffff.ffff" '
'nic="eth101"',
'res_cinder_haproxy': 'op monitor interval="5s"'
},
'corosync_bindiface': 'eth100',
'clones': {'cl_cinder_haproxy': 'res_cinder_haproxy'},
'resources': {
'res_cinder_eth101_vip': 'ocf:heartbeat:IPv6addr',
'res_cinder_haproxy': 'lsb:haproxy'
}
}
self.relation_set.assert_called_with(**ex_args)
@patch.object(hooks, 'identity_joined')
def test_ha_changed_clustered(self, joined):
self.relation_get.return_value = True

View File

@ -64,6 +64,7 @@ class CharmTestCase(unittest.TestCase):
self.test_relation = TestRelation()
self.patch_all()
# FIXME: This is bad. These mocks will persist across all tests.
def patch(self, method):
_m = patch.object(self.obj, method)
mock = _m.start()
@ -80,7 +81,9 @@ class TestConfig(object):
def __init__(self):
self.config = get_default_config()
def get(self, attr):
def get(self, attr=None):
if not attr:
return self.config
try:
return self.config[attr]
except KeyError: