charm-designate/src/reactive/designate_handlers.py

230 lines
8.2 KiB
Python

# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import subprocess
import charm.openstack.designate as designate
import charms.reactive as reactive
import charms.reactive.relations as relations
import charmhelpers.core.hookenv as hookenv
import charmhelpers.contrib.network.ip as ip
from charms_openstack.charm import provide_charm_instance
from charms_openstack.charm.utils import is_data_changed
# If either dns-backend.available is set OR config('dns-slaves') is valid, then
# the following state will be set.
DNS_CONFIG_AVAILABLE = 'dns-config.available'
COMPLETE_INTERFACE_STATES = [
DNS_CONFIG_AVAILABLE,
'shared-db.available',
'identity-service.available',
'amqp.available',
'coordinator-memcached.available',
]
@reactive.hook('config-changed')
def check_dns_slaves():
"""verify if the config('dns-slaves') is valid and set or remove the state
accordingly. Note, that hooks run BEFORE the reactive handlers so this
should happen first during a hook.
"""
if hookenv.config('dns-slaves'):
with provide_charm_instance() as instance:
if not instance.options.invalid_pool_config():
reactive.set_state('dns-slaves-config-valid')
return
reactive.remove_state('dns-slaves-config-valid')
@reactive.when_any('dns-slaves-config-valid',
'dns-backend.available')
def set_dns_config_available(*args):
reactive.set_state(DNS_CONFIG_AVAILABLE)
@reactive.when_none('dns-slaves-config-valid',
'dns-backend.available')
def clear_dns_config_available():
reactive.remove_state(DNS_CONFIG_AVAILABLE)
@reactive.when_not('installed')
def install_packages():
"""Install charms packages"""
with provide_charm_instance() as instance:
instance.install()
reactive.set_state('installed')
reactive.remove_state('shared-db.setup')
reactive.remove_state('base-config.rendered')
reactive.remove_state('db.synched')
reactive.remove_state('pool-manager-cache.synched')
@reactive.when('amqp.connected')
def setup_amqp_req(amqp):
"""Send request for rabbit access and vhost"""
amqp.request_access(username='designate',
vhost='openstack')
@reactive.when('shared-db.connected')
@reactive.when_not('shared-db.setup')
def setup_database(database):
"""Send request designate accounts and dbs"""
hostname = None
if database.access_network():
hostname = ip.get_address_in_network(database.access_network())
database.configure('designate', 'designate', prefix='designate',
hostname=hostname)
database.configure('dpm', 'dpm', prefix='dpm',
hostname=hostname)
if database.base_data_complete():
reactive.set_state('shared-db.setup')
@reactive.when('identity-service.connected')
def maybe_setup_endpoint(keystone):
"""When the keystone interface connects, register this unit in the keystone
catalogue.
"""
with provide_charm_instance() as instance:
args = [instance.service_type, instance.region, instance.public_url,
instance.internal_url, instance.admin_url]
# This function checkes that the data has changed before sending it
with is_data_changed('charms.openstack.register-endpoints', args) as c:
if c:
keystone.register_endpoints(*args)
@reactive.when('cluster.connected')
def expose_rndc_address(cluster):
rndc_address = ip.get_relation_ip('dns-backend')
cluster.set_address('rndc', rndc_address)
@reactive.when_not('base-config.rendered')
@reactive.when(*COMPLETE_INTERFACE_STATES)
def configure_designate_basic(*args):
"""Configure the minimum to boostrap designate"""
# If cluster relation is available it needs to passed in
cluster = relations.endpoint_from_flag('cluster.available')
if cluster is not None:
args = args + (cluster, )
dns_backend = relations.endpoint_from_flag('dns-backend.available')
if dns_backend is not None:
args = args + (dns_backend, )
with provide_charm_instance() as instance:
instance.render_base_config(args)
reactive.set_state('base-config.rendered')
@reactive.when_not('db.synched')
@reactive.when('base-config.rendered')
@reactive.when(*COMPLETE_INTERFACE_STATES)
def run_db_migration(*args):
"""Run database migrations"""
with provide_charm_instance() as instance:
instance.db_sync()
if instance.db_sync_done():
reactive.set_state('db.synched')
@reactive.when_not('pool-manager-cache.synched')
@reactive.when('base-config.rendered')
@reactive.when(*COMPLETE_INTERFACE_STATES)
def sync_pool_manager_cache(*args):
with provide_charm_instance() as instance:
instance.pool_manager_cache_sync()
if instance.pool_manager_cache_sync_done():
reactive.set_state('pool-manager-cache.synched')
@reactive.when('cluster.available')
def update_peers(cluster):
"""Inform designate peers about this unit"""
with provide_charm_instance() as instance:
# This function ONLY updates the peers if the data has changed. Thus
# it's okay to call it on every hook invocation.
instance.update_peers(cluster)
@reactive.when('db.synched')
@reactive.when('pool-manager-cache.synched')
@reactive.when(*COMPLETE_INTERFACE_STATES)
def configure_designate_full(*args):
"""Write out all designate config include bootstrap domain info"""
# If cluster relation is available it needs to passed in
cluster = relations.endpoint_from_flag('cluster.available')
if cluster is not None:
args = args + (cluster, )
dns_backend = relations.endpoint_from_flag('dns-backend.available')
if dns_backend is not None:
args = args + (dns_backend, )
with provide_charm_instance() as instance:
instance.upgrade_if_available(args)
instance.configure_ssl()
instance.render_full_config(args)
try:
# the following function should only run once for the leader.
if instance.configure_sink():
instance.create_initial_servers_and_domains()
_render_sink_configs(instance, args)
instance.render_rndc_keys()
instance.update_pools()
except subprocess.CalledProcessError as e:
hookenv.log("ensure_api_responding() errored out: {}"
.format(str(e)),
level=hookenv.ERROR)
def _render_sink_configs(instance, interfaces_list):
"""Helper: use the singleton from the DesignateCharm to render sink configs
@param instance: an instance that has the render_with_intefaces() method
@param interfaces_list: List of instances of interface classes.
@returns: None
"""
configs = [designate.NOVA_SINK_FILE,
designate.NEUTRON_SINK_FILE,
designate.DESIGNATE_DEFAULT]
instance.render_with_interfaces(interfaces_list, configs=configs)
@reactive.when('ha.connected')
def cluster_connected(hacluster):
"""Configure HA resources in corosync"""
with provide_charm_instance() as instance:
instance.configure_ha_resources(hacluster)
@reactive.when('dnsaas.connected')
def expose_endpoint(endpoint):
with provide_charm_instance() as instance:
endpoint.expose_endpoint(instance.public_url)
@reactive.when_not('dont-set-assess-status')
def run_assess_status_on_every_hook():
"""The call to charm instance.assess_status() sets up the assess status
functionality to be called atexit() of the charm. i.e. as the last thing
it does. Thus, this handle gets called for EVERY hook invocation, which
means that no other handlers need to call the assess_status function.
"""
with provide_charm_instance() as instance:
instance.assess_status()