charm-ceph-radosgw/hooks/ceph_radosgw_context.py

218 lines
7.0 KiB
Python

# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import socket
import tempfile
import shutil
from charmhelpers.contrib.openstack import context
from charmhelpers.contrib.hahelpers.cluster import (
determine_api_port,
determine_apache_port,
)
from charmhelpers.core.host import cmp_pkgrevno
from charmhelpers.core.hookenv import (
DEBUG,
WARNING,
config,
log,
related_units,
relation_get,
relation_ids,
unit_public_ip,
)
from charmhelpers.contrib.network.ip import (
format_ipv6_addr,
get_ipv6_addr,
)
from charmhelpers.contrib.storage.linux.ceph import CephConfContext
class ApacheSSLContext(context.ApacheSSLContext):
interfaces = ['https']
service_namespace = 'ceph-radosgw'
def __call__(self):
self.external_ports = [config('port')]
return super(ApacheSSLContext, self).__call__()
class HAProxyContext(context.HAProxyContext):
def __call__(self):
ctxt = super(HAProxyContext, self).__call__()
port = config('port')
# Apache ports
a_cephradosgw_api = determine_apache_port(port, singlenode_mode=True)
port_mapping = {
'cephradosgw-server': [port, a_cephradosgw_api]
}
ctxt['cephradosgw_bind_port'] = determine_api_port(
port,
singlenode_mode=True,
)
# for haproxy.conf
ctxt['service_ports'] = port_mapping
return ctxt
class IdentityServiceContext(context.IdentityServiceContext):
interfaces = ['identity-service']
def __call__(self):
ctxt = super(IdentityServiceContext, self).__call__()
if not ctxt:
return
ctxt['admin_token'] = None
for relid in relation_ids('identity-service'):
for unit in related_units(relid):
if not ctxt.get('admin_token'):
ctxt['admin_token'] = \
relation_get('admin_token', unit, relid)
if cmp_pkgrevno('radosgw', "10.2.0") >= 0:
ctxt['auth_keystone_v3_supported'] = True
if (not ctxt.get('admin_domain_id') and
float(ctxt.get('api_version', '2.0')) < 3):
ctxt.pop('admin_domain_id')
ctxt['auth_type'] = 'keystone'
ctxt['user_roles'] = config('operator-roles')
ctxt['cache_size'] = config('cache-size')
if self.context_complete(ctxt):
return ctxt
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.CephContext):
interfaces = ['ceph-radosgw']
def __call__(self):
if not relation_ids('mon'):
return {}
host = socket.gethostname()
systemd_rgw = False
mon_hosts = []
auths = []
for rid in relation_ids('mon'):
for unit in related_units(rid):
_auth = relation_get('auth', rid=rid, unit=unit)
if _auth:
auths.append(_auth)
ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
unit=unit)
unit_priv_addr = relation_get('private-address', rid=rid,
unit=unit)
ceph_addr = ceph_pub_addr or unit_priv_addr
ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
if ceph_addr:
mon_hosts.append(ceph_addr)
if relation_get('rgw.{}_key'.format(host), rid=rid, unit=unit):
systemd_rgw = True
if len(set(auths)) != 1:
e = ("Inconsistent or absent auth returned by mon units. Setting "
"auth_supported to 'none'")
log(e, level=WARNING)
auth = 'none'
else:
auth = auths[0]
# /etc/init.d/radosgw mandates that a dns name is used for this
# parameter so ensure that address is resolvable
if config('prefer-ipv6'):
ensure_host_resolvable_v6(host)
port = determine_api_port(config('port'), singlenode_mode=True)
if config('prefer-ipv6'):
port = "[::]:%s" % (port)
mon_hosts.sort()
ctxt = {
'auth_supported': auth,
'mon_hosts': ' '.join(mon_hosts),
'hostname': host,
'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0,
'systemd_rgw': systemd_rgw,
'use_syslog': str(config('use-syslog')).lower(),
'loglevel': config('loglevel'),
'port': port,
'ipv6': config('prefer-ipv6'),
# The public unit IP is only used in case the authentication is
# *Not* keystone - in which case it is used to make sure the
# storage endpoint returned by the built-in auth is the HAproxy
# (since it defaults to the port the service runs on, and that is
# not available externally). ~tribaal
'unit_public_ip': unit_public_ip(),
}
# NOTE(dosaboy): these sections must correspond to what is supported in
# the config template.
sections = ['global', 'client.radosgw.gateway']
user_provided = CephConfContext(permitted_sections=sections)()
user_provided = {k.replace('.', '_'): user_provided[k]
for k in user_provided}
ctxt.update(user_provided)
if self.context_complete(ctxt):
return ctxt
return {}