The allowed_units key needs to be set by all units rather than just the leader incase the leader dies and the setting is lost. Change-Id: I0ed687e5615ee1de5ea34c9169e2728fe557886b Closes-Bug: #1712383
		
			
				
	
	
		
			781 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			781 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/python
 | 
						|
# TODO: Support changes to root and sstuser passwords
 | 
						|
import sys
 | 
						|
import json
 | 
						|
import os
 | 
						|
import socket
 | 
						|
import time
 | 
						|
 | 
						|
from charmhelpers.core.hookenv import (
 | 
						|
    Hooks, UnregisteredHookError,
 | 
						|
    is_relation_made,
 | 
						|
    log,
 | 
						|
    relation_get,
 | 
						|
    relation_set,
 | 
						|
    relation_id,
 | 
						|
    relation_ids,
 | 
						|
    related_units,
 | 
						|
    unit_get,
 | 
						|
    config,
 | 
						|
    remote_unit,
 | 
						|
    relation_type,
 | 
						|
    DEBUG,
 | 
						|
    INFO,
 | 
						|
    WARNING,
 | 
						|
    is_leader,
 | 
						|
    network_get_primary_address,
 | 
						|
    charm_name,
 | 
						|
    leader_get,
 | 
						|
    open_port,
 | 
						|
    status_set,
 | 
						|
)
 | 
						|
from charmhelpers.core.host import (
 | 
						|
    service_restart,
 | 
						|
    service_start,
 | 
						|
    file_hash,
 | 
						|
    lsb_release,
 | 
						|
    CompareHostReleases,
 | 
						|
)
 | 
						|
from charmhelpers.core.templating import render
 | 
						|
from charmhelpers.fetch import (
 | 
						|
    apt_update,
 | 
						|
    apt_install,
 | 
						|
    add_source,
 | 
						|
)
 | 
						|
from charmhelpers.contrib.peerstorage import (
 | 
						|
    peer_echo,
 | 
						|
    peer_store_and_set,
 | 
						|
    peer_retrieve_by_prefix,
 | 
						|
)
 | 
						|
from charmhelpers.contrib.database.mysql import (
 | 
						|
    PerconaClusterHelper,
 | 
						|
)
 | 
						|
from charmhelpers.contrib.hahelpers.cluster import (
 | 
						|
    is_elected_leader,
 | 
						|
    is_clustered,
 | 
						|
    DC_RESOURCE_NAME,
 | 
						|
    get_hacluster_config,
 | 
						|
)
 | 
						|
from charmhelpers.payload.execd import execd_preinstall
 | 
						|
from charmhelpers.contrib.network.ip import (
 | 
						|
    get_address_in_network,
 | 
						|
    get_iface_for_address,
 | 
						|
    get_netmask_for_address,
 | 
						|
    get_ipv6_addr,
 | 
						|
    is_address_in_network,
 | 
						|
    resolve_network_cidr,
 | 
						|
)
 | 
						|
from charmhelpers.contrib.charmsupport import nrpe
 | 
						|
from charmhelpers.contrib.hardening.harden import harden
 | 
						|
from charmhelpers.contrib.hardening.mysql.checks import run_mysql_checks
 | 
						|
from charmhelpers.contrib.openstack.utils import (
 | 
						|
    is_unit_paused_set,
 | 
						|
)
 | 
						|
from charmhelpers.contrib.openstack.ha.utils import (
 | 
						|
    update_dns_ha_resource_params,
 | 
						|
)
 | 
						|
 | 
						|
from percona_utils import (
 | 
						|
    determine_packages,
 | 
						|
    setup_percona_repo,
 | 
						|
    resolve_hostname_to_ip,
 | 
						|
    get_cluster_hosts,
 | 
						|
    configure_sstuser,
 | 
						|
    configure_mysql_root_password,
 | 
						|
    relation_clear,
 | 
						|
    assert_charm_supports_ipv6,
 | 
						|
    unit_sorted,
 | 
						|
    get_db_helper,
 | 
						|
    mark_seeded, seeded,
 | 
						|
    install_mysql_ocf,
 | 
						|
    notify_bootstrapped,
 | 
						|
    is_bootstrapped,
 | 
						|
    get_wsrep_value,
 | 
						|
    assess_status,
 | 
						|
    register_configs,
 | 
						|
    resolve_cnf_file,
 | 
						|
    create_binlogs_directory,
 | 
						|
    bootstrap_pxc,
 | 
						|
    get_cluster_host_ip,
 | 
						|
    client_node_is_ready,
 | 
						|
    leader_node_is_ready,
 | 
						|
    DEFAULT_MYSQL_PORT,
 | 
						|
    sst_password,
 | 
						|
    root_password,
 | 
						|
    pxc_installed,
 | 
						|
    update_bootstrap_uuid,
 | 
						|
    LeaderNoBootstrapUUIDError,
 | 
						|
    update_root_password,
 | 
						|
)
 | 
						|
 | 
						|
