#!/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 sys from subprocess import ( check_call, CalledProcessError, ) import time _path = os.path.dirname(os.path.realpath(__file__)) _parent = os.path.abspath(os.path.join(_path, '..')) def _add_path(path): if path not in sys.path: sys.path.insert(1, path) _add_path(_parent) from lib.swift_utils import ( SwiftProxyCharmException, register_configs, restart_map, services, determine_packages, ensure_swift_dir, SWIFT_RINGS, get_www_dir, initialize_ring, SWIFT_HA_RES, get_zone, do_openstack_upgrade, setup_ipv6, update_rings, balance_rings, fully_synced, sync_proxy_rings, broadcast_rings_available, mark_www_rings_deleted, SwiftProxyClusterRPC, get_first_available_value, all_responses_equal, ensure_www_dir_permissions, sync_builders_and_rings_if_changed, cluster_sync_rings, is_most_recent_timestamp, timestamps_available, assess_status, try_initialize_swauth, clear_storage_rings_available, determine_replicas, fetch_swift_rings_and_builders, is_ring_consumer, is_ring_distributor, set_role_ring_consumer, set_role_ring_distributor, unset_role_ring_consumer, unset_role_ring_distributor, ) from lib.swift_context import get_swift_hash import charmhelpers.contrib.openstack.utils as openstack import charmhelpers.contrib.openstack.policyd as policyd from charmhelpers.contrib.openstack.ha.utils import ( generate_ha_relation_data, ) from charmhelpers.contrib.hahelpers.cluster import ( is_elected_leader, determine_api_port, get_managed_services_and_ports, ) from charmhelpers.core.hookenv import ( config, local_unit, remote_unit, relation_set, relation_ids, relation_get, related_units, log, DEBUG, INFO, WARNING, Hooks, UnregisteredHookError, open_port, status_set, is_leader, leader_get, ) from charmhelpers.core.host import ( service_reload, service_restart, service_stop, service_start, service_resume, ) from charmhelpers.fetch import ( apt_install, apt_update, filter_installed_packages, ) from charmhelpers.payload.execd import execd_preinstall from charmhelpers.contrib.openstack.ip import ( canonical_url, PUBLIC, INTERNAL, ADMIN, ) from charmhelpers.contrib.network.ip import ( get_relation_ip, format_ipv6_addr, ) from charmhelpers.contrib.openstack.context import ADDRESS_TYPES 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, ) extra_pkgs = [ "haproxy", ] extra_pkgs_py2 = [ "python-jinja2", ] extra_pkgs_py3 = [ "python3-jinja2", ] hooks = Hooks() CONFIGS = register_configs() restart_on_change = openstack.pausable_restart_on_change @hooks.hook('install.real') @harden() def install(): status_set('maintenance', 'Executing pre-install') execd_preinstall() src = config('openstack-origin') if src != 'distro': openstack.configure_installation_source(src) status_set('maintenance', 'Installing apt packages') apt_update(fatal=True) rel = openstack.get_os_codename_install_source(src) pkgs = determine_packages(rel) apt_install(pkgs, fatal=True) apt_install(extra_pkgs, fatal=True) if (sys.version_info > (3, 0)): apt_install(extra_pkgs_py3, fatal=True) else: apt_install(extra_pkgs_py2, fatal=True) ensure_swift_dir() # configure a directory on webserver for distributing rings. ensure_www_dir_permissions(get_www_dir()) # call the policy overrides handler which will install any policy overrides policyd.maybe_do_policyd_overrides( openstack.os_release('swift-proxy'), 'swift') @hooks.hook('config-changed') @restart_on_change(restart_map()) @harden() def config_changed(): if is_elected_leader(SWIFT_HA_RES): log("Leader established, generating ring builders", level=INFO) # initialize new storage rings. for ring, path in SWIFT_RINGS.items(): if not os.path.exists(path): initialize_ring(path, config('partition-power'), determine_replicas(ring), config('min-hours')) if config('prefer-ipv6'): status_set('maintenance', 'Configuring ipv6') setup_ipv6() configure_https() open_port(config('bind-port')) update_nrpe_config() # Determine whether or not we should do an upgrade. if not config('action-managed-upgrade') and \ openstack.openstack_upgrade_available('swift'): do_openstack_upgrade(CONFIGS) status_set('maintenance', 'Running openstack upgrade') if not leader_get('swift-proxy-rings-consumer'): status_set('maintenance', 'Updating and (maybe) balancing rings') update_rings(min_part_hours=config('min-hours'), replicas=config('replicas')) if not config('disable-ring-balance') and is_elected_leader(SWIFT_HA_RES): # Try ring balance. If rings are balanced, no sync will occur. balance_rings() for r_id in relation_ids('identity-service'): keystone_joined(relid=r_id) for r_id in relation_ids('cluster'): cluster_joined(relation_id=r_id) for r_id in relation_ids('object-store'): object_store_joined(relation_id=r_id) for r_id in relation_ids('amqp'): amqp_joined(relation_id=r_id) for r_id in relation_ids('ha'): ha_relation_joined(relation_id=r_id) try_initialize_swauth() # call the policy overrides handler which will install any policy overrides policyd.maybe_do_policyd_overrides( openstack.os_release('swift-proxy'), 'swift') @hooks.hook('identity-service-relation-joined') def keystone_joined(relid=None): port = config('bind-port') admin_url = '{}:{}'.format(canonical_url(CONFIGS, ADMIN), port) internal_url = ('{}:{}/v1/AUTH_$(tenant_id)s' .format(canonical_url(CONFIGS, INTERNAL), port)) public_url = ('{}:{}/v1/AUTH_$(tenant_id)s' .format(canonical_url(CONFIGS, PUBLIC), port)) region = config('region') s3_public_url = ('{}:{}'.format(canonical_url(CONFIGS, PUBLIC), port)) s3_internal_url = ('{}:{}'.format(canonical_url(CONFIGS, INTERNAL), port)) s3_admin_url = '{}:{}'.format(canonical_url(CONFIGS, ADMIN), port) relation_set(relation_id=relid, region=None, public_url=None, internal_url=None, admin_url=None, service=None, swift_service='swift', swift_region=region, swift_public_url=public_url, swift_internal_url=internal_url, swift_admin_url=admin_url, s3_service='s3', s3_region=region, s3_public_url=s3_public_url, s3_admin_url=s3_admin_url, s3_internal_url=s3_internal_url) @hooks.hook('identity-service-relation-changed') @restart_on_change(restart_map()) def keystone_changed(): configure_https() @hooks.hook('swift-storage-relation-joined') def storage_joined(rid=None): if not is_elected_leader(SWIFT_HA_RES): log("New storage relation joined - stopping proxy until ring builder " "synced", level=INFO) service_stop('swift-proxy') # This unit is not currently responsible for distributing rings but # may become so at some time in the future so we do this to avoid the # possibility of storage nodes getting out-of-date rings by deprecating # any existing ones from the www dir. mark_www_rings_deleted() clear_storage_rings_available() try_initialize_swauth() # NOTE(jamespage): Ensure private-address is set to any network-space # binding provided by the charm user. relation_set( relation_id=rid, relation_settings={'private-address': get_relation_ip('swift-storage')} ) def get_host_ip(rid=None, unit=None): addr = relation_get(rid=rid, unit=unit).get('ip_cls') if not addr: addr = relation_get('private-address', rid=rid, unit=unit) if config('prefer-ipv6'): host_ip = format_ipv6_addr(addr) if host_ip: return host_ip else: msg = ("Did not get IPv6 address from storage relation " "(got={})".format(addr)) log(msg, level=WARNING) return openstack.get_host_ip(addr) def update_rsync_acls(): """Get Host IP of each storage unit and broadcast acl to all units.""" hosts = [] if not is_elected_leader(SWIFT_HA_RES): log("Skipping rsync acl update since not leader", level=DEBUG) return # Get all unit addresses for rid in relation_ids('swift-storage'): for unit in related_units(rid): hosts.append(get_host_ip(rid=rid, unit=unit)) rsync_hosts = ' '.join(hosts) log("Broadcasting acl '{}' to all storage units".format(rsync_hosts), level=DEBUG) # We add a timestamp so that the storage units know which is the newest settings = {'rsync_allowed_hosts': rsync_hosts, 'timestamp': time.time()} for rid in relation_ids('swift-storage'): relation_set(relation_id=rid, **settings) @hooks.hook('swift-storage-relation-changed') @restart_on_change(restart_map()) def storage_changed(): """Storage relation. Only the leader unit can update and distribute rings so if we are not the leader we ignore this event and wait for a resync request from the leader. """ if not is_elected_leader(SWIFT_HA_RES): log("Not the leader - deferring storage relation change to leader " "unit.", level=DEBUG) return log("Storage relation changed -processing", level=DEBUG) host_ip = get_host_ip() if not host_ip: log("No host ip found in storage relation - deferring storage " "relation", level=WARNING) return update_rsync_acls() zone = get_zone(config('zone-assignment')) node_settings = { 'ip': host_ip, 'zone': zone, 'account_port': relation_get('account_port'), 'object_port': relation_get('object_port'), 'container_port': relation_get('container_port'), } node_repl_settings = { 'ip_rep': relation_get('ip_rep'), 'region': relation_get('region'), 'account_port_rep': relation_get('account_port_rep'), 'object_port_rep': relation_get('object_port_rep'), 'container_port_rep': relation_get('container_port_rep')} if any(node_repl_settings.values()): node_settings.update(node_repl_settings) if None in node_settings.values(): missing = [k for k, v in node_settings.items() if v is None] log("Relation not ready - some required values not provided by " "relation (missing={})".format(', '.join(missing)), level=INFO) return None for k in ['region', 'zone', 'account_port', 'account_port_rep', 'object_port', 'object_port_rep', 'container_port', 'container_port_rep']: if node_settings.get(k) is not None: node_settings[k] = int(node_settings[k]) CONFIGS.write_all() # Allow for multiple devs per unit, passed along as a : separated list # Update and balance rings. nodes = [] devs = relation_get('device') if devs: for dev in devs.split(':'): node = {k: v for k, v in node_settings.items()} node['device'] = dev nodes.append(node) update_rings(nodes) if not openstack.is_unit_paused_set(): # Restart proxy here in case no config changes made (so # restart_on_change() ineffective). service_restart('swift-proxy') @hooks.hook('swift-storage-relation-broken') @restart_on_change(restart_map()) def storage_broken(): CONFIGS.write_all() @hooks.hook('object-store-relation-joined') def object_store_joined(relation_id=None): relation_data = { 'swift-url': "{}:{}".format(canonical_url(CONFIGS, INTERNAL), config('bind-port')) } relation_set(relation_id=relation_id, **relation_data) @hooks.hook('cluster-relation-joined') def cluster_joined(relation_id=None): 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=relation_id, relation_settings=settings) def is_all_peers_stopped(responses): """Establish whether all peers have stopped their proxy services. Each peer unit will set stop-proxy-service-ack to rq value to indicate that it has stopped its proxy service. We wait for all units to be stopped before triggering a sync. Peer services will be restarted once their rings are synced with the leader. To be safe, default expectation is that api is still running. """ rq_key = SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC ack_key = SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC_ACK token = relation_get(attribute=rq_key, unit=local_unit()) if not token or token != responses[0].get(ack_key): log("Token mismatch, rq and ack tokens differ (expected ack={}, " "got={})".format(token, responses[0].get(ack_key)), level=DEBUG) return False if not all_responses_equal(responses, ack_key): log("Not all ack responses are equal. Either we are still waiting " "for responses or we were not the request originator.", level=DEBUG) return False return True def cluster_leader_actions(): """Cluster relation hook actions to be performed by leader units. NOTE: must be called by leader from cluster relation hook. """ log("Cluster changed by unit={} (local is leader)".format(remote_unit()), level=DEBUG) rx_settings = relation_get() or {} tx_settings = relation_get(unit=local_unit()) or {} rx_rq_token = rx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC) rx_ack_token = rx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC_ACK) tx_rq_token = tx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC) tx_ack_token = tx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC_ACK) rx_leader_changed = \ rx_settings.get(SwiftProxyClusterRPC.KEY_NOTIFY_LEADER_CHANGED) if rx_leader_changed: log("Leader change notification received and this is leader so " "retrying sync.", level=INFO) # FIXME: check that we were previously part of a successful sync to # ensure we have good rings. cluster_sync_rings(peers_only=tx_settings.get('peers-only', False), token=rx_leader_changed) return rx_resync_request = \ rx_settings.get(SwiftProxyClusterRPC.KEY_REQUEST_RESYNC) resync_request_ack_key = SwiftProxyClusterRPC.KEY_REQUEST_RESYNC_ACK tx_resync_request_ack = tx_settings.get(resync_request_ack_key) if rx_resync_request and tx_resync_request_ack != rx_resync_request: log("Unit '{}' has requested a resync".format(remote_unit()), level=INFO) cluster_sync_rings(peers_only=True) relation_set(**{resync_request_ack_key: rx_resync_request}) return # If we have received an ack token ensure it is not associated with a # request we received from another peer. If it is, this would indicate # a leadership change during a sync and this unit will abort the sync or # attempt to restore the original leader so to be able to complete the # sync. if rx_ack_token and rx_ack_token == tx_rq_token: # Find out if all peer units have been stopped. responses = [] for rid in relation_ids('cluster'): for unit in related_units(rid): responses.append(relation_get(rid=rid, unit=unit)) # Ensure all peers stopped before starting sync if is_all_peers_stopped(responses): key = 'peers-only' if not all_responses_equal(responses, key, must_exist=False): msg = ("Did not get equal response from every peer unit for " "'{}'".format(key)) raise SwiftProxyCharmException(msg) peers_only = bool(get_first_available_value(responses, key, default=0)) log("Syncing rings and builders (peers-only={})" .format(peers_only), level=DEBUG) broadcast_rings_available(broker_token=rx_ack_token, storage=not peers_only) else: key = SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC_ACK acks = ', '.join([rsp[key] for rsp in responses if key in rsp]) log("Not all peer apis stopped - skipping sync until all peers " "ready (current='{}', token='{}')".format(acks, tx_ack_token), level=INFO) elif ((rx_ack_token and (rx_ack_token == tx_ack_token)) or (rx_rq_token and (rx_rq_token == rx_ack_token))): log("It appears that the cluster leader has changed mid-sync - " "stopping proxy service", level=WARNING) service_stop('swift-proxy') broker = rx_settings.get('builder-broker') if broker: # If we get here, manual intervention will be required in order # to restore the cluster. raise SwiftProxyCharmException( "Failed to restore previous broker '{}' as leader" .format(broker)) else: raise SwiftProxyCharmException( "No builder-broker on rx_settings relation from '{}' - " "unable to attempt leader restore".format(remote_unit())) else: log("Not taking any sync actions", level=DEBUG) CONFIGS.write_all() def cluster_non_leader_actions(): """Cluster relation hook actions to be performed by non-leader units. NOTE: must be called by non-leader from cluster relation hook. """ log("Cluster changed by unit={} (local is non-leader)" .format(remote_unit()), level=DEBUG) rx_settings = relation_get() or {} tx_settings = relation_get(unit=local_unit()) or {} token = rx_settings.get(SwiftProxyClusterRPC.KEY_NOTIFY_LEADER_CHANGED) if token: log("Leader-changed notification received from peer unit. Since " "this most likely occurred during a ring sync proxies will " "be disabled until the leader is restored and a fresh sync " "request is set out", level=WARNING) service_stop("swift-proxy") return rx_rq_token = rx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC) # Check whether we have been requested to stop proxy service if rx_rq_token: log("Peer request to stop proxy service received ({}) - sending ack" .format(rx_rq_token), level=INFO) service_stop('swift-proxy') peers_only = rx_settings.get('peers-only', None) rq = SwiftProxyClusterRPC().stop_proxy_ack(echo_token=rx_rq_token, echo_peers_only=peers_only) relation_set(relation_settings=rq) return # Check if there are any builder files we can sync from the leader. broker = rx_settings.get('builder-broker', None) broker_token = rx_settings.get('broker-token', None) broker_timestamp = rx_settings.get('broker-timestamp', None) tx_ack_token = tx_settings.get(SwiftProxyClusterRPC.KEY_STOP_PROXY_SVC_ACK) if not broker: log("No ring/builder update available", level=DEBUG) if not openstack.is_unit_paused_set(): service_start('swift-proxy') return elif broker_token: if tx_ack_token: if broker_token == tx_ack_token: log("Broker and ACK tokens match ({})".format(broker_token), level=DEBUG) else: log("Received ring/builder update notification but tokens do " "not match (broker-token={}/ack-token={})" .format(broker_token, tx_ack_token), level=WARNING) return else: log("Broker token available without handshake, assuming we just " "joined and rings won't change", level=DEBUG) else: log("Not taking any sync actions", level=DEBUG) return # If we upgrade from cluster that did not use timestamps, the new peer will # need to request a re-sync from the leader if not is_most_recent_timestamp(broker_timestamp): if not timestamps_available(excluded_unit=remote_unit()): log("Requesting resync") rq = SwiftProxyClusterRPC().request_resync(broker_token) relation_set(relation_settings=rq) else: log("Did not receive most recent broker timestamp but timestamps " "are available - waiting for next timestamp", level=INFO) return log("Ring/builder update available", level=DEBUG) builders_only = int(rx_settings.get('sync-only-builders', 0)) path = os.path.basename(get_www_dir()) try: sync_proxy_rings('http://{}/{}'.format(broker, path), rings=not builders_only) except CalledProcessError: log("Ring builder sync failed, builders not yet available - " "leader not ready?", level=WARNING) return # Re-enable the proxy once all builders and rings are synced if fully_synced(): log("Ring builders synced - starting proxy", level=INFO) CONFIGS.write_all() if not openstack.is_unit_paused_set(): service_start('swift-proxy') else: log("Not all builders and rings synced yet - waiting for peer sync " "before starting proxy", level=INFO) @hooks.hook('cluster-relation-changed') @restart_on_change(restart_map()) def cluster_changed(): if is_elected_leader(SWIFT_HA_RES): cluster_leader_actions() else: cluster_non_leader_actions() @hooks.hook('ha-relation-changed') @sync_builders_and_rings_if_changed def ha_relation_changed(): clustered = relation_get('clustered') if clustered: log("Cluster configured, notifying other services and updating " "keystone endpoint configuration", level=INFO) # Tell all related services to start using # the VIP instead for r_id in relation_ids('identity-service'): keystone_joined(relid=r_id) @hooks.hook('ha-relation-joined') def ha_relation_joined(relation_id=None): settings = generate_ha_relation_data('swift') relation_set(relation_id=relation_id, **settings) 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() if 'https' in CONFIGS.complete_contexts(): cmd = ['a2ensite', 'openstack_https_frontend'] check_call(cmd) else: cmd = ['a2dissite', 'openstack_https_frontend'] check_call(cmd) # Apache 2.4 required enablement of configuration if os.path.exists('/usr/sbin/a2enconf'): check_call(['a2enconf', 'swift-rings']) if not openstack.is_unit_paused_set(): # TODO: improve this by checking if local CN certs are available # first then checking reload status (see LP #1433114). service_reload('apache2', restart_on_failure=True) for rid in relation_ids('identity-service'): keystone_joined(relid=rid) env_vars = {'OPENSTACK_SERVICE_SWIFT': 'proxy-server', 'OPENSTACK_PORT_API': config('bind-port'), 'OPENSTACK_PORT_MEMCACHED': 11211} openstack.save_script_rc(**env_vars) @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() nrpe.add_init_service_checks(nrpe_setup, services(), current_unit) nrpe.add_haproxy_checks(nrpe_setup, current_unit) api_port = determine_api_port(config('bind-port'), singlenode_mode=True) nrpe_setup.add_check( shortname="swift-proxy-healthcheck", description="Check Swift Proxy Healthcheck", check_cmd="/usr/lib/nagios/plugins/check_http \ -I localhost -u /healthcheck -p {} \ -e \"OK\"".format(api_port) ) nrpe_setup.write() @hooks.hook('upgrade-charm') @harden() def upgrade_charm(): rel = openstack.get_os_codename_install_source(config('openstack-origin')) pkgs = determine_packages(rel) new_packages = filter_installed_packages(pkgs) if new_packages: apt_install(new_packages) update_rsync_acls() # call the policy overrides handler which will install any policy overrides policyd.maybe_do_policyd_overrides( openstack.os_release('swift-proxy'), 'swift') @hooks.hook('update-status') @harden() def update_status(): log('Updating status.') @hooks.hook('amqp-relation-joined') def amqp_joined(relation_id=None): relation_set(relation_id=relation_id, username=config('rabbit-user'), vhost=config('rabbit-vhost')) @hooks.hook('amqp-relation-changed', 'amqp-relation-departed') @restart_on_change(restart_map()) def amqp_changed(): CONFIGS.write_all() @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') @restart_on_change(restart_map(), stopstart=True) def certs_changed(): process_certificates('swift', None, None) configure_https() @hooks.hook('pre-series-upgrade') def pre_series_upgrade(): log("Running prepare series upgrade hook", "INFO") if not openstack.is_unit_paused_set(): for service in services(): stopped = service_stop(service) if not stopped: raise Exception("{} didn't stop cleanly.".format(service)) openstack.set_unit_paused() openstack.set_unit_upgrading() @hooks.hook('post-series-upgrade') def post_series_upgrade(): log("Running complete series upgrade hook", "INFO") openstack.clear_unit_paused() openstack.clear_unit_upgrading() _services, _ = get_managed_services_and_ports(services(), []) if not openstack.is_unit_paused_set(): for service in _services: service_resume(service) started = service_start(service) if not started: raise Exception("{} didn't start cleanly.".format(service)) @hooks.hook('rings-distributor-relation-joined') def rings_distributor_joined(): if is_ring_consumer(): msg = ("Swift Proxy cannot act as both rings distributor and rings " "consumer") status_set('blocked', msg) raise SwiftProxyCharmException(msg) if is_leader(): set_role_ring_distributor() @hooks.hook('rings-distributor-relation-changed') def rings_distributor_changed(): broadcast_rings_available() @hooks.hook('rings-distributor-relation-departed') def rings_distributor_departed(): if is_leader(): unset_role_ring_distributor() @hooks.hook('rings-consumer-relation-joined') def rings_consumer_joined(): if is_ring_distributor(): msg = ("Swift Proxy cannot act as both rings distributor and rings " "consumer") status_set('blocked', msg) elif is_ring_consumer(): msg = "Swift Proxy already acting as rings consumer" status_set('blocked', msg) elif is_leader(): set_role_ring_consumer() @hooks.hook('rings-consumer-relation-changed') @sync_builders_and_rings_if_changed @restart_on_change(restart_map()) def rings_consumer_changed(): """Based on 'swift_storage_relation_changed' function from the swift-storage charm.""" rings_url = relation_get('rings_url') swift_hash = relation_get('swift_hash') if not all([rings_url, swift_hash]): log('rings_consumer_relation_changed: Peer not ready?') return if swift_hash != get_swift_hash(): msg = "Swift hash has to be unique in multi-region setup" status_set('blocked', msg) raise SwiftProxyCharmException(msg) try: fetch_swift_rings_and_builders(rings_url) except CalledProcessError: log("Failed to sync rings from {} - no longer available from that " "unit?".format(rings_url), level=WARNING) broadcast_rings_available() @hooks.hook('rings-consumer-relation-departed') def rings_consumer_departed(): if is_leader(): unset_role_ring_consumer() def main(): try: hooks.execute(sys.argv) except UnregisteredHookError as e: log('Unknown hook {} - skipping.'.format(e), level=DEBUG) assess_status(CONFIGS) if __name__ == '__main__': main()