def ha_changed():
    if not cluster.is_clustered():
        return
    vip = utils.config_get('vip')
    utils.juju_log('INFO', 'ha_changed(): We are now HA clustered. '
                   'Advertising our VIP (%s) to all AMQP clients.' %
                   vip)
    # need to re-authenticate all clients since node-name changed.
    for rid in utils.relation_ids('amqp'):
        for unit in utils.relation_list(rid):
            amqp_changed(relation_id=rid, remote_unit=unit)
Esempio n. 2
0
def ha_changed():
    if not cluster.is_clustered():
        return
    vip = utils.config_get('vip')
    utils.juju_log(
        'INFO', 'ha_changed(): We are now HA clustered. '
        'Advertising our VIP (%s) to all AMQP clients.' % vip)
    # need to re-authenticate all clients since node-name changed.
    for rid in utils.relation_ids('amqp'):
        for unit in utils.relation_list(rid):
            amqp_changed(relation_id=rid, remote_unit=unit)
def amqp_changed(relation_id=None, remote_unit=None):
    if not cluster.eligible_leader('res_rabbitmq_vip'):
        msg = 'amqp_changed(): Deferring amqp_changed to eligible_leader.'
        utils.juju_log('INFO', msg)
        return

    relation_settings = {}
    settings = hookenv.relation_get(rid=relation_id, unit=remote_unit)

    singleset = set([
        'username',
        'vhost'
        ])

    if singleset.issubset(settings):
        if None in [settings['username'], settings['vhost']]:
            utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
            return

        relation_settings['password'] = configure_amqp(username=settings['username'],
                                                       vhost=settings['vhost'])
    else:
        queues = {}
        for k, v in settings.iteritems():
            amqp = k.split('_')[0]
            x = '_'.join(k.split('_')[1:])
            if amqp not in queues:
                queues[amqp] = {}
            queues[amqp][x] = v
        relation_settings = {}
        for amqp in queues:
            if singleset.issubset(queues[amqp]):
                relation_settings['_'.join([amqp, 'password'])] = configure_amqp(queues[amqp]['username'],
                                                                                 queues[amqp]['vhost'])

    relation_settings['hostname'] = utils.unit_get('private-address')

    if cluster.is_clustered():
        relation_settings['clustered'] = 'true'
        if utils.is_relation_made('ha'):
            # active/passive settings
            relation_settings['vip'] = utils.config_get('vip')

    if relation_id:
        relation_settings['rid'] = relation_id
    utils.relation_set(**relation_settings)

    # sync new creds to all peers
    rabbit.synchronize_service_credentials()
Esempio n. 4
0
def amqp_changed(relation_id=None, remote_unit=None):
    if not cluster.eligible_leader('res_rabbitmq_vip'):
        msg = 'amqp_changed(): Deferring amqp_changed to eligible_leader.'
        utils.juju_log('INFO', msg)
        return

    relation_settings = {}
    settings = hookenv.relation_get(rid=relation_id, unit=remote_unit)

    singleset = set(['username', 'vhost'])

    if singleset.issubset(settings):
        if None in [settings['username'], settings['vhost']]:
            utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
            return

        relation_settings['password'] = configure_amqp(
            username=settings['username'], vhost=settings['vhost'])
    else:
        queues = {}
        for k, v in settings.iteritems():
            amqp = k.split('_')[0]
            x = '_'.join(k.split('_')[1:])
            if amqp not in queues:
                queues[amqp] = {}
            queues[amqp][x] = v
        relation_settings = {}
        for amqp in queues:
            if singleset.issubset(queues[amqp]):
                relation_settings['_'.join([amqp,
                                            'password'])] = configure_amqp(
                                                queues[amqp]['username'],
                                                queues[amqp]['vhost'])

    relation_settings['hostname'] = utils.unit_get('private-address')

    if cluster.is_clustered():
        relation_settings['clustered'] = 'true'
        if utils.is_relation_made('ha'):
            # active/passive settings
            relation_settings['vip'] = utils.config_get('vip')

    if relation_id:
        relation_settings['rid'] = relation_id
    utils.relation_set(**relation_settings)

    # sync new creds to all peers
    rabbit.synchronize_service_credentials()