from charmhelpers.core.unitdata import kv
 | 
						|
 | 
						|
hooks = Hooks()
 | 
						|
 | 
						|
RES_MONITOR_PARAMS = ('params user="sstuser" password="%(sstpass)s" '
 | 
						|
                      'pid="/var/run/mysqld/mysqld.pid" '
 | 
						|
                      'socket="/var/run/mysqld/mysqld.sock" '
 | 
						|
                      'max_slave_lag="5" '
 | 
						|
                      'cluster_type="pxc" '
 | 
						|
                      'op monitor interval="1s" timeout="30s" '
 | 
						|
                      'OCF_CHECK_LEVEL="1"')
 | 
						|
 | 
						|
INITIAL_CLIENT_UPDATE_KEY = 'initial_client_update_done'
 | 
						|
 | 
						|
 | 
						|
def install_percona_xtradb_cluster():
 | 
						|
    '''Attempt PXC install based on seeding of passwords for users'''
 | 
						|
    if pxc_installed():
 | 
						|
        log('MySQL already installed, skipping')
 | 
						|
        return
 | 
						|
 | 
						|
    _root_password = root_password()
 | 
						|
    _sst_password = sst_password()
 | 
						|
    if not _root_password or not _sst_password:
 | 
						|
        log('Passwords not seeded, unable to install MySQL at this'
 | 
						|
            ' point so deferring installation')
 | 
						|
        return
 | 
						|
    configure_mysql_root_password(_root_password)
 | 
						|
 | 
						|
    apt_install(determine_packages(), fatal=True)
 | 
						|
 | 
						|
    configure_sstuser(_sst_password)
 | 
						|
    if config('harden') and 'mysql' in config('harden'):
 | 
						|
        run_mysql_checks()
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('install.real')
 | 
						|
@harden()
 | 
						|
def install():
 | 
						|
    execd_preinstall()
 | 
						|
    _release = lsb_release()['DISTRIB_CODENAME'].lower()
 | 
						|
    if (config('source') is None and
 | 
						|
            CompareHostReleases(_release) < 'trusty'):
 | 
						|
        setup_percona_repo()
 | 
						|
    elif config('source') is not None:
 | 
						|
        add_source(config('source'), config('key'))
 | 
						|
    apt_update(fatal=True)
 | 
						|
 | 
						|
    install_percona_xtradb_cluster()
 | 
						|
 | 
						|
 | 
						|
def render_config(clustered=False, hosts=None):
 | 
						|
    if hosts is None:
 | 
						|
        hosts = []
 | 
						|
 | 
						|
    config_file = resolve_cnf_file()
 | 
						|
    if not os.path.exists(os.path.dirname(config_file)):
 | 
						|
        os.makedirs(os.path.dirname(config_file))
 | 
						|
 | 
						|
    context = {
 | 
						|
        'cluster_name': 'juju_cluster',
 | 
						|
        'private_address': get_cluster_host_ip(),
 | 
						|
        'clustered': clustered,
 | 
						|
        'cluster_hosts': ",".join(hosts),
 | 
						|
        'sst_method': config('sst-method'),
 | 
						|
        'sst_password': sst_password(),
 | 
						|
        'innodb_file_per_table': config('innodb-file-per-table'),
 | 
						|
        'table_open_cache': config('table-open-cache'),
 | 
						|
        'lp1366997_workaround': config('lp1366997-workaround'),
 | 
						|
        'binlogs_path': config('binlogs-path'),
 | 
						|
        'enable_binlogs': config('enable-binlogs'),
 | 
						|
        'binlogs_max_size': config('binlogs-max-size'),
 | 
						|
        'binlogs_expire_days': config('binlogs-expire-days'),
 | 
						|
        'performance_schema': config('performance-schema'),
 | 
						|
    }
 | 
						|
 | 
						|
    if config('prefer-ipv6'):
 | 
						|
        # NOTE(hopem): this is a kludge to get percona working with ipv6.
 | 
						|
        # See lp 1380747 for more info. This is intended as a stop gap until
 | 
						|
        # percona package is fixed to support ipv6.
 | 
						|
        context['bind_address'] = '::'
 | 
						|
        context['wsrep_provider_options'] = 'gmcast.listen_addr=tcp://:::4567;'
 | 
						|
        context['ipv6'] = True
 | 
						|
    else:
 | 
						|
        context['ipv6'] = False
 | 
						|
 | 
						|
    context.update(PerconaClusterHelper().parse_config())
 | 
						|
    render(os.path.basename(config_file),
 | 
						|
           config_file, context, perms=0o444)
 | 
						|
 | 
						|
 | 
						|
