Resync, rebase

This commit is contained in:
James Page 2014-10-01 23:14:32 +01:00
commit c508203a2c
17 changed files with 400 additions and 65 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

@ -115,4 +115,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

@ -8,6 +8,7 @@ from charmhelpers.core.hookenv import (
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
ApacheSSLContext as SSLContext,
BindHostContext
)
from charmhelpers.contrib.hahelpers.cluster import (
@ -81,3 +82,15 @@ class LoggingConfigContext(OSContextGenerator):
def __call__(self):
return {'debug': config('debug'), 'verbose': config('verbose')}
class GlanceIPv6Context(BindHostContext):
def __call__(self):
ctxt = super(GlanceIPv6Context, self).__call__()
if config('prefer-ipv6'):
ctxt['registry_host'] = '[::]'
else:
ctxt['registry_host'] = '0.0.0.0'
return ctxt

View File

@ -16,7 +16,8 @@ from glance_utils import (
GLANCE_API_CONF,
GLANCE_API_PASTE_INI,
HAPROXY_CONF,
ceph_config_file)
ceph_config_file,
setup_ipv6)
from charmhelpers.core.hookenv import (
config,
@ -53,7 +54,8 @@ from charmhelpers.contrib.openstack.utils import (
configure_installation_source,
get_os_codename_package,
openstack_upgrade_available,
lsb_release, )
lsb_release,
sync_db_with_multi_ipv6_addresses)
from charmhelpers.contrib.storage.linux.ceph import ensure_ceph_keyring
from charmhelpers.payload.execd import execd_preinstall
@ -61,6 +63,8 @@ from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_netmask_for_address,
get_iface_for_address,
get_ipv6_addr,
is_ipv6
)
from charmhelpers.contrib.openstack.ip import (
canonical_url,
@ -105,8 +109,14 @@ def db_joined():
juju_log(e, level=ERROR)
raise Exception(e)
relation_set(database=config('database'), username=config('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')
relation_set(database=config('database'),
username=config('database-user'),
hostname=host)
@hooks.hook('pgsql-db-relation-joined')
@ -275,6 +285,11 @@ def keystone_changed():
@hooks.hook('config-changed')
@restart_on_change(restart_map(), stopstart=True)
def config_changed():
if config('prefer-ipv6'):
setup_ipv6()
sync_db_with_multi_ipv6_addresses(config('database'),
config('database-user'))
if openstack_upgrade_available('glance-common'):
juju_log('Upgrading OpenStack release')
do_openstack_upgrade(CONFIGS)
@ -300,6 +315,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': private_addr})
@hooks.hook('cluster-relation-changed')
@ -320,7 +339,7 @@ def upgrade_charm():
@hooks.hook('ha-relation-joined')
def ha_relation_joined():
config = get_hacluster_config()
cluster_config = get_hacluster_config()
resources = {
'res_glance_haproxy': 'lsb:haproxy'
@ -331,14 +350,22 @@ def ha_relation_joined():
}
vip_group = []
for vip in config['vip'].split():
for vip in cluster_config['vip'].split():
if is_ipv6(vip):
res_ks_vip = 'ocf:heartbeat:IPv6addr'
vip_params = 'ipv6addr'
else:
res_ks_vip = 'ocf:heartbeat:IPaddr2'
vip_params = 'ip'
iface = get_iface_for_address(vip)
if iface is not None:
vip_key = 'res_glance_{}_vip'.format(iface)
resources[vip_key] = 'ocf:heartbeat:IPaddr2'
resources[vip_key] = res_ks_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))
)
@ -356,8 +383,8 @@ def ha_relation_joined():
}
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