def ensure_initial_admin(config):
    """ Ensures the minimum admin stuff exists in whatever database we're
        using.
        This and the helper functions it calls are meant to be idempotent and
        run during install as well as during db-changed.  This will maintain
        the admin tenant, user, role, service entry and endpoint across every
        datastore we might use.
        TODO: Possibly migrate data from one backend to another after it
        changes?
    """
    create_tenant("admin")
    create_tenant(config["service-tenant"])

    passwd = ""
    if config["admin-password"] != "None":
        passwd = config["admin-password"]
    elif os.path.isfile(stored_passwd):
        utils.juju_log('INFO', "Loading stored passwd from %s" % stored_passwd)
        passwd = open(stored_passwd, 'r').readline().strip('\n')
    if passwd == "":
        utils.juju_log('INFO', "Generating new passwd for user: %s" % \
                       config["admin-user"])
        passwd = execute("pwgen -c 16 1", die=True)[0]
        open(stored_passwd, 'w+').writelines("%s\n" % passwd)

    create_user(config['admin-user'], passwd, tenant='admin')
    update_user_password(config['admin-user'], passwd)
    create_role(config['admin-role'], config['admin-user'], 'admin')
    # TODO(adam_g): The following roles are likely not needed since redux merge
    create_role("KeystoneAdmin", config["admin-user"], 'admin')
    create_role("KeystoneServiceAdmin", config["admin-user"], 'admin')
    create_service_entry("keystone", "identity", "Keystone Identity Service")

    if cluster.is_clustered():
        utils.juju_log('INFO', "Creating endpoint for clustered configuration")
        service_host = auth_host = config["vip"]
    else:
        utils.juju_log('INFO', "Creating standard endpoint")
        service_host = auth_host = config["hostname"]

    for region in config['region'].split():
        create_keystone_endpoint(service_host=service_host,
                                 service_port=config["service-port"],
                                 auth_host=auth_host,
                                 auth_port=config["admin-port"],
                                 region=region)
def amqp_changed(relation_id=None, remote_unit=None):
    if not cluster.eligible_leader('res_rabbitmq_vip'):
        msg = 'amqp_changed(): Deferring amqp_changed to eligible_leader.'
        utils.juju_log('INFO', msg)
        return

    rabbit_user = utils.relation_get('username', rid=relation_id,
                                     unit=remote_unit)
    vhost = utils.relation_get('vhost', rid=relation_id, unit=remote_unit)
    if None in [rabbit_user, vhost]:
        utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
        return

    password_file = os.path.join(RABBIT_DIR, '%s.passwd' % rabbit_user)
    if os.path.exists(password_file):
        password = open(password_file).read().strip()
    else:
        cmd = ['pwgen', '64', '1']
        password = subprocess.check_output(cmd).strip()
        with open(password_file, 'wb') as out:
            out.write(password)

    rabbit.create_vhost(vhost)
    rabbit.create_user(rabbit_user, password)
    rabbit.grant_permissions(rabbit_user, vhost)
    rabbit_hostname = utils.unit_get('private-address')

    relation_settings = {
        'password': password,
        'hostname': rabbit_hostname
    }
    if cluster.is_clustered():
        relation_settings['clustered'] = 'true'
        relation_settings['vip'] = utils.config_get('vip')
    if relation_id:
        relation_settings['rid'] = relation_id
    utils.relation_set(**relation_settings)
