Add Ipv6 support

Adds support for configuring the Rados Gateway to use IPv6
addresses and networks. This can be enabled by setting
prefer-ipv6=True.

Change-Id: I801fab14accd8c3498ea5468d135f34f159717cb
Closes-Bug: 1513524
This commit is contained in:
Edward Hope-Morley 2016-03-02 12:23:53 +00:00
parent ea4b4200f1
commit 379f5d78a5
8 changed files with 324 additions and 222 deletions

View File

@ -153,3 +153,15 @@ options:
description: | description: |
Connect timeout configuration in ms for haproxy, used in HA Connect timeout configuration in ms for haproxy, used in HA
configurations. If not provided, default value of 5000ms is used. configurations. If not provided, default value of 5000ms is used.
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,3 +1,11 @@
import os
import re
import socket
import tempfile
import glob
import shutil
import subprocess
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_api_port, determine_api_port,
@ -5,17 +13,69 @@ from charmhelpers.contrib.hahelpers.cluster import (
) )
from charmhelpers.core.host import cmp_pkgrevno from charmhelpers.core.host import cmp_pkgrevno
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
DEBUG,
WARNING, WARNING,
config, config,
log, log,
relation_ids, relation_ids,
related_units, related_units,
relation_get, relation_get,
unit_get, status_set,
) )
import os from charmhelpers.contrib.network.ip import (
import socket format_ipv6_addr,
import dns.resolver get_host_ip,
get_ipv6_addr,
)
def is_apache_24():
if os.path.exists('/etc/apache2/conf-available'):
return True
else:
return False
class ApacheContext(context.OSContextGenerator):
interfaces = ['http']
service_namespace = 'ceph-radosgw'
def __call__(self):
ctxt = {}
if config('use-embedded-webserver'):
log("Skipping ApacheContext since we are using the embedded "
"webserver")
return {}
status_set('maintenance', 'configuring apache')
src = 'files/www/*'
dst = '/var/www/'
log("Installing www scripts", level=DEBUG)
try:
for x in glob.glob(src):
shutil.copy(x, dst)
except IOError as e:
log("Error copying files from '%s' to '%s': %s" % (src, dst, e),
level=WARNING)
try:
subprocess.check_call(['a2enmod', 'fastcgi'])
subprocess.check_call(['a2enmod', 'rewrite'])
except subprocess.CalledProcessError as e:
log("Error enabling apache modules - %s" % e, level=WARNING)
try:
if is_apache_24():
subprocess.check_call(['a2dissite', '000-default'])
else:
subprocess.check_call(['a2dissite', 'default'])
except subprocess.CalledProcessError as e:
log("Error disabling apache sites - %s" % e, level=WARNING)
ctxt['hostname'] = socket.gethostname()
ctxt['port'] = determine_api_port(config('port'), singlenode_mode=True)
return ctxt
class HAProxyContext(context.HAProxyContext): class HAProxyContext(context.HAProxyContext):
@ -66,24 +126,60 @@ class IdentityServiceContext(context.IdentityServiceContext):
return {} return {}
def ensure_host_resolvable_v6(hostname):
"""Ensure that we can resolve our hostname to an IPv6 address by adding it
to /etc/hosts if it is not already resolvable.
"""
try:
socket.getaddrinfo(hostname, None, socket.AF_INET6)
except socket.gaierror:
log("Host '%s' is not ipv6 resolvable - adding to /etc/hosts" %
hostname, level=DEBUG)
else:
log("Host '%s' appears to be ipv6 resolvable" % (hostname),
level=DEBUG)
return
# This must be the backend address used by haproxy
host_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
dtmp = tempfile.mkdtemp()
try:
tmp_hosts = os.path.join(dtmp, 'hosts')
shutil.copy('/etc/hosts', tmp_hosts)
with open(tmp_hosts, 'a+') as fd:
lines = fd.readlines()
for line in lines:
key = "^%s\s+" % (host_addr)
if re.search(key, line):
break
else:
fd.write("%s\t%s\n" % (host_addr, hostname))
os.rename(tmp_hosts, '/etc/hosts')
finally:
shutil.rmtree(dtmp)
class MonContext(context.OSContextGenerator): class MonContext(context.OSContextGenerator):
interfaces = ['ceph-radosgw'] interfaces = ['ceph-radosgw']
def __call__(self): def __call__(self):
if not relation_ids('mon'): if not relation_ids('mon'):
return {} return {}
hosts = [] mon_hosts = []
auths = [] auths = []
for relid in relation_ids('mon'): for relid in relation_ids('mon'):
for unit in related_units(relid): for unit in related_units(relid):
ceph_public_addr = relation_get('ceph-public-address', unit, ceph_public_addr = relation_get('ceph-public-address', unit,
relid) relid)
if ceph_public_addr: if ceph_public_addr:
host_ip = self.get_host_ip(ceph_public_addr) host_ip = format_ipv6_addr(ceph_public_addr) or \
hosts.append('{}:6789'.format(host_ip)) get_host_ip(ceph_public_addr)
mon_hosts.append('{}:6789'.format(host_ip))
_auth = relation_get('auth', unit, relid) _auth = relation_get('auth', unit, relid)
if _auth: if _auth:
auths.append(_auth) auths.append(_auth)
if len(set(auths)) != 1: if len(set(auths)) != 1:
e = ("Inconsistent or absent auth returned by mon units. Setting " e = ("Inconsistent or absent auth returned by mon units. Setting "
"auth_supported to 'none'") "auth_supported to 'none'")
@ -91,17 +187,28 @@ class MonContext(context.OSContextGenerator):
auth = 'none' auth = 'none'
else: else:
auth = auths[0] auth = auths[0]
hosts.sort()
# /etc/init.d/radosgw mandates that a dns name is used for this
# parameter so ensure that address is resolvable
host = socket.gethostname()
if config('prefer-ipv6'):
ensure_host_resolvable_v6(host)
port = determine_apache_port(config('port'), singlenode_mode=True)
if config('prefer-ipv6'):
port = "[::]:%s" % (port)
mon_hosts.sort()
ctxt = { ctxt = {
'auth_supported': auth, 'auth_supported': auth,
'mon_hosts': ' '.join(hosts), 'mon_hosts': ' '.join(mon_hosts),
'hostname': socket.gethostname(), 'hostname': host,
'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0, 'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0,
'use_syslog': str(config('use-syslog')).lower(), 'use_syslog': str(config('use-syslog')).lower(),
'embedded_webserver': config('use-embedded-webserver'), 'embedded_webserver': config('use-embedded-webserver'),
'loglevel': config('loglevel'), 'loglevel': config('loglevel'),
'port': determine_apache_port(config('port'), 'port': port,
singlenode_mode=True) 'ipv6': config('prefer-ipv6')
} }
certs_path = '/var/lib/ceph/nss' certs_path = '/var/lib/ceph/nss'
@ -121,17 +228,3 @@ class MonContext(context.OSContextGenerator):
return ctxt return ctxt
return {} return {}
def get_host_ip(self, hostname=None):
try:
if not hostname:
hostname = unit_get('private-address')
# Test to see if already an IPv4 address
socket.inet_aton(hostname)
return hostname
except socket.error:
# This may throw an NXDOMAIN exception; in which case
# things are badly broken so just let it kill the hook
answers = dns.resolver.query(hostname, 'A')
if answers:
return answers[0].address

