charm-ceph-radosgw/hooks/hooks.py
Rodrigo Barbieri fb2f757494 Add config option for keystone admin roles
RADOS Gateway supports setting keystone operator and admin
roles. RADOS Gateway requires admin roles for keystone users
to change their user quota. Regular operator/member roles
are not allowed to do so.

The lack of this config option prevents swift users with admin
roles from being able to set their quotas. Therefore, a config
option 'admin-roles' is now added to the charm to map to
'rgw keystone accepted admin roles' RADOS Gateway config.

Please note that this is only effective from Luminous
Ceph Release.

Change-Id: Ic0b9aa39eef9fbc6c43eb4e66ab72d90787c2017
Closes-Bug: #1831577
2019-07-01 17:37:19 -03:00

636 lines
20 KiB
Python
Executable File

#!/usr/bin/env python3
#
# 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 subprocess
import sys
import socket
import uuid
sys.path.append('lib')
import ceph_rgw as ceph
import ceph.utils as ceph_utils
import multisite
from charmhelpers.core.hookenv import (
relation_get,
relation_ids,
related_units,
config,
open_port,
relation_set,
log,
DEBUG,
Hooks, UnregisteredHookError,
status_set,
is_leader,
leader_set,
leader_get,
)
from charmhelpers.fetch import (
apt_update,
apt_install,
apt_purge,
add_source,
filter_installed_packages,
filter_missing_packages,
)
from charmhelpers.payload.execd import execd_preinstall
from charmhelpers.core.host import (
cmp_pkgrevno,
is_container,
service_reload,
service_restart,
service_stop,
service,
)
from charmhelpers.contrib.network.ip import (
get_relation_ip,
)
from charmhelpers.contrib.openstack.context import ADDRESS_TYPES
from charmhelpers.contrib.openstack.ip import (
canonical_url,
PUBLIC, INTERNAL, ADMIN,
)
from charmhelpers.contrib.storage.linux.ceph import (
send_request_if_needed,
is_request_complete,
)
from charmhelpers.contrib.openstack.utils import (
is_unit_paused_set,
pausable_restart_on_change as restart_on_change,
series_upgrade_prepare,
series_upgrade_complete,
)
from charmhelpers.contrib.openstack.ha.utils import (
generate_ha_relation_data,
)
from utils import (
register_configs,
setup_ipv6,
services,
assess_status,
disable_unused_apache_sites,
pause_unit_helper,
resume_unit_helper,
restart_map,
service_name,
systemd_based_radosgw,
request_per_unit_key,
ready_for_service,
restart_nonce_changed,
multisite_deployment,
)
from charmhelpers.contrib.charmsupport import nrpe
from charmhelpers.contrib.hardening.harden import harden
from charmhelpers.contrib.openstack.cert_utils import (
get_certificate_request,
process_certificates,
)
hooks = Hooks()
CONFIGS = register_configs()
PACKAGES = [
'haproxy',
'ntp',
'radosgw',
'apache2'
]
APACHE_PACKAGES = [
'libapache2-mod-fastcgi',
]
MULTISITE_SYSTEM_USER = 'multisite-sync'
def upgrade_available():
"""Check for upgrade for ceph
:returns: whether an upgrade is available
:rtype: boolean
"""
c = config()
old_version = ceph_utils.resolve_ceph_version(c.previous('source') or
'distro')
new_version = ceph_utils.resolve_ceph_version(c.get('source'))
if (old_version in ceph_utils.UPGRADE_PATHS and
new_version == ceph_utils.UPGRADE_PATHS[old_version]):
return True
return False
def install_packages():
c = config()
if c.changed('source') or c.changed('key'):
add_source(c.get('source'), c.get('key'))
apt_update(fatal=True)
if is_container():
PACKAGES.remove('ntp')
# NOTE: just use full package list if we're in an upgrade
# config-changed execution
pkgs = (
PACKAGES if upgrade_available() else
filter_installed_packages(PACKAGES)
)
if pkgs:
status_set('maintenance', 'Installing radosgw packages')
apt_install(pkgs, fatal=True)
pkgs = filter_missing_packages(APACHE_PACKAGES)
if pkgs:
apt_purge(pkgs)
disable_unused_apache_sites()
@hooks.hook('install.real')
@harden()
def install():
status_set('maintenance', 'Executing pre-install')
execd_preinstall()
install_packages()
if not os.path.exists('/etc/ceph'):
os.makedirs('/etc/ceph')
@hooks.hook('config-changed')
@harden()
def config_changed():
@restart_on_change(restart_map())
def _config_changed():
# if we are paused, delay doing any config changed hooks.
# It is forced on the resume.
if is_unit_paused_set():
log("Unit is pause or upgrading. Skipping config_changed", "WARN")
return
install_packages()
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)
# NOTE(jamespage): Re-exec mon relation for any changes to
# enable ceph pool permissions restrictions
for r_id in relation_ids('mon'):
for unit in related_units(r_id):
mon_relation(r_id, unit)
# Re-trigger hacluster relations to switch to ifaceless
# vip configuration
for r_id in relation_ids('ha'):
ha_relation_joined(r_id)
# Refire certificates relations for VIP changes
for r_id in relation_ids('certificates'):
certs_joined(r_id)
process_multisite_relations()
CONFIGS.write_all()
configure_https()
update_nrpe_config()
open_port(port=config('port'))
_config_changed()
@hooks.hook('mon-relation-departed',
'mon-relation-changed')
def mon_relation(rid=None, unit=None):
@restart_on_change(restart_map())
def _mon_relation():
key_name = 'rgw.{}'.format(socket.gethostname())
if request_per_unit_key():
relation_set(relation_id=rid,
key_name=key_name)
# NOTE: prefer zone name if in use over pool-prefix.
rq = ceph.get_create_rgw_pools_rq(
prefix=config('zone') or config('pool-prefix'))
if is_request_complete(rq, relation='mon'):
log('Broker request complete', level=DEBUG)
CONFIGS.write_all()
# New style per unit keys
key = relation_get(attribute='{}_key'.format(key_name),
rid=rid, unit=unit)
if not key:
# Fallback to old style global key
key = relation_get(attribute='radosgw_key',
rid=rid, unit=unit)
key_name = None
if key:
new_keyring = ceph.import_radosgw_key(key,
name=key_name)
# NOTE(jamespage):
# Deal with switch from radosgw init script to
# systemd named units for radosgw instances by
# stopping and disabling the radosgw unit
if systemd_based_radosgw():
service_stop('radosgw')
service('disable', 'radosgw')
# Update the nrpe config. If we wait for the below
# to be called elsewhere, there exists a period
# where nagios will report the radosgw service as
# down, and also not be monitoring the per
# host services.
update_nrpe_config(checks_to_remove=['radosgw'])
service('enable', service_name())
# NOTE(jamespage):
# Multi-site deployments need to defer restart as the
# zone is not created until the master relation is
# joined; restarting here will cause a restart burst
# in systemd and stop the process restarting once
# zone configuration is complete.
if (not is_unit_paused_set() and
new_keyring and
not multisite_deployment()):
service_restart(service_name())
process_multisite_relations()
else:
send_request_if_needed(rq, relation='mon')
_mon_relation()
@hooks.hook('gateway-relation-joined')
def gateway_relation():
relation_set(hostname=get_relation_ip('gateway-relation'),
port=config('port'))
@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 = config('port')
admin_url = '%s:%i/swift' % (canonical_url(CONFIGS, ADMIN), port)
internal_url = '%s:%s/swift/v1' % \
(canonical_url(CONFIGS, INTERNAL), port)
public_url = '%s:%s/swift/v1' % \
(canonical_url(CONFIGS, PUBLIC), port)
roles = [x for x in [config('operator-roles'), config('admin-roles')] if x]
requested_roles = ''
if roles:
requested_roles = ','.join(roles) if len(roles) > 1 else roles[0]
relation_set(service='swift',
region=config('region'),
public_url=public_url, internal_url=internal_url,
admin_url=admin_url,
requested_roles=requested_roles,
relation_id=relid)
@hooks.hook('identity-service-relation-changed')
def identity_changed(relid=None):
@restart_on_change(restart_map())
def _identity_changed():
identity_joined(relid)
CONFIGS.write_all()
configure_https()
_identity_changed()
@hooks.hook('cluster-relation-joined')
def cluster_joined(rid=None):
@restart_on_change(restart_map())
def _cluster_joined():
settings = {}
for addr_type in ADDRESS_TYPES:
address = get_relation_ip(
addr_type,
cidr_network=config('os-{}-network'.format(addr_type)))
if address:
settings['{}-address'.format(addr_type)] = address
settings['private-address'] = get_relation_ip('cluster')
relation_set(relation_id=rid, relation_settings=settings)
_cluster_joined()
@hooks.hook('cluster-relation-changed')
def cluster_changed():
@restart_on_change(restart_map())
def _cluster_changed():
CONFIGS.write_all()
for r_id in relation_ids('identity-service'):
identity_joined(relid=r_id)
for r_id in relation_ids('certificates'):
for unit in related_units(r_id):
certs_changed(r_id, unit)
_cluster_changed()
@hooks.hook('ha-relation-joined')
def ha_relation_joined(relation_id=None):
settings = generate_ha_relation_data('cephrg')
relation_set(relation_id=relation_id, **settings)
@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)
@hooks.hook('nrpe-external-master-relation-joined',
'nrpe-external-master-relation-changed')
def update_nrpe_config(checks_to_remove=None):
"""
Update the checks for the nagios plugin.
:param checks_to_remove: list of short names of nrpe checks to
remove. For example, pass ['radosgw'] to remove the check for
the default systemd radosgw service, to make way for per host
services.
:type checks_to_remove: list
"""
# python-dbus is used by check_upstart_job
apt_install('python-dbus')
hostname = nrpe.get_nagios_hostname()
current_unit = nrpe.get_nagios_unit_name()
nrpe_setup = nrpe.NRPE(hostname=hostname)
nrpe.copy_nrpe_checks()
if checks_to_remove is not None:
log("Removing the following nrpe checks: {}".format(checks_to_remove),
level=DEBUG)
for svc in checks_to_remove:
nrpe_setup.remove_check(shortname=svc)
nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)
nrpe.add_haproxy_checks(nrpe_setup, current_unit)
nrpe_setup.write()
def configure_https():
'''Enables SSL API Apache config if appropriate and kicks
identity-service and image-service with any required
updates
'''
CONFIGS.write_all()
if 'https' in CONFIGS.complete_contexts():
cmd = ['a2ensite', 'openstack_https_frontend']
subprocess.check_call(cmd)
else:
cmd = ['a2dissite', 'openstack_https_frontend']
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
# The site is not yet enabled or
# https is not configured
pass
# TODO: improve this by checking if local CN certs are available
# first then checking reload status (see LP #1433114).
if not is_unit_paused_set():
service_reload('apache2', restart_on_failure=True)
@hooks.hook('update-status')
@harden()
def update_status():
log('Updating status.')
@hooks.hook('pre-series-upgrade')
def pre_series_upgrade():
log("Running prepare series upgrade hook", "INFO")
series_upgrade_prepare(
pause_unit_helper, CONFIGS)
@hooks.hook('post-series-upgrade')
def post_series_upgrade():
log("Running complete series upgrade hook", "INFO")
series_upgrade_complete(
resume_unit_helper, CONFIGS)
@hooks.hook('certificates-relation-joined')
def certs_joined(relation_id=None):
relation_set(
relation_id=relation_id,
relation_settings=get_certificate_request())
@hooks.hook('certificates-relation-changed')
def certs_changed(relation_id=None, unit=None):
@restart_on_change(restart_map(), stopstart=True)
def _certs_changed():
process_certificates('ceph-radosgw', relation_id, unit)
configure_https()
_certs_changed()
@hooks.hook('master-relation-joined')
def master_relation_joined(relation_id=None):
if not ready_for_service(legacy=False):
log('unit not ready, deferring multisite configuration')
return
internal_url = '{}:{}'.format(
canonical_url(CONFIGS, INTERNAL),
config('port')
)
endpoints = [internal_url]
realm = config('realm')
zonegroup = config('zonegroup')
zone = config('zone')
access_key = leader_get('access_key')
secret = leader_get('secret')
if not all((realm, zonegroup, zone)):
return
relation_set(relation_id=relation_id,
realm=realm,
zonegroup=zonegroup,
url=endpoints[0],
access_key=access_key,
secret=secret)
if not is_leader():
return
if not leader_get('restart_nonce'):
# NOTE(jamespage):
# This is an ugly kludge to force creation of the required data
# items in the .rgw.root pool prior to the radosgw process being
# started; radosgw-admin does not currently have a way of doing
# this operation but a period update will force it to be created.
multisite.update_period(fatal=False)
mutation = False
if realm not in multisite.list_realms():
multisite.create_realm(realm, default=True)
mutation = True
if zonegroup not in multisite.list_zonegroups():
multisite.create_zonegroup(zonegroup,
endpoints=endpoints,
default=True, master=True,
realm=realm)
mutation = True
if zone not in multisite.list_zones():
multisite.create_zone(zone,
endpoints=endpoints,
default=True, master=True,
zonegroup=zonegroup)
mutation = True
if MULTISITE_SYSTEM_USER not in multisite.list_users():
access_key, secret = multisite.create_system_user(
MULTISITE_SYSTEM_USER
)
multisite.modify_zone(zone,
access_key=access_key,
secret=secret)
leader_set(access_key=access_key,
secret=secret)
mutation = True
if mutation:
multisite.update_period()
service_restart(service_name())
leader_set(restart_nonce=str(uuid.uuid4()))
relation_set(relation_id=relation_id,
access_key=access_key,
secret=secret)
@hooks.hook('slave-relation-changed')
def slave_relation_changed(relation_id=None, unit=None):
if not is_leader():
return
if not ready_for_service(legacy=False):
log('unit not ready, deferring multisite configuration')
return
master_data = relation_get(rid=relation_id, unit=unit)
if not all((master_data.get('realm'),
master_data.get('zonegroup'),
master_data.get('access_key'),
master_data.get('secret'),
master_data.get('url'))):
log("Defer processing until master RGW has provided required data")
return
internal_url = '{}:{}'.format(
canonical_url(CONFIGS, INTERNAL),
config('port')
)
endpoints = [internal_url]
realm = config('realm')
zonegroup = config('zonegroup')
zone = config('zone')
if (realm, zonegroup) != (master_data['realm'],
master_data['zonegroup']):
log("Mismatched configuration so stop multi-site configuration now")
return
if not leader_get('restart_nonce'):
# NOTE(jamespage):
# This is an ugly kludge to force creation of the required data
# items in the .rgw.root pool prior to the radosgw process being
# started; radosgw-admin does not currently have a way of doing
# this operation but a period update will force it to be created.
multisite.update_period(fatal=False)
mutation = False
if realm not in multisite.list_realms():
multisite.pull_realm(url=master_data['url'],
access_key=master_data['access_key'],
secret=master_data['secret'])
multisite.pull_period(url=master_data['url'],
access_key=master_data['access_key'],
secret=master_data['secret'])
multisite.set_default_realm(realm)
mutation = True
if zone not in multisite.list_zones():
multisite.create_zone(zone,
endpoints=endpoints,
default=False, master=False,
zonegroup=zonegroup,
access_key=master_data['access_key'],
secret=master_data['secret'])
mutation = True
if mutation:
multisite.update_period()
service_restart(service_name())
leader_set(restart_nonce=str(uuid.uuid4()))
@hooks.hook('leader-settings-changed')
def leader_settings_changed():
# NOTE: leader unit will only ever set leader storage
# data when multi-site realm, zonegroup, zone or user
# data has been created/changed - trigger restarts
# of rgw services.
if restart_nonce_changed(leader_get('restart_nonce')):
service_restart(service_name())
if not is_leader():
for r_id in relation_ids('master'):
master_relation_joined(r_id)
def process_multisite_relations():
"""Re-trigger any pending master/slave relations"""
for r_id in relation_ids('master'):
master_relation_joined(r_id)
for r_id in relation_ids('slave'):
for unit in related_units(r_id):
slave_relation_changed(r_id, unit)
if __name__ == '__main__':
try:
hooks.execute(sys.argv)
except UnregisteredHookError as e:
log('Unknown hook {} - skipping.'.format(e))
assess_status(CONFIGS)