def identity_changed(relation_id=None, remote_unit=None):
    """ A service has advertised its API endpoints, create an entry in the
        service catalog.
        Optionally allow this hook to be re-fired for an existing
        relation+unit, for context see see db_changed().
    """
    if not cluster.eligible_leader(CLUSTER_RES):
        utils.juju_log('INFO',
                       'Deferring identity_changed() to service leader.')
        return

    settings = utils.relation_get_dict(relation_id=relation_id,
                                       remote_unit=remote_unit)

    # the minimum settings needed per endpoint
    single = set(['service', 'region', 'public_url', 'admin_url',
                  'internal_url'])
    if single.issubset(settings):
        # other end of relation advertised only one endpoint
        if 'None' in [v for k, v in settings.iteritems()]:
            # Some backend services advertise no endpoint but require a
            # hook execution to update auth strategy.
            relation_data = {}
            # Check if clustered and use vip + haproxy ports if so
            if cluster.is_clustered():
                relation_data["auth_host"] = config['vip']
                relation_data["service_host"] = config['vip']
            else:
                relation_data["auth_host"] = config['hostname']
                relation_data["service_host"] = config['hostname']
            relation_data["auth_port"] = config['admin-port']
            relation_data["service_port"] = config['service-port']
            if config['https-service-endpoints'] in ['True', 'true']:
                # Pass CA cert as client will need it to
                # verify https connections
                ca = get_ca(user=SSH_USER)
                ca_bundle = ca.get_ca_bundle()
                relation_data['https_keystone'] = 'True'
                relation_data['ca_cert'] = b64encode(ca_bundle)
            if relation_id:
                relation_data['rid'] = relation_id
            # Allow the remote service to request creation of any additional
            # roles. Currently used by Horizon
            for role in get_requested_roles(settings):
                utils.juju_log('INFO',
                               "Creating requested role: %s" % role)
                create_role(role)
            utils.relation_set(**relation_data)
            return
        else:
            ensure_valid_service(settings['service'])
            add_endpoint(region=settings['region'],
                         service=settings['service'],
                         publicurl=settings['public_url'],
                         adminurl=settings['admin_url'],
                         internalurl=settings['internal_url'])
            service_username = settings['service']
            https_cn = urlparse.urlparse(settings['internal_url'])
            https_cn = https_cn.hostname
    else:
        # assemble multiple endpoints from relation data. service name
        # should be prepended to setting name, ie:
        #  realtion-set ec2_service=$foo ec2_region=$foo ec2_public_url=$foo
        #  relation-set nova_service=$foo nova_region=$foo nova_public_url=$foo
        # Results in a dict that looks like:
        # { 'ec2': {
        #       'service': $foo
        #       'region': $foo
        #       'public_url': $foo
        #   }
        #   'nova': {
        #       'service': $foo
        #       'region': $foo
        #       'public_url': $foo
        #   }
        # }
        endpoints = {}
        for k, v in settings.iteritems():
            ep = k.split('_')[0]
            x = '_'.join(k.split('_')[1:])
            if ep not in endpoints:
                endpoints[ep] = {}
            endpoints[ep][x] = v
        services = []
        https_cn = None
        for ep in endpoints:
            # weed out any unrelated relation stuff Juju might have added
            # by ensuring each possible endpiont has appropriate fields
            #  ['service', 'region', 'public_url', 'admin_url', 'internal_url']
            if single.issubset(endpoints[ep]):
                ep = endpoints[ep]
                ensure_valid_service(ep['service'])
                add_endpoint(region=ep['region'], service=ep['service'],
                             publicurl=ep['public_url'],
                             adminurl=ep['admin_url'],
                             internalurl=ep['internal_url'])
                services.append(ep['service'])
                if not https_cn:
                    https_cn = urlparse.urlparse(ep['internal_url'])
                    https_cn = https_cn.hostname
        service_username = '******'.join(services)

    if 'None' in [v for k, v in settings.iteritems()]:
        return

    if not service_username:
        return

    token = get_admin_token()
    utils.juju_log('INFO',
                   "Creating service credentials for '%s'" % service_username)

    service_password = get_service_password(service_username)
    create_user(service_username, service_password, config['service-tenant'])
    grant_role(service_username, config['admin-role'],
               config['service-tenant'])

    # Allow the remote service to request creation of any additional roles.
    # Currently used by Swift and Ceilometer.
    for role in get_requested_roles(settings):
        utils.juju_log('INFO',
                       "Creating requested role: %s" % role)
        create_role(role, service_username,
                    config['service-tenant'])

    # As of https://review.openstack.org/#change,4675, all nodes hosting
    # an endpoint(s) needs a service username and password assigned to
    # the service tenant and granted admin role.
    # note: config['service-tenant'] is created in utils.ensure_initial_admin()
    # we return a token, information about our API endpoints, and the generated
    # service credentials
    relation_data = {
        "admin_token": token,
        "service_host": config["hostname"],
        "service_port": config["service-port"],
        "auth_host": config["hostname"],
        "auth_port": config["admin-port"],
        "service_username": service_username,
        "service_password": service_password,
        "service_tenant": config['service-tenant'],
        "https_keystone": "False",
        "ssl_cert": "",
        "ssl_key": "",
        "ca_cert": ""
    }

    if relation_id:
        relation_data['rid'] = relation_id

    # Check if clustered and use vip + haproxy ports if so
    if cluster.is_clustered():
        relation_data["auth_host"] = config['vip']
        relation_data["service_host"] = config['vip']

    # generate or get a new cert/key for service if set to manage certs.
    if config['https-service-endpoints'] in ['True', 'true']:
        ca = get_ca(user=SSH_USER)
        cert, key = ca.get_cert_and_key(common_name=https_cn)
        ca_bundle = ca.get_ca_bundle()
        relation_data['ssl_cert'] = b64encode(cert)
        relation_data['ssl_key'] = b64encode(key)
        relation_data['ca_cert'] = b64encode(ca_bundle)
        relation_data['https_keystone'] = 'True'
        unison.sync_to_peers(peer_interface='cluster',
                             paths=[SSL_DIR], user=SSH_USER, verbose=True)
    utils.relation_set(**relation_data)
    synchronize_service_credentials()
