diff --git a/actions.yaml b/actions.yaml index 81bbd5b52380bb3c5ce6be4b5d3a211b7c24ab67..767bc8abaa8cdd3cfd50094ae41c1499354ca761 100644 --- a/actions.yaml +++ b/actions.yaml @@ -1,5 +1,3 @@ -git-reinstall: - description: Reinstall keystone from the openstack-origin-git repositories. pause: description: | Pause keystone services. diff --git a/actions/charmhelpers b/actions/charmhelpers new file mode 120000 index 0000000000000000000000000000000000000000..702de734b0c015b34565dfbd7ba8c48ace8cb262 --- /dev/null +++ b/actions/charmhelpers @@ -0,0 +1 @@ +../charmhelpers \ No newline at end of file diff --git a/actions/git-reinstall b/actions/git-reinstall deleted file mode 100755 index 2abc2e405272d0950d129b6efd36223f02ada341..0000000000000000000000000000000000000000 --- a/actions/git-reinstall +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/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 traceback - -from charmhelpers.contrib.openstack.utils import ( - git_install_requested, -) - -from charmhelpers.core.hookenv import ( - action_set, - action_fail, - config, -) - -from hooks.keystone_utils import ( - git_install, -) - -from hooks.keystone_hooks import ( - config_changed, -) - - -def git_reinstall(): - """Reinstall from source and restart services. - - If the openstack-origin-git config option was used to install openstack - from source git repositories, then this action can be used to reinstall - from updated git repositories, followed by a restart of services.""" - if not git_install_requested(): - action_fail('openstack-origin-git is not configured') - return - - try: - git_install(config('openstack-origin-git')) - config_changed() - except: - action_set({'traceback': traceback.format_exc()}) - action_fail('git-reinstall resulted in an unexpected error') - - -if __name__ == '__main__': - git_reinstall() diff --git a/actions/git_reinstall.py b/actions/git_reinstall.py deleted file mode 100755 index 2abc2e405272d0950d129b6efd36223f02ada341..0000000000000000000000000000000000000000 --- a/actions/git_reinstall.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/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 traceback - -from charmhelpers.contrib.openstack.utils import ( - git_install_requested, -) - -from charmhelpers.core.hookenv import ( - action_set, - action_fail, - config, -) - -from hooks.keystone_utils import ( - git_install, -) - -from hooks.keystone_hooks import ( - config_changed, -) - - -def git_reinstall(): - """Reinstall from source and restart services. - - If the openstack-origin-git config option was used to install openstack - from source git repositories, then this action can be used to reinstall - from updated git repositories, followed by a restart of services.""" - if not git_install_requested(): - action_fail('openstack-origin-git is not configured') - return - - try: - git_install(config('openstack-origin-git')) - config_changed() - except: - action_set({'traceback': traceback.format_exc()}) - action_fail('git-reinstall resulted in an unexpected error') - - -if __name__ == '__main__': - git_reinstall() diff --git a/actions/hooks b/actions/hooks new file mode 120000 index 0000000000000000000000000000000000000000..f631275e19cd320f570733cb0ce1f287d6f02702 --- /dev/null +++ b/actions/hooks @@ -0,0 +1 @@ +../hooks \ No newline at end of file diff --git a/hooks/charmhelpers b/hooks/charmhelpers new file mode 120000 index 0000000000000000000000000000000000000000..702de734b0c015b34565dfbd7ba8c48ace8cb262 --- /dev/null +++ b/hooks/charmhelpers @@ -0,0 +1 @@ +../charmhelpers \ No newline at end of file diff --git a/hooks/pgsql-db-relation-changed b/hooks/pgsql-db-relation-changed deleted file mode 100755 index 505db8a7b867de2c46c8ef6e79bc57c1a0294ca1..0000000000000000000000000000000000000000 --- a/hooks/pgsql-db-relation-changed +++ /dev/null @@ -1,1102 +0,0 @@ -#!/usr/bin/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 hashlib -import json -import os -import sys - -from subprocess import check_call - -from charmhelpers.contrib import unison -from charmhelpers.core import unitdata - -from charmhelpers.core.hookenv import ( - Hooks, - UnregisteredHookError, - config, - is_relation_made, - log, - local_unit, - DEBUG, - INFO, - WARNING, - ERROR, - relation_get, - relation_ids, - relation_set, - related_units, - status_set, - open_port, - is_leader, -) - -from charmhelpers.core.host import ( - mkdir, - service_pause, - service_stop, - service_start, - service_restart, -) - -from charmhelpers.core.strutils import ( - bool_from_string, -) - -from charmhelpers.fetch import ( - apt_install, apt_update, - filter_installed_packages -) - -from charmhelpers.contrib.openstack.utils import ( - config_value_changed, - configure_installation_source, - git_install_requested, - openstack_upgrade_available, - sync_db_with_multi_ipv6_addresses, - os_release, - pausable_restart_on_change as restart_on_change, - is_unit_paused_set, - CompareOpenStackReleases, - snap_install_requested, - install_os_snaps, - get_snaps_install_info_from_origin, - enable_memcache, -) - -from keystone_utils import ( - add_service_to_keystone, - add_credentials_to_keystone, - determine_packages, - disable_unused_apache_sites, - do_openstack_upgrade_reexec, - ensure_initial_admin, - get_admin_passwd, - git_install, - migrate_database, - save_script_rc, - post_snap_install, - synchronize_ca_if_changed, - register_configs, - restart_map, - services, - CLUSTER_RES, - KEYSTONE_CONF, - KEYSTONE_USER, - POLICY_JSON, - TOKEN_FLUSH_CRON_FILE, - SSH_USER, - setup_ipv6, - send_notifications, - check_peer_actions, - get_ssl_sync_request_units, - is_ssl_cert_master, - is_db_ready, - clear_ssl_synced_units, - is_db_initialised, - update_certs_if_available, - ensure_ssl_dir, - ensure_pki_dir_permissions, - ensure_permissions, - force_ssl_sync, - filter_null, - ensure_ssl_dirs, - ensure_pki_cert_paths, - is_service_present, - delete_service_entry, - assess_status, - run_in_apache, - restart_function_map, - WSGI_KEYSTONE_API_CONF, - SHIBSP_FILES, - OIDC_MAPPING_FILE, - SAML2_MAPPING_FILE, - install_apache_error_handler, - restart_pid_check, - get_api_version, - ADMIN_DOMAIN, - ADMIN_PROJECT, - create_or_show_domain, - keystone_service, -) - -from charmhelpers.contrib.hahelpers.cluster import ( - is_elected_leader, - get_hacluster_config, - peer_units, - https, - is_clustered, -) - -from charmhelpers.contrib.openstack.ha.utils import ( - update_dns_ha_resource_params, - expect_ha, -) - -from charmhelpers.payload.execd import execd_preinstall -from charmhelpers.contrib.peerstorage import ( - peer_retrieve_by_prefix, - peer_echo, - relation_get as relation_get_and_migrate, -) -from charmhelpers.contrib.openstack.ip import ( - ADMIN, - resolve_address, -) -from charmhelpers.contrib.network.ip import ( - get_iface_for_address, - get_netmask_for_address, - is_ipv6, - get_relation_ip, -) -from charmhelpers.contrib.openstack.context import ADDRESS_TYPES - -from charmhelpers.contrib.charmsupport import nrpe - -from charmhelpers.contrib.hardening.harden import harden - -hooks = Hooks() -CONFIGS = register_configs() - - -@hooks.hook('install.real') -@harden() -def install(): - status_set('maintenance', 'Executing pre-install') - execd_preinstall() - configure_installation_source(config('openstack-origin')) - status_set('maintenance', 'Installing apt packages') - apt_update() - apt_install(determine_packages(), fatal=True) - - if snap_install_requested(): - status_set('maintenance', 'Installing keystone snap') - # NOTE(thedac) Setting devmode until LP#1719636 is fixed - install_os_snaps( - get_snaps_install_info_from_origin( - ['keystone'], - config('openstack-origin'), - mode='devmode')) - post_snap_install() - service_stop('snap.keystone.*') - else: - # unconfigured keystone service will prevent start of haproxy in some - # circumstances. make sure haproxy runs. LP #1648396 - service_stop('keystone') - service_start('haproxy') - if run_in_apache(): - disable_unused_apache_sites() - if not git_install_requested(): - service_pause('keystone') - install_apache_error_handler(config('no-user-mapping-url')) - - status_set('maintenance', 'Git install') - git_install(config('openstack-origin-git')) - - unison.ensure_user(user=SSH_USER, group=SSH_USER) - unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER) - - -@hooks.hook('config-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed(fatal=True) -@harden() -def config_changed(): - if config('prefer-ipv6'): - status_set('maintenance', 'configuring ipv6') - setup_ipv6() - sync_db_with_multi_ipv6_addresses(config('database'), - config('database-user')) - - unison.ensure_user(user=SSH_USER, group=SSH_USER) - unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER) - homedir = unison.get_homedir(SSH_USER) - if not os.path.isdir(homedir): - mkdir(homedir, SSH_USER, SSH_USER, 0o775) - - if git_install_requested(): - if config_value_changed('openstack-origin-git'): - status_set('maintenance', 'Running Git install') - git_install(config('openstack-origin-git')) - elif not config('action-managed-upgrade'): - if openstack_upgrade_available('keystone'): - status_set('maintenance', 'Running openstack upgrade') - do_openstack_upgrade_reexec(configs=CONFIGS) - - for r_id in relation_ids('cluster'): - cluster_joined(rid=r_id, ssl_sync_request=False) - - config_changed_postupgrade() - - -@hooks.hook('config-changed-postupgrade') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed(fatal=True) -@harden() -def config_changed_postupgrade(): - # Ensure ssl dir exists and is unison-accessible - ensure_ssl_dir() - - if not snap_install_requested(): - check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) - - ensure_ssl_dirs() - - save_script_rc() - release = os_release('keystone') - if run_in_apache(release=release): - # Need to ensure mod_wsgi is installed and apache2 is reloaded - # immediatly as charm querys its local keystone before restart - # decorator can fire - apt_install(filter_installed_packages(determine_packages())) - # when deployed from source, init scripts aren't installed - if not git_install_requested(): - service_pause('keystone') - - disable_unused_apache_sites() - if WSGI_KEYSTONE_API_CONF in CONFIGS.templates: - CONFIGS.write(WSGI_KEYSTONE_API_CONF) - if not is_unit_paused_set(): - restart_pid_check('apache2') - if config('enable-oidc'): - CONFIGS.write(OIDC_MAPPING_FILE) - configure_oidc() - if config('enable-saml2'): - CONFIGS.write(SAML2_MAPPING_FILE) - for shibsp_file in SHIBSP_FILES: - CONFIGS.write(shibsp_file) - configure_saml2() - service_stop('shibd') - if not is_unit_paused_set(): - service_start('shibd') - install_apache_error_handler(config('no-user-mapping-url')) - - if enable_memcache(release=release): - # If charm or OpenStack have been upgraded then the list of required - # packages may have changed so ensure they are installed. - apt_install(filter_installed_packages(determine_packages())) - - configure_https() - open_port(config('service-port')) - - update_nrpe_config() - - CONFIGS.write_all() - - if snap_install_requested() and not is_unit_paused_set(): - service_restart('snap.keystone.*') - - initialise_pki() - - update_all_identity_relation_units() - update_all_domain_backends() - - # Ensure sync request is sent out (needed for any/all ssl change) - send_ssl_sync_request() - - for r_id in relation_ids('ha'): - ha_joined(relation_id=r_id) - - -@synchronize_ca_if_changed(fatal=True) -def initialise_pki(): - """Create certs and keys required for token signing. - - Used for PKI and signing token revocation list. - - NOTE: keystone.conf [signing] section must be up-to-date prior to - executing this. - """ - if CompareOpenStackReleases(os_release('keystone-common')) >= 'pike': - # pike dropped support for PKI token; skip function - return - ensure_pki_cert_paths() - if not peer_units() or is_ssl_cert_master(): - log("Ensuring PKI token certs created", level=DEBUG) - if snap_install_requested(): - cmd = ['/snap/bin/keystone-manage', 'pki_setup', - '--keystone-user', KEYSTONE_USER, - '--keystone-group', KEYSTONE_USER] - _log_dir = '/var/snap/keystone/common/log' - else: - cmd = ['keystone-manage', 'pki_setup', - '--keystone-user', KEYSTONE_USER, - '--keystone-group', KEYSTONE_USER] - _log_dir = '/var/log/keystone' - check_call(cmd) - - # Ensure logfile has keystone perms since we may have just created it - # with root. - ensure_permissions(_log_dir, user=KEYSTONE_USER, - group=KEYSTONE_USER, perms=0o744) - ensure_permissions('{}/keystone.log'.format(_log_dir), - user=KEYSTONE_USER, group=KEYSTONE_USER, - perms=0o644) - - ensure_pki_dir_permissions() - - -@hooks.hook('shared-db-relation-joined') -def db_joined(): - if is_relation_made('pgsql-db'): - # error, postgresql is used - e = ('Attempting to associate a mysql database when there is already ' - 'associated a postgresql one') - log(e, level=ERROR) - raise Exception(e) - - if config('prefer-ipv6'): - sync_db_with_multi_ipv6_addresses(config('database'), - config('database-user')) - else: - # Avoid churn check for access-network early - access_network = None - for unit in related_units(): - access_network = relation_get(unit=unit, - attribute='access-network') - if access_network: - break - host = get_relation_ip('shared-db', cidr_network=access_network) - - relation_set(database=config('database'), - username=config('database-user'), - hostname=host) - - -@hooks.hook('pgsql-db-relation-joined') -def pgsql_db_joined(): - if is_relation_made('shared-db'): - # raise error - e = ('Attempting to associate a postgresql database when there' - ' is already associated a mysql one') - log(e, level=ERROR) - raise Exception(e) - - relation_set(database=config('database')) - - -def update_all_identity_relation_units(check_db_ready=True): - if is_unit_paused_set(): - return - CONFIGS.write_all() - configure_https() - if check_db_ready and not is_db_ready(): - log('Allowed_units list provided and this unit not present', - level=INFO) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring identity-relation " - "updates", level=INFO) - return - - if is_elected_leader(CLUSTER_RES): - ensure_initial_admin(config) - - log('Firing identity_changed hook for all related services.') - for rid in relation_ids('identity-service'): - for unit in related_units(rid): - identity_changed(relation_id=rid, remote_unit=unit) - log('Firing admin_relation_changed hook for all related services.') - for rid in relation_ids('identity-admin'): - admin_relation_changed(rid) - log('Firing identity_credentials_changed hook for all related services.') - for rid in relation_ids('identity-credentials'): - for unit in related_units(rid): - identity_credentials_changed(relation_id=rid, remote_unit=unit) - - -@synchronize_ca_if_changed(force=True) -def update_all_identity_relation_units_force_sync(): - update_all_identity_relation_units() - - -def update_all_domain_backends(): - """Re-trigger hooks for all domain-backend relations/units""" - for rid in relation_ids('domain-backend'): - for unit in related_units(rid): - domain_backend_changed(relation_id=rid, unit=unit) - - -def leader_init_db_if_ready(use_current_context=False): - """ Initialise the keystone db if it is ready and mark it as initialised. - - NOTE: this must be idempotent. - """ - if not is_elected_leader(CLUSTER_RES): - log("Not leader - skipping db init", level=DEBUG) - return - - if is_db_initialised(): - log("Database already initialised - skipping db init", level=DEBUG) - update_all_identity_relation_units(check_db_ready=False) - return - - # Bugs 1353135 & 1187508. Dbs can appear to be ready before the - # units acl entry has been added. So, if the db supports passing - # a list of permitted units then check if we're in the list. - if not is_db_ready(use_current_context=use_current_context): - log('Allowed_units list provided and this unit not present', - level=INFO) - return - - migrate_database() - # Ensure any existing service entries are updated in the - # new database backend. Also avoid duplicate db ready check. - update_all_identity_relation_units(check_db_ready=False) - update_all_domain_backends() - - -@hooks.hook('shared-db-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def db_changed(): - if 'shared-db' not in CONFIGS.complete_contexts(): - log('shared-db relation incomplete. Peer not ready?') - else: - CONFIGS.write(KEYSTONE_CONF) - leader_init_db_if_ready(use_current_context=True) - if CompareOpenStackReleases( - os_release('keystone-common')) >= 'liberty': - CONFIGS.write(POLICY_JSON) - - -@hooks.hook('pgsql-db-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def pgsql_db_changed(): - if 'pgsql-db' not in CONFIGS.complete_contexts(): - log('pgsql-db relation incomplete. Peer not ready?') - else: - CONFIGS.write(KEYSTONE_CONF) - leader_init_db_if_ready(use_current_context=True) - if CompareOpenStackReleases( - os_release('keystone-common')) >= 'liberty': - CONFIGS.write(POLICY_JSON) - - -@hooks.hook('identity-service-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def identity_changed(relation_id=None, remote_unit=None): - CONFIGS.write_all() - - notifications = {} - if is_elected_leader(CLUSTER_RES): - if not is_db_ready(): - log("identity-service-relation-changed hook fired before db " - "ready - deferring until db ready", level=WARNING) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring identity-relation " - "updates", level=INFO) - return - - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - - add_service_to_keystone(relation_id, remote_unit) - if is_service_present('neutron', 'network'): - delete_service_entry('quantum', 'network') - settings = relation_get(rid=relation_id, unit=remote_unit) - service = settings.get('service', None) - if service: - # If service is known and endpoint has changed, notify service if - # it is related with notifications interface. - csum = hashlib.sha256() - # We base the decision to notify on whether these parameters have - # changed (if csum is unchanged from previous notify, relation will - # not fire). - csum.update(settings.get('public_url', None)) - csum.update(settings.get('admin_url', None)) - csum.update(settings.get('internal_url', None)) - notifications['%s-endpoint-changed' % (service)] = csum.hexdigest() - else: - # Each unit needs to set the db information otherwise if the unit - # with the info dies the settings die with it Bug# 1355848 - for rel_id in relation_ids('identity-service'): - peerdb_settings = peer_retrieve_by_prefix(rel_id) - # Ensure the null'd settings are unset in the relation. - peerdb_settings = filter_null(peerdb_settings) - if 'service_password' in peerdb_settings: - relation_set(relation_id=rel_id, **peerdb_settings) - - log('Deferring identity_changed() to service leader.') - - if notifications: - send_notifications(notifications) - - -@hooks.hook('identity-credentials-relation-joined', - 'identity-credentials-relation-changed') -def identity_credentials_changed(relation_id=None, remote_unit=None): - """Update the identity credentials relation on change - - Calls add_credentials_to_keystone - - :param relation_id: Relation id of the relation - :param remote_unit: Related unit on the relation - """ - if is_elected_leader(CLUSTER_RES): - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - if not is_db_ready(): - log("identity-credentials-relation-changed hook fired before db " - "ready - deferring until db ready", level=WARNING) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring " - "identity-credentials-relation updates", level=INFO) - return - - # Create the tenant user - add_credentials_to_keystone(relation_id, remote_unit) - else: - log('Deferring identity_credentials_changed() to service leader.') - - -def send_ssl_sync_request(): - """Set sync request on cluster relation. - - Value set equals number of ssl configs currently enabled so that if they - change, we ensure that certs are synced. This setting is consumed by - cluster-relation-changed ssl master. We also clear the 'synced' set to - guarantee that a sync will occur. - - Note the we do nothing if the setting is already applied. - """ - unit = local_unit().replace('/', '-') - # Start with core config (e.g. used for signing revoked token list) - ssl_config = 0b1 - - use_https = config('use-https') - if use_https and bool_from_string(use_https): - ssl_config ^= 0b10 - - https_service_endpoints = config('https-service-endpoints') - if (https_service_endpoints and - bool_from_string(https_service_endpoints)): - ssl_config ^= 0b100 - - enable_pki = config('enable-pki') - if enable_pki and bool_from_string(enable_pki): - ssl_config ^= 0b1000 - - key = 'ssl-sync-required-%s' % (unit) - settings = {key: ssl_config} - - prev = 0b0 - rid = None - for rid in relation_ids('cluster'): - for unit in related_units(rid): - _prev = relation_get(rid=rid, unit=unit, attribute=key) or 0b0 - if _prev and _prev > prev: - prev = bin(_prev) - - if rid and prev ^ ssl_config: - if is_leader(): - clear_ssl_synced_units() - - log("Setting %s=%s" % (key, bin(ssl_config)), level=DEBUG) - relation_set(relation_id=rid, relation_settings=settings) - - -@hooks.hook('cluster-relation-joined') -def cluster_joined(rid=None, ssl_sync_request=True): - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - - 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) - - if ssl_sync_request: - send_ssl_sync_request() - - -@hooks.hook('cluster-relation-changed') -@restart_on_change(restart_map(), stopstart=True) -@update_certs_if_available -def cluster_changed(): - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - # NOTE(jamespage) re-echo passwords for peer storage - echo_whitelist = ['_passwd', 'identity-service:', - 'db-initialised', 'ssl-cert-available-updates'] - # Don't echo if leader since a re-election may be in progress. - if not is_leader(): - echo_whitelist.append('ssl-cert-master') - - log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG) - peer_echo(includes=echo_whitelist, force=True) - - check_peer_actions() - - initialise_pki() - - if is_leader(): - # Figure out if we need to mandate a sync - units = get_ssl_sync_request_units() - synced_units = relation_get_and_migrate(attribute='ssl-synced-units', - unit=local_unit()) - diff = None - if synced_units: - synced_units = json.loads(synced_units) - diff = set(units).symmetric_difference(set(synced_units)) - else: - units = None - - if units and (not synced_units or diff): - log("New peers joined and need syncing - %s" % - (', '.join(units)), level=DEBUG) - update_all_identity_relation_units_force_sync() - else: - update_all_identity_relation_units() - - if not is_leader() and is_ssl_cert_master(): - # Force and sync and trigger a sync master re-election since we are not - # leader anymore. - force_ssl_sync() - else: - CONFIGS.write_all() - - -@hooks.hook('leader-elected') -@restart_on_change(restart_map(), stopstart=True) -def leader_elected(): - log('Unit has been elected leader.', level=DEBUG) - # When the local unit has been elected the leader, update the cron jobs - # to ensure that the cron jobs are active on this unit. - CONFIGS.write(TOKEN_FLUSH_CRON_FILE) - - update_all_identity_relation_units() - - update_all_identity_relation_units() - - -@hooks.hook('leader-settings-changed') -@restart_on_change(restart_map(), stopstart=True) -def leader_settings_changed(): - # Since minions are notified of a regime change via the - # leader-settings-changed hook, rewrite the token flush cron job to make - # sure only the leader is running the cron job. - CONFIGS.write(TOKEN_FLUSH_CRON_FILE) - - update_all_identity_relation_units() - - -@hooks.hook('ha-relation-joined') -def ha_joined(relation_id=None): - cluster_config = get_hacluster_config() - resources = { - 'res_ks_haproxy': 'lsb:haproxy', - } - resource_params = { - 'res_ks_haproxy': 'op monitor interval="5s"' - } - - if config('dns-ha'): - update_dns_ha_resource_params(relation_id=relation_id, - resources=resources, - resource_params=resource_params) - else: - vip_group = [] - for vip in cluster_config['vip'].split(): - if is_ipv6(vip): - res_ks_vip = 'ocf:heartbeat:IPv6addr' - vip_params = 'ipv6addr' - else: - res_ks_vip = 'ocf:heartbeat:IPaddr2' - vip_params = 'ip' - - iface = (get_iface_for_address(vip) or - config('vip_iface')) - netmask = (get_netmask_for_address(vip) or - config('vip_cidr')) - - if iface is not None: - vip_key = 'res_ks_{}_vip'.format(iface) - if vip_key in vip_group: - if vip not in resource_params[vip_key]: - vip_key = '{}_{}'.format(vip_key, vip_params) - else: - log("Resource '%s' (vip='%s') already exists in " - "vip group - skipping" % (vip_key, vip), WARNING) - continue - - vip_group.append(vip_key) - resources[vip_key] = res_ks_vip - resource_params[vip_key] = ( - 'params {ip}="{vip}" cidr_netmask="{netmask}"' - ' nic="{iface}"'.format(ip=vip_params, - vip=vip, - iface=iface, - netmask=netmask) - ) - - if len(vip_group) >= 1: - relation_set(relation_id=relation_id, - groups={CLUSTER_RES: ' '.join(vip_group)}) - - init_services = { - 'res_ks_haproxy': 'haproxy' - } - clones = { - 'cl_ks_haproxy': 'res_ks_haproxy' - } - relation_set(relation_id=relation_id, - init_services=init_services, - corosync_bindiface=cluster_config['ha-bindiface'], - corosync_mcastport=cluster_config['ha-mcastport'], - resources=resources, - resource_params=resource_params, - clones=clones) - - -@hooks.hook('ha-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def ha_changed(): - CONFIGS.write_all() - - clustered = relation_get('clustered') - if clustered: - log('Cluster configured, notifying other services and updating ' - 'keystone endpoint configuration') - if is_ssl_cert_master(): - update_all_identity_relation_units_force_sync() - else: - update_all_identity_relation_units() - - -@hooks.hook('identity-admin-relation-changed') -def admin_relation_changed(relation_id=None): - # TODO: fixup - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - relation_data = { - 'service_hostname': resolve_address(ADMIN), - 'service_port': config('service-port'), - 'service_username': config('admin-user'), - 'service_tenant_name': config('admin-role'), - 'service_region': config('region'), - 'service_protocol': 'https' if https() else 'http', - 'api_version': get_api_version(), - } - if relation_data['api_version'] > 2: - relation_data['service_user_domain_name'] = ADMIN_DOMAIN - relation_data['service_project_domain_name'] = ADMIN_DOMAIN - relation_data['service_project_name'] = ADMIN_PROJECT - relation_data['service_password'] = get_admin_passwd() - relation_set(relation_id=relation_id, **relation_data) - - -@hooks.hook('domain-backend-relation-changed') -def domain_backend_changed(relation_id=None, unit=None): - if get_api_version() < 3: - log('Domain specific backend identity configuration only supported ' - 'with Keystone v3 API, skipping domain creation and ' - 'restart.') - return - - domain_name = relation_get(attribute='domain-name', - unit=unit, - rid=relation_id) - if domain_name: - # NOTE(jamespage): Only create domain data from lead - # unit when clustered and database - # is configured and created. - if is_leader() and is_db_ready() and is_db_initialised(): - create_or_show_domain(domain_name) - # NOTE(jamespage): Deployment may have multiple domains, - # with different identity backends so - # ensure that a domain specific nonce - # is checked for restarts of keystone - restart_nonce = relation_get(attribute='restart-nonce', - unit=unit, - rid=relation_id) - domain_nonce_key = 'domain-restart-nonce-{}'.format(domain_name) - db = unitdata.kv() - if restart_nonce != db.get(domain_nonce_key): - if not is_unit_paused_set(): - if snap_install_requested(): - service_restart('snap.keystone.*') - else: - service_restart(keystone_service()) - db.set(domain_nonce_key, restart_nonce) - db.flush() - - -@synchronize_ca_if_changed(fatal=True) -def configure_https(): - ''' - Enables SSL API Apache config if appropriate and kicks identity-service - with any required api updates. - ''' - # need to write all to ensure changes to the entire request pipeline - # propagate (c-api, haprxy, apache) - CONFIGS.write_all() - # NOTE (thedac): When using snaps, nginx is installed, skip any apache2 - # config. - if snap_install_requested(): - return - if 'https' in CONFIGS.complete_contexts(): - cmd = ['a2ensite', 'openstack_https_frontend'] - check_call(cmd) - else: - cmd = ['a2dissite', 'openstack_https_frontend'] - check_call(cmd) - - -def configure_idp(idp, remote_ids, mappings_file, mapping_id, protocol): - ''' - Configure Federated Identity Provider. - See: https://developer.openstack.org/api-ref/identity/v3-ext/ - #os-federation-api - See: https://docs.openstack.org/python-keystoneclient/3.11.0/api/ - keystoneclient.v3.contrib.federation.html - ''' - - from keystone_utils import ( - get_local_endpoint, - get_admin_token - ) - import requests - - if not idp: - log("ERROR Missing Identity Provider name for %s" % protocol, - level=ERROR) - return - if not remote_ids: - log("ERROR Missing remote ids for %s provider" % protocol, level=ERROR) - return - if not mappings_file: - log("ERROR Missing mappings file for %s provider" % protocol, - level=ERROR) - return - if not mapping_id: - log("ERROR Missing mapping ID for %s provider" % protocol, level=ERROR) - return - - federation_uri = os.path.join(get_local_endpoint(), - 'OS-FEDERATION') - ks_admin_token = get_admin_token() - headers = {'X-Auth-Token': ks_admin_token} - - # Is provider configured? - federation_idp_uri = os.path.join(federation_uri, - 'identity_providers', - idp) - try: - resGet = requests.get(federation_idp_uri, headers=headers) - except requests.exceptions.RequestException as error: - log("ERROR %s, trying to GET %s, headers: %s" % - (error, federation_idp_uri, headers), level=WARNING) - return - - data = { - "identity_provider": { - "description": "Identity provider %s" % idp, - "remote_ids": remote_ids, - "enabled": True - } - } - if not resGet.ok: - # Register a new Identity Provider - resPut = requests.put(federation_idp_uri, json=data, headers=headers) - if not resPut.ok: - log("ERROR IdP PUT: %s, %s, %s, %s" % - (resPut.reason, federation_idp_uri, headers, remote_ids), - level=WARNING) - else: - # Update the Identity Provider - resPatch = requests.patch(federation_idp_uri, json=data, - headers=headers) - if not resPatch.ok: - log("ERROR IdP PATCH %s, %s, %s, %s" % - (resPatch.reason, federation_idp_uri, headers, remote_ids), - level=WARNING) - - # IdP users mapping - federation_mappings_uri = os.path.join(federation_uri, - 'mappings', - mapping_id) - # Is IdP mapping for federated users already created? - try: - resGet = requests.get(federation_mappings_uri, headers=headers) - except requests.exceptions.RequestException as resError: - log("ERROR GET %s, %s, headers: %s" % - (resError, federation_mappings_uri, headers), - level=WARNING) - if os.path.isfile(mappings_file): - with open(mappings_file) as f: - data = f.read() - if not resGet.ok: - # Create the IdP mapping for federated users - resPut = requests.put(federation_mappings_uri, - data=data, - headers=headers) - if not resPut.ok: - log("ERROR IdP PUT %s, %s" % - (resPut.reason, federation_mappings_uri), - level=WARNING) - else: - # Update the IdP mapping for federated users - resPatch = requests.patch(federation_mappings_uri, - data=data, - headers=headers) - if not resPatch.ok: - log("ERROR IdP PATCH %s, %s" % - (resPatch.reason, federation_mappings_uri), - level=WARNING) - - # IdP protocol - federation_idp_protocols_uri = os.path.join(federation_uri, - 'identity_providers', - idp, 'protocols', protocol) - # Is IdP protocol for federated users already created? - try: - resGet = requests.get(federation_idp_protocols_uri, headers=headers) - except requests.exceptions.RequestException as getProError: - log("ERROR GET %s, headers: %s, message: %s" % - (federation_idp_protocols_uri, headers, getProError), - level=WARNING) - if not resGet.ok: - # Add the protocol - data = { - "protocol": { - "mapping_id": mapping_id - } - } - resPut = requests.put(federation_idp_protocols_uri, - json=data, - headers=headers) - if not resPut.ok: - log("ERROR IdP PUT %s, %s" % - (resPut.reason, federation_idp_protocols_uri), - level=WARNING) - - -def configure_saml2(): - ''' - Configure SAML Provider. - ''' - from keystone_context import SamlContext - - configure_idp(config('shibsp-identity-provider'), - config('shibsp-idp-remote-ids'), - SAML2_MAPPING_FILE, - config('saml2-mapping'), - 'saml2') - - samlContext = SamlContext() - samlContext.configure() - - -def configure_oidc(): - ''' - Configure OIDC Provider. - ''' - configure_idp(config('oidc-identity-provider'), - [config('oidc-idp-remote-id')], - OIDC_MAPPING_FILE, - config('oidc-mapping'), - 'oidc') - - -@hooks.hook('upgrade-charm') -@restart_on_change(restart_map(), stopstart=True) -@synchronize_ca_if_changed() -@harden() -def upgrade_charm(): - status_set('maintenance', 'Installing apt packages') - apt_install(filter_installed_packages(determine_packages())) - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - - ensure_ssl_dirs() - - if run_in_apache(): - disable_unused_apache_sites() - - CONFIGS.write_all() - - # See LP bug 1519035 - leader_init_db_if_ready() - - update_nrpe_config() - - if is_elected_leader(CLUSTER_RES): - log('Cluster leader - ensuring endpoint configuration is up to ' - 'date', level=DEBUG) - update_all_identity_relation_units() - - -@hooks.hook('update-status') -@harden() -def update_status(): - log('Updating status.') - - -@hooks.hook('nrpe-external-master-relation-joined', - 'nrpe-external-master-relation-changed') -def update_nrpe_config(): - # 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() - _services = [] - for service in services(): - if service.startswith('snap.'): - service = service.split('.')[1] - _services.append(service) - nrpe.add_init_service_checks(nrpe_setup, _services, current_unit) - nrpe.add_haproxy_checks(nrpe_setup, current_unit) - nrpe_setup.write() - - -def main(): - try: - hooks.execute(sys.argv) - except UnregisteredHookError as e: - log('Unknown hook {} - skipping.'.format(e)) - assess_status(CONFIGS) - - -if __name__ == '__main__': - main() diff --git a/hooks/pgsql-db-relation-changed b/hooks/pgsql-db-relation-changed new file mode 120000 index 0000000000000000000000000000000000000000..dd3b3eff4b7109293b4cfd9b81f5fc49643432a0 --- /dev/null +++ b/hooks/pgsql-db-relation-changed @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/hooks/pgsql-db-relation-joined b/hooks/pgsql-db-relation-joined deleted file mode 100755 index 505db8a7b867de2c46c8ef6e79bc57c1a0294ca1..0000000000000000000000000000000000000000 --- a/hooks/pgsql-db-relation-joined +++ /dev/null @@ -1,1102 +0,0 @@ -#!/usr/bin/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 hashlib -import json -import os -import sys - -from subprocess import check_call - -from charmhelpers.contrib import unison -from charmhelpers.core import unitdata - -from charmhelpers.core.hookenv import ( - Hooks, - UnregisteredHookError, - config, - is_relation_made, - log, - local_unit, - DEBUG, - INFO, - WARNING, - ERROR, - relation_get, - relation_ids, - relation_set, - related_units, - status_set, - open_port, - is_leader, -) - -from charmhelpers.core.host import ( - mkdir, - service_pause, - service_stop, - service_start, - service_restart, -) - -from charmhelpers.core.strutils import ( - bool_from_string, -) - -from charmhelpers.fetch import ( - apt_install, apt_update, - filter_installed_packages -) - -from charmhelpers.contrib.openstack.utils import ( - config_value_changed, - configure_installation_source, - git_install_requested, - openstack_upgrade_available, - sync_db_with_multi_ipv6_addresses, - os_release, - pausable_restart_on_change as restart_on_change, - is_unit_paused_set, - CompareOpenStackReleases, - snap_install_requested, - install_os_snaps, - get_snaps_install_info_from_origin, - enable_memcache, -) - -from keystone_utils import ( - add_service_to_keystone, - add_credentials_to_keystone, - determine_packages, - disable_unused_apache_sites, - do_openstack_upgrade_reexec, - ensure_initial_admin, - get_admin_passwd, - git_install, - migrate_database, - save_script_rc, - post_snap_install, - synchronize_ca_if_changed, - register_configs, - restart_map, - services, - CLUSTER_RES, - KEYSTONE_CONF, - KEYSTONE_USER, - POLICY_JSON, - TOKEN_FLUSH_CRON_FILE, - SSH_USER, - setup_ipv6, - send_notifications, - check_peer_actions, - get_ssl_sync_request_units, - is_ssl_cert_master, - is_db_ready, - clear_ssl_synced_units, - is_db_initialised, - update_certs_if_available, - ensure_ssl_dir, - ensure_pki_dir_permissions, - ensure_permissions, - force_ssl_sync, - filter_null, - ensure_ssl_dirs, - ensure_pki_cert_paths, - is_service_present, - delete_service_entry, - assess_status, - run_in_apache, - restart_function_map, - WSGI_KEYSTONE_API_CONF, - SHIBSP_FILES, - OIDC_MAPPING_FILE, - SAML2_MAPPING_FILE, - install_apache_error_handler, - restart_pid_check, - get_api_version, - ADMIN_DOMAIN, - ADMIN_PROJECT, - create_or_show_domain, - keystone_service, -) - -from charmhelpers.contrib.hahelpers.cluster import ( - is_elected_leader, - get_hacluster_config, - peer_units, - https, - is_clustered, -) - -from charmhelpers.contrib.openstack.ha.utils import ( - update_dns_ha_resource_params, - expect_ha, -) - -from charmhelpers.payload.execd import execd_preinstall -from charmhelpers.contrib.peerstorage import ( - peer_retrieve_by_prefix, - peer_echo, - relation_get as relation_get_and_migrate, -) -from charmhelpers.contrib.openstack.ip import ( - ADMIN, - resolve_address, -) -from charmhelpers.contrib.network.ip import ( - get_iface_for_address, - get_netmask_for_address, - is_ipv6, - get_relation_ip, -) -from charmhelpers.contrib.openstack.context import ADDRESS_TYPES - -from charmhelpers.contrib.charmsupport import nrpe - -from charmhelpers.contrib.hardening.harden import harden - -hooks = Hooks() -CONFIGS = register_configs() - - -@hooks.hook('install.real') -@harden() -def install(): - status_set('maintenance', 'Executing pre-install') - execd_preinstall() - configure_installation_source(config('openstack-origin')) - status_set('maintenance', 'Installing apt packages') - apt_update() - apt_install(determine_packages(), fatal=True) - - if snap_install_requested(): - status_set('maintenance', 'Installing keystone snap') - # NOTE(thedac) Setting devmode until LP#1719636 is fixed - install_os_snaps( - get_snaps_install_info_from_origin( - ['keystone'], - config('openstack-origin'), - mode='devmode')) - post_snap_install() - service_stop('snap.keystone.*') - else: - # unconfigured keystone service will prevent start of haproxy in some - # circumstances. make sure haproxy runs. LP #1648396 - service_stop('keystone') - service_start('haproxy') - if run_in_apache(): - disable_unused_apache_sites() - if not git_install_requested(): - service_pause('keystone') - install_apache_error_handler(config('no-user-mapping-url')) - - status_set('maintenance', 'Git install') - git_install(config('openstack-origin-git')) - - unison.ensure_user(user=SSH_USER, group=SSH_USER) - unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER) - - -@hooks.hook('config-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed(fatal=True) -@harden() -def config_changed(): - if config('prefer-ipv6'): - status_set('maintenance', 'configuring ipv6') - setup_ipv6() - sync_db_with_multi_ipv6_addresses(config('database'), - config('database-user')) - - unison.ensure_user(user=SSH_USER, group=SSH_USER) - unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER) - homedir = unison.get_homedir(SSH_USER) - if not os.path.isdir(homedir): - mkdir(homedir, SSH_USER, SSH_USER, 0o775) - - if git_install_requested(): - if config_value_changed('openstack-origin-git'): - status_set('maintenance', 'Running Git install') - git_install(config('openstack-origin-git')) - elif not config('action-managed-upgrade'): - if openstack_upgrade_available('keystone'): - status_set('maintenance', 'Running openstack upgrade') - do_openstack_upgrade_reexec(configs=CONFIGS) - - for r_id in relation_ids('cluster'): - cluster_joined(rid=r_id, ssl_sync_request=False) - - config_changed_postupgrade() - - -@hooks.hook('config-changed-postupgrade') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed(fatal=True) -@harden() -def config_changed_postupgrade(): - # Ensure ssl dir exists and is unison-accessible - ensure_ssl_dir() - - if not snap_install_requested(): - check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) - - ensure_ssl_dirs() - - save_script_rc() - release = os_release('keystone') - if run_in_apache(release=release): - # Need to ensure mod_wsgi is installed and apache2 is reloaded - # immediatly as charm querys its local keystone before restart - # decorator can fire - apt_install(filter_installed_packages(determine_packages())) - # when deployed from source, init scripts aren't installed - if not git_install_requested(): - service_pause('keystone') - - disable_unused_apache_sites() - if WSGI_KEYSTONE_API_CONF in CONFIGS.templates: - CONFIGS.write(WSGI_KEYSTONE_API_CONF) - if not is_unit_paused_set(): - restart_pid_check('apache2') - if config('enable-oidc'): - CONFIGS.write(OIDC_MAPPING_FILE) - configure_oidc() - if config('enable-saml2'): - CONFIGS.write(SAML2_MAPPING_FILE) - for shibsp_file in SHIBSP_FILES: - CONFIGS.write(shibsp_file) - configure_saml2() - service_stop('shibd') - if not is_unit_paused_set(): - service_start('shibd') - install_apache_error_handler(config('no-user-mapping-url')) - - if enable_memcache(release=release): - # If charm or OpenStack have been upgraded then the list of required - # packages may have changed so ensure they are installed. - apt_install(filter_installed_packages(determine_packages())) - - configure_https() - open_port(config('service-port')) - - update_nrpe_config() - - CONFIGS.write_all() - - if snap_install_requested() and not is_unit_paused_set(): - service_restart('snap.keystone.*') - - initialise_pki() - - update_all_identity_relation_units() - update_all_domain_backends() - - # Ensure sync request is sent out (needed for any/all ssl change) - send_ssl_sync_request() - - for r_id in relation_ids('ha'): - ha_joined(relation_id=r_id) - - -@synchronize_ca_if_changed(fatal=True) -def initialise_pki(): - """Create certs and keys required for token signing. - - Used for PKI and signing token revocation list. - - NOTE: keystone.conf [signing] section must be up-to-date prior to - executing this. - """ - if CompareOpenStackReleases(os_release('keystone-common')) >= 'pike': - # pike dropped support for PKI token; skip function - return - ensure_pki_cert_paths() - if not peer_units() or is_ssl_cert_master(): - log("Ensuring PKI token certs created", level=DEBUG) - if snap_install_requested(): - cmd = ['/snap/bin/keystone-manage', 'pki_setup', - '--keystone-user', KEYSTONE_USER, - '--keystone-group', KEYSTONE_USER] - _log_dir = '/var/snap/keystone/common/log' - else: - cmd = ['keystone-manage', 'pki_setup', - '--keystone-user', KEYSTONE_USER, - '--keystone-group', KEYSTONE_USER] - _log_dir = '/var/log/keystone' - check_call(cmd) - - # Ensure logfile has keystone perms since we may have just created it - # with root. - ensure_permissions(_log_dir, user=KEYSTONE_USER, - group=KEYSTONE_USER, perms=0o744) - ensure_permissions('{}/keystone.log'.format(_log_dir), - user=KEYSTONE_USER, group=KEYSTONE_USER, - perms=0o644) - - ensure_pki_dir_permissions() - - -@hooks.hook('shared-db-relation-joined') -def db_joined(): - if is_relation_made('pgsql-db'): - # error, postgresql is used - e = ('Attempting to associate a mysql database when there is already ' - 'associated a postgresql one') - log(e, level=ERROR) - raise Exception(e) - - if config('prefer-ipv6'): - sync_db_with_multi_ipv6_addresses(config('database'), - config('database-user')) - else: - # Avoid churn check for access-network early - access_network = None - for unit in related_units(): - access_network = relation_get(unit=unit, - attribute='access-network') - if access_network: - break - host = get_relation_ip('shared-db', cidr_network=access_network) - - relation_set(database=config('database'), - username=config('database-user'), - hostname=host) - - -@hooks.hook('pgsql-db-relation-joined') -def pgsql_db_joined(): - if is_relation_made('shared-db'): - # raise error - e = ('Attempting to associate a postgresql database when there' - ' is already associated a mysql one') - log(e, level=ERROR) - raise Exception(e) - - relation_set(database=config('database')) - - -def update_all_identity_relation_units(check_db_ready=True): - if is_unit_paused_set(): - return - CONFIGS.write_all() - configure_https() - if check_db_ready and not is_db_ready(): - log('Allowed_units list provided and this unit not present', - level=INFO) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring identity-relation " - "updates", level=INFO) - return - - if is_elected_leader(CLUSTER_RES): - ensure_initial_admin(config) - - log('Firing identity_changed hook for all related services.') - for rid in relation_ids('identity-service'): - for unit in related_units(rid): - identity_changed(relation_id=rid, remote_unit=unit) - log('Firing admin_relation_changed hook for all related services.') - for rid in relation_ids('identity-admin'): - admin_relation_changed(rid) - log('Firing identity_credentials_changed hook for all related services.') - for rid in relation_ids('identity-credentials'): - for unit in related_units(rid): - identity_credentials_changed(relation_id=rid, remote_unit=unit) - - -@synchronize_ca_if_changed(force=True) -def update_all_identity_relation_units_force_sync(): - update_all_identity_relation_units() - - -def update_all_domain_backends(): - """Re-trigger hooks for all domain-backend relations/units""" - for rid in relation_ids('domain-backend'): - for unit in related_units(rid): - domain_backend_changed(relation_id=rid, unit=unit) - - -def leader_init_db_if_ready(use_current_context=False): - """ Initialise the keystone db if it is ready and mark it as initialised. - - NOTE: this must be idempotent. - """ - if not is_elected_leader(CLUSTER_RES): - log("Not leader - skipping db init", level=DEBUG) - return - - if is_db_initialised(): - log("Database already initialised - skipping db init", level=DEBUG) - update_all_identity_relation_units(check_db_ready=False) - return - - # Bugs 1353135 & 1187508. Dbs can appear to be ready before the - # units acl entry has been added. So, if the db supports passing - # a list of permitted units then check if we're in the list. - if not is_db_ready(use_current_context=use_current_context): - log('Allowed_units list provided and this unit not present', - level=INFO) - return - - migrate_database() - # Ensure any existing service entries are updated in the - # new database backend. Also avoid duplicate db ready check. - update_all_identity_relation_units(check_db_ready=False) - update_all_domain_backends() - - -@hooks.hook('shared-db-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def db_changed(): - if 'shared-db' not in CONFIGS.complete_contexts(): - log('shared-db relation incomplete. Peer not ready?') - else: - CONFIGS.write(KEYSTONE_CONF) - leader_init_db_if_ready(use_current_context=True) - if CompareOpenStackReleases( - os_release('keystone-common')) >= 'liberty': - CONFIGS.write(POLICY_JSON) - - -@hooks.hook('pgsql-db-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def pgsql_db_changed(): - if 'pgsql-db' not in CONFIGS.complete_contexts(): - log('pgsql-db relation incomplete. Peer not ready?') - else: - CONFIGS.write(KEYSTONE_CONF) - leader_init_db_if_ready(use_current_context=True) - if CompareOpenStackReleases( - os_release('keystone-common')) >= 'liberty': - CONFIGS.write(POLICY_JSON) - - -@hooks.hook('identity-service-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def identity_changed(relation_id=None, remote_unit=None): - CONFIGS.write_all() - - notifications = {} - if is_elected_leader(CLUSTER_RES): - if not is_db_ready(): - log("identity-service-relation-changed hook fired before db " - "ready - deferring until db ready", level=WARNING) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring identity-relation " - "updates", level=INFO) - return - - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - - add_service_to_keystone(relation_id, remote_unit) - if is_service_present('neutron', 'network'): - delete_service_entry('quantum', 'network') - settings = relation_get(rid=relation_id, unit=remote_unit) - service = settings.get('service', None) - if service: - # If service is known and endpoint has changed, notify service if - # it is related with notifications interface. - csum = hashlib.sha256() - # We base the decision to notify on whether these parameters have - # changed (if csum is unchanged from previous notify, relation will - # not fire). - csum.update(settings.get('public_url', None)) - csum.update(settings.get('admin_url', None)) - csum.update(settings.get('internal_url', None)) - notifications['%s-endpoint-changed' % (service)] = csum.hexdigest() - else: - # Each unit needs to set the db information otherwise if the unit - # with the info dies the settings die with it Bug# 1355848 - for rel_id in relation_ids('identity-service'): - peerdb_settings = peer_retrieve_by_prefix(rel_id) - # Ensure the null'd settings are unset in the relation. - peerdb_settings = filter_null(peerdb_settings) - if 'service_password' in peerdb_settings: - relation_set(relation_id=rel_id, **peerdb_settings) - - log('Deferring identity_changed() to service leader.') - - if notifications: - send_notifications(notifications) - - -@hooks.hook('identity-credentials-relation-joined', - 'identity-credentials-relation-changed') -def identity_credentials_changed(relation_id=None, remote_unit=None): - """Update the identity credentials relation on change - - Calls add_credentials_to_keystone - - :param relation_id: Relation id of the relation - :param remote_unit: Related unit on the relation - """ - if is_elected_leader(CLUSTER_RES): - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - if not is_db_ready(): - log("identity-credentials-relation-changed hook fired before db " - "ready - deferring until db ready", level=WARNING) - return - - if not is_db_initialised(): - log("Database not yet initialised - deferring " - "identity-credentials-relation updates", level=INFO) - return - - # Create the tenant user - add_credentials_to_keystone(relation_id, remote_unit) - else: - log('Deferring identity_credentials_changed() to service leader.') - - -def send_ssl_sync_request(): - """Set sync request on cluster relation. - - Value set equals number of ssl configs currently enabled so that if they - change, we ensure that certs are synced. This setting is consumed by - cluster-relation-changed ssl master. We also clear the 'synced' set to - guarantee that a sync will occur. - - Note the we do nothing if the setting is already applied. - """ - unit = local_unit().replace('/', '-') - # Start with core config (e.g. used for signing revoked token list) - ssl_config = 0b1 - - use_https = config('use-https') - if use_https and bool_from_string(use_https): - ssl_config ^= 0b10 - - https_service_endpoints = config('https-service-endpoints') - if (https_service_endpoints and - bool_from_string(https_service_endpoints)): - ssl_config ^= 0b100 - - enable_pki = config('enable-pki') - if enable_pki and bool_from_string(enable_pki): - ssl_config ^= 0b1000 - - key = 'ssl-sync-required-%s' % (unit) - settings = {key: ssl_config} - - prev = 0b0 - rid = None - for rid in relation_ids('cluster'): - for unit in related_units(rid): - _prev = relation_get(rid=rid, unit=unit, attribute=key) or 0b0 - if _prev and _prev > prev: - prev = bin(_prev) - - if rid and prev ^ ssl_config: - if is_leader(): - clear_ssl_synced_units() - - log("Setting %s=%s" % (key, bin(ssl_config)), level=DEBUG) - relation_set(relation_id=rid, relation_settings=settings) - - -@hooks.hook('cluster-relation-joined') -def cluster_joined(rid=None, ssl_sync_request=True): - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - - 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) - - if ssl_sync_request: - send_ssl_sync_request() - - -@hooks.hook('cluster-relation-changed') -@restart_on_change(restart_map(), stopstart=True) -@update_certs_if_available -def cluster_changed(): - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - # NOTE(jamespage) re-echo passwords for peer storage - echo_whitelist = ['_passwd', 'identity-service:', - 'db-initialised', 'ssl-cert-available-updates'] - # Don't echo if leader since a re-election may be in progress. - if not is_leader(): - echo_whitelist.append('ssl-cert-master') - - log("Peer echo whitelist: %s" % (echo_whitelist), level=DEBUG) - peer_echo(includes=echo_whitelist, force=True) - - check_peer_actions() - - initialise_pki() - - if is_leader(): - # Figure out if we need to mandate a sync - units = get_ssl_sync_request_units() - synced_units = relation_get_and_migrate(attribute='ssl-synced-units', - unit=local_unit()) - diff = None - if synced_units: - synced_units = json.loads(synced_units) - diff = set(units).symmetric_difference(set(synced_units)) - else: - units = None - - if units and (not synced_units or diff): - log("New peers joined and need syncing - %s" % - (', '.join(units)), level=DEBUG) - update_all_identity_relation_units_force_sync() - else: - update_all_identity_relation_units() - - if not is_leader() and is_ssl_cert_master(): - # Force and sync and trigger a sync master re-election since we are not - # leader anymore. - force_ssl_sync() - else: - CONFIGS.write_all() - - -@hooks.hook('leader-elected') -@restart_on_change(restart_map(), stopstart=True) -def leader_elected(): - log('Unit has been elected leader.', level=DEBUG) - # When the local unit has been elected the leader, update the cron jobs - # to ensure that the cron jobs are active on this unit. - CONFIGS.write(TOKEN_FLUSH_CRON_FILE) - - update_all_identity_relation_units() - - update_all_identity_relation_units() - - -@hooks.hook('leader-settings-changed') -@restart_on_change(restart_map(), stopstart=True) -def leader_settings_changed(): - # Since minions are notified of a regime change via the - # leader-settings-changed hook, rewrite the token flush cron job to make - # sure only the leader is running the cron job. - CONFIGS.write(TOKEN_FLUSH_CRON_FILE) - - update_all_identity_relation_units() - - -@hooks.hook('ha-relation-joined') -def ha_joined(relation_id=None): - cluster_config = get_hacluster_config() - resources = { - 'res_ks_haproxy': 'lsb:haproxy', - } - resource_params = { - 'res_ks_haproxy': 'op monitor interval="5s"' - } - - if config('dns-ha'): - update_dns_ha_resource_params(relation_id=relation_id, - resources=resources, - resource_params=resource_params) - else: - vip_group = [] - for vip in cluster_config['vip'].split(): - if is_ipv6(vip): - res_ks_vip = 'ocf:heartbeat:IPv6addr' - vip_params = 'ipv6addr' - else: - res_ks_vip = 'ocf:heartbeat:IPaddr2' - vip_params = 'ip' - - iface = (get_iface_for_address(vip) or - config('vip_iface')) - netmask = (get_netmask_for_address(vip) or - config('vip_cidr')) - - if iface is not None: - vip_key = 'res_ks_{}_vip'.format(iface) - if vip_key in vip_group: - if vip not in resource_params[vip_key]: - vip_key = '{}_{}'.format(vip_key, vip_params) - else: - log("Resource '%s' (vip='%s') already exists in " - "vip group - skipping" % (vip_key, vip), WARNING) - continue - - vip_group.append(vip_key) - resources[vip_key] = res_ks_vip - resource_params[vip_key] = ( - 'params {ip}="{vip}" cidr_netmask="{netmask}"' - ' nic="{iface}"'.format(ip=vip_params, - vip=vip, - iface=iface, - netmask=netmask) - ) - - if len(vip_group) >= 1: - relation_set(relation_id=relation_id, - groups={CLUSTER_RES: ' '.join(vip_group)}) - - init_services = { - 'res_ks_haproxy': 'haproxy' - } - clones = { - 'cl_ks_haproxy': 'res_ks_haproxy' - } - relation_set(relation_id=relation_id, - init_services=init_services, - corosync_bindiface=cluster_config['ha-bindiface'], - corosync_mcastport=cluster_config['ha-mcastport'], - resources=resources, - resource_params=resource_params, - clones=clones) - - -@hooks.hook('ha-relation-changed') -@restart_on_change(restart_map(), restart_functions=restart_function_map()) -@synchronize_ca_if_changed() -def ha_changed(): - CONFIGS.write_all() - - clustered = relation_get('clustered') - if clustered: - log('Cluster configured, notifying other services and updating ' - 'keystone endpoint configuration') - if is_ssl_cert_master(): - update_all_identity_relation_units_force_sync() - else: - update_all_identity_relation_units() - - -@hooks.hook('identity-admin-relation-changed') -def admin_relation_changed(relation_id=None): - # TODO: fixup - if expect_ha() and not is_clustered(): - log("Expected to be HA but no hacluster relation yet", level=INFO) - return - relation_data = { - 'service_hostname': resolve_address(ADMIN), - 'service_port': config('service-port'), - 'service_username': config('admin-user'), - 'service_tenant_name': config('admin-role'), - 'service_region': config('region'), - 'service_protocol': 'https' if https() else 'http', - 'api_version': get_api_version(), - } - if relation_data['api_version'] > 2: - relation_data['service_user_domain_name'] = ADMIN_DOMAIN - relation_data['service_project_domain_name'] = ADMIN_DOMAIN - relation_data['service_project_name'] = ADMIN_PROJECT - relation_data['service_password'] = get_admin_passwd() - relation_set(relation_id=relation_id, **relation_data) - - -@hooks.hook('domain-backend-relation-changed') -def domain_backend_changed(relation_id=None, unit=None): - if get_api_version() < 3: - log('Domain specific backend identity configuration only supported ' - 'with Keystone v3 API, skipping domain creation and ' - 'restart.') - return - - domain_name = relation_get(attribute='domain-name', - unit=unit, - rid=relation_id) - if domain_name: - # NOTE(jamespage): Only create domain data from lead - # unit when clustered and database - # is configured and created. - if is_leader() and is_db_ready() and is_db_initialised(): - create_or_show_domain(domain_name) - # NOTE(jamespage): Deployment may have multiple domains, - # with different identity backends so - # ensure that a domain specific nonce - # is checked for restarts of keystone - restart_nonce = relation_get(attribute='restart-nonce', - unit=unit, - rid=relation_id) - domain_nonce_key = 'domain-restart-nonce-{}'.format(domain_name) - db = unitdata.kv() - if restart_nonce != db.get(domain_nonce_key): - if not is_unit_paused_set(): - if snap_install_requested(): - service_restart('snap.keystone.*') - else: - service_restart(keystone_service()) - db.set(domain_nonce_key, restart_nonce) - db.flush() - - -@synchronize_ca_if_changed(fatal=True) -def configure_https(): - ''' - Enables SSL API Apache config if appropriate and kicks identity-service - with any required api updates. - ''' - # need to write all to ensure changes to the entire request pipeline - # propagate (c-api, haprxy, apache) - CONFIGS.write_all() - # NOTE (thedac): When using snaps, nginx is installed, skip any apache2 - # config. - if snap_install_requested(): - return - if 'https' in CONFIGS.complete_contexts(): - cmd = ['a2ensite', 'openstack_https_frontend'] - check_call(cmd) - else: - cmd = ['a2dissite', 'openstack_https_frontend'] - check_call(cmd) - - -def configure_idp(idp, remote_ids, mappings_file, mapping_id, protocol): - ''' - Configure Federated Identity Provider. - See: https://developer.openstack.org/api-ref/identity/v3-ext/ - #os-federation-api - See: https://docs.openstack.org/python-keystoneclient/3.11.0/api/ - keystoneclient.v3.contrib.federation.html - ''' - - from keystone_utils import ( - get_local_endpoint, - get_admin_token - ) - import requests - - if not idp: - log("ERROR Missing Identity Provider name for %s" % protocol, - level=ERROR) - return - if not remote_ids: - log("ERROR Missing remote ids for %s provider" % protocol, level=ERROR) - return - if not mappings_file: - log("ERROR Missing mappings file for %s provider" % protocol, - level=ERROR) - return - if not mapping_id: - log("ERROR Missing mapping ID for %s provider" % protocol, level=ERROR) - return - - federation_uri = os.path.join(get_local_endpoint(), - 'OS-FEDERATION') - ks_admin_token = get_admin_token() - headers = {'X-Auth-Token': ks_admin_token} - - # Is provider configured? - federation_idp_uri = os.path.join(federation_uri, - 'identity_providers', - idp) - try: - resGet = requests.get(federation_idp_uri, headers=headers) - except requests.exceptions.RequestException as error: - log("ERROR %s, trying to GET %s, headers: %s" % - (error, federation_idp_uri, headers), level=WARNING) - return - - data = { - "identity_provider": { - "description": "Identity provider %s" % idp, - "remote_ids": remote_ids, - "enabled": True - } - } - if not resGet.ok: - # Register a new Identity Provider - resPut = requests.put(federation_idp_uri, json=data, headers=headers) - if not resPut.ok: - log("ERROR IdP PUT: %s, %s, %s, %s" % - (resPut.reason, federation_idp_uri, headers, remote_ids), - level=WARNING) - else: - # Update the Identity Provider - resPatch = requests.patch(federation_idp_uri, json=data, - headers=headers) - if not resPatch.ok: - log("ERROR IdP PATCH %s, %s, %s, %s" % - (resPatch.reason, federation_idp_uri, headers, remote_ids), - level=WARNING) - - # IdP users mapping - federation_mappings_uri = os.path.join(federation_uri, - 'mappings', - mapping_id) - # Is IdP mapping for federated users already created? - try: - resGet = requests.get(federation_mappings_uri, headers=headers) - except requests.exceptions.RequestException as resError: - log("ERROR GET %s, %s, headers: %s" % - (resError, federation_mappings_uri, headers), - level=WARNING) - if os.path.isfile(mappings_file): - with open(mappings_file) as f: - data = f.read() - if not resGet.ok: - # Create the IdP mapping for federated users - resPut = requests.put(federation_mappings_uri, - data=data, - headers=headers) - if not resPut.ok: - log("ERROR IdP PUT %s, %s" % - (resPut.reason, federation_mappings_uri), - level=WARNING) - else: - # Update the IdP mapping for federated users - resPatch = requests.patch(federation_mappings_uri, - data=data, - headers=headers) - if not resPatch.ok: - log("ERROR IdP PATCH %s, %s" % - (resPatch.reason, federation_mappings_uri), - level=WARNING) - - # IdP protocol - federation_idp_protocols_uri = os.path.join(federation_uri, - 'identity_providers', - idp, 'protocols', protocol) - # Is IdP protocol for federated users already created? - try: - resGet = requests.get(federation_idp_protocols_uri, headers=headers) - except requests.exceptions.RequestException as getProError: - log("ERROR GET %s, headers: %s, message: %s" % - (federation_idp_protocols_uri, headers, getProError), - level=WARNING) - if not resGet.ok: - # Add the protocol - data = { - "protocol": { - "mapping_id": mapping_id - } - } - resPut = requests.put(federation_idp_protocols_uri, - json=data, - headers=headers) - if not resPut.ok: - log("ERROR IdP PUT %s, %s" % - (resPut.reason, federation_idp_protocols_uri), - level=WARNING) - - -def configure_saml2(): - ''' - Configure SAML Provider. - ''' - from keystone_context import SamlContext - - configure_idp(config('shibsp-identity-provider'), - config('shibsp-idp-remote-ids'), - SAML2_MAPPING_FILE, - config('saml2-mapping'), - 'saml2') - - samlContext = SamlContext() - samlContext.configure() - - -def configure_oidc(): - ''' - Configure OIDC Provider. - ''' - configure_idp(config('oidc-identity-provider'), - [config('oidc-idp-remote-id')], - OIDC_MAPPING_FILE, - config('oidc-mapping'), - 'oidc') - - -@hooks.hook('upgrade-charm') -@restart_on_change(restart_map(), stopstart=True) -@synchronize_ca_if_changed() -@harden() -def upgrade_charm(): - status_set('maintenance', 'Installing apt packages') - apt_install(filter_installed_packages(determine_packages())) - unison.ssh_authorized_peers(user=SSH_USER, - group=SSH_USER, - peer_interface='cluster', - ensure_local_user=True) - - ensure_ssl_dirs() - - if run_in_apache(): - disable_unused_apache_sites() - - CONFIGS.write_all() - - # See LP bug 1519035 - leader_init_db_if_ready() - - update_nrpe_config() - - if is_elected_leader(CLUSTER_RES): - log('Cluster leader - ensuring endpoint configuration is up to ' - 'date', level=DEBUG) - update_all_identity_relation_units() - - -@hooks.hook('update-status') -@harden() -def update_status(): - log('Updating status.') - - -@hooks.hook('nrpe-external-master-relation-joined', - 'nrpe-external-master-relation-changed') -def update_nrpe_config(): - # 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() - _services = [] - for service in services(): - if service.startswith('snap.'): - service = service.split('.')[1] - _services.append(service) - nrpe.add_init_service_checks(nrpe_setup, _services, current_unit) - nrpe.add_haproxy_checks(nrpe_setup, current_unit) - nrpe_setup.write() - - -def main(): - try: - hooks.execute(sys.argv) - except UnregisteredHookError as e: - log('Unknown hook {} - skipping.'.format(e)) - assess_status(CONFIGS) - - -if __name__ == '__main__': - main() diff --git a/hooks/pgsql-db-relation-joined b/hooks/pgsql-db-relation-joined new file mode 120000 index 0000000000000000000000000000000000000000..dd3b3eff4b7109293b4cfd9b81f5fc49643432a0 --- /dev/null +++ b/hooks/pgsql-db-relation-joined @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file