def render_config_restart_on_changed(clustered, hosts, bootstrap=False):
 | 
						|
    """Render mysql config and restart mysql service if file changes as a
 | 
						|
    result.
 | 
						|
 | 
						|
    If bootstrap is True we do a bootstrap-pxc in order to bootstrap the
 | 
						|
    percona cluster. This should only be performed once at cluster creation
 | 
						|
    time.
 | 
						|
 | 
						|
    If percona is already bootstrapped we can get away with just ensuring that
 | 
						|
    it is started so long as the new node to be added is guaranteed to have
 | 
						|
    been restarted so as to apply the new config.
 | 
						|
    """
 | 
						|
    config_file = resolve_cnf_file()
 | 
						|
    pre_hash = file_hash(config_file)
 | 
						|
    render_config(clustered, hosts)
 | 
						|
    create_binlogs_directory()
 | 
						|
    update_db_rels = False
 | 
						|
    if file_hash(config_file) != pre_hash or bootstrap:
 | 
						|
        if bootstrap:
 | 
						|
            bootstrap_pxc()
 | 
						|
            # NOTE(dosaboy): this will not actually do anything if no cluster
 | 
						|
            # relation id exists yet.
 | 
						|
            notify_bootstrapped()
 | 
						|
            update_db_rels = True
 | 
						|
        else:
 | 
						|
            delay = 1
 | 
						|
            attempts = 0
 | 
						|
            max_retries = 5
 | 
						|
            # NOTE(dosaboy): avoid unnecessary restarts. Once mysql is started
 | 
						|
            # it needn't be restarted when new units join the cluster since the
 | 
						|
            # new units will join and apply their own config.
 | 
						|
            if not seeded():
 | 
						|
                action = service_restart
 | 
						|
            else:
 | 
						|
                action = service_start
 | 
						|
 | 
						|
            while not action('mysql'):
 | 
						|
                if attempts == max_retries:
 | 
						|
                    raise Exception("Failed to start mysql (max retries "
 | 
						|
                                    "reached)")
 | 
						|
 | 
						|
                log("Failed to start mysql - retrying in %ss" % (delay),
 | 
						|
                    WARNING)
 | 
						|
                time.sleep(delay)
 | 
						|
                delay += 2
 | 
						|
                attempts += 1
 | 
						|
 | 
						|
        # If we get here we assume prior actions have succeeded to always
 | 
						|
        # this unit is marked as seeded so that subsequent calls don't result
 | 
						|
        # in a restart.
 | 
						|
        mark_seeded()
 | 
						|
 | 
						|
        if update_db_rels:
 | 
						|
            update_shared_db_rels()
 | 
						|
    else:
 | 
						|
        log("Config file '{}' unchanged".format(config_file), level=DEBUG)
 | 
						|
 | 
						|
 | 
						|
def update_shared_db_rels():
 | 
						|
    """ Upate client shared-db relations IFF ready
 | 
						|
    """
 | 
						|
    if leader_node_is_ready() or client_node_is_ready():
 | 
						|
        for r_id in relation_ids('shared-db'):
 | 
						|
            for unit in related_units(r_id):
 | 
						|
                shared_db_changed(r_id, unit)
 | 
						|
        kvstore = kv()
 | 
						|
        update_done = kvstore.get(INITIAL_CLIENT_UPDATE_KEY, False)
 | 
						|
        if not update_done:
 | 
						|
            kvstore.set(key=INITIAL_CLIENT_UPDATE_KEY, value=True)
 | 
						|
            kvstore.flush()
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('upgrade-charm')
 | 
						|
@harden()
 | 
						|
def upgrade():
 | 
						|
 | 
						|
    if is_leader():
 | 
						|
        if is_unit_paused_set():
 | 
						|
            log('Unit is paused, skiping upgrade', level=INFO)
 | 
						|
            return
 | 
						|
 | 
						|
        # broadcast the bootstrap-uuid
 | 
						|
        wsrep_ready = get_wsrep_value('wsrep_ready') or ""
 | 
						|
        if wsrep_ready.lower() in ['on', 'ready']:
 | 
						|
            cluster_state_uuid = get_wsrep_value('wsrep_cluster_state_uuid')
 | 
						|
            if cluster_state_uuid:
 | 
						|
                mark_seeded()
 | 
						|
                notify_bootstrapped(cluster_uuid=cluster_state_uuid)
 | 
						|
    else:
 | 
						|
        # Ensure all the peers have the bootstrap-uuid attribute set
 | 
						|
        # as this is all happening during the upgrade-charm hook is reasonable
 | 
						|
        # to expect the cluster is running.
 | 
						|
 | 
						|
        # Wait until the leader has set the
 | 
						|
        try:
 | 
						|
            update_bootstrap_uuid()
 | 
						|
        except LeaderNoBootstrapUUIDError:
 | 
						|
            status_set('waiting', "Waiting for bootstrap-uuid set by leader")
 | 
						|
 | 
						|
    config_changed()
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('config-changed')
 | 
						|
