charm-ceph-radosgw/hooks/utils.py

325 lines
11 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 socket
import subprocess
from collections import OrderedDict
from copy import deepcopy
import ceph_radosgw_context
from charmhelpers.core.hookenv import (
relation_get,
relation_ids,
related_units,
application_version_set,
)
from charmhelpers.contrib.openstack import (
context,
templating,
)
from charmhelpers.contrib.openstack.utils import (
make_assess_status_func,
pause_unit,
resume_unit,
)
from charmhelpers.contrib.hahelpers.cluster import (
get_hacluster_config,
https,
)
from charmhelpers.core.host import (
cmp_pkgrevno,
lsb_release,
CompareHostReleases,
init_is_systemd,
)
from charmhelpers.fetch import (
apt_cache,
apt_install,
apt_update,
add_source,
filter_installed_packages,
get_upstream_version,
)
# The interface is said to be satisfied if anyone of the interfaces in the
# list has a complete context.
REQUIRED_INTERFACES = {
'mon': ['ceph-radosgw'],
}
CEPHRG_HA_RES = 'grp_cephrg_vips'
TEMPLATES_DIR = 'templates'
TEMPLATES = 'templates/'
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
CEPH_CONF = '/etc/ceph/ceph.conf'
VERSION_PACKAGE = 'radosgw'
UNUSED_APACHE_SITE_FILES = ["/etc/apache2/sites-available/000-default.conf"]
APACHE_PORTS_FILE = "/etc/apache2/ports.conf"
APACHE_SITE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
APACHE_SITE_24_CONF = '/etc/apache2/sites-available/' \
'openstack_https_frontend.conf'
BASE_RESOURCE_MAP = OrderedDict([
(HAPROXY_CONF, {
'contexts': [context.HAProxyContext(singlenode_mode=True),
ceph_radosgw_context.HAProxyContext()],
'services': ['haproxy'],
}),
(CEPH_CONF, {
'contexts': [ceph_radosgw_context.MonContext()],
'services': [],
}),
(APACHE_SITE_CONF, {
'contexts': [ceph_radosgw_context.ApacheSSLContext()],
'services': ['apache2'],
}),
(APACHE_SITE_24_CONF, {
'contexts': [ceph_radosgw_context.ApacheSSLContext()],
'services': ['apache2'],
}),
])
def resource_map():
"""Dynamically generate a map of resources.
These will be managed for a single hook execution.
"""
resource_map = deepcopy(BASE_RESOURCE_MAP)
if not https():
resource_map.pop(APACHE_SITE_CONF)
resource_map.pop(APACHE_SITE_24_CONF)
else:
if os.path.exists('/etc/apache2/conf-available'):
resource_map.pop(APACHE_SITE_CONF)
else:
resource_map.pop(APACHE_SITE_24_CONF)
resource_map[CEPH_CONF]['services'] = [service_name()]
return resource_map
def restart_map():
return OrderedDict([(cfg, v['services'])
for cfg, v in resource_map().items()
if v['services']])
# Hardcoded to icehouse to enable use of charmhelper templating/context tools
# Ideally these function would support non-OpenStack services
def register_configs(release='icehouse'):
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
openstack_release=release)
CONFIGS = resource_map()
pkg = 'radosgw'
if not filter_installed_packages([pkg]) and cmp_pkgrevno(pkg, '0.55') >= 0:
# Add keystone configuration if found
CONFIGS[CEPH_CONF]['contexts'].append(
ceph_radosgw_context.IdentityServiceContext()
)
for cfg, rscs in CONFIGS.items():
configs.register(cfg, rscs['contexts'])
return configs
def services():
"""Returns a list of services associate with this charm."""
_services = []
for v in resource_map().values():
_services.extend(v.get('services', []))
return list(set(_services))
def get_optional_interfaces():
"""Return the optional interfaces that should be checked if the relavent
relations have appeared.
:returns: {general_interface: [specific_int1, specific_int2, ...], ...}
"""
optional_interfaces = {}
if relation_ids('ha'):
optional_interfaces['ha'] = ['cluster']
if (cmp_pkgrevno('radosgw', '0.55') >= 0 and
relation_ids('identity-service')):
optional_interfaces['identity'] = ['identity-service']
return optional_interfaces
def check_optional_relations(configs):
"""Check that if we have a relation_id for high availability that we can
get the hacluster config. If we can't then we are blocked. This function
is called from assess_status/set_os_workload_status as the charm_func and
needs to return either 'unknown', '' if there is no problem or the status,
message if there is a problem.
:param configs: an OSConfigRender() instance.
:return 2-tuple: (string, string) = (status, message)
"""
if relation_ids('ha'):
try:
get_hacluster_config()
except:
return ('blocked',
'hacluster missing configuration: '
'vip, vip_iface, vip_cidr')
# return 'unknown' as the lowest priority to not clobber an existing
# status.
return 'unknown', ''
def setup_ipv6():
ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(ubuntu_rel) < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04")
from apt import apt_pkg
apt_pkg.init()
# 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.
vc = apt_pkg.version_compare(get_pkg_version('haproxy'), '1.5.3')
if ubuntu_rel == 'trusty' and vc == -1:
add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
'main')
apt_update(fatal=True)
apt_install('haproxy/trusty-backports', fatal=True)
def assess_status(configs):
"""Assess status of current unit.
Decides what the state of the unit should be based on the current
configuration.
SIDE EFFECT: calls set_os_workload_status(...) which sets the workload
status of the unit.
Also calls status_set(...) directly if paused state isn't complete.
@param configs: a templating.OSConfigRenderer() object
@returns None - this function is executed for its side-effect
"""
assess_status_func(configs)()
application_version_set(get_upstream_version(VERSION_PACKAGE))
def assess_status_func(configs):
"""Helper function to create the function that will assess_status() for
the unit.
Uses charmhelpers.contrib.openstack.utils.make_assess_status_func() to
create the appropriate status function and then returns it.
Used directly by assess_status() and also for pausing and resuming
the unit.
NOTE: REQUIRED_INTERFACES is augmented with the optional interfaces
depending on the current config before being passed to the
make_assess_status_func() function.
NOTE(ajkavanagh) ports are not checked due to race hazards with services
that don't behave sychronously w.r.t their service scripts. e.g.
apache2.
@param configs: a templating.OSConfigRenderer() object
@return f() -> None : a function that assesses the unit's workload status
"""
required_interfaces = REQUIRED_INTERFACES.copy()
required_interfaces.update(get_optional_interfaces())
return make_assess_status_func(
configs, required_interfaces,
charm_func=check_optional_relations,
services=services(), ports=None)
def pause_unit_helper(configs):
"""Helper function to pause a unit, and then call assess_status(...) in
effect, so that the status is correctly updated.
Uses charmhelpers.contrib.openstack.utils.pause_unit() to do the work.
@param configs: a templating.OSConfigRenderer() object
@returns None - this function is executed for its side-effect
"""
_pause_resume_helper(pause_unit, configs)
def resume_unit_helper(configs):
"""Helper function to resume a unit, and then call assess_status(...) in
effect, so that the status is correctly updated.
Uses charmhelpers.contrib.openstack.utils.resume_unit() to do the work.
@param configs: a templating.OSConfigRenderer() object
@returns None - this function is executed for its side-effect
"""
_pause_resume_helper(resume_unit, configs)
def _pause_resume_helper(f, configs):
"""Helper function that uses the make_assess_status_func(...) from
charmhelpers.contrib.openstack.utils to create an assess_status(...)
function that can be used with the pause/resume of the unit
@param f: the function to be used with the assess_status(...) function
@returns None - this function is executed for its side-effect
"""
# TODO(ajkavanagh) - ports= has been left off because of the race hazard
# that exists due to service_start()
f(assess_status_func(configs),
services=services(),
ports=None)
def get_pkg_version(name):
from apt import apt_pkg
pkg = apt_cache()[name]
version = apt_pkg.upstream_version(pkg.current_ver.ver_str)
return version
def disable_unused_apache_sites():
"""Ensure that unused apache configurations are disabled to prevent them
from conflicting with the charm-provided version.
"""
for apache_site_file in UNUSED_APACHE_SITE_FILES:
apache_site = apache_site_file.split('/')[-1].split('.')[0]
if os.path.exists(apache_site_file):
try:
# Try it cleanly
subprocess.check_call(['a2dissite', apache_site])
except subprocess.CalledProcessError:
# Remove the file
os.remove(apache_site_file)
with open(APACHE_PORTS_FILE, 'w') as ports:
ports.write("")
def systemd_based_radosgw():
"""Determine if install should use systemd based radosgw instances"""
host = socket.gethostname()
for rid in relation_ids('mon'):
for unit in related_units(rid):
if relation_get('rgw.{}_key'.format(host), rid=rid, unit=unit):
return True
return False
def request_per_unit_key():
"""Determine if a per-unit cephx key should be requested"""
return (cmp_pkgrevno('radosgw', '12.2.0') >= 0 and init_is_systemd())
def service_name():
"""Determine the name of the RADOS Gateway service"""
if systemd_based_radosgw():
return 'ceph-radosgw@rgw.{}'.format(socket.gethostname())
else:
return 'radosgw'