def shared_db_changed():
    def configure_db(hostname, database, username):
        passwd_file = "/var/lib/mysql/mysql-{}.passwd"\
                        .format(username)
        if hostname != local_hostname:
            remote_ip = socket.gethostbyname(hostname)
        else:
            remote_ip = '127.0.0.1'

        if not os.path.exists(passwd_file):
            password = pwgen()
            with open(passwd_file, 'w') as pfile:
                pfile.write(password)
        else:
            with open(passwd_file) as pfile:
                password = pfile.read().strip()

        if not database_exists(database):
            create_database(database)
        if not grant_exists(database, username, remote_ip):
            create_grant(database, username, remote_ip, password)
        return password

    if not cluster.eligible_leader(LEADER_RES):
        utils.juju_log(
            'INFO', 'MySQL service is peered, bailing shared-db relation'
            ' as this service unit is not the leader')
        return

    settings = relation_get()
    local_hostname = utils.unit_get('private-address')
    singleset = set(['database', 'username', 'hostname'])

    if singleset.issubset(settings):
        # Process a single database configuration
        password = configure_db(settings['hostname'], settings['database'],
                                settings['username'])
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname, password=password)
        else:
            utils.relation_set(db_host=utils.config_get("vip"),
                               password=password)

    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
        return_data = {}
        for db in databases:
            if singleset.issubset(databases[db]):
                return_data['_'.join([db, 'password'])] = \
                    configure_db(databases[db]['hostname'],
                                 databases[db]['database'],
                                 databases[db]['username'])
        if len(return_data) > 0:
            utils.relation_set(**return_data)
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname)
        else:
            utils.relation_set(db_host=utils.config_get("vip"))
def shared_db_changed():
    if not cluster.eligible_leader(LEADER_RES):
        utils.juju_log('INFO',
                       'MySQL service is peered, bailing shared-db relation'
                       ' as this service unit is not the leader')
        return

    if utils.config_get('prefer-ipv6'):
        local_hostname = get_ipv6_addr(exc_list=[utils.config_get('vip')])[0]
    else:
        local_hostname = utils.unit_get('private-address')

    settings = relation_get()
    singleset = set([
        'database',
        'username',
        'hostname'])

    db_helper = get_db_helper()

    if singleset.issubset(settings):
        # Process a single database configuration
        hostname = settings['hostname']
        database = settings['database']
        username = settings['username']

        # Hostname can be json-encoded list of hostnames
        try:
            hostname = json.loads(hostname)
        except ValueError:
            hostname = [hostname]

        for host in hostname:
            password = db_helper.configure_db(host, database, username)

        allowed_units = db_helper.get_allowed_units(database, username)
        allowed_units = unit_sorted(allowed_units)
        allowed_units = ' '.join(allowed_units)

        if cluster.is_clustered():
            db_host = utils.config_get("vip")
        else:
            db_host = local_hostname

        utils.relation_set(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

        return_data = {}
        for db in databases:
            if singleset.issubset(databases[db]):
                database = databases[db]['database']
                hostname = databases[db]['hostname']
                username = databases[db]['username']

                try:
                    # Can be json-encoded list of hostnames
                    hostname = json.loads(hostname)
                except ValueError:
                    # Otherwise expected to be single hostname
                    hostname = [hostname]

                for host in hostname:
                    password = db_helper.configure_db(host, database, username)

                a_units = db_helper.get_allowed_units(database, username)
                a_units = ' '.join(unit_sorted(a_units))
                return_data['%s_allowed_units' % (db)] = a_units

                return_data['%s_password' % (db)] = password

        if len(return_data) > 0:
            utils.relation_set(**return_data)
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname)
        else:
            utils.relation_set(db_host=utils.config_get("vip"))
