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 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()
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"))
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 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"))