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:
parent
ea4b4200f1
commit
379f5d78a5
12
config.yaml
12
config.yaml
@ -153,3 +153,15 @@ options:
|
||||
description: |
|
||||
Connect timeout configuration in ms for haproxy, used in HA
|
||||
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.
|
||||
|
@ -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.hahelpers.cluster import (
|
||||
determine_api_port,
|
||||
@ -5,17 +13,69 @@ from charmhelpers.contrib.hahelpers.cluster import (
|
||||
)
|
||||
from charmhelpers.core.host import cmp_pkgrevno
|
||||
from charmhelpers.core.hookenv import (
|
||||
DEBUG,
|
||||
WARNING,
|
||||
config,
|
||||
log,
|
||||
relation_ids,
|
||||
related_units,
|
||||
relation_get,
|
||||
unit_get,
|
||||
status_set,
|
||||
)
|
||||
import os
|
||||
import socket
|
||||
import dns.resolver
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
format_ipv6_addr,
|
||||
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):
|
||||
@ -66,24 +126,60 @@ class IdentityServiceContext(context.IdentityServiceContext):
|
||||
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):
|
||||
interfaces = ['ceph-radosgw']
|
||||
|
||||
def __call__(self):
|
||||
if not relation_ids('mon'):
|
||||
return {}
|
||||
hosts = []
|
||||
mon_hosts = []
|
||||
auths = []
|
||||
for relid in relation_ids('mon'):
|
||||
for unit in related_units(relid):
|
||||
ceph_public_addr = relation_get('ceph-public-address', unit,
|
||||
relid)
|
||||
if ceph_public_addr:
|
||||
host_ip = self.get_host_ip(ceph_public_addr)
|
||||
hosts.append('{}:6789'.format(host_ip))
|
||||
host_ip = format_ipv6_addr(ceph_public_addr) or \
|
||||
get_host_ip(ceph_public_addr)
|
||||
mon_hosts.append('{}:6789'.format(host_ip))
|
||||
_auth = relation_get('auth', unit, relid)
|
||||
if _auth:
|
||||
auths.append(_auth)
|
||||
|
||||
if len(set(auths)) != 1:
|
||||
e = ("Inconsistent or absent auth returned by mon units. Setting "
|
||||
"auth_supported to 'none'")
|
||||
@ -91,17 +187,28 @@ class MonContext(context.OSContextGenerator):
|
||||
auth = 'none'
|
||||
else:
|
||||
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 = {
|
||||
'auth_supported': auth,
|
||||
'mon_hosts': ' '.join(hosts),
|
||||
'hostname': socket.gethostname(),
|
||||
'mon_hosts': ' '.join(mon_hosts),
|
||||
'hostname': host,
|
||||
'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0,
|
||||
'use_syslog': str(config('use-syslog')).lower(),
|
||||
'embedded_webserver': config('use-embedded-webserver'),
|
||||
'loglevel': config('loglevel'),
|
||||
'port': determine_apache_port(config('port'),
|
||||
singlenode_mode=True)
|
||||
'port': port,
|
||||
'ipv6': config('prefer-ipv6')
|
||||
}
|
||||
|
||||
certs_path = '/var/lib/ceph/nss'
|
||||
@ -121,17 +228,3 @@ class MonContext(context.OSContextGenerator):
|
||||
return ctxt
|
||||
|
||||
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
|
||||
|
156
hooks/hooks.py
156
hooks/hooks.py
@ -1,17 +1,16 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
#
|
||||
# Copyright 2012 Canonical Ltd.
|
||||
# Copyright 2016 Canonical Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# James Page <james.page@ubuntu.com>
|
||||
# Edward Hope-Morley <edward.hope-morley@canonical.com>
|
||||
#
|
||||
|
||||
import shutil
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
|
||||
import ceph
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
@ -39,27 +38,17 @@ from charmhelpers.core.host import (
|
||||
lsb_release,
|
||||
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.core.host import (
|
||||
cmp_pkgrevno,
|
||||
mkdir,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
format_ipv6_addr,
|
||||
get_ipv6_addr,
|
||||
get_iface_for_address,
|
||||
get_netmask_for_address,
|
||||
is_ipv6,
|
||||
)
|
||||
from charmhelpers.contrib.openstack.ip import (
|
||||
canonical_url,
|
||||
@ -72,18 +61,17 @@ from charmhelpers.contrib.storage.linux.ceph import (
|
||||
send_request_if_needed,
|
||||
is_request_complete,
|
||||
)
|
||||
|
||||
APACHE_PORTS_CONF = '/etc/apache2/ports.conf'
|
||||
from utils import (
|
||||
enable_pocket,
|
||||
CEPHRG_HA_RES,
|
||||
register_configs,
|
||||
REQUIRED_INTERFACES,
|
||||
check_optional_relations,
|
||||
setup_ipv6,
|
||||
)
|
||||
|
||||
hooks = Hooks()
|
||||
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'
|
||||
|
||||
|
||||
@ -145,43 +133,6 @@ def install():
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
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()):
|
||||
log("Missing relation settings (%s) - skipping cert setup" %
|
||||
(', '.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']})
|
||||
def config_changed():
|
||||
install_packages()
|
||||
CONFIGS.write_all()
|
||||
if not config('use-embedded-webserver'):
|
||||
status_set('maintenance', 'configuring apache')
|
||||
emit_apacheconf()
|
||||
install_www_scripts()
|
||||
apache_sites()
|
||||
apache_modules()
|
||||
apache_ports()
|
||||
apache_reload()
|
||||
|
||||
if config('prefer-ipv6'):
|
||||
status_set('maintenance', 'configuring ipv6')
|
||||
setup_ipv6()
|
||||
|
||||
for r_id in relation_ids('identity-service'):
|
||||
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',
|
||||
'mon-relation-changed')
|
||||
@ -373,8 +337,18 @@ def identity_changed(relid=None):
|
||||
restart()
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-changed',
|
||||
'cluster-relation-joined')
|
||||
@hooks.hook('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']})
|
||||
def cluster_changed():
|
||||
CONFIGS.write_all()
|
||||
@ -384,17 +358,12 @@ def cluster_changed():
|
||||
|
||||
@hooks.hook('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')
|
||||
if not vip:
|
||||
log('Unable to configure hacluster as vip not provided',
|
||||
level=ERROR)
|
||||
log('Unable to configure hacluster as vip not provided', level=ERROR)
|
||||
sys.exit(1)
|
||||
|
||||
# Obtain resources
|
||||
# SWIFT_HA_RES = 'grp_swift_vips'
|
||||
resources = {
|
||||
'res_cephrg_haproxy': 'lsb:haproxy'
|
||||
}
|
||||
@ -404,15 +373,25 @@ def ha_relation_joined():
|
||||
|
||||
vip_group = []
|
||||
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)
|
||||
netmask = get_netmask_for_address(vip)
|
||||
|
||||
if iface is not None:
|
||||
vip_key = 'res_cephrg_{}_vip'.format(iface)
|
||||
resources[vip_key] = 'ocf:heartbeat:IPaddr2'
|
||||
resources[vip_key] = res_rgw_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))
|
||||
netmask=netmask)
|
||||
)
|
||||
vip_group.append(vip_key)
|
||||
|
||||
@ -426,6 +405,11 @@ def ha_relation_joined():
|
||||
'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,
|
||||
corosync_bindiface=corosync_bindiface,
|
||||
corosync_mcastport=corosync_mcastport,
|
||||
|
@ -1,27 +1,45 @@
|
||||
#
|
||||
# Copyright 2012 Canonical Ltd.
|
||||
# Copyright 2016 Canonical Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# James Page <james.page@ubuntu.com>
|
||||
# Paul Collins <paul.collins@canonical.com>
|
||||
# Edward Hope-Morley <edward.hope-morley@canonical.com>
|
||||
#
|
||||
|
||||
import socket
|
||||
import re
|
||||
import os
|
||||
import dns.resolver
|
||||
import re
|
||||
import jinja2
|
||||
|
||||
from copy import deepcopy
|
||||
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
|
||||
|
||||
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
|
||||
# list has a complete context.
|
||||
REQUIRED_INTERFACES = {
|
||||
@ -32,6 +50,9 @@ TEMPLATES_DIR = 'templates'
|
||||
TEMPLATES = 'templates/'
|
||||
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
|
||||
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([
|
||||
(HAPROXY_CONF, {
|
||||
@ -39,6 +60,18 @@ BASE_RESOURCE_MAP = OrderedDict([
|
||||
ceph_radosgw_context.HAProxyContext()],
|
||||
'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, {
|
||||
'contexts': [ceph_radosgw_context.MonContext()],
|
||||
'services': ['radosgw'],
|
||||
@ -51,6 +84,11 @@ def resource_map():
|
||||
Dynamically generate a map of resources that will be managed for a single
|
||||
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)
|
||||
return resource_map
|
||||
|
||||
@ -92,28 +130,6 @@ def enable_pocket(pocket):
|
||||
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):
|
||||
required_interfaces = {}
|
||||
if relation_ids('ha'):
|
||||
@ -132,3 +148,18 @@ def check_optional_relations(configs):
|
||||
return status_get()
|
||||
else:
|
||||
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)
|
||||
|
@ -11,6 +11,9 @@ log to syslog = {{ use_syslog }}
|
||||
err to syslog = {{ use_syslog }}
|
||||
clog to syslog = {{ use_syslog }}
|
||||
debug rgw = {{ loglevel }}/5
|
||||
{% if ipv6 -%}
|
||||
ms bind ipv6 = true
|
||||
{% endif %}
|
||||
|
||||
[client.radosgw.gateway]
|
||||
host = {{ hostname }}
|
||||
|
25
templates/rgw.conf
Normal file
25
templates/rgw.conf
Normal 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¶ms=$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>
|
@ -13,6 +13,7 @@ TO_PATCH = [
|
||||
'related_units',
|
||||
'cmp_pkgrevno',
|
||||
'socket',
|
||||
'is_apache_24',
|
||||
]
|
||||
|
||||
|
||||
@ -147,8 +148,9 @@ class MonContextTest(CharmTestCase):
|
||||
super(MonContextTest, self).setUp(context, TO_PATCH)
|
||||
self.config.side_effect = self.test_config.get
|
||||
|
||||
def test_ctxt(self):
|
||||
self.socket.gethostname.return_value = '10.0.0.10'
|
||||
@patch.object(context, 'ensure_host_resolvable_v6')
|
||||
def test_ctxt(self, mock_ensure_rsv_v6):
|
||||
self.socket.gethostname.return_value = 'testhost'
|
||||
mon_ctxt = context.MonContext()
|
||||
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
|
||||
|
||||
@ -157,6 +159,7 @@ class MonContextTest(CharmTestCase):
|
||||
return addresses.pop()
|
||||
elif attr == 'auth':
|
||||
return 'cephx'
|
||||
|
||||
self.relation_get.side_effect = _relation_get
|
||||
self.relation_ids.return_value = ['mon:6']
|
||||
self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2']
|
||||
@ -164,17 +167,26 @@ class MonContextTest(CharmTestCase):
|
||||
'auth_supported': 'cephx',
|
||||
'embedded_webserver': False,
|
||||
'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',
|
||||
'old_auth': False,
|
||||
'use_syslog': 'false',
|
||||
'loglevel': 1,
|
||||
'port': 70
|
||||
'port': 70,
|
||||
'ipv6': False
|
||||
}
|
||||
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):
|
||||
self.socket.gethostname.return_value = '10.0.0.10'
|
||||
self.socket.gethostname.return_value = 'testhost'
|
||||
mon_ctxt = context.MonContext()
|
||||
self.relation_get.return_value = None
|
||||
self.relation_ids.return_value = ['mon:6']
|
||||
@ -182,7 +194,7 @@ class MonContextTest(CharmTestCase):
|
||||
self.assertEqual({}, mon_ctxt())
|
||||
|
||||
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()
|
||||
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
|
||||
auths = ['cephx', 'cephy', 'cephz']
|
||||
@ -199,17 +211,18 @@ class MonContextTest(CharmTestCase):
|
||||
'auth_supported': 'none',
|
||||
'embedded_webserver': False,
|
||||
'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',
|
||||
'old_auth': False,
|
||||
'use_syslog': 'false',
|
||||
'loglevel': 1,
|
||||
'port': 70
|
||||
'port': 70,
|
||||
'ipv6': False
|
||||
}
|
||||
self.assertEqual(expect, mon_ctxt())
|
||||
|
||||
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()
|
||||
addresses = ['10.5.4.1', '10.5.4.2', '10.5.4.3']
|
||||
auths = ['cephx', 'cephx', 'cephx']
|
||||
@ -226,11 +239,19 @@ class MonContextTest(CharmTestCase):
|
||||
'auth_supported': 'cephx',
|
||||
'embedded_webserver': False,
|
||||
'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',
|
||||
'old_auth': False,
|
||||
'use_syslog': 'false',
|
||||
'loglevel': 1,
|
||||
'port': 70
|
||||
'port': 70,
|
||||
'ipv6': False
|
||||
}
|
||||
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
|
||||
|
@ -6,7 +6,6 @@ from mock import (
|
||||
|
||||
from test_utils import (
|
||||
CharmTestCase,
|
||||
patch_open
|
||||
)
|
||||
from charmhelpers.contrib.openstack.ip import PUBLIC
|
||||
|
||||
@ -34,8 +33,6 @@ TO_PATCH = [
|
||||
'enable_pocket',
|
||||
'get_iface_for_address',
|
||||
'get_netmask_for_address',
|
||||
'glob',
|
||||
'is_apache_24',
|
||||
'log',
|
||||
'lsb_release',
|
||||
'open_port',
|
||||
@ -44,8 +41,6 @@ TO_PATCH = [
|
||||
'relation_set',
|
||||
'relation_get',
|
||||
'related_units',
|
||||
'render_template',
|
||||
'shutil',
|
||||
'status_set',
|
||||
'subprocess',
|
||||
'sys',
|
||||
@ -62,11 +57,6 @@ class CephRadosGWTests(CharmTestCase):
|
||||
self.test_config.set('key', 'secretkey')
|
||||
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):
|
||||
self.lsb_release.return_value = {'DISTRIB_CODENAME': 'vivid'}
|
||||
fastcgi_source = (
|
||||
@ -122,69 +112,12 @@ class CephRadosGWTests(CharmTestCase):
|
||||
self.enable_pocket.assert_called_with('multiverse')
|
||||
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)
|
||||
def test_config_changed(self):
|
||||
_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()
|
||||
self.assertTrue(_install_packages.called)
|
||||
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',
|
||||
lambda *args, **kwargs: True)
|
||||
|
Loading…
Reference in New Issue
Block a user