View File

@ -1,17 +1,16 @@
#!/usr/bin/python #!/usr/bin/python
# #
# Copyright 2012 Canonical Ltd. # Copyright 2016 Canonical Ltd.
# #
# Authors: # Authors:
# James Page <james.page@ubuntu.com> # James Page <james.page@ubuntu.com>
# Edward Hope-Morley <edward.hope-morley@canonical.com>
# #
import shutil import os
import subprocess import subprocess
import sys import sys
import glob
import os
import ceph import ceph
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
@ -39,27 +38,17 @@ from charmhelpers.core.host import (
lsb_release, lsb_release,
restart_on_change, restart_on_change,
) )
from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port,
)
from utils import (
render_template,
enable_pocket,
is_apache_24,
CEPHRG_HA_RES,
register_configs,
REQUIRED_INTERFACES,
check_optional_relations,
)
from charmhelpers.payload.execd import execd_preinstall from charmhelpers.payload.execd import execd_preinstall
from charmhelpers.core.host import ( from charmhelpers.core.host import (
cmp_pkgrevno, cmp_pkgrevno,
mkdir, mkdir,
) )
from charmhelpers.contrib.network.ip import ( from charmhelpers.contrib.network.ip import (
format_ipv6_addr,
get_ipv6_addr,
get_iface_for_address, get_iface_for_address,
get_netmask_for_address, get_netmask_for_address,
is_ipv6,
) )
from charmhelpers.contrib.openstack.ip import ( from charmhelpers.contrib.openstack.ip import (
canonical_url, canonical_url,
@ -72,18 +61,17 @@ from charmhelpers.contrib.storage.linux.ceph import (
send_request_if_needed, send_request_if_needed,
is_request_complete, is_request_complete,
) )
from utils import (
APACHE_PORTS_CONF = '/etc/apache2/ports.conf' enable_pocket,
CEPHRG_HA_RES,
register_configs,
REQUIRED_INTERFACES,
check_optional_relations,
setup_ipv6,
)
hooks = Hooks() hooks = Hooks()
CONFIGS = register_configs() CONFIGS = register_configs()
def install_www_scripts():
for x in glob.glob('files/www/*'):
shutil.copy(x, '/var/www/')
NSS_DIR = '/var/lib/ceph/nss' NSS_DIR = '/var/lib/ceph/nss'
@ -145,43 +133,6 @@ def install():
os.makedirs('/etc/ceph') os.makedirs('/etc/ceph')
def emit_apacheconf():
apachecontext = {
"hostname": unit_get('private-address'),
"port": determine_apache_port(config('port'), singlenode_mode=True)
}
site_conf = '/etc/apache2/sites-available/rgw'
if is_apache_24():
site_conf = '/etc/apache2/sites-available/rgw.conf'
with open(site_conf, 'w') as apacheconf:
apacheconf.write(render_template('rgw', apachecontext))
def apache_sites():
if is_apache_24():
subprocess.check_call(['a2dissite', '000-default'])
else:
subprocess.check_call(['a2dissite', 'default'])
subprocess.check_call(['a2ensite', 'rgw'])
def apache_modules():
subprocess.check_call(['a2enmod', 'fastcgi'])
subprocess.check_call(['a2enmod', 'rewrite'])
def apache_reload():
subprocess.call(['service', 'apache2', 'reload'])
def apache_ports():
portscontext = {
"port": determine_apache_port(config('port'), singlenode_mode=True)
}
with open(APACHE_PORTS_CONF, 'w') as portsconf:
portsconf.write(render_template('ports.conf', portscontext))
def setup_keystone_certs(unit=None, rid=None): def setup_keystone_certs(unit=None, rid=None):
""" """
Get CA and signing certs from Keystone used to decrypt revoked token list. Get CA and signing certs from Keystone used to decrypt revoked token list.
@ -213,6 +164,9 @@ def setup_keystone_certs(unit=None, rid=None):
for key in required_keys: for key in required_keys:
settings[key] = rdata.get(key) settings[key] = rdata.get(key)
if is_ipv6(settings.get('auth_host')):
settings['auth_host'] = format_ipv6_addr(settings.get('auth_host'))
if not all(settings.values()): if not all(settings.values()):
log("Missing relation settings (%s) - skipping cert setup" % log("Missing relation settings (%s) - skipping cert setup" %
(', '.join([k for k in settings.keys() if not settings[k]])), (', '.join([k for k in settings.keys() if not settings[k]])),
@ -288,19 +242,29 @@ def setup_keystone_certs(unit=None, rid=None):
'/etc/haproxy/haproxy.cfg': ['haproxy']}) '/etc/haproxy/haproxy.cfg': ['haproxy']})
def config_changed(): def config_changed():
install_packages() install_packages()
CONFIGS.write_all()
if not config('use-embedded-webserver'): if config('prefer-ipv6'):
status_set('maintenance', 'configuring apache') status_set('maintenance', 'configuring ipv6')
emit_apacheconf() setup_ipv6()
install_www_scripts()
apache_sites()
apache_modules()
apache_ports()
apache_reload()
for r_id in relation_ids('identity-service'): for r_id in relation_ids('identity-service'):
identity_changed(relid=r_id) identity_changed(relid=r_id)
for r_id in relation_ids('cluster'):
cluster_joined(rid=r_id)
CONFIGS.write_all()
if not config('use-embedded-webserver'):
try:
subprocess.check_call(['a2ensite', 'rgw'])
except subprocess.CalledProcessError as e:
log("Error enabling apache module 'rgw' - %s" % e, level=WARNING)
# Ensure started but do a soft reload
subprocess.call(['service', 'apache2', 'start'])
subprocess.call(['service', 'apache2', 'reload'])
@hooks.hook('mon-relation-departed', @hooks.hook('mon-relation-departed',
'mon-relation-changed') 'mon-relation-changed')
@ -373,8 +337,18 @@ def identity_changed(relid=None):
restart() restart()
@hooks.hook('cluster-relation-changed', @hooks.hook('cluster-relation-joined')
'cluster-relation-joined') @restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']})
def cluster_joined(rid=None):
settings = {}
if config('prefer-ipv6'):
private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
settings['private-address'] = private_addr
relation_set(relation_id=rid, **settings)
@hooks.hook('cluster-relation-changed')
@restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']}) @restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']})
def cluster_changed(): def cluster_changed():
CONFIGS.write_all() CONFIGS.write_all()
@ -384,17 +358,12 @@ def cluster_changed():
@hooks.hook('ha-relation-joined') @hooks.hook('ha-relation-joined')
def ha_relation_joined(): def ha_relation_joined():
# Obtain the config values necessary for the cluster config. These
# include multicast port and interface to bind to.
corosync_bindiface = config('ha-bindiface')
corosync_mcastport = config('ha-mcastport')
vip = config('vip') vip = config('vip')
if not vip: if not vip:
log('Unable to configure hacluster as vip not provided', log('Unable to configure hacluster as vip not provided', level=ERROR)
level=ERROR)
sys.exit(1) sys.exit(1)
# Obtain resources # Obtain resources
# SWIFT_HA_RES = 'grp_swift_vips'
resources = { resources = {
'res_cephrg_haproxy': 'lsb:haproxy' 'res_cephrg_haproxy': 'lsb:haproxy'
} }
@ -404,15 +373,25 @@ def ha_relation_joined():
vip_group = [] vip_group = []
for vip in vip.split(): for vip in vip.split():
if is_ipv6(vip):
res_rgw_vip = 'ocf:heartbeat:IPv6addr'
vip_params = 'ipv6addr'
else:
res_rgw_vip = 'ocf:heartbeat:IPaddr2'
vip_params = 'ip'
iface = get_iface_for_address(vip) iface = get_iface_for_address(vip)
netmask = get_netmask_for_address(vip)
if iface is not None: if iface is not None:
vip_key = 'res_cephrg_{}_vip'.format(iface) vip_key = 'res_cephrg_{}_vip'.format(iface)
resources[vip_key] = 'ocf:heartbeat:IPaddr2' resources[vip_key] = res_rgw_vip
resource_params[vip_key] = ( resource_params[vip_key] = (
'params ip="{vip}" cidr_netmask="{netmask}"' 'params {ip}="{vip}" cidr_netmask="{netmask}"'
' nic="{iface}"'.format(vip=vip, ' nic="{iface}"'.format(ip=vip_params,
vip=vip,
iface=iface, iface=iface,
netmask=get_netmask_for_address(vip)) netmask=netmask)
) )
vip_group.append(vip_key) vip_group.append(vip_key)
@ -426,6 +405,11 @@ def ha_relation_joined():
'cl_cephrg_haproxy': 'res_cephrg_haproxy' 'cl_cephrg_haproxy': 'res_cephrg_haproxy'
} }
# Obtain the config values necessary for the cluster config. These
# include multicast port and interface to bind to.
corosync_bindiface = config('ha-bindiface')
corosync_mcastport = config('ha-mcastport')
relation_set(init_services=init_services, relation_set(init_services=init_services,
corosync_bindiface=corosync_bindiface, corosync_bindiface=corosync_bindiface,
corosync_mcastport=corosync_mcastport, corosync_mcastport=corosync_mcastport,

View File

@ -1,27 +1,45 @@
# #
# Copyright 2012 Canonical Ltd. # Copyright 2016 Canonical Ltd.
# #
# Authors: # Authors:
# James Page <james.page@ubuntu.com> # James Page <james.page@ubuntu.com>
# Paul Collins <paul.collins@canonical.com> # Paul Collins <paul.collins@canonical.com>
# Edward Hope-Morley <edward.hope-morley@canonical.com>
# #
import socket
import re
import os import os
import dns.resolver import re
import jinja2 import jinja2
from copy import deepcopy from copy import deepcopy
from collections import OrderedDict from collections import OrderedDict
from charmhelpers.core.hookenv import unit_get, relation_ids, status_get
from charmhelpers.contrib.openstack import context, templating
from charmhelpers.contrib.openstack.utils import set_os_workload_status
from charmhelpers.contrib.hahelpers.cluster import get_hacluster_config
from charmhelpers.core.host import cmp_pkgrevno
from charmhelpers.fetch import filter_installed_packages
import ceph_radosgw_context import ceph_radosgw_context
from charmhelpers.core.hookenv import (
relation_ids,
status_get,
)
from charmhelpers.contrib.openstack import (
context,
templating,
)
from charmhelpers.contrib.openstack.utils import (
os_release,
set_os_workload_status,
)
from charmhelpers.contrib.hahelpers.cluster import get_hacluster_config
from charmhelpers.core.host import (
cmp_pkgrevno,
lsb_release,
)
from charmhelpers.fetch import (
apt_install,
apt_update,
add_source,
filter_installed_packages,
)
# The interface is said to be satisfied if anyone of the interfaces in the # The interface is said to be satisfied if anyone of the interfaces in the
# list has a complete context. # list has a complete context.
REQUIRED_INTERFACES = { REQUIRED_INTERFACES = {
@ -32,6 +50,9 @@ TEMPLATES_DIR = 'templates'
TEMPLATES = 'templates/' TEMPLATES = 'templates/'
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
CEPH_CONF = '/etc/ceph/ceph.conf' CEPH_CONF = '/etc/ceph/ceph.conf'
APACHE_CONF = '/etc/apache2/sites-available/rgw'
APACHE_24_CONF = '/etc/apache2/sites-available/rgw.conf'
APACHE_PORTS_CONF = '/etc/apache2/ports.conf'
BASE_RESOURCE_MAP = OrderedDict([ BASE_RESOURCE_MAP = OrderedDict([
(HAPROXY_CONF, { (HAPROXY_CONF, {
@ -39,6 +60,18 @@ BASE_RESOURCE_MAP = OrderedDict([
ceph_radosgw_context.HAProxyContext()], ceph_radosgw_context.HAProxyContext()],
'services': ['haproxy'], 'services': ['haproxy'],
}), }),
(APACHE_CONF, {
'contexts': [ceph_radosgw_context.ApacheContext()],
'services': ['apache2'],
}),
(APACHE_24_CONF, {
'contexts': [ceph_radosgw_context.ApacheContext()],
'services': ['apache2'],
}),
(APACHE_PORTS_CONF, {
'contexts': [ceph_radosgw_context.ApacheContext()],
'services': ['apache2'],
}),
(CEPH_CONF, { (CEPH_CONF, {
'contexts': [ceph_radosgw_context.MonContext()], 'contexts': [ceph_radosgw_context.MonContext()],
'services': ['radosgw'], 'services': ['radosgw'],
@ -51,6 +84,11 @@ def resource_map():
Dynamically generate a map of resources that will be managed for a single Dynamically generate a map of resources that will be managed for a single
hook execution. hook execution.
''' '''
if os.path.exists('/etc/apache2/conf-available'):
BASE_RESOURCE_MAP.pop(APACHE_CONF)
else:
BASE_RESOURCE_MAP.pop(APACHE_24_CONF)
resource_map = deepcopy(BASE_RESOURCE_MAP) resource_map = deepcopy(BASE_RESOURCE_MAP)
return resource_map return resource_map
@ -92,28 +130,6 @@ def enable_pocket(pocket):
sources.write(line) sources.write(line)
def get_host_ip(hostname=None):
try:
if not hostname:
hostname = unit_get('private-address')
# Test to see if already an IPv4 address
socket.inet_aton(hostname)
return hostname
except socket.error:
# This may throw an NXDOMAIN exception; in which case
# things are badly broken so just let it kill the hook
answers = dns.resolver.query(hostname, 'A')
if answers:
return answers[0].address
def is_apache_24():
if os.path.exists('/etc/apache2/conf-available'):
return True
else:
return False
def check_optional_relations(configs): def check_optional_relations(configs):
required_interfaces = {} required_interfaces = {}
if relation_ids('ha'): if relation_ids('ha'):
@ -132,3 +148,18 @@ def check_optional_relations(configs):
return status_get() return status_get()
else: else:
return 'unknown', 'No optional relations' return 'unknown', 'No optional relations'
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")
# Need haproxy >= 1.5.3 for ipv6 so for Trusty if we are <= Kilo we need to
# use trusty-backports otherwise we can use the UCA.
if ubuntu_rel == 'trusty' and os_release('ceph-common') < 'liberty':
add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
'main')
apt_update(fatal=True)
apt_install('haproxy/trusty-backports', fatal=True)

View File

@ -11,6 +11,9 @@ log to syslog = {{ use_syslog }}
err to syslog = {{ use_syslog }} err to syslog = {{ use_syslog }}
clog to syslog = {{ use_syslog }} clog to syslog = {{ use_syslog }}
debug rgw = {{ loglevel }}/5 debug rgw = {{ loglevel }}/5
{% if ipv6 -%}
ms bind ipv6 = true
{% endif %}
[client.radosgw.gateway] [client.radosgw.gateway]
host = {{ hostname }} host = {{ hostname }}

25
templates/rgw.conf Normal file
View File

@ -0,0 +1,25 @@
<IfModule mod_fastcgi.c>
FastCgiExternalServer /var/www/s3gw.fcgi -socket /tmp/radosgw.sock
</IfModule>
<VirtualHost *:{{ port }}>
ServerName {{ hostname }}
ServerAdmin ceph@ubuntu.com
DocumentRoot /var/www
RewriteEngine On
RewriteRule ^/([a-zA-Z0-9-_.]*)([/]?.*) /s3gw.fcgi?page=$1&params=$2&%{QUERY_STRING} [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
<IfModule mod_fastcgi.c>
<Directory /var/www>
Options +ExecCGI
AllowOverride All
SetHandler fastcgi-script
Order allow,deny
Allow from all
AuthBasicAuthoritative Off
</Directory>
</IfModule>
AllowEncodedSlashes On
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
ServerSignature Off
</VirtualHost>

View File

@ -13,6 +13,7 @@ TO_PATCH = [
'related_units', 'related_units',
'cmp_pkgrevno', 'cmp_pkgrevno',
'socket', 'socket',
'is_apache_24',
] ]
@ -147,8 +148,9 @@ class MonContextTest(CharmTestCase):
super(MonContextTest, self).setUp(context, TO_PATCH) super(MonContextTest, self).setUp(context, TO_PATCH)
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
def test_ctxt(self): @patch.object(context, 'ensure_host_resolvable_v6')
self.socket.gethostname.return_value = '10.0.0.10' def test_ctxt(self, mock_ensure_rsv_v6):
self.socket.gethostname.return_value = 'testhost'
mon_ctxt = context.MonContext() mon_ctxt = context.MonContext()
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
@ -157,6 +159,7 @@ class MonContextTest(CharmTestCase):
return addresses.pop() return addresses.pop()
elif attr == 'auth': elif attr == 'auth':
return 'cephx' return 'cephx'
self.relation_get.side_effect = _relation_get self.relation_get.side_effect = _relation_get
self.relation_ids.return_value = ['mon:6'] self.relation_ids.return_value = ['mon:6']
self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2'] self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2']
@ -164,17 +167,26 @@ class MonContextTest(CharmTestCase):
'auth_supported': 'cephx', 'auth_supported': 'cephx',
'embedded_webserver': False, 'embedded_webserver': False,
'disable_100_continue': True, 'disable_100_continue': True,
'hostname': '10.0.0.10', 'hostname': 'testhost',
'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789', 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
'old_auth': False, 'old_auth': False,
'use_syslog': 'false', 'use_syslog': 'false',
'loglevel': 1, 'loglevel': 1,
'port': 70 'port': 70,
'ipv6': False
} }
self.assertEqual(expect, mon_ctxt()) self.assertEqual(expect, mon_ctxt())
self.assertFalse(mock_ensure_rsv_v6.called)
self.test_config.set('prefer-ipv6', True)
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
expect['ipv6'] = True
expect['port'] = "[::]:%s" % (70)
self.assertEqual(expect, mon_ctxt())
self.assertTrue(mock_ensure_rsv_v6.called)
def test_ctxt_missing_data(self): def test_ctxt_missing_data(self):
self.socket.gethostname.return_value = '10.0.0.10' self.socket.gethostname.return_value = 'testhost'
mon_ctxt = context.MonContext() mon_ctxt = context.MonContext()
self.relation_get.return_value = None self.relation_get.return_value = None
self.relation_ids.return_value = ['mon:6'] self.relation_ids.return_value = ['mon:6']
@ -182,7 +194,7 @@ class MonContextTest(CharmTestCase):
self.assertEqual({}, mon_ctxt()) self.assertEqual({}, mon_ctxt())
def test_ctxt_inconsistent_auths(self): def test_ctxt_inconsistent_auths(self):
self.socket.gethostname.return_value = '10.0.0.10' self.socket.gethostname.return_value = 'testhost'
mon_ctxt = context.MonContext() mon_ctxt = context.MonContext()
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
auths = ['cephx', 'cephy', 'cephz'] auths = ['cephx', 'cephy', 'cephz']
@ -199,17 +211,18 @@ class MonContextTest(CharmTestCase):
'auth_supported': 'none', 'auth_supported': 'none',
'embedded_webserver': False, 'embedded_webserver': False,
'disable_100_continue': True, 'disable_100_continue': True,
'hostname': '10.0.0.10', 'hostname': 'testhost',
'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789', 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
'old_auth': False, 'old_auth': False,
'use_syslog': 'false', 'use_syslog': 'false',
'loglevel': 1, 'loglevel': 1,
'port': 70 'port': 70,
'ipv6': False
} }
self.assertEqual(expect, mon_ctxt()) self.assertEqual(expect, mon_ctxt())
def test_ctxt_consistent_auths(self): def test_ctxt_consistent_auths(self):
self.socket.gethostname.return_value = '10.0.0.10' self.socket.gethostname.return_value = 'testhost'
mon_ctxt = context.MonContext() mon_ctxt = context.MonContext()
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3'] addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
auths = ['cephx', 'cephx', 'cephx'] auths = ['cephx', 'cephx', 'cephx']
@ -226,11 +239,19 @@ class MonContextTest(CharmTestCase):
'auth_supported': 'cephx', 'auth_supported': 'cephx',
'embedded_webserver': False, 'embedded_webserver': False,
'disable_100_continue': True, 'disable_100_continue': True,
'hostname': '10.0.0.10', 'hostname': 'testhost',
'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789', 'mon_hosts': '10.5.4.1:6789 10.5.4.2:6789 10.5.4.3:6789',
'old_auth': False, 'old_auth': False,
'use_syslog': 'false', 'use_syslog': 'false',
'loglevel': 1, 'loglevel': 1,
'port': 70 'port': 70,
'ipv6': False
} }
self.assertEqual(expect, mon_ctxt()) self.assertEqual(expect, mon_ctxt())
class ApacheContextTest(CharmTestCase):
def setUp(self):
super(ApacheContextTest, self).setUp(context, TO_PATCH)
self.config.side_effect = self.test_config.get

View File

@ -6,7 +6,6 @@ from mock import (
from test_utils import ( from test_utils import (
CharmTestCase, CharmTestCase,
patch_open
) )
from charmhelpers.contrib.openstack.ip import PUBLIC from charmhelpers.contrib.openstack.ip import PUBLIC
@ -34,8 +33,6 @@ TO_PATCH = [
'enable_pocket', 'enable_pocket',
'get_iface_for_address', 'get_iface_for_address',
'get_netmask_for_address', 'get_netmask_for_address',
'glob',
'is_apache_24',
'log', 'log',
'lsb_release', 'lsb_release',
'open_port', 'open_port',
@ -44,8 +41,6 @@ TO_PATCH = [
'relation_set', 'relation_set',
'relation_get', 'relation_get',
'related_units', 'related_units',
'render_template',
'shutil',
'status_set', 'status_set',
'subprocess', 'subprocess',
'sys', 'sys',
@ -62,11 +57,6 @@ class CephRadosGWTests(CharmTestCase):
self.test_config.set('key', 'secretkey') self.test_config.set('key', 'secretkey')
self.test_config.set('use-syslog', False) self.test_config.set('use-syslog', False)
def test_install_www_scripts(self):
self.glob.glob.return_value = ['files/www/bob']
ceph_hooks.install_www_scripts()
self.shutil.copy.assert_called_with('files/www/bob', '/var/www/')
def test_install_ceph_optimised_packages(self): def test_install_ceph_optimised_packages(self):
self.lsb_release.return_value = {'DISTRIB_CODENAME': 'vivid'} self.lsb_release.return_value = {'DISTRIB_CODENAME': 'vivid'}
fastcgi_source = ( fastcgi_source = (
@ -122,69 +112,12 @@ class CephRadosGWTests(CharmTestCase):
self.enable_pocket.assert_called_with('multiverse') self.enable_pocket.assert_called_with('multiverse')
self.os.makedirs.called_with('/var/lib/ceph/nss') self.os.makedirs.called_with('/var/lib/ceph/nss')
def test_emit_apacheconf(self):
self.is_apache_24.return_value = True
self.unit_get.return_value = '10.0.0.1'
apachecontext = {
"hostname": '10.0.0.1',
"port": 70,
}
vhost_file = '/etc/apache2/sites-available/rgw.conf'
with patch_open() as (_open, _file):
ceph_hooks.emit_apacheconf()
_open.assert_called_with(vhost_file, 'w')
self.render_template.assert_called_with('rgw', apachecontext)
def test_apache_sites24(self):
self.is_apache_24.return_value = True
ceph_hooks.apache_sites()
calls = [
call(['a2dissite', '000-default']),
call(['a2ensite', 'rgw']),
]
self.subprocess.check_call.assert_has_calls(calls)
def test_apache_sites22(self):
self.is_apache_24.return_value = False
ceph_hooks.apache_sites()
calls = [
call(['a2dissite', 'default']),
call(['a2ensite', 'rgw']),
]
self.subprocess.check_call.assert_has_calls(calls)
def test_apache_modules(self):
ceph_hooks.apache_modules()
calls = [
call(['a2enmod', 'fastcgi']),
call(['a2enmod', 'rewrite']),
]
self.subprocess.check_call.assert_has_calls(calls)
def test_apache_reload(self):
ceph_hooks.apache_reload()
calls = [
call(['service', 'apache2', 'reload']),
]
self.subprocess.call.assert_has_calls(calls)
@patch.object(ceph_hooks, 'apache_ports', lambda *args: True)
@patch.object(ceph_hooks, 'mkdir', lambda *args: None) @patch.object(ceph_hooks, 'mkdir', lambda *args: None)
def test_config_changed(self): def test_config_changed(self):
_install_packages = self.patch('install_packages') _install_packages = self.patch('install_packages')
_emit_apacheconf = self.patch('emit_apacheconf')
_install_www_scripts = self.patch('install_www_scripts')
_apache_sites = self.patch('apache_sites')
_apache_modules = self.patch('apache_modules')
_apache_reload = self.patch('apache_reload')
ceph_hooks.config_changed() ceph_hooks.config_changed()
self.assertTrue(_install_packages.called) self.assertTrue(_install_packages.called)
self.CONFIGS.write_all.assert_called_with() self.CONFIGS.write_all.assert_called_with()
self.assertTrue(_emit_apacheconf.called)
self.assertTrue(_install_www_scripts.called)
self.assertTrue(_apache_sites.called)
self.assertTrue(_apache_modules.called)
self.assertTrue(_apache_reload.called)
@patch.object(ceph_hooks, 'is_request_complete', @patch.object(ceph_hooks, 'is_request_complete',
lambda *args, **kwargs: True) lambda *args, **kwargs: True)