@harden()
 | 
						|
def config_changed():
 | 
						|
    # if we are paused, delay doing any config changed hooks.  It is forced on
 | 
						|
    # the resume.
 | 
						|
    if is_unit_paused_set():
 | 
						|
        return
 | 
						|
 | 
						|
    if config('prefer-ipv6'):
 | 
						|
        assert_charm_supports_ipv6()
 | 
						|
 | 
						|
    hosts = get_cluster_hosts()
 | 
						|
    clustered = len(hosts) > 1
 | 
						|
    bootstrapped = is_bootstrapped()
 | 
						|
 | 
						|
    # NOTE: only configure the cluster if we have sufficient peers. This only
 | 
						|
    # applies if min-cluster-size is provided and is used to avoid extraneous
 | 
						|
    # configuration changes and premature bootstrapping as the cluster is
 | 
						|
    # deployed.
 | 
						|
    if is_leader():
 | 
						|
        log("Leader unit - bootstrap required=%s" % (not bootstrapped),
 | 
						|
            DEBUG)
 | 
						|
        render_config_restart_on_changed(clustered, hosts,
 | 
						|
                                         bootstrap=not bootstrapped)
 | 
						|
    elif bootstrapped:
 | 
						|
        log("Cluster is bootstrapped - configuring mysql on this node",
 | 
						|
            DEBUG)
 | 
						|
        render_config_restart_on_changed(clustered, hosts)
 | 
						|
    else:
 | 
						|
        log("Not configuring", DEBUG)
 | 
						|
 | 
						|
    # Notify any changes to the access network
 | 
						|
    update_shared_db_rels()
 | 
						|
 | 
						|
    # (re)install pcmkr agent
 | 
						|
    install_mysql_ocf()
 | 
						|
 | 
						|
    for rid in relation_ids('ha'):
 | 
						|
        # make sure all the HA resources are (re)created
 | 
						|
        ha_relation_joined(relation_id=rid)
 | 
						|
 | 
						|
    if is_relation_made('nrpe-external-master'):
 | 
						|
        update_nrpe_config()
 | 
						|
 | 
						|
    open_port(DEFAULT_MYSQL_PORT)
 | 
						|
 | 
						|
    # the password needs to be updated only if the node was already
 | 
						|
    # bootstrapped
 | 
						|
    if bootstrapped:
 | 
						|
        update_root_password()
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('cluster-relation-joined')
 | 
						|
def cluster_joined():
 | 
						|
    relation_settings = {}
 | 
						|
 | 
						|
    if config('prefer-ipv6'):
 | 
						|
        addr = get_ipv6_addr(exc_list=[config('vip')])[0]
 | 
						|
        relation_settings = {'private-address': addr,
 | 
						|
                             'hostname': socket.gethostname()}
 | 
						|
 | 
						|
    relation_settings['cluster-address'] = get_cluster_host_ip()
 | 
						|
 | 
						|
    log("Setting cluster relation: '%s'" % (relation_settings),
 | 
						|
        level=INFO)
 | 
						|
    relation_set(relation_settings=relation_settings)
 | 
						|
 | 
						|
    # Ensure all new peers are aware
 | 
						|
    cluster_state_uuid = leader_get('bootstrap-uuid')
 | 
						|
    if cluster_state_uuid:
 | 
						|
        notify_bootstrapped(cluster_rid=relation_id(),
 | 
						|
                            cluster_uuid=cluster_state_uuid)
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('cluster-relation-departed')
 | 
						|
@hooks.hook('cluster-relation-changed')
 | 
						|
def cluster_changed():
 | 
						|
    # Need to make sure hostname is excluded to build inclusion list (paying
 | 
						|
    # attention to those excluded by default in peer_echo().
 | 
						|
    # TODO(dosaboy): extend peer_echo() to support providing exclusion list as
 | 
						|
    #                well as inclusion list.
 | 
						|
    # NOTE(jamespage): deprecated - leader-election
 | 
						|
    rdata = relation_get()
 | 
						|
    inc_list = []
 | 
						|
    for attr in rdata.iterkeys():
 | 
						|
        if attr not in ['hostname', 'private-address', 'cluster-address',
 | 
						|
                        'public-address']:
 | 
						|
            inc_list.append(attr)
 | 
						|
 | 
						|
    peer_echo(includes=inc_list)
 | 
						|
    # NOTE(jamespage): deprecated - leader-election
 | 
						|
 | 
						|
    cluster_joined()
 | 
						|
    config_changed()
 | 
						|
 | 
						|
 | 
						|
