From 7ce1bb2dd737c46d712c67b2c2a5494f63de77d9 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 18 Mar 2013 12:56:57 +0000 Subject: [PATCH] Refactoring to use openstack charm helpers --- hooks/cluster-relation-changed | 2 +- hooks/cluster-relation-departed | 2 +- hooks/cluster-relation-joined | 2 +- hooks/config-changed | 2 +- hooks/ha-relation-changed | 2 +- hooks/ha-relation-joined | 2 +- hooks/identity-service-relation-changed | 2 +- hooks/identity-service-relation-joined | 2 +- hooks/install | 2 +- hooks/{keystone-hooks => keystone_hooks.py} | 301 ++++++++++--------- hooks/{utils.py => keystone_utils.py} | 302 ++++++++------------ hooks/lib/openstack_common.py | 11 +- hooks/manager.py | 1 + hooks/shared-db-relation-changed | 2 +- hooks/shared-db-relation-joined | 2 +- hooks/upgrade-charm | 2 +- 16 files changed, 296 insertions(+), 343 deletions(-) rename hooks/{keystone-hooks => keystone_hooks.py} (63%) rename hooks/{utils.py => keystone_utils.py} (68%) diff --git a/hooks/cluster-relation-changed b/hooks/cluster-relation-changed index cac7baa1..dd3b3eff 120000 --- a/hooks/cluster-relation-changed +++ b/hooks/cluster-relation-changed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/cluster-relation-departed b/hooks/cluster-relation-departed index cac7baa1..dd3b3eff 120000 --- a/hooks/cluster-relation-departed +++ b/hooks/cluster-relation-departed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/cluster-relation-joined b/hooks/cluster-relation-joined index cac7baa1..dd3b3eff 120000 --- a/hooks/cluster-relation-joined +++ b/hooks/cluster-relation-joined @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/config-changed b/hooks/config-changed index cac7baa1..dd3b3eff 120000 --- a/hooks/config-changed +++ b/hooks/config-changed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/ha-relation-changed b/hooks/ha-relation-changed index cac7baa1..dd3b3eff 120000 --- a/hooks/ha-relation-changed +++ b/hooks/ha-relation-changed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/ha-relation-joined b/hooks/ha-relation-joined index cac7baa1..dd3b3eff 120000 --- a/hooks/ha-relation-joined +++ b/hooks/ha-relation-joined @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/identity-service-relation-changed b/hooks/identity-service-relation-changed index cac7baa1..dd3b3eff 120000 --- a/hooks/identity-service-relation-changed +++ b/hooks/identity-service-relation-changed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/identity-service-relation-joined b/hooks/identity-service-relation-joined index cac7baa1..dd3b3eff 120000 --- a/hooks/identity-service-relation-joined +++ b/hooks/identity-service-relation-joined @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/install b/hooks/install index cac7baa1..dd3b3eff 120000 --- a/hooks/install +++ b/hooks/install @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/keystone-hooks b/hooks/keystone_hooks.py similarity index 63% rename from hooks/keystone-hooks rename to hooks/keystone_hooks.py index 16f277de..143f87e6 100755 --- a/hooks/keystone-hooks +++ b/hooks/keystone_hooks.py @@ -1,19 +1,53 @@ #!/usr/bin/python -import sys +import os import time import urlparse from base64 import b64encode -from utils import * +from keystone_utils import ( + config_get, + execute, + update_config_block, + set_admin_token, + ensure_initial_admin, + relation_get_dict, + create_service_entry, + create_endpoint_template, + create_role, + get_admin_token, + get_service_password, + create_user, + grant_role, + get_ca, + synchronize_service_credentials, + do_openstack_upgrade, + configure_pki_tokens, + SSH_USER, + SSL_DIR, + CLUSTER_RES + ) -from lib.openstack_common import * +from lib.openstack_common import ( + configure_installation_source, + get_os_codename_install_source, + get_os_codename_package, + get_os_version_codename, + get_os_version_package, + save_script_rc + ) import lib.unison as unison +import lib.utils as utils +import lib.cluster_utils as cluster +import lib.haproxy_utils as haproxy config = config_get() -packages = "keystone python-mysqldb pwgen haproxy python-jinja2 openssl unison" +packages = [ + "keystone", "python-mysqldb", "pwgen", + "haproxy", "python-jinja2", "openssl", "unison" + ] service = "keystone" # used to verify joined services are valid openstack components. @@ -62,13 +96,15 @@ valid_services = { } } + def install_hook(): if config["openstack-origin"] != "distro": configure_installation_source(config["openstack-origin"]) - execute("apt-get update", die=True) - execute("apt-get -y install %s" % packages, die=True, echo=True) - update_config_block('DEFAULT', public_port=config["service-port"]) - update_config_block('DEFAULT', admin_port=config["admin-port"]) + utils.install(packages) + update_config_block('DEFAULT', + public_port=cluster.determine_api_port(config["service-port"])) + update_config_block('DEFAULT', + admin_port=cluster.determine_api_port(config["admin-port"])) set_admin_token(config['admin-token']) # set all backends to use sql+sqlite, if they are not already by default @@ -83,9 +119,9 @@ def install_hook(): update_config_block('ec2', driver='keystone.contrib.ec2.backends.sql.Ec2') - execute("service keystone stop", echo=True) + utils.stop('keystone') execute("keystone-manage db_sync") - execute("service keystone start", echo=True) + utils.start('keystone') # ensure /var/lib/keystone is g+wrx for peer relations that # may be syncing data there via SSH_USER. @@ -96,17 +132,21 @@ def install_hook(): def db_joined(): - relation_data = { "database": config["database"], - "username": config["database-user"], - "hostname": config["hostname"] } - relation_set(relation_data) + relation_data = { + "database": config["database"], + "username": config["database-user"], + "hostname": config["hostname"] + } + utils.relation_set(**relation_data) + def db_changed(): relation_data = relation_get_dict() if ('password' not in relation_data or 'db_host' not in relation_data): - juju_log("db_host or password not set. Peer not ready, exit 0") - exit(0) + utils.juju_log('INFO', + "db_host or password not set. Peer not ready, exit 0") + return update_config_block('sql', connection="mysql://%s:%s@%s/%s" % (config["database-user"], @@ -114,15 +154,13 @@ def db_changed(): relation_data["db_host"], config["database"])) - execute("service keystone stop", echo=True) + utils.stop('keystone') + if cluster.eligible_leader(CLUSTER_RES): + utils.juju_log('INFO', + 'Cluster leader, performing db-sync') + execute("keystone-manage db_sync", echo=True) + utils.start('keystone') - if not eligible_leader(): - juju_log('Deferring DB initialization to service leader.') - execute("service keystone start") - return - - execute("keystone-manage db_sync", echo=True) - execute("service keystone start") time.sleep(5) ensure_initial_admin(config) @@ -130,54 +168,46 @@ def db_changed(): # are existing identity-service relations,, service entries need to be # recreated in the new database. Re-executing identity-service-changed # will do this. - for id in relation_ids(relation_name='identity-service'): - for unit in relation_list(relation_id=id): - juju_log("Re-exec'ing identity-service-changed for: %s - %s" % - (id, unit)) - identity_changed(relation_id=id, remote_unit=unit) + for rid in utils.relation_ids('identity-service'): + for unit in utils.relation_list(rid=rid): + utils.juju_log('INFO', + "Re-exec'ing identity-service-changed" + " for: %s - %s" % (rid, unit)) + identity_changed(relation_id=rid, remote_unit=unit) + def ensure_valid_service(service): if service not in valid_services.keys(): - juju_log("WARN: Invalid service requested: '%s'" % service) - realtion_set({ "admin_token": -1 }) + utils.juju_log('WARNING', + "Invalid service requested: '%s'" % service) + utils.relation_set(admin_token=-1) return -def add_endpoint(region, service, public_url, admin_url, internal_url): + +def add_endpoint(region, service, publicurl, adminurl, internalurl): desc = valid_services[service]["desc"] service_type = valid_services[service]["type"] create_service_entry(service, service_type, desc) create_endpoint_template(region=region, service=service, - public_url=public_url, - admin_url=admin_url, - internal_url=internal_url) + publicurl=publicurl, + adminurl=adminurl, + internalurl=internalurl) + def identity_joined(): """ Do nothing until we get information about requested service """ pass + 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(). """ - def ensure_valid_service(service): - if service not in valid_services.keys(): - juju_log("WARN: Invalid service requested: '%s'" % service) - realtion_set({ "admin_token": -1 }) - return - - def add_endpoint(region, service, publicurl, adminurl, internalurl): - desc = valid_services[service]["desc"] - service_type = valid_services[service]["type"] - create_service_entry(service, service_type, desc) - create_endpoint_template(region=region, service=service, - publicurl=publicurl, - adminurl=adminurl, - internalurl=internalurl) - - if not eligible_leader(): - juju_log('Deferring identity_changed() to service leader.') + if not cluster.eligible_leader(CLUSTER_RES): + utils.juju_log('INFO', + 'Deferring identity_changed() to service leader.') return settings = relation_get_dict(relation_id=relation_id, @@ -187,7 +217,8 @@ def identity_changed(relation_id=None, remote_unit=None): # Currently used by Swift. if 'requested_roles' in settings and settings['requested_roles'] != 'None': roles = settings['requested_roles'].split(',') - juju_log("Creating requested roles: %s" % roles) + utils.juju_log('INFO', + "Creating requested roles: %s" % roles) for role in roles: create_role(role, user=config['admin-user'], tenant='admin') @@ -196,25 +227,22 @@ def identity_changed(relation_id=None, remote_unit=None): 'internal_url']) if single.issubset(settings): # other end of relation advertised only one endpoint - if 'None' in [v for k,v in settings.iteritems()]: + 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 is_clustered(): + if cluster.is_clustered(): relation_data["auth_host"] = config['vip'] - relation_data["auth_port"] = SERVICE_PORTS['keystone_admin'] relation_data["service_host"] = config['vip'] - relation_data["service_port"] = SERVICE_PORTS['keystone_service'] else: relation_data["auth_host"] = config['hostname'] - relation_data["auth_port"] = config['admin-port'] relation_data["service_host"] = config['hostname'] - relation_data["service_port"] = config['service-port'] - relation_set(relation_data) + relation_data["auth_port"] = config['admin-port'] + relation_data["service_port"] = config['service-port'] + utils.relation_set(**relation_data) return - ensure_valid_service(settings['service']) add_endpoint(region=settings['region'], service=settings['service'], @@ -242,7 +270,7 @@ def identity_changed(relation_id=None, remote_unit=None): # } # } endpoints = {} - for k,v in settings.iteritems(): + for k, v in settings.iteritems(): ep = k.split('_')[0] x = '_'.join(k.split('_')[1:]) if ep not in endpoints: @@ -267,18 +295,20 @@ def identity_changed(relation_id=None, remote_unit=None): https_cn = https_cn.hostname service_username = '_'.join(services) - if 'None' in [v for k,v in settings.iteritems()]: + if 'None' in [v for k, v in settings.iteritems()]: return if not service_username: return token = get_admin_token() - juju_log("Creating service credentials for '%s'" % service_username) + 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']) + grant_role(service_username, config['admin-role'], + 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 @@ -305,27 +335,25 @@ def identity_changed(relation_id=None, remote_unit=None): relation_data['rid'] = relation_id # Check if clustered and use vip + haproxy ports if so - if is_clustered(): + if cluster.is_clustered(): relation_data["auth_host"] = config['vip'] - relation_data["auth_port"] = SERVICE_PORTS['keystone_admin'] relation_data["service_host"] = config['vip'] - relation_data["service_port"] = SERVICE_PORTS['keystone_service'] # 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) - service = os.getenv('JUJU_REMOTE_UNIT').split('/')[0] cert, key = ca.get_cert_and_key(common_name=https_cn) - ca_bundle= ca.get_ca_bundle() + 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) - relation_set_2(**relation_data) + utils.relation_set(**relation_data) synchronize_service_credentials() + def config_changed(): # Determine whether or not we should do an upgrade, based on the @@ -334,7 +362,8 @@ def config_changed(): installed = get_os_codename_package('keystone') if (available and - get_os_version_codename(available) > get_os_version_codename(installed)): + get_os_version_codename(available) > \ + get_os_version_codename(installed)): do_openstack_upgrade(config['openstack-origin'], packages) env_vars = {'OPENSTACK_SERVICE_KEYSTONE': 'keystone', @@ -344,8 +373,10 @@ def config_changed(): set_admin_token(config['admin-token']) - if eligible_leader(): - juju_log('Cluster leader - ensuring endpoint configuration is up to date') + if cluster.eligible_leader(CLUSTER_RES): + utils.juju_log('INFO', + 'Cluster leader - ensuring endpoint configuration' + ' is up to date') ensure_initial_admin(config) update_config_block('logger_root', level=config['log-level'], @@ -354,69 +385,68 @@ def config_changed(): # PKI introduced in Grizzly configure_pki_tokens(config) - execute("service keystone restart", echo=True) + utils.restart('keystone') cluster_changed() def upgrade_charm(): cluster_changed() - if eligible_leader(): - juju_log('Cluster leader - ensuring endpoint configuration is up to date') + if cluster.eligible_leader(CLUSTER_RES): + utils.juju_log('INFO', + 'Cluster leader - ensuring endpoint configuration' + ' is up to date') ensure_initial_admin(config) -SERVICE_PORTS = { - "keystone_admin": int(config['admin-port']) + 1, - "keystone_service": int(config['service-port']) + 1 - } - def cluster_joined(): unison.ssh_authorized_peers(user=SSH_USER, group='keystone', peer_interface='cluster', ensure_user=True) + update_config_block('DEFAULT', + public_port=cluster.determine_api_port(config["service-port"])) + update_config_block('DEFAULT', + admin_port=cluster.determine_api_port(config["admin-port"])) + utils.restart('keystone') + service_ports = { + "keystone_admin": \ + cluster.determine_haproxy_port(config['admin-port']), + "keystone_service": \ + cluster.determine_haproxy_port(config['service-port']) + } + haproxy.configure_haproxy(service_ports) + def cluster_changed(): unison.ssh_authorized_peers(user=SSH_USER, - group='keystone', - peer_interface='cluster', - ensure_user=True) - cluster_hosts = {} - cluster_hosts['self'] = config['hostname'] - for r_id in relation_ids('cluster'): - for unit in relation_list(r_id): - # trigger identity-changed to reconfigure HTTPS - # as necessary. - identity_changed(relation_id=r_id, remote_unit=unit) - cluster_hosts[unit.replace('/','-')] = \ - relation_get_dict(relation_id=r_id, - remote_unit=unit)['private-address'] - configure_haproxy(cluster_hosts, - SERVICE_PORTS) - + group='keystone', + peer_interface='cluster', + ensure_user=True) synchronize_service_credentials() + service_ports = { + "keystone_admin": \ + cluster.determine_haproxy_port(config['admin-port']), + "keystone_service": \ + cluster.determine_haproxy_port(config['service-port']) + } + haproxy.configure_haproxy(service_ports) - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - # trigger identity-changed to reconfigure HTTPS as necessary - identity_changed(relation_id=r_id, remote_unit=unit) def ha_relation_changed(): relation_data = relation_get_dict() if ('clustered' in relation_data and - is_leader()): - juju_log('Cluster configured, notifying other services and updating' - 'keystone endpoint configuration') + cluster.is_leader(CLUSTER_RES)): + utils.juju_log('INFO', + 'Cluster configured, notifying other services' + ' and updating keystone endpoint configuration') # Update keystone endpoint to point at VIP ensure_initial_admin(config) # Tell all related services to start using # the VIP and haproxy ports instead - for r_id in relation_ids('identity-service'): - relation_set_2(rid=r_id, - auth_host=config['vip'], - service_host=config['vip'], - service_port=SERVICE_PORTS['keystone_service'], - auth_port=SERVICE_PORTS['keystone_admin']) + for r_id in utils.relation_ids('identity-service'): + utils.relation_set(rid=r_id, + auth_host=config['vip'], + service_host=config['vip']) def ha_relation_joined(): @@ -424,41 +454,33 @@ def ha_relation_joined(): # include multicast port and interface to bind to. corosync_bindiface = config['ha-bindiface'] corosync_mcastport = config['ha-mcastport'] + vip = config['vip'] + vip_cidr = config['vip_cidr'] + vip_iface = config['vip_iface'] # Obtain resources resources = { - 'res_ks_vip':'ocf:heartbeat:IPaddr2', - 'res_ks_haproxy':'lsb:haproxy' + 'res_ks_vip': 'ocf:heartbeat:IPaddr2', + 'res_ks_haproxy': 'lsb:haproxy' } - # TODO: Obtain netmask and nic where to place VIP. resource_params = { - 'res_ks_vip':'params ip="%s" cidr_netmask="%s" nic="%s"' % (config['vip'], - config['vip_cidr'], config['vip_iface']), - 'res_ks_haproxy':'op monitor interval="5s"' + 'res_ks_vip': 'params ip="%s" cidr_netmask="%s" nic="%s"' % \ + (vip, vip_cidr, vip_iface), + 'res_ks_haproxy': 'op monitor interval="5s"' } init_services = { - 'res_ks_haproxy':'haproxy' + 'res_ks_haproxy': 'haproxy' } - groups = { - 'grp_ks_haproxy':'res_ks_vip res_ks_haproxy' + clones = { + 'gl_ks_haproxy': 'res_ks_haproxy' } - #clones = { - # 'cln_ks_haproxy':'res_ks_haproxy meta globally-unique="false" interleave="true"' - # } - #orders = { - # 'ord_vip_before_haproxy':'inf: res_ks_vip res_ks_haproxy' - # } - #colocations = { - # 'col_vip_on_haproxy':'inf: res_ks_haproxy res_ks_vip' - # } - - relation_set_2(init_services=init_services, - corosync_bindiface=corosync_bindiface, - corosync_mcastport=corosync_mcastport, - resources=resources, - resource_params=resource_params, - groups=groups) + utils.relation_set(init_services=init_services, + corosync_bindiface=corosync_bindiface, + corosync_mcastport=corosync_mcastport, + resources=resources, + resource_params=resource_params, + clones=clones) hooks = { @@ -476,9 +498,4 @@ hooks = { "upgrade-charm": upgrade_charm } -# keystone-hooks gets called by symlink corresponding to the requested relation -# hook. -hook = os.path.basename(sys.argv[0]) -if hook not in hooks.keys(): - error_out("Unsupported hook: %s" % hook) -hooks[hook]() +utils.do_hooks(hooks) diff --git a/hooks/utils.py b/hooks/keystone_utils.py similarity index 68% rename from hooks/utils.py rename to hooks/keystone_utils.py index ef1a179b..adfdc7b4 100755 --- a/hooks/utils.py +++ b/hooks/keystone_utils.py @@ -1,17 +1,23 @@ #!/usr/bin/python import ConfigParser -import subprocess import sys import json -import os -import tarfile -import tempfile import time +import subprocess +import os -from lib.openstack_common import * +from lib.openstack_common import( + get_os_codename_install_source, + get_os_codename_package, + error_out, + configure_installation_source + ) import keystone_ssl as ssl import lib.unison as unison +import lib.utils as utils +import lib.cluster_utils as cluster + keystone_conf = "/etc/keystone/keystone.conf" stored_passwd = "/var/lib/keystone/keystone.passwd" @@ -20,8 +26,9 @@ SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd' SSL_DIR = '/var/lib/keystone/juju_ssl/' SSL_CA_NAME = 'Ubuntu Cloud' +CLUSTER_RES = 'res_ks_vip' +SSH_USER = 'juju_keystone' -SSH_USER='juju_keystone' def execute(cmd, die=False, echo=False): """ Executes a command @@ -35,8 +42,8 @@ def execute(cmd, die=False, echo=False): stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) - stdout="" - stderr="" + stdout = "" + stderr = "" def print_line(l): if echo: @@ -59,7 +66,7 @@ def execute(cmd, die=False, echo=False): def config_get(): - """ Obtain the units config via 'config-get' + """ Obtain the units config via 'config-get' Returns a dict representing current config. private-address and IP of the unit is also tacked on for convienence @@ -68,71 +75,12 @@ def config_get(): config = json.loads(output) # make sure no config element is blank after config-get for c in config.keys(): - if not config[c]: + if not config[c]: error_out("ERROR: Config option has no paramter: %s" % c) # tack on our private address and ip - hostname = execute("unit-get private-address")[0].strip() - config["hostname"] = execute("unit-get private-address")[0].strip() + config["hostname"] = utils.unit_get('private-address') return config -def relation_ids(relation_name=None): - j = execute('relation-ids --format=json %s' % relation_name)[0] - return json.loads(j) - -def relation_list(relation_id=None): - cmd = 'relation-list --format=json' - if relation_id: - cmd += ' -r %s' % relation_id - j = execute(cmd)[0] - return json.loads(j) - -def relation_set(relation_data): - """ calls relation-set for all key=values in dict """ - for k in relation_data: - execute("relation-set %s=%s" % (k, relation_data[k]), die=True) - -def relation_set_2(**kwargs): - cmd = [ - 'relation-set' - ] - args = [] - for k, v in kwargs.items(): - if k == 'rid': - cmd.append('-r') - cmd.append(v) - else: - args.append('{}={}'.format(k, v)) - cmd += args - subprocess.check_call(cmd) - - -def unit_get(attribute): - cmd = [ - 'unit-get', - attribute - ] - value = subprocess.check_output(cmd).strip() # IGNORE:E1103 - if value == "": - return None - else: - return value - - -def relation_get(relation_data): - """ Obtain all current relation data - relation_data is a list of options to query from the relation - Returns a k,v dict of the results. - Leave empty responses out of the results as they haven't yet been - set on the other end. - Caller can then "len(results.keys()) == len(relation_data)" to find out if - all relation values have been set on the other side - """ - results = {} - for r in relation_data: - result = execute("relation-get %s" % r, die=True)[0].strip('\n') - if result != "": - results[r] = result - return results def relation_get_dict(relation_id=None, remote_unit=None): """Obtain all relation data as dict by way of JSON""" @@ -152,18 +100,23 @@ def relation_get_dict(relation_id=None, remote_unit=None): settings[str(k)] = str(v) return settings + def set_admin_token(admin_token): """Set admin token according to deployment config or use a randomly generated token if none is specified (default). """ if admin_token != 'None': - juju_log('Configuring Keystone to use a pre-configured admin token.') + utils.juju_log('INFO', + 'Configuring Keystone to use' + ' a pre-configured admin token.') token = admin_token else: - juju_log('Configuring Keystone to use a random admin token.') + utils.juju_log('INFO', + 'Configuring Keystone to use a random admin token.') if os.path.isfile(stored_token): - msg = 'Loading a previously generated admin token from %s' % stored_token - juju_log(msg) + msg = 'Loading a previously generated' \ + ' admin token from %s' % stored_token + utils.juju_log('INFO', msg) f = open(stored_token, 'r') token = f.read().strip() f.close() @@ -174,6 +127,7 @@ def set_admin_token(admin_token): out.close() update_config_block('DEFAULT', admin_token=token) + def get_admin_token(): """Temporary utility to grab the admin token as configured in keystone.conf @@ -188,6 +142,7 @@ def get_admin_token(): keystone_conf) error_out('Could not find admin_token line in %s' % keystone_conf) + def update_config_block(section, **kwargs): """ Updates keystone.conf blocks given kwargs. Update a config setting in a specific setting of a config @@ -209,6 +164,7 @@ def update_config_block(section, **kwargs): with open(conf_file, 'wb') as out: config.write(out) + def create_service_entry(service_name, service_type, service_desc, owner=None): """ Add a new service entry to keystone if one does not already exist """ import manager @@ -216,12 +172,15 @@ def create_service_entry(service_name, service_type, service_desc, owner=None): token=get_admin_token()) for service in [s._info for s in manager.api.services.list()]: if service['name'] == service_name: - juju_log("Service entry for '%s' already exists." % service_name) + utils.juju_log('INFO', + "Service entry for '%s' already exists." % \ + service_name) return manager.api.services.create(name=service_name, service_type=service_type, description=service_desc) - juju_log("Created new service entry '%s'" % service_name) + utils.juju_log('INFO', "Created new service entry '%s'" % service_name) + def create_endpoint_template(region, service, publicurl, adminurl, internalurl): @@ -233,8 +192,9 @@ def create_endpoint_template(region, service, publicurl, adminurl, service_id = manager.resolve_service_id(service) for ep in [e._info for e in manager.api.endpoints.list()]: if ep['service_id'] == service_id and ep['region'] == region: - juju_log("Endpoint template already exists for '%s' in '%s'" - % (service, region)) + utils.juju_log('INFO', + "Endpoint template already exists for '%s' in '%s'" + % (service, region)) up_to_date = True for k in ['publicurl', 'adminurl', 'internalurl']: @@ -245,7 +205,9 @@ def create_endpoint_template(region, service, publicurl, adminurl, return else: # delete endpoint and recreate if endpoint urls need updating. - juju_log("Updating endpoint template with new endpoint urls.") + utils.juju_log('INFO', + "Updating endpoint template with" + " new endpoint urls.") manager.api.endpoints.delete(ep['id']) manager.api.endpoints.create(region=region, @@ -253,8 +215,9 @@ def create_endpoint_template(region, service, publicurl, adminurl, publicurl=publicurl, adminurl=adminurl, internalurl=internalurl) - juju_log("Created new endpoint template for '%s' in '%s'" % - (region, service)) + utils.juju_log('INFO', "Created new endpoint template for '%s' in '%s'" % + (region, service)) + def create_tenant(name): """ creates a tenant if it does not already exist """ @@ -265,9 +228,10 @@ def create_tenant(name): if not tenants or name not in [t['name'] for t in tenants]: manager.api.tenants.create(tenant_name=name, description='Created by Juju') - juju_log("Created new tenant: %s" % name) + utils.juju_log('INFO', "Created new tenant: %s" % name) return - juju_log("Tenant '%s' already exists." % name) + utils.juju_log('INFO', "Tenant '%s' already exists." % name) + def create_user(name, password, tenant): """ creates a user if it doesn't already exist, as a member of tenant """ @@ -283,9 +247,11 @@ def create_user(name, password, tenant): password=password, email='juju@localhost', tenant_id=tenant_id) - juju_log("Created new user '%s' tenant: %s" % (name, tenant_id)) + utils.juju_log('INFO', "Created new user '%s' tenant: %s" % \ + (name, tenant_id)) return - juju_log("A user named '%s' already exists" % name) + utils.juju_log('INFO', "A user named '%s' already exists" % name) + def create_role(name, user=None, tenant=None): """ creates a role if it doesn't already exist. grants role to user """ @@ -295,9 +261,9 @@ def create_role(name, user=None, tenant=None): roles = [r._info for r in manager.api.roles.list()] if not roles or name not in [r['name'] for r in roles]: manager.api.roles.create(name=name) - juju_log("Created new role '%s'" % name) + utils.juju_log('INFO', "Created new role '%s'" % name) else: - juju_log("A role named '%s' already exists" % name) + utils.juju_log('INFO', "A role named '%s' already exists" % name) if not user and not tenant: return @@ -308,32 +274,35 @@ def create_role(name, user=None, tenant=None): tenant_id = manager.resolve_tenant_id(tenant) if None in [user_id, role_id, tenant_id]: - error_out("Could not resolve [user_id, role_id, tenant_id]" % - [user_id, role_id, tenant_id]) + error_out("Could not resolve [%s, %s, %s]" % + (user_id, role_id, tenant_id)) grant_role(user, name, tenant) + def grant_role(user, role, tenant): """grant user+tenant a specific role""" import manager manager = manager.KeystoneManager(endpoint='http://localhost:35357/v2.0/', token=get_admin_token()) - juju_log("Granting user '%s' role '%s' on tenant '%s'" %\ - (user, role, tenant)) + utils.juju_log('INFO', "Granting user '%s' role '%s' on tenant '%s'" % \ + (user, role, tenant)) user_id = manager.resolve_user_id(user) role_id = manager.resolve_role_id(role) tenant_id = manager.resolve_tenant_id(tenant) - cur_roles = manager.api.roles.roles_for_user(user_id, tenant_id) + cur_roles = manager.api.roles.roles_for_user(user_id, tenant_id) if not cur_roles or role_id not in [r.id for r in cur_roles]: manager.api.roles.add_user_role(user=user_id, role=role_id, tenant=tenant_id) - juju_log("Granted user '%s' role '%s' on tenant '%s'" %\ - (user, role, tenant)) + utils.juju_log('INFO', "Granted user '%s' role '%s' on tenant '%s'" % \ + (user, role, tenant)) else: - juju_log("User '%s' already has role '%s' on tenant '%s'" %\ - (user, role, tenant)) + utils.juju_log('INFO', + "User '%s' already has role '%s' on tenant '%s'" % \ + (user, role, tenant)) + def generate_admin_token(config): """ generate and add an admin token """ @@ -345,12 +314,15 @@ def generate_admin_token(config): token = random.randrange(1000000000000, 9999999999999) else: return config["admin-token"] - manager.api.add_token(token, config["admin-user"], "admin", config["token-expiry"]) - juju_log("Generated and added new random admin token.") + manager.api.add_token(token, config["admin-user"], + "admin", config["token-expiry"]) + utils.juju_log('INFO', "Generated and added new random admin token.") return token + def ensure_initial_admin(config): - """ Ensures the minimum admin stuff exists in whatever database we're using. + """ 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 @@ -365,10 +337,11 @@ def ensure_initial_admin(config): if config["admin-password"] != "None": passwd = config["admin-password"] elif os.path.isfile(stored_passwd): - juju_log("Loading stored passwd from %s" % stored_passwd) + utils.juju_log('INFO', "Loading stored passwd from %s" % stored_passwd) passwd = open(stored_passwd, 'r').readline().strip('\n') if passwd == "": - juju_log("Generating new passwd for user: %s" % config["admin-user"]) + 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) @@ -380,22 +353,13 @@ def ensure_initial_admin(config): create_role("KeystoneServiceAdmin", config["admin-user"], 'admin') create_service_entry("keystone", "identity", "Keystone Identity Service") - if is_clustered(): - juju_log("Creating endpoint for clustered configuration") - for region in config['region'].split(): - create_keystone_endpoint(service_host=config["vip"], - service_port=int(config["service-port"]) + 1, - auth_host=config["vip"], - auth_port=int(config["admin-port"]) + 1, - region=region) - else: - juju_log("Creating standard endpoint") - for region in config['region'].split(): - create_keystone_endpoint(service_host=config["hostname"], - service_port=config["service-port"], - auth_host=config["hostname"], - auth_port=config["admin-port"], - region=region) + utils.juju_log('INFO', "Creating standard endpoint") + for region in config['region'].split(): + create_keystone_endpoint(service_host=config["hostname"], + service_port=config["service-port"], + auth_host=config["hostname"], + auth_port=config["admin-port"], + region=region) def create_keystone_endpoint(service_host, service_port, @@ -411,14 +375,16 @@ def update_user_password(username, password): import manager manager = manager.KeystoneManager(endpoint='http://localhost:35357/v2.0/', token=get_admin_token()) - juju_log("Updating password for user '%s'" % username) + utils.juju_log('INFO', "Updating password for user '%s'" % username) user_id = manager.resolve_user_id(username) if user_id is None: error_out("Could not resolve user id for '%s'" % username) manager.api.users.update_password(user=user_id, password=password) - juju_log("Successfully updated password for user '%s'" % username) + utils.juju_log('INFO', "Successfully updated password for user '%s'" % \ + username) + def load_stored_passwords(path=SERVICE_PASSWD_PATH): creds = {} @@ -431,10 +397,12 @@ def load_stored_passwords(path=SERVICE_PASSWD_PATH): creds[user] = passwd return creds + def save_stored_passwords(path=SERVICE_PASSWD_PATH, **creds): with open(path, 'wb') as stored_passwd: [stored_passwd.write('%s:%s\n' % (u, p)) for u, p in creds.iteritems()] + def get_service_password(service_username): creds = load_stored_passwords() if service_username in creds: @@ -446,12 +414,13 @@ def get_service_password(service_username): return passwd + def configure_pki_tokens(config): '''Configure PKI token signing, if enabled.''' if config['enable-pki'] not in ['True', 'true']: update_config_block('signing', token_format='UUID') else: - juju_log('TODO: PKI Support, setting to UUID for now.') + utils.juju_log('INFO', 'TODO: PKI Support, setting to UUID for now.') update_config_block('signing', token_format='UUID') @@ -462,10 +431,12 @@ def do_openstack_upgrade(install_src, packages): old_vers = get_os_codename_package('keystone') new_vers = get_os_codename_install_source(install_src) - juju_log("Beginning Keystone upgrade: %s -> %s" % (old_vers, new_vers)) + utils.juju_log('INFO', + "Beginning Keystone upgrade: %s -> %s" % \ + (old_vers, new_vers)) # Backup previous config. - juju_log("Backing up contents of /etc/keystone.") + utils.juju_log('INFO', "Backing up contents of /etc/keystone.") stamp = time.strftime('%Y%m%d%H%M') cmd = 'tar -pcf /var/lib/juju/keystone-backup-%s.tar /etc/keystone' % stamp execute(cmd, die=True, echo=True) @@ -482,14 +453,16 @@ def do_openstack_upgrade(install_src, packages): set_admin_token(config['admin-token']) # set the sql connection string if a shared-db relation is found. - ids = relation_ids(relation_name='shared-db') + ids = utils.relation_ids('shared-db') if ids: - for id in ids: - for unit in relation_list(id): - juju_log('Configuring new keystone.conf for datbase access '\ - 'on existing database relation to %s' % unit) - relation_data = relation_get_dict(relation_id=id, + for rid in ids: + for unit in utils.relation_list(rid): + utils.juju_log('INFO', + 'Configuring new keystone.conf for ' + 'database access on existing database' + ' relation to %s' % unit) + relation_data = relation_get_dict(relation_id=rid, remote_unit=unit) update_config_block('sql', connection="mysql://%s:%s@%s/%s" % @@ -498,66 +471,21 @@ def do_openstack_upgrade(install_src, packages): relation_data["private-address"], config["database"])) - execute('service keystone stop', echo=True) - if ((is_clustered() and is_leader()) or - not is_clustered()): - juju_log('Running database migrations for %s' % new_vers) + utils.stop('keystone') + if (cluster.eligible_leader(CLUSTER_RES)): + utils.juju_log('INFO', + 'Running database migrations for %s' % new_vers) execute('keystone-manage db_sync', echo=True, die=True) else: - juju_log('Not cluster leader; snoozing whilst leader upgrades DB') + utils.juju_log('INFO', + 'Not cluster leader; snoozing whilst' + ' leader upgrades DB') time.sleep(10) - execute('service keystone start', echo=True) + utils.start('keystone') time.sleep(5) - juju_log('Completed Keystone upgrade: %s -> %s' % (old_vers, new_vers)) - - -def is_clustered(): - for r_id in (relation_ids('ha') or []): - for unit in (relation_list(r_id) or []): - relation_data = \ - relation_get_dict(relation_id=r_id, - remote_unit=unit) - if 'clustered' in relation_data: - return True - return False - - -def is_leader(): - status = execute('crm resource show res_ks_vip', echo=True)[0].strip() - hostname = execute('hostname', echo=True)[0].strip() - if hostname in status: - return True - else: - return False - - -def peer_units(): - peers = [] - for r_id in (relation_ids('cluster') or []): - for unit in (relation_list(r_id) or []): - peers.append(unit) - return peers - -def oldest_peer(peers): - local_unit_no = os.getenv('JUJU_UNIT_NAME').split('/')[1] - for peer in peers: - remote_unit_no = peer.split('/')[1] - if remote_unit_no < local_unit_no: - return False - return True - - -def eligible_leader(): - if is_clustered(): - if not is_leader(): - juju_log('Deferring action to CRM leader.') - return False - else: - peers = peer_units() - if peers and not oldest_peer(peers): - juju_log('Deferring action to oldest service unit.') - return False - return True + utils.juju_log('INFO', + 'Completed Keystone upgrade: ' + '%s -> %s' % (old_vers, new_vers)) def synchronize_service_credentials(): @@ -565,15 +493,17 @@ def synchronize_service_credentials(): Broadcast service credentials to peers or consume those that have been broadcasted by peer, depending on hook context. ''' - if (not eligible_leader() or + if (not cluster.eligible_leader(CLUSTER_RES) or not os.path.isfile(SERVICE_PASSWD_PATH)): return - juju_log('Synchronizing service passwords to all peers.') + utils.juju_log('INFO', 'Synchronizing service passwords to all peers.') unison.sync_to_peers(peer_interface='cluster', paths=[SERVICE_PASSWD_PATH], user=SSH_USER, verbose=True) CA = [] + + def get_ca(user='keystone', group='keystone'): """ Initialize a new CA object if one hasn't already been loaded. diff --git a/hooks/lib/openstack_common.py b/hooks/lib/openstack_common.py index 60726e90..bcd71220 100644 --- a/hooks/lib/openstack_common.py +++ b/hooks/lib/openstack_common.py @@ -13,7 +13,7 @@ ubuntu_openstack_release = { 'oneiric': 'diablo', 'precise': 'essex', 'quantal': 'folsom', - 'raring' : 'grizzly', + 'raring': 'grizzly', } @@ -34,6 +34,7 @@ swift_codenames = { '1.7.7': 'grizzly', } + def juju_log(msg): subprocess.check_call(['juju-log', msg]) @@ -78,6 +79,7 @@ def get_os_codename_install_source(src): if v in src: return v + def get_os_codename_version(vers): '''Determine OpenStack codename from version number.''' try: @@ -136,6 +138,7 @@ def get_os_version_package(pkg): e = "Could not determine OpenStack version for package: %s" % pkg error_out(e) + def configure_installation_source(rel): '''Configure apt installation source.''' @@ -154,7 +157,7 @@ def configure_installation_source(rel): subprocess.check_call(["add-apt-repository", "-y", src]) elif rel[:3] == "deb": l = len(rel.split('|')) - if l == 2: + if l == 2: src, key = rel.split('|') juju_log("Importing PPA key from keyserver for %s" % src) _import_key(key) @@ -211,6 +214,7 @@ def configure_installation_source(rel): HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' HAPROXY_DEFAULT = '/etc/default/haproxy' + def configure_haproxy(units, service_ports, template_dir=None): template_dir = template_dir or 'templates' import jinja2 @@ -229,6 +233,7 @@ def configure_haproxy(units, service_ports, template_dir=None): with open(HAPROXY_DEFAULT, 'w') as f: f.write('ENABLED=1') + def save_script_rc(script_path="scripts/scriptrc", **env_vars): """ Write an rc file in the charm-delivered directory containing @@ -238,7 +243,7 @@ def save_script_rc(script_path="scripts/scriptrc", **env_vars): service changes. """ unit_name = os.getenv('JUJU_UNIT_NAME').replace('/', '-') - juju_rc_path="/var/lib/juju/units/%s/charm/%s" % (unit_name, script_path) + juju_rc_path = "/var/lib/juju/units/%s/charm/%s" % (unit_name, script_path) with open(juju_rc_path, 'wb') as rc_script: rc_script.write( "#!/bin/bash\n") diff --git a/hooks/manager.py b/hooks/manager.py index afc5d292..e7058820 100644 --- a/hooks/manager.py +++ b/hooks/manager.py @@ -1,6 +1,7 @@ #!/usr/bin/python from keystoneclient.v2_0 import client + class KeystoneManager(object): def __init__(self, endpoint, token): self.api = client.Client(endpoint=endpoint, token=token) diff --git a/hooks/shared-db-relation-changed b/hooks/shared-db-relation-changed index cac7baa1..dd3b3eff 120000 --- a/hooks/shared-db-relation-changed +++ b/hooks/shared-db-relation-changed @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/shared-db-relation-joined b/hooks/shared-db-relation-joined index cac7baa1..dd3b3eff 120000 --- a/hooks/shared-db-relation-joined +++ b/hooks/shared-db-relation-joined @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file diff --git a/hooks/upgrade-charm b/hooks/upgrade-charm index cac7baa1..dd3b3eff 120000 --- a/hooks/upgrade-charm +++ b/hooks/upgrade-charm @@ -1 +1 @@ -keystone-hooks \ No newline at end of file +keystone_hooks.py \ No newline at end of file