def shared_db_changed():

    def configure_db(hostname,
                     database,
                     username):
        passwd_file = "/var/lib/mysql/mysql-{}.passwd"\
                        .format(username)
        if hostname != local_hostname:
            remote_ip = socket.gethostbyname(hostname)
        else:
            remote_ip = '127.0.0.1'

        if not os.path.exists(passwd_file):
            password = pwgen()
            with open(passwd_file, 'w') as pfile:
                pfile.write(password)
        else:
            with open(passwd_file) as pfile:
                password = pfile.read().strip()

        if not database_exists(database):
            create_database(database)
        if not grant_exists(database,
                            username,
                            remote_ip):
            create_grant(database,
                         username,
                         remote_ip, password)
        return password

    if not cluster.eligible_leader(LEADER_RES):
        utils.juju_log('INFO',
                       'MySQL service is peered, bailing shared-db relation'
                       ' as this service unit is not the leader')
        return

    settings = relation_get()
    local_hostname = utils.unit_get('private-address')
    singleset = set([
        'database',
        'username',
        'hostname'
        ])

    if singleset.issubset(settings):
        # Process a single database configuration
        password = configure_db(settings['hostname'],
                                settings['database'],
                                settings['username'])
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname,
                               password=password)
        else:
            utils.relation_set(db_host=utils.config_get("vip"),
                               password=password)

    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
        return_data = {}
        for db in databases:
            if singleset.issubset(databases[db]):
                return_data['_'.join([db, 'password'])] = \
                    configure_db(databases[db]['hostname'],
                                 databases[db]['database'],
                                 databases[db]['username'])
        if len(return_data) > 0:
            utils.relation_set(**return_data)
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname)
        else:
            utils.relation_set(db_host=utils.config_get("vip"))
Esempio n. 11
0
def shared_db_changed():
    if not cluster.eligible_leader(LEADER_RES):
        utils.juju_log(
            'INFO', 'MySQL service is peered, bailing shared-db relation'
            ' as this service unit is not the leader')
        return

    if utils.config_get('prefer-ipv6'):
        local_hostname = get_ipv6_addr(exc_list=[utils.config_get('vip')])[0]
    else:
        local_hostname = utils.unit_get('private-address')

    settings = relation_get()
    singleset = set(['database', 'username', 'hostname'])

    db_helper = get_db_helper()

    if singleset.issubset(settings):
        # Process a single database configuration
        hostname = settings['hostname']
        database = settings['database']
        username = settings['username']

        # Hostname can be json-encoded list of hostnames
        try:
            hostname = json.loads(hostname)
        except ValueError:
            hostname = [hostname]

        for host in hostname:
            password = db_helper.configure_db(host, database, username)

        allowed_units = db_helper.get_allowed_units(database, username)
        allowed_units = unit_sorted(allowed_units)
        allowed_units = ' '.join(allowed_units)

        if cluster.is_clustered():
            db_host = utils.config_get("vip")
        else:
            db_host = local_hostname

        utils.relation_set(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

        return_data = {}
        for db in databases:
            if singleset.issubset(databases[db]):
                database = databases[db]['database']
                hostname = databases[db]['hostname']
                username = databases[db]['username']

                try:
                    # Can be json-encoded list of hostnames
                    hostname = json.loads(hostname)
                except ValueError:
                    # Otherwise expected to be single hostname
                    hostname = [hostname]

                for host in hostname:
                    password = db_helper.configure_db(host, database, username)

                a_units = db_helper.get_allowed_units(database, username)
                a_units = ' '.join(unit_sorted(a_units))
                return_data['%s_allowed_units' % (db)] = a_units

                return_data['%s_password' % (db)] = password

        if len(return_data) > 0:
            utils.relation_set(**return_data)
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname)
        else:
            utils.relation_set(db_host=utils.config_get("vip"))