# TODO: This could be a hook common between mysql and percona-cluster
 | 
						|
@hooks.hook('db-relation-changed')
 | 
						|
@hooks.hook('db-admin-relation-changed')
 | 
						|
def db_changed(relation_id=None, unit=None, admin=None):
 | 
						|
    if not is_elected_leader(DC_RESOURCE_NAME):
 | 
						|
        log('Service is peered, clearing db relation'
 | 
						|
            ' as this service unit is not the leader')
 | 
						|
        relation_clear(relation_id)
 | 
						|
        return
 | 
						|
 | 
						|
    if admin not in [True, False]:
 | 
						|
        admin = relation_type() == 'db-admin'
 | 
						|
 | 
						|
    db_name, _ = (unit or remote_unit()).split("/")
 | 
						|
    username = db_name
 | 
						|
    db_helper = get_db_helper()
 | 
						|
    addr = relation_get('private-address', unit=unit, rid=relation_id)
 | 
						|
    password = db_helper.configure_db(addr, db_name, username, admin=admin)
 | 
						|
 | 
						|
    db_host = get_db_host(addr, interface=relation_type())
 | 
						|
 | 
						|
    relation_set(relation_id=relation_id,
 | 
						|
                 relation_settings={
 | 
						|
                     'user': username,
 | 
						|
                     'password': password,
 | 
						|
                     'host': db_host,
 | 
						|
                     'database': db_name,
 | 
						|
                 })
 | 
						|
 | 
						|
 | 
						|
def get_db_host(client_hostname, interface='shared-db'):
 | 
						|
    """Get address of local database host for use by db clients
 | 
						|
 | 
						|
    If an access-network has been configured, expect selected address to be
 | 
						|
    on that network. If none can be found, revert to primary address.
 | 
						|
 | 
						|
    If network spaces are supported (Juju >= 2.0), use network-get to
 | 
						|
    retrieve the network binding for the interface.
 | 
						|
 | 
						|
    If DNSHA is set pass os-access-hostname
 | 
						|
 | 
						|
    If vip(s) are configured, chooses first available.
 | 
						|
 | 
						|
    @param client_hostname: hostname of client side relation setting hostname.
 | 
						|
                            Only used if access-network is configured
 | 
						|
    @param interface: Network space binding to check.
 | 
						|
                      Usually the relationship name.
 | 
						|
    @returns IP for use with db clients
 | 
						|
    """
 | 
						|
    vips = config('vip').split() if config('vip') else []
 | 
						|
    dns_ha = config('dns-ha')
 | 
						|
    access_network = config('access-network')
 | 
						|
    if is_clustered() and dns_ha:
 | 
						|
        log("Using DNS HA hostname: {}".format(config('os-access-hostname')))
 | 
						|
        return config('os-access-hostname')
 | 
						|
    elif access_network:
 | 
						|
        client_ip = resolve_hostname_to_ip(client_hostname)
 | 
						|
        if is_address_in_network(access_network, client_ip):
 | 
						|
            if is_clustered():
 | 
						|
                for vip in vips:
 | 
						|
                    if is_address_in_network(access_network, vip):
 | 
						|
                        return vip
 | 
						|
 | 
						|
                log("Unable to identify a VIP in the access-network '%s'" %
 | 
						|
                    (access_network), level=WARNING)
 | 
						|
            else:
 | 
						|
                return get_address_in_network(access_network)
 | 
						|
        else:
 | 
						|
            log("Client address '%s' not in access-network '%s'" %
 | 
						|
                (client_ip, access_network), level=WARNING)
 | 
						|
    else:
 | 
						|
        try:
 | 
						|
            # NOTE(jamespage)
 | 
						|
            # Try to use network spaces to resolve binding for
 | 
						|
            # interface, and to resolve the VIP associated with
 | 
						|
            # the binding if provided.
 | 
						|
            interface_binding = network_get_primary_address(interface)
 | 
						|
            if is_clustered() and vips:
 | 
						|
                interface_cidr = resolve_network_cidr(interface_binding)
 | 
						|
                for vip in vips:
 | 
						|
                    if is_address_in_network(interface_cidr, vip):
 | 
						|
                        return vip
 | 
						|
            return interface_binding
 | 
						|
        except NotImplementedError:
 | 
						|
            # NOTE(jamespage): skip - fallback to previous behaviour
 | 
						|
            pass
 | 
						|
 | 
						|
    if is_clustered() and vips:
 | 
						|
        return vips[0]  # NOTE on private network
 | 
						|
 | 
						|
    if config('prefer-ipv6'):
 | 
						|
        return get_ipv6_addr(exc_list=vips)[0]
 | 
						|
 | 
						|
    # Last resort
 | 
						|
    return unit_get('private-address')
 | 
						|
 | 
						|
 | 
						|
