393 lines
11 KiB
Python
Executable File
393 lines
11 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
#
|
|
# Copyright 2012 Canonical Ltd.
|
|
#
|
|
# Authors:
|
|
# James Page <james.page@ubuntu.com>
|
|
#
|
|
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import glob
|
|
import os
|
|
import ceph
|
|
from charmhelpers.core.hookenv import (
|
|
relation_get,
|
|
relation_ids,
|
|
related_units,
|
|
config,
|
|
unit_get,
|
|
open_port,
|
|
relation_set,
|
|
log, ERROR,
|
|
Hooks, UnregisteredHookError,
|
|
)
|
|
from charmhelpers.fetch import (
|
|
apt_update,
|
|
apt_install,
|
|
apt_purge,
|
|
add_source,
|
|
)
|
|
from charmhelpers.core.host import (
|
|
lsb_release,
|
|
restart_on_change
|
|
)
|
|
from utils import (
|
|
render_template,
|
|
get_host_ip,
|
|
enable_pocket,
|
|
is_apache_24,
|
|
CEPHRG_HA_RES,
|
|
register_configs,
|
|
)
|
|
|
|
from charmhelpers.payload.execd import execd_preinstall
|
|
from charmhelpers.core.host import cmp_pkgrevno
|
|
from socket import gethostname as get_unit_hostname
|
|
|
|
from charmhelpers.contrib.network.ip import (
|
|
get_iface_for_address,
|
|
get_netmask_for_address,
|
|
is_ipv6,
|
|
)
|
|
from charmhelpers.contrib.openstack.ip import (
|
|
resolve_address,
|
|
PUBLIC, INTERNAL, ADMIN,
|
|
)
|
|
|
|
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'
|
|
|
|
|
|
def install_ceph_optimised_packages():
|
|
"""Inktank provides patched/optimised packages for HTTP 100-continue
|
|
support that does has not yet been ported to upstream. These can
|
|
optionally be installed from ceph.com archives.
|
|
"""
|
|
prolog = "http://gitbuilder.ceph.com/"
|
|
epilog = "-x86_64-basic/ref/master"
|
|
rel = lsb_release()['DISTRIB_CODENAME']
|
|
fastcgi_source = "%slibapache-mod-fastcgi-deb-%s%s" % (prolog, rel, epilog)
|
|
apache_source = "%sapache2-deb-%s%s" % (prolog, rel, epilog)
|
|
|
|
for source in [fastcgi_source, apache_source]:
|
|
add_source(source, key='6EAEAE2203C3951A')
|
|
|
|
|
|
PACKAGES = [
|
|
'radosgw',
|
|
'ntp',
|
|
'haproxy',
|
|
]
|
|
|
|
APACHE_PACKAGES = [
|
|
'libapache2-mod-fastcgi',
|
|
'apache2',
|
|
]
|
|
|
|
|
|
def install_packages():
|
|
add_source(config('source'), config('key'))
|
|
if (config('use-ceph-optimised-packages') and
|
|
not config('use-embedded-webserver')):
|
|
install_ceph_optimised_packages()
|
|
|
|
apt_update(fatal=True)
|
|
apt_install(PACKAGES, fatal=True)
|
|
if config('use-embedded-webserver'):
|
|
apt_purge(APACHE_PACKAGES)
|
|
else:
|
|
apt_install(APACHE_PACKAGES, fatal=True)
|
|
|
|
|
|
@hooks.hook('install')
|
|
def install():
|
|
execd_preinstall()
|
|
enable_pocket('multiverse')
|
|
install_packages()
|
|
os.makedirs(NSS_DIR)
|
|
|
|
|
|
def emit_cephconf():
|
|
# Ensure ceph directory actually exists
|
|
if not os.path.exists('/etc/ceph'):
|
|
os.makedirs('/etc/ceph')
|
|
|
|
cephcontext = {
|
|
'auth_supported': get_auth() or 'none',
|
|
'mon_hosts': ' '.join(get_mon_hosts()),
|
|
'hostname': get_unit_hostname(),
|
|
'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0,
|
|
'use_syslog': str(config('use-syslog')).lower(),
|
|
'embedded_webserver': config('use-embedded-webserver'),
|
|
}
|
|
|
|
# Check to ensure that correct version of ceph is
|
|
# in use
|
|
if cmp_pkgrevno('radosgw', '0.55') >= 0:
|
|
# Add keystone configuration if found
|
|
ks_conf = get_keystone_conf()
|
|
if ks_conf:
|
|
cephcontext.update(ks_conf)
|
|
|
|
with open('/etc/ceph/ceph.conf', 'w') as cephconf:
|
|
cephconf.write(render_template('ceph.conf', cephcontext))
|
|
|
|
|
|
def emit_apacheconf():
|
|
apachecontext = {
|
|
"hostname": unit_get('private-address')
|
|
}
|
|
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():
|
|
shutil.copy('files/ports.conf', '/etc/apache2/ports.conf')
|
|
|
|
|
|
@hooks.hook('upgrade-charm',
|
|
'config-changed')
|
|
@restart_on_change({'/etc/ceph/ceph.conf': ['radosgw'],
|
|
'/etc/haproxy/haproxy.cfg': ['haproxy']})
|
|
def config_changed():
|
|
install_packages()
|
|
emit_cephconf()
|
|
CONFIGS.write_all()
|
|
if not config('use-embedded-webserver'):
|
|
emit_apacheconf()
|
|
install_www_scripts()
|
|
apache_sites()
|
|
apache_modules()
|
|
apache_ports()
|
|
apache_reload()
|
|
for r_id in relation_ids('identity-service'):
|
|
identity_joined(relid=r_id)
|
|
|
|
|
|
def get_mon_hosts():
|
|
hosts = []
|
|
for relid in relation_ids('mon'):
|
|
for unit in related_units(relid):
|
|
hosts.append(
|
|
'{}:6789'.format(get_host_ip(
|
|
relation_get('private-address',
|
|
unit, relid)))
|
|
)
|
|
|
|
hosts.sort()
|
|
return hosts
|
|
|
|
|
|
def get_auth():
|
|
return get_conf('auth')
|
|
|
|
|
|
def get_conf(name):
|
|
for relid in relation_ids('mon'):
|
|
for unit in related_units(relid):
|
|
conf = relation_get(name,
|
|
unit, relid)
|
|
if conf:
|
|
return conf
|
|
return None
|
|
|
|
|
|
def get_keystone_conf():
|
|
for relid in relation_ids('identity-service'):
|
|
for unit in related_units(relid):
|
|
ks_auth = {
|
|
'auth_type': 'keystone',
|
|
'auth_protocol':
|
|
relation_get('auth_protocol', unit, relid) or "http",
|
|
'auth_host': relation_get('auth_host', unit, relid),
|
|
'auth_port': relation_get('auth_port', unit, relid),
|
|
'admin_token': relation_get('admin_token', unit, relid),
|
|
'user_roles': config('operator-roles'),
|
|
'cache_size': config('cache-size'),
|
|
'revocation_check_interval':
|
|
config('revocation-check-interval')
|
|
}
|
|
if None not in ks_auth.itervalues():
|
|
return ks_auth
|
|
return None
|
|
|
|
|
|
@hooks.hook('mon-relation-departed',
|
|
'mon-relation-changed')
|
|
@restart_on_change({'/etc/ceph/ceph.conf': ['radosgw']})
|
|
def mon_relation():
|
|
emit_cephconf()
|
|
key = relation_get('radosgw_key')
|
|
if key:
|
|
ceph.import_radosgw_key(key)
|
|
restart() # TODO figure out a better way todo this
|
|
|
|
|
|
@hooks.hook('gateway-relation-joined')
|
|
def gateway_relation():
|
|
relation_set(hostname=unit_get('private-address'),
|
|
port=80)
|
|
|
|
|
|
def start():
|
|
subprocess.call(['service', 'radosgw', 'start'])
|
|
open_port(port=80)
|
|
|
|
|
|
def stop():
|
|
subprocess.call(['service', 'radosgw', 'stop'])
|
|
open_port(port=80)
|
|
|
|
|
|
def restart():
|
|
subprocess.call(['service', 'radosgw', 'restart'])
|
|
open_port(port=80)
|
|
|
|
|
|
# XXX Define local canonical_url until charm has been updated to use the
|
|
# standard context architecture.
|
|
def canonical_url(configs, endpoint_type=PUBLIC):
|
|
scheme = 'http'
|
|
address = resolve_address(endpoint_type)
|
|
if is_ipv6(address):
|
|
address = "[{}]".format(address)
|
|
return '%s://%s' % (scheme, address)
|
|
|
|
|
|
@hooks.hook('identity-service-relation-joined')
|
|
def identity_joined(relid=None):
|
|
if cmp_pkgrevno('radosgw', '0.55') < 0:
|
|
log('Integration with keystone requires ceph >= 0.55')
|
|
sys.exit(1)
|
|
|
|
port = 80
|
|
admin_url = '%s:%i/swift' % (canonical_url(ADMIN), port)
|
|
internal_url = '%s:%s/swift/v1' % \
|
|
(canonical_url(INTERNAL), port)
|
|
public_url = '%s:%s/swift/v1' % \
|
|
(canonical_url(PUBLIC), port)
|
|
relation_set(service='swift',
|
|
region=config('region'),
|
|
public_url=public_url, internal_url=internal_url,
|
|
admin_url=admin_url,
|
|
requested_roles=config('operator-roles'),
|
|
relation_id=relid)
|
|
|
|
|
|
@hooks.hook('identity-service-relation-changed')
|
|
@restart_on_change({'/etc/ceph/ceph.conf': ['radosgw']})
|
|
def identity_changed():
|
|
emit_cephconf()
|
|
restart()
|
|
|
|
|
|
@hooks.hook('cluster-relation-changed',
|
|
'cluster-relation-joined')
|
|
@restart_on_change({'/etc/haproxy/haproxy.cfg': ['haproxy']})
|
|
def cluster_changed():
|
|
CONFIGS.write_all()
|
|
for r_id in relation_ids('identity-service'):
|
|
identity_joined(relid=r_id)
|
|
|
|
|
|
@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)
|
|
sys.exit(1)
|
|
# Obtain resources
|
|
# SWIFT_HA_RES = 'grp_swift_vips'
|
|
resources = {
|
|
'res_cephrg_haproxy': 'lsb:haproxy'
|
|
}
|
|
resource_params = {
|
|
'res_cephrg_haproxy': 'op monitor interval="5s"'
|
|
}
|
|
|
|
vip_group = []
|
|
for vip in vip.split():
|
|
iface = get_iface_for_address(vip)
|
|
if iface is not None:
|
|
vip_key = 'res_cephrg_{}_vip'.format(iface)
|
|
resources[vip_key] = 'ocf:heartbeat:IPaddr2'
|
|
resource_params[vip_key] = (
|
|
'params ip="{vip}" cidr_netmask="{netmask}"'
|
|
' nic="{iface}"'.format(vip=vip,
|
|
iface=iface,
|
|
netmask=get_netmask_for_address(vip))
|
|
)
|
|
vip_group.append(vip_key)
|
|
|
|
if len(vip_group) >= 1:
|
|
relation_set(groups={CEPHRG_HA_RES: ' '.join(vip_group)})
|
|
|
|
init_services = {
|
|
'res_cephrg_haproxy': 'haproxy'
|
|
}
|
|
clones = {
|
|
'cl_cephrg_haproxy': 'res_cephrg_haproxy'
|
|
}
|
|
|
|
relation_set(init_services=init_services,
|
|
corosync_bindiface=corosync_bindiface,
|
|
corosync_mcastport=corosync_mcastport,
|
|
resources=resources,
|
|
resource_params=resource_params,
|
|
clones=clones)
|
|
|
|
|
|
@hooks.hook('ha-relation-changed')
|
|
def ha_relation_changed():
|
|
clustered = relation_get('clustered')
|
|
if clustered:
|
|
log('Cluster configured, notifying other services and'
|
|
'updating keystone endpoint configuration')
|
|
# Tell all related services to start using
|
|
# the VIP instead
|
|
for r_id in relation_ids('identity-service'):
|
|
identity_joined(relid=r_id)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
hooks.execute(sys.argv)
|
|
except UnregisteredHookError as e:
|
|
log('Unknown hook {} - skipping.'.format(e))
|