Esempio n. 12
0
def shared_db_changed():

    def get_allowed_units(database, username):
        allowed_units = set()
        for relid in hookenv.relation_ids('shared-db'):
            for unit in hookenv.related_units(relid):
                attr = "%s_%s" % (database, 'hostname')
                hosts = hookenv.relation_get(attribute=attr, unit=unit,
                                             rid=relid)
                if not hosts:
                    hosts = [hookenv.relation_get(attribute='private-address',
                                                  unit=unit, rid=relid)]
                else:
                    # hostname can be json-encoded list of hostnames
                    try:
                        hosts = json.loads(hosts)
                    except ValueError:
                        pass

                if not isinstance(hosts, list):
                    hosts = [hosts]

                if hosts:
                    for host in hosts:
                        utils.juju_log('INFO', "Checking host '%s' grant" %
                                       (host))
                        if grant_exists(database, username, host):
                            if unit not in allowed_units:
                                allowed_units.add(unit)
                else:
                    utils.juju_log('INFO', "No hosts found for grant check")

        return allowed_units

    def configure_db(hostname,
                     database,
                     username):
        passwd_file = "/var/lib/mysql/mysql-{}.passwd".format(username)
        if hostname != local_hostname:
            try:
                remote_ip = socket.gethostbyname(hostname)
            except Exception:
                # socket.gethostbyname doesn't support ipv6
                remote_ip = hostname
        else:
            remote_ip = '127.0.0.1'

        if not os.path.exists(passwd_file):
            password = pwgen()
            with open(passwd_file, 'w') as pfile:
                pfile.write(password)
                os.chmod(pfile.name, 0600)
        else:
            with open(passwd_file) as pfile:
                password = pfile.read().strip()

        if not database_exists(database):
            create_database(database)
        if not grant_exists(database,
                            username,
                            remote_ip):
            create_grant(database,
                         username,
                         remote_ip, password)
        return password

    if not cluster.eligible_leader(LEADER_RES):
        utils.juju_log('INFO',
                       'MySQL service is peered, bailing shared-db relation'
                       ' as this service unit is not the leader')
        return

    if utils.config_get('prefer-ipv6'):
        local_hostname = get_ipv6_addr(exc_list=[utils.config_get('vip')])[0]
    else:
        local_hostname = utils.unit_get('private-address')

    settings = relation_get()
    singleset = set([
        'database',
        'username',
        'hostname'])

    if singleset.issubset(settings):
        # Process a single database configuration
        hostname = settings['hostname']
        database = settings['database']
        username = settings['username']

        # Hostname can be json-encoded list of hostnames
        try:
            hostname = json.loads(hostname)
        except ValueError:
            pass

        if isinstance(hostname, list):
            for host in hostname:
                password = configure_db(host, database, username)
        else:
            password = configure_db(hostname, database, username)

        allowed_units = " ".join(unit_sorted(get_allowed_units(database,
                                                               username)))

        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname,
                               password=password,
                               allowed_units=allowed_units)
        else:
            utils.relation_set(db_host=utils.config_get("vip"),
                               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

        return_data = {}
        for db in databases:
            if singleset.issubset(databases[db]):
                database = databases[db]['database']
                hostname = databases[db]['hostname']
                username = databases[db]['username']
                try:
                    hostname = json.loads(hostname)
                except ValueError:
                    hostname = hostname

                if isinstance(hostname, list):
                    for host in hostname:
                        password = configure_db(host, database, username)
                else:
                    password = configure_db(hostname, database, username)

                return_data['_'.join([db, 'password'])] = password
                allowed_units = unit_sorted(get_allowed_units(database,
                                                              username))
                return_data['_'.join([db, 'allowed_units'])] = \
                    " ".join(allowed_units)
        if len(return_data) > 0:
            utils.relation_set(**return_data)
        if not cluster.is_clustered():
            utils.relation_set(db_host=local_hostname)
        else:
            utils.relation_set(db_host=utils.config_get("vip"))