def configure_db_for_hosts(hosts, database, username, db_helper):
 | 
						|
    """Hosts may be a json-encoded list of hosts or a single hostname."""
 | 
						|
    try:
 | 
						|
        hosts = json.loads(hosts)
 | 
						|
        log("Multiple hostnames provided by relation: %s" % (', '.join(hosts)),
 | 
						|
            level=DEBUG)
 | 
						|
    except ValueError:
 | 
						|
        log("Single hostname provided by relation: %s" % (hosts),
 | 
						|
            level=DEBUG)
 | 
						|
        hosts = [hosts]
 | 
						|
 | 
						|
    for host in hosts:
 | 
						|
        password = db_helper.configure_db(host, database, username)
 | 
						|
 | 
						|
    return password
 | 
						|
 | 
						|
 | 
						|
# TODO: This could be a hook common between mysql and percona-cluster
 | 
						|
@hooks.hook('shared-db-relation-changed')
 | 
						|
def shared_db_changed(relation_id=None, unit=None):
 | 
						|
    if not seeded():
 | 
						|
        log("Percona cluster not yet bootstrapped - deferring shared-db rel "
 | 
						|
            "until bootstrapped", DEBUG)
 | 
						|
        return
 | 
						|
 | 
						|
    if not is_leader() and client_node_is_ready():
 | 
						|
        # NOTE(jamespage): relation level data candidate
 | 
						|
        log('Service is peered, clearing shared-db relation '
 | 
						|
            'as this service unit is not the leader')
 | 
						|
        relation_clear(relation_id)
 | 
						|
        # Each unit needs to set the db information otherwise if the unit
 | 
						|
        # with the info dies the settings die with it Bug# 1355848
 | 
						|
        if is_relation_made('cluster'):
 | 
						|
            for rel_id in relation_ids('shared-db'):
 | 
						|
                client_settings = \
 | 
						|
                    peer_retrieve_by_prefix(rel_id, exc_list=['hostname'])
 | 
						|
 | 
						|
                passwords = [key for key in client_settings.keys()
 | 
						|
                             if 'password' in key.lower()]
 | 
						|
                if len(passwords) > 0:
 | 
						|
                    relation_set(relation_id=rel_id, **client_settings)
 | 
						|
        return
 | 
						|
 | 
						|
    # Bail if leader is not ready
 | 
						|
    if not leader_node_is_ready():
 | 
						|
        return
 | 
						|
 | 
						|
    settings = relation_get(unit=unit, rid=relation_id)
 | 
						|
    access_network = config('access-network')
 | 
						|
    db_helper = get_db_helper()
 | 
						|
 | 
						|
    peer_store_and_set(relation_id=relation_id,
 | 
						|
                       relation_settings={'access-network': access_network})
 | 
						|
 | 
						|
    singleset = set(['database', 'username', 'hostname'])
 | 
						|
    if singleset.issubset(settings):
 | 
						|
        # Process a single database configuration
 | 
						|
        hostname = settings['hostname']
 | 
						|
        database = settings['database']
 | 
						|
        username = settings['username']
 | 
						|
 | 
						|
        normalized_address = resolve_hostname_to_ip(hostname)
 | 
						|
        if access_network and not is_address_in_network(access_network,
 | 
						|
                                                        normalized_address):
 | 
						|
            # NOTE: for configurations using access-network, only setup
 | 
						|
            #       database access if remote unit has presented a
 | 
						|
            #       hostname or ip address thats within the configured
 | 
						|
            #       network cidr
 | 
						|
            log("Host '%s' not in access-network '%s' - ignoring" %
 | 
						|
                (normalized_address, access_network), level=INFO)
 | 
						|
            return
 | 
						|
 | 
						|
        # NOTE: do this before querying access grants
 | 
						|
        password = configure_db_for_hosts(hostname, database, username,
 | 
						|
                                          db_helper)
 | 
						|
 | 
						|
        allowed_units = db_helper.get_allowed_units(database, username,
 | 
						|
                                                    relation_id=relation_id)
 | 
						|
        allowed_units = unit_sorted(allowed_units)
 | 
						|
        allowed_units = ' '.join(allowed_units)
 | 
						|
        relation_set(relation_id=relation_id, allowed_units=allowed_units)
 | 
						|
 | 
						|
        db_host = get_db_host(hostname)
 | 
						|
        peer_store_and_set(relation_id=relation_id,
 | 
						|
                           db_host=db_host,
 | 
						|
                           password=password,
 | 
						|
                           allowed_units=allowed_units)
 | 
						|
    else:
 | 
						|
        # Process multiple database setup requests.
 | 
						|
        # from incoming relation data:
 | 
						|
        #  nova_database=xxx nova_username=xxx nova_hostname=xxx
 | 
						|
        #  quantum_database=xxx quantum_username=xxx quantum_hostname=xxx
 | 
						|
        # create
 | 
						|
        # {
 | 
						|
        #   "nova": {
 | 
						|
        #        "username": xxx,
 | 
						|
        #        "database": xxx,
 | 
						|
        #        "hostname": xxx
 | 
						|
        #    },
 | 
						|
        #    "quantum": {
 | 
						|
        #        "username": xxx,
 | 
						|
        #        "database": xxx,
 | 
						|
        #        "hostname": xxx
 | 
						|
        #    }
 | 
						|
        # }
 | 
						|
        #
 | 
						|
        databases = {}
 | 
						|
        for k, v in settings.iteritems():
 | 
						|
            db = k.split('_')[0]
 | 
						|
            x = '_'.join(k.split('_')[1:])
 | 
						|
            if db not in databases:
 | 
						|
                databases[db] = {}
 | 
						|
            databases[db][x] = v
 | 
						|
 | 
						|
        allowed_units = {}
 | 
						|
        return_data = {}
 | 
						|
        for db in databases:
 | 
						|
            if singleset.issubset(databases[db]):
 | 
						|
                database = databases[db]['database']
 | 
						|
                hostname = databases[db]['hostname']
 | 
						|
                username = databases[db]['username']
 | 
						|
 | 
						|
                normalized_address = resolve_hostname_to_ip(hostname)
 | 
						|
                if (access_network and
 | 
						|
                        not is_address_in_network(access_network,
 | 
						|
                                                  normalized_address)):
 | 
						|
                    # NOTE: for configurations using access-network,
 | 
						|
                    #       only setup database access if remote unit
 | 
						|
                    #       has presented a hostname or ip address
 | 
						|
                    #       thats within the configured network cidr
 | 
						|
                    return
 | 
						|
 | 
						|
                # NOTE: do this before querying access grants
 | 
						|
                password = configure_db_for_hosts(hostname, database, username,
 | 
						|
                                                  db_helper)
 | 
						|
 | 
						|
                a_units = db_helper.get_allowed_units(database, username,
 | 
						|
                                                      relation_id=relation_id)
 | 
						|
                a_units = ' '.join(unit_sorted(a_units))
 | 
						|
                allowed_units_key = '%s_allowed_units' % (db)
 | 
						|
                allowed_units[allowed_units_key] = a_units
 | 
						|
 | 
						|
                return_data['%s_password' % (db)] = password
 | 
						|
                return_data[allowed_units_key] = a_units
 | 
						|
                db_host = get_db_host(hostname)
 | 
						|
 | 
						|
        if allowed_units:
 | 
						|
            relation_set(relation_id=relation_id, **allowed_units)
 | 
						|
        else:
 | 
						|
            log("No allowed_units - not setting relation settings",
 | 
						|
                level=DEBUG)
 | 
						|
 | 
						|
        if return_data:
 | 
						|
            peer_store_and_set(relation_id=relation_id, db_host=db_host,
 | 
						|
                               **return_data)
 | 
						|
        else:
 | 
						|
            log("No return data - not setting relation settings", level=DEBUG)
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('ha-relation-joined')
 | 
						|
