# 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, relation_ids, related_units, relation_get, ) from charmhelpers.contrib.network.ip import ( format_ipv6_addr, get_host_ip, get_ipv6_addr, ) from charmhelpers.contrib.storage.linux.ceph import CephConfContext 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) ctxt['auth_type'] = 'keystone' ctxt['user_roles'] = config('operator-roles') ctxt['cache_size'] = config('cache-size') ctxt['revocation_check_interval'] = config('revocation-check-interval') 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.OSContextGenerator): interfaces = ['ceph-radosgw'] def __call__(self): if not relation_ids('mon'): return {} 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 = 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'") 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 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(mon_hosts), 'hostname': host, 'old_auth': cmp_pkgrevno('radosgw', "0.51") < 0, 'use_syslog': str(config('use-syslog')).lower(), 'loglevel': config('loglevel'), 'port': port, 'ipv6': config('prefer-ipv6') } certs_path = '/var/lib/ceph/nss' paths = [os.path.join(certs_path, 'ca.pem'), os.path.join(certs_path, 'signing_certificate.pem')] if all([os.path.isfile(p) for p in paths]): ctxt['cms'] = True # 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 {}