@ -10,7 +10,8 @@ from collections import OrderedDict
from charmhelpers.fetch import (
apt_upgrade,
apt_update,
apt_install, )
apt_install,
add_source)
from charmhelpers.core.hookenv import (
config,
@ -21,7 +22,8 @@ from charmhelpers.core.hookenv import (
from charmhelpers.core.host import (
mkdir,
service_stop,
service_start
service_start,
lsb_release
)
from charmhelpers.contrib.openstack import (
@ -80,7 +82,8 @@ CONFIG_FILES = OrderedDict([
context.PostgresqlDBContext(),
context.IdentityServiceContext(),
context.SyslogContext(),
glance_contexts.LoggingConfigContext()],
glance_contexts.LoggingConfigContext(),
glance_contexts.GlanceIPv6Context()],
'services': ['glance-registry']
}),
(GLANCE_API_CONF, {
@ -92,7 +95,8 @@ CONFIG_FILES = OrderedDict([
glance_contexts.ObjectStoreContext(),
glance_contexts.HAProxyContext(),
context.SyslogContext(),
glance_contexts.LoggingConfigContext()],
glance_contexts.LoggingConfigContext(),
glance_contexts.GlanceIPv6Context()],
'services': ['glance-api']
}),
(GLANCE_API_PASTE_INI, {
@ -236,3 +240,19 @@ def services():
for v in restart_map().values():
_services = _services + v
return list(set(_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

@ -11,7 +11,7 @@ default_store = swift
default_store = file
{% endif -%}
bind_host = 0.0.0.0
bind_host = {{ bind_host }}
{% if ext -%}
bind_port = {{ ext }}
@ -26,7 +26,7 @@ backlog = 4096
sql_idle_timeout = 3600
workers = 1
registry_host = 0.0.0.0
registry_host = {{ registry_host }}
registry_port = 9191
registry_client_protocol = http

View File

@ -3,7 +3,7 @@ verbose = {{ verbose }}
use_syslog = {{ use_syslog }}
debug = {{ debug }}
bind_host = 0.0.0.0
bind_host = {{ bind_host }}
bind_port = 9191
log_file = /var/log/glance/registry.log
backlog = 4096

View File

@ -16,7 +16,8 @@ class OpenStackAmuletDeployment(AmuletDeployment):
self.openstack = openstack
self.source = source
self.stable = stable
# Note(coreycb): this needs to be changed when new next branches come out.
# Note(coreycb): this needs to be changed when new next branches come
# out.
self.current_next = "trusty"
def _determine_branch_locations(self, other_services):
@ -51,7 +52,8 @@ class OpenStackAmuletDeployment(AmuletDeployment):
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:

View File

@ -70,3 +70,23 @@ class TestGlanceContexts(CharmTestCase):
'namespace': 'glance'})
self.assertTrue(mock_https.called)
mock_unit_get.assert_called_with('private-address')
@patch('charmhelpers.contrib.openstack.context.config')
@patch('glance_contexts.config')
def test_glance_ipv6_context_service_enabled(self, mock_config,
mock_context_config):
mock_config.return_value = True
mock_context_config.return_value = True
ctxt = contexts.GlanceIPv6Context()
self.assertEquals(ctxt(), {'bind_host': '::',
'registry_host': '[::]'})
@patch('charmhelpers.contrib.openstack.context.config')
@patch('glance_contexts.config')
def test_glance_ipv6_context_service_disabled(self, mock_config,
mock_context_config):
mock_config.return_value = False
mock_context_config.return_value = False
ctxt = contexts.GlanceIPv6Context()
self.assertEquals(ctxt(), {'bind_host': '0.0.0.0',
'registry_host': '0.0.0.0'})

View File

@ -60,7 +60,9 @@ TO_PATCH = [
'filter_installed_packages',
'get_hacluster_config',
'get_netmask_for_address',
'get_iface_for_address'
'get_iface_for_address',
'get_ipv6_addr',
'sync_db_with_multi_ipv6_addresses',
]
@ -87,7 +89,8 @@ class GlanceRelationTests(CharmTestCase):
def test_install_hook_precise_distro(self):
self.test_config.set('openstack-origin', 'distro')
self.lsb_release.return_value = {'DISTRIB_CODENAME': 'precise'}
self.lsb_release.return_value = {'DISTRIB_RELEASE': 12.04,
'DISTRIB_CODENAME': 'precise'}
self.service_stop.return_value = True
relations.install_hook()
self.configure_installation_source.assert_called_with(
@ -103,6 +106,25 @@ class GlanceRelationTests(CharmTestCase):
hostname='glance.foohost.com')
self.unit_get.assert_called_with('private-address')
@patch.object(relations, 'sync_db_with_multi_ipv6_addresses')
@patch.object(relations, 'get_ipv6_addr')
def test_db_joined_with_ipv6(self, mock_get_ipv6_addr,
mock_sync_db):
self.test_config.set('prefer-ipv6', True)
mock_get_ipv6_addr.return_value = ['2001:db8:1::1']
mock_sync_db.return_value = MagicMock()
self.is_relation_made.return_value = False
relations.db_joined()
relation_data = {
'database': 'glance',
'username': 'glance',
}
relation_data['hostname'] = '2001:db8:1::1'
self.sync_db_with_multi_ipv6_addresses.assert_called_with_once(
'glance', 'glance')
self.get_ipv6_addr.assert_called_once()
def test_postgresql_db_joined(self):
self.unit_get.return_value = 'glance.foohost.com'
self.is_relation_made.return_value = False
@ -420,6 +442,7 @@ class GlanceRelationTests(CharmTestCase):
@patch.object(relations, 'CONFIGS')
def test_cluster_changed(self, configs):
self.test_config.set('prefer-ipv6', False)
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['cluster']
configs.write = MagicMock()
@ -428,6 +451,20 @@ class GlanceRelationTests(CharmTestCase):
call('/etc/haproxy/haproxy.cfg')],
configs.write.call_args_list)
@patch.object(relations, 'relation_set')
@patch.object(relations, 'CONFIGS')
def test_cluster_changed_with_ipv6(self, configs, relation_set):
self.test_config.set('prefer-ipv6', True)
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['cluster']
configs.write = MagicMock()
self.get_ipv6_addr.return_value = '2001:db8:1::1'
self.relation_ids.return_value = ['cluster:0']
relations.cluster_changed()
self.assertEquals([call('/etc/glance/glance-api.conf'),
call('/etc/haproxy/haproxy.cfg')],
configs.write.call_args_list)
@patch.object(relations, 'CONFIGS')
def test_upgrade_charm(self, configs):
self.filter_installed_packages.return_value = ['test']
@ -461,6 +498,30 @@ class GlanceRelationTests(CharmTestCase):
call(**args),
])
def test_ha_relation_joined_with_ipv6(self):
self.test_config.set('prefer-ipv6', True)
self.get_hacluster_config.return_value = {
'ha-bindiface': 'em0',
'ha-mcastport': '8080',
'vip': '2001:db8:1::1',
}
self.get_iface_for_address.return_value = 'eth1'
self.get_netmask_for_address.return_value = '64'
relations.ha_relation_joined()
args = {
'corosync_bindiface': 'em0',
'corosync_mcastport': '8080',
'init_services': {'res_glance_haproxy': 'haproxy'},
'resources': {'res_glance_eth1_vip': 'ocf:heartbeat:IPv6addr',
'res_glance_haproxy': 'lsb:haproxy'},
'resource_params': {
'res_glance_eth1_vip': 'params ipv6addr="2001:db8:1::1"'
' cidr_netmask="64" nic="eth1"',
'res_glance_haproxy': 'op monitor interval="5s"'},
'clones': {'cl_glance_haproxy': 'res_glance_haproxy'}
}
self.relation_set.assert_called_with(**args)
def test_ha_relation_changed_not_clustered(self):
self.relation_get.return_value = False
relations.ha_relation_changed()