def ha_relation_joined(relation_id=None):
 | 
						|
    cluster_config = get_hacluster_config()
 | 
						|
    sstpsswd = sst_password()
 | 
						|
    resources = {'res_mysql_monitor': 'ocf:percona:mysql_monitor'}
 | 
						|
    resource_params = {'res_mysql_monitor':
 | 
						|
                       RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
 | 
						|
 | 
						|
    if config('dns-ha'):
 | 
						|
        update_dns_ha_resource_params(relation_id=relation_id,
 | 
						|
                                      resources=resources,
 | 
						|
                                      resource_params=resource_params)
 | 
						|
        group_name = 'grp_{}_hostnames'.format(charm_name())
 | 
						|
        groups = {group_name: 'res_{}_access_hostname'.format(charm_name())}
 | 
						|
 | 
						|
    else:
 | 
						|
        vip_iface = (get_iface_for_address(cluster_config['vip']) or
 | 
						|
                     config('vip_iface'))
 | 
						|
        vip_cidr = (get_netmask_for_address(cluster_config['vip']) or
 | 
						|
                    config('vip_cidr'))
 | 
						|
 | 
						|
        if config('prefer-ipv6'):
 | 
						|
            res_mysql_vip = 'ocf:heartbeat:IPv6addr'
 | 
						|
            vip_params = 'params ipv6addr="%s" cidr_netmask="%s" nic="%s"' % \
 | 
						|
                         (cluster_config['vip'], vip_cidr, vip_iface)
 | 
						|
        else:
 | 
						|
            res_mysql_vip = 'ocf:heartbeat:IPaddr2'
 | 
						|
            vip_params = 'params ip="%s" cidr_netmask="%s" nic="%s"' % \
 | 
						|
                         (cluster_config['vip'], vip_cidr, vip_iface)
 | 
						|
 | 
						|
        resources['res_mysql_vip'] = res_mysql_vip
 | 
						|
 | 
						|
        resource_params['res_mysql_vip'] = vip_params
 | 
						|
 | 
						|
        group_name = 'grp_percona_cluster'
 | 
						|
        groups = {group_name: 'res_mysql_vip'}
 | 
						|
 | 
						|
    clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
 | 
						|
 | 
						|
    colocations = {'colo_percona_cluster': 'inf: {} cl_mysql_monitor'
 | 
						|
                                           ''.format(group_name)}
 | 
						|
 | 
						|
    locations = {'loc_percona_cluster':
 | 
						|
                 '{} rule inf: writable eq 1'
 | 
						|
                 ''.format(group_name)}
 | 
						|
 | 
						|
    for rel_id in relation_ids('ha'):
 | 
						|
        relation_set(relation_id=rel_id,
 | 
						|
                     corosync_bindiface=cluster_config['ha-bindiface'],
 | 
						|
                     corosync_mcastport=cluster_config['ha-mcastport'],
 | 
						|
                     resources=resources,
 | 
						|
                     resource_params=resource_params,
 | 
						|
                     groups=groups,
 | 
						|
                     clones=clones,
 | 
						|
                     colocations=colocations,
 | 
						|
                     locations=locations)
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('ha-relation-changed')
 | 
						|
def ha_relation_changed():
 | 
						|
    clustered = relation_get('clustered')
 | 
						|
    if (clustered and is_elected_leader(DC_RESOURCE_NAME)):
 | 
						|
        log('Cluster configured, notifying other services')
 | 
						|
        # Tell all related services to start using the VIP
 | 
						|
        update_shared_db_rels()
 | 
						|
        for r_id in relation_ids('db'):
 | 
						|
            for unit in related_units(r_id):
 | 
						|
                db_changed(r_id, unit, admin=False)
 | 
						|
        for r_id in relation_ids('db-admin'):
 | 
						|
            for unit in related_units(r_id):
 | 
						|
                db_changed(r_id, unit, admin=True)
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('leader-settings-changed')
 | 
						|
def leader_settings_changed():
 | 
						|
    '''Re-trigger install once leader has seeded passwords into install'''
 | 
						|
    install_percona_xtradb_cluster()
 | 
						|
    # Notify any changes to data in leader storage
 | 
						|
    update_shared_db_rels()
 | 
						|
    log('leader-settings-changed', level='DEBUG')
 | 
						|
    try:
 | 
						|
        update_bootstrap_uuid()
 | 
						|
    except LeaderNoBootstrapUUIDError:
 | 
						|
        # until the bootstrap-uuid attribute is not replicated cluster_ready()
 | 
						|
        # will evaluate to False, so it is necessary to feed back this info
 | 
						|
        # to the user.
 | 
						|
        status_set('waiting', "Waiting for bootstrap-uuid set by leader")
 | 
						|
 | 
						|
 | 
						|
@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.add_init_service_checks(nrpe_setup, ['mysql'], current_unit)
 | 
						|
    nrpe_setup.add_check(
 | 
						|
        shortname='mysql_proc',
 | 
						|
        description='Check MySQL process {%s}' % current_unit,
 | 
						|
        check_cmd='check_procs -c 1:1 -C mysqld'
 | 
						|
    )
 | 
						|
    nrpe_setup.write()
 | 
						|
 | 
						|
 | 
						|
@hooks.hook('update-status')
 | 
						|
@harden()
 | 
						|
def update_status():
 | 
						|
    log('Updating status.')
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    try:
 | 
						|
        hooks.execute(sys.argv)
 | 
						|
    except UnregisteredHookError as e:
 | 
						|
        log('Unknown hook {} - skipping.'.format(e))
 | 
						|
    kvstore = kv()
 | 
						|
    if not kvstore.get(INITIAL_CLIENT_UPDATE_KEY, False):
 | 
						|
        update_shared_db_rels()
 | 
						|
    assess_status(register_configs())
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |