From ee65bb87fa2ba16a14122410d36278492fdec34c Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 4 Dec 2013 10:16:10 +0000 Subject: [PATCH 01/57] Add icehouse support --- hooks/charmhelpers/core/host.py | 10 ++++++++++ hooks/lib/openstack_common.py | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py index d386f02c..c8c81b28 100644 --- a/hooks/charmhelpers/core/host.py +++ b/hooks/charmhelpers/core/host.py @@ -279,3 +279,13 @@ def get_nic_mtu(nic): if 'mtu' in words: mtu = words[words.index("mtu") + 1] return mtu + + +def get_nic_hwaddr(nic): + cmd = ['ip', '-o', '-0', 'addr', 'show', nic] + ip_output = subprocess.check_output(cmd) + hwaddr = "" + words = ip_output.split() + if 'link/ether' in words: + hwaddr = words[words.index('link/ether') + 1] + return hwaddr diff --git a/hooks/lib/openstack_common.py b/hooks/lib/openstack_common.py index 2c540e56..a990dc2a 100644 --- a/hooks/lib/openstack_common.py +++ b/hooks/lib/openstack_common.py @@ -15,6 +15,7 @@ ubuntu_openstack_release = { 'quantal': 'folsom', 'raring': 'grizzly', 'saucy': 'havana', + 'trusty': 'icehouse', } @@ -24,6 +25,7 @@ openstack_codenames = { '2012.2': 'folsom', '2013.1': 'grizzly', '2013.2': 'havana', + '2014.1': 'icehouse', } # The ugly duckling @@ -199,7 +201,10 @@ def configure_installation_source(rel): 'grizzly/proposed': 'precise-proposed/grizzly', 'havana': 'precise-updates/havana', 'havana/updates': 'precise-updates/havana', - 'havana/proposed': 'precise-proposed/havana' + 'havana/proposed': 'precise-proposed/havana', + 'icehouse': 'precise-updates/icehouse', + 'icehouse/updates': 'precise-updates/icehouse', + 'icehouse/proposed': 'precise-proposed/icehouse', } try: From ed0a94aa66efa8dc31cdc26fc91623c6f689a621 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 10 Dec 2013 23:09:15 +0000 Subject: [PATCH 02/57] Add missing icehouse pockets --- hooks/lib/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hooks/lib/utils.py b/hooks/lib/utils.py index e6dd2ba1..8095e86a 100644 --- a/hooks/lib/utils.py +++ b/hooks/lib/utils.py @@ -75,6 +75,9 @@ CLOUD_ARCHIVE_POCKETS = { 'havana': 'precise-updates/havana', 'havana/updates': 'precise-updates/havana', 'havana/proposed': 'precise-proposed/havana', + 'icehouse': 'precise-updates/icehouse', + 'icehouse/updates': 'precise-updates/icehouse', + 'icehouse/proposed': 'precise-proposed/icehouse', } From 0c0c5ff9c09a25de6f2e3c077e8df027aff8c44e Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 16:54:26 +0000 Subject: [PATCH 03/57] Re-instate a load of lost function --- hooks/charmhelpers/contrib/__init__.py | 0 .../contrib/hahelpers/__init__.py | 0 .../contrib/openstack/__init__.py | 0 .../charmhelpers/contrib/openstack/context.py | 25 +++++- .../charmhelpers/contrib/storage/__init__.py | 0 .../contrib/storage/linux/__init__.py | 0 hooks/keystone_hooks.py | 89 ++++++++++--------- hooks/keystone_utils.py | 22 ++++- 8 files changed, 85 insertions(+), 51 deletions(-) create mode 100644 hooks/charmhelpers/contrib/__init__.py create mode 100644 hooks/charmhelpers/contrib/hahelpers/__init__.py create mode 100644 hooks/charmhelpers/contrib/openstack/__init__.py create mode 100644 hooks/charmhelpers/contrib/storage/__init__.py create mode 100644 hooks/charmhelpers/contrib/storage/linux/__init__.py diff --git a/hooks/charmhelpers/contrib/__init__.py b/hooks/charmhelpers/contrib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/charmhelpers/contrib/hahelpers/__init__.py b/hooks/charmhelpers/contrib/hahelpers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/charmhelpers/contrib/openstack/__init__.py b/hooks/charmhelpers/contrib/openstack/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 81ac12b6..5785b6a1 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -29,6 +29,7 @@ from charmhelpers.contrib.hahelpers.cluster import ( determine_apache_port, determine_api_port, https, + is_clustered ) from charmhelpers.contrib.hahelpers.apache import ( @@ -240,17 +241,19 @@ class CephContext(OSContextGenerator): '''This generates context for /etc/ceph/ceph.conf templates''' if not relation_ids('ceph'): return {} + log('Generating template context for ceph') + mon_hosts = [] auth = None key = None + use_syslog = str(config('use-syslog')).lower() for rid in relation_ids('ceph'): for unit in related_units(rid): mon_hosts.append(relation_get('private-address', rid=rid, unit=unit)) auth = relation_get('auth', rid=rid, unit=unit) key = relation_get('key', rid=rid, unit=unit) - use_syslog = str(config('use-syslog')).lower() ctxt = { 'mon_hosts': ' '.join(mon_hosts), @@ -393,7 +396,7 @@ class ApacheSSLContext(OSContextGenerator): return ctxt -class NeutronContext(object): +class NeutronContext(OSContextGenerator): interfaces = [] @property @@ -454,6 +457,22 @@ class NeutronContext(object): return nvp_ctxt + def neutron_ctxt(self): + if https(): + proto = 'https' + else: + proto = 'http' + if is_clustered(): + host = config('vip') + else: + host = unit_get('private-address') + url = '%s://%s:%s' % (proto, host, '9292') + ctxt = { + 'network_manager': self.network_manager, + 'neutron_url': url, + } + return ctxt + def __call__(self): self._ensure_packages() @@ -463,7 +482,7 @@ class NeutronContext(object): if not self.plugin: return {} - ctxt = {'network_manager': self.network_manager} + ctxt = self.neutron_ctxt() if self.plugin == 'ovs': ctxt.update(self.ovs_ctxt()) diff --git a/hooks/charmhelpers/contrib/storage/__init__.py b/hooks/charmhelpers/contrib/storage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/charmhelpers/contrib/storage/linux/__init__.py b/hooks/charmhelpers/contrib/storage/linux/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 4d327d88..8fcee361 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -2,6 +2,7 @@ import os import sys +import time from subprocess import check_call @@ -21,11 +22,14 @@ from charmhelpers.core.hookenv import ( from charmhelpers.core.host import ( mkdir, restart_on_change, - service_restart + service_restart, + service_stop, + service_start ) from charmhelpers.fetch import ( - apt_install, apt_update + apt_install, apt_update, + filter_installed_packages ) from charmhelpers.contrib.openstack.utils import ( @@ -60,6 +64,7 @@ from charmhelpers.payload.execd import execd_preinstall hooks = Hooks() CONFIGS = register_configs() + @hooks.hook() def install(): execd_preinstall() @@ -67,21 +72,25 @@ def install(): apt_update() apt_install(determine_packages(), fatal=True) + @hooks.hook('config-changed') -@restart_on_change(restart_map(), stopstart=True) def config_changed(): - unison.ensure_user(user=SSH_USER, group='juju_keystone') + unison.ensure_user(user=SSH_USER, group='keystone') homedir = unison.get_homedir(SSH_USER) if not os.path.isdir(homedir): - mkdir(homedir, SSH_USER, 'juju_keystone', 0775) - check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) + mkdir(homedir, SSH_USER, 'keystone', 0775) + if openstack_upgrade_available('keystone'): do_openstack_upgrade(configs=CONFIGS) - check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) + + check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/']) + save_script_rc() configure_https() CONFIGS.write_all() service_restart('keystone') + time.sleep(10) + if eligible_leader(CLUSTER_RES): migrate_database() ensure_initial_admin(config) @@ -89,9 +98,9 @@ def config_changed(): # HTTPS may have been set - so fire all identity relations # again for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - identity_changed(relation_id=r_id, - remote_unit=unit) + for unit in relation_list(r_id): + identity_changed(relation_id=r_id, + remote_unit=unit) @hooks.hook('shared-db-relation-joined') @@ -106,12 +115,11 @@ def db_joined(): def db_changed(): if 'shared-db' not in CONFIGS.complete_contexts(): log('shared-db relation incomplete. Peer not ready?') - return - CONFIGS.write(KEYSTONE_CONF) - service_restart('keystone') - if eligible_leader(CLUSTER_RES): - migrate_database() - ensure_initial_admin(config) + else: + CONFIGS.write(KEYSTONE_CONF) + if eligible_leader(CLUSTER_RES): + migrate_database() + ensure_initial_admin(config) @hooks.hook('identity-service-relation-joined') @@ -121,16 +129,12 @@ def identity_joined(): @hooks.hook('identity-service-relation-changed') -@restart_on_change(restart_map()) -def identity_changed(): - if not eligible_leader(CLUSTER_RES): - log('Deferring identity_changed() to service leader.') - #if 'identity-service' not in CONFIGS.complete_contexts(): - # return +def identity_changed(relation_id=None, remote_unit=None): if eligible_leader(CLUSTER_RES): - add_service_to_keystone() + add_service_to_keystone(relation_id, remote_unit) synchronize_service_credentials() - + else: + log('Deferring identity_changed() to service leader.') @hooks.hook('cluster-relation-joined') @@ -146,7 +150,7 @@ def cluster_joined(): @restart_on_change(restart_map(), stopstart=True) def cluster_changed(): unison.ssh_authorized_peers(user=SSH_USER, - group='juju_keystone', + group='keystone', peer_interface='cluster', ensure_local_user=True) synchronize_service_credentials() @@ -183,18 +187,15 @@ def ha_joined(): @hooks.hook('ha-relation-changed') def ha_changed(): clustered = relation_get('clustered') - if not clustered or clustered in [None, 'None', '']: - log('ha_changed: hacluster subordinate not fully clustered.') - return - if not is_leader(CLUSTER_RES): - log('ha_changed: hacluster complete but we are not leader.') - return - ensure_initial_admin(config) - log('Cluster configured, notifying other services and updating ' - 'keystone endpoint configuration') - for rid in relation_ids('identity-service'): - identity_joined(rid=rid) - CONFIGS.write_all() + if (clustered is not None and + is_leader(CLUSTER_RES)): + ensure_initial_admin(config) + log('Cluster configured, notifying other services and updating ' + 'keystone endpoint configuration') + for rid in relation_ids('identity-service'): + relation_set(rid=rid, + auth_host=config('vip'), + service_host=config('vip')) def configure_https(): @@ -212,16 +213,16 @@ def configure_https(): cmd = ['a2dissite', 'openstack_https_frontend'] check_call(cmd) - for rid in relation_ids('identity-service'): - identity_joined(rid=rid) - @hooks.hook('upgrade-charm') +@restart_on_change(restart_map(), stopstart=True) def upgrade_charm(): - if openstack_upgrade_available('keystone'): - do_openstack_upgrade(configs=CONFIGS) - save_script_rc() - configure_https() + apt_install(filter_installed_packages(determine_packages())) + cluster_changed() + if eligible_leader(CLUSTER_RES): + log('Cluster leader - ensuring endpoint configuration' + ' is up to date') + ensure_initial_admin(config) CONFIGS.write_all() diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 220c3087..b77372d7 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -2,6 +2,7 @@ import subprocess import os import urlparse +import time from base64 import b64encode from collections import OrderedDict @@ -39,6 +40,11 @@ from charmhelpers.fetch import ( apt_update, ) +from charmhelpers.core.host import ( + service_stop, + service_start, +) + import keystone_context import keystone_ssl as ssl @@ -155,6 +161,7 @@ valid_services = { } } + def resource_map(): ''' Dynamically generate a map of resources that will be managed for a single @@ -233,11 +240,15 @@ def do_openstack_upgrade(configs): if eligible_leader(CLUSTER_RES): migrate_database() + def migrate_database(): '''Runs keystone-manage to initialize a new database or migrate existing''' log('Migrating the keystone database.', level=INFO) + service_stop('keystone') cmd = ['keystone-manage', 'db_sync'] subprocess.check_output(cmd) + service_start('keystone') + time.sleep(10) ## OLD @@ -578,8 +589,9 @@ def relation_list(rid): else: return result -def add_service_to_keystone(): - settings = relation_get() + +def add_service_to_keystone(relation_id=None, remote_unit=None): + settings = relation_get(rid=relation_id, unit=remote_unit) # the minimum settings needed per endpoint single = set(['service', 'region', 'public_url', 'admin_url', 'internal_url']) @@ -610,7 +622,8 @@ def add_service_to_keystone(): for role in get_requested_roles(settings): log("Creating requested role: %s" % role) create_role(role) - relation_set(**relation_data) + relation_set(relation_id=relation_id, + **relation_data) return else: ensure_valid_service(settings['service']) @@ -724,7 +737,8 @@ def add_service_to_keystone(): if is_clustered(): unison.sync_to_peers(peer_interface='cluster', paths=[SSL_DIR], user=SSH_USER, verbose=True) - relation_set(**relation_data) + relation_set(relation_id=relation_id, + **relation_data) def ensure_valid_service(service): From 0f8869029d074aa1b21bb132ae076c1e825f2917 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:05:40 +0000 Subject: [PATCH 04/57] Restart if need be --- hooks/keystone_hooks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 8fcee361..00c07977 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -23,8 +23,6 @@ from charmhelpers.core.host import ( mkdir, restart_on_change, service_restart, - service_stop, - service_start ) from charmhelpers.fetch import ( @@ -74,6 +72,7 @@ def install(): @hooks.hook('config-changed') +@restart_on_change(restart_map()) def config_changed(): unison.ensure_user(user=SSH_USER, group='keystone') homedir = unison.get_homedir(SSH_USER) From 327ea12707c3935bd32baa539a5f21bdb995c5c5 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:10:31 +0000 Subject: [PATCH 05/57] stop using old hostname config --- hooks/keystone_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index b77372d7..54445f43 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -606,8 +606,8 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): 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_host"] = unit_private_ip() + relation_data["service_host"] = unit_private_ip() relation_data["auth_port"] = config('admin-port') relation_data["service_port"] = config('service-port') if config('https-service-endpoints') in ['True', 'true']: From c32b14764ec7d8f1cbc49914fc9227ffde5d8f6e Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:17:13 +0000 Subject: [PATCH 06/57] Drop more use of hostname config --- hooks/keystone_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 54445f43..db5c8219 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -707,9 +707,9 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): # service credentials relation_data = { "admin_token": token, - "service_host": config("hostname"), + "service_host": unit_private_ip(), "service_port": config("service-port"), - "auth_host": config("hostname"), + "auth_host": unit_private_ip(), "auth_port": config("admin-port"), "service_username": service_username, "service_password": service_password, From 90e376f3569b779934822d781e5d11c0d4872e0e Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:28:37 +0000 Subject: [PATCH 07/57] Restore logging options --- hooks/keystone_context.py | 3 +++ templates/essex/etc_keystone_keystone.conf | 3 +++ templates/folsom/etc_keystone_keystone.conf | 3 +++ templates/grizzly/etc_keystone_keystone.conf | 3 +++ templates/havana/etc_keystone_keystone.conf | 3 +++ 5 files changed, 15 insertions(+) diff --git a/hooks/keystone_context.py b/hooks/keystone_context.py index dbed890d..3badbe78 100644 --- a/hooks/keystone_context.py +++ b/hooks/keystone_context.py @@ -17,6 +17,7 @@ import os CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' + class ApacheSSLContext(context.ApacheSSLContext): interfaces = ['https'] @@ -98,6 +99,8 @@ class KeystoneContext(context.OSContextGenerator): ctxt['token'] = set_admin_token() ctxt['admin_port'] = determine_api_port(api_port('keystone-admin')) ctxt['public_port'] = determine_api_port(api_port('keystone-public')) + ctxt['debug'] = config('debug') in ['yes', 'true', 'True'] + ctxt['verbose'] = config('verbose') in ['yes', 'true', 'True'] if config('enable-pki') not in ['false', 'False', 'no', 'No']: ctxt['signing'] = True return ctxt diff --git a/templates/essex/etc_keystone_keystone.conf b/templates/essex/etc_keystone_keystone.conf index 502a2c8d..a5f94780 100644 --- a/templates/essex/etc_keystone_keystone.conf +++ b/templates/essex/etc_keystone_keystone.conf @@ -9,6 +9,9 @@ admin_port = {{ admin_port }} public_port = {{ public_port }} use_syslog = {{ use_syslog }} log_config = /etc/keystone/logging.conf +debug = {{ debug }} +verbose = {{ verbose }} + [sql] {% if database_host -%} connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} diff --git a/templates/folsom/etc_keystone_keystone.conf b/templates/folsom/etc_keystone_keystone.conf index 3834adde..220b10dc 100644 --- a/templates/folsom/etc_keystone_keystone.conf +++ b/templates/folsom/etc_keystone_keystone.conf @@ -9,6 +9,9 @@ admin_port = {{ admin_port }} public_port = {{ public_port }} use_syslog = {{ use_syslog }} log_config = /etc/keystone/logging.conf +debug = {{ debug }} +verbose = {{ verbose }} + [sql] {% if database_host -%} connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} diff --git a/templates/grizzly/etc_keystone_keystone.conf b/templates/grizzly/etc_keystone_keystone.conf index ef717452..a5c821be 100644 --- a/templates/grizzly/etc_keystone_keystone.conf +++ b/templates/grizzly/etc_keystone_keystone.conf @@ -9,6 +9,9 @@ admin_port = {{ admin_port }} public_port = {{ public_port }} use_syslog = {{ use_syslog }} log_config = /etc/keystone/logging.conf +debug = {{ debug }} +verbose = {{ verbose }} + [sql] {% if database_host -%} connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} diff --git a/templates/havana/etc_keystone_keystone.conf b/templates/havana/etc_keystone_keystone.conf index 76ff171d..4c30e04b 100644 --- a/templates/havana/etc_keystone_keystone.conf +++ b/templates/havana/etc_keystone_keystone.conf @@ -9,6 +9,9 @@ admin_port = {{ admin_port }} public_port = {{ public_port }} use_syslog = {{ use_syslog }} log_config = /etc/keystone/logging.conf +debug = {{ debug }} +verbose = {{ verbose }} + [sql] {% if database_host -%} connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} From 2bc62e8235669db6ddf05d9cf70cae46f98a044d Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:30:34 +0000 Subject: [PATCH 08/57] Add sleep for upgrade hook if leader --- hooks/keystone_hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 00c07977..6fe66918 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -221,6 +221,7 @@ def upgrade_charm(): if eligible_leader(CLUSTER_RES): log('Cluster leader - ensuring endpoint configuration' ' is up to date') + time.sleep(10) ensure_initial_admin(config) CONFIGS.write_all() From d96ad889549f394f86927b1c840c7596629a15a6 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 17:57:08 +0000 Subject: [PATCH 09/57] Put temp fix into charmhelpers unison --- hooks/charmhelpers/contrib/unison/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/charmhelpers/contrib/unison/__init__.py b/hooks/charmhelpers/contrib/unison/__init__.py index c9cd0b10..06956085 100644 --- a/hooks/charmhelpers/contrib/unison/__init__.py +++ b/hooks/charmhelpers/contrib/unison/__init__.py @@ -253,5 +253,5 @@ def sync_to_peer(host, user, paths=[], verbose=False): def sync_to_peers(peer_interface, user, paths=[], verbose=False): '''Sync all hosts to an specific path''' - for host in collect_authed_hosts(): + for host in collect_authed_hosts(peer_interface): sync_to_peer(host, user, paths, verbose) From fe09bc6ef9535cd19e004b79f7acdd75a6cd5002 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 18:48:58 +0000 Subject: [PATCH 10/57] Add standard options for ssl_cert and key --- config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config.yaml b/config.yaml index f6011b66..9b37eff0 100644 --- a/config.yaml +++ b/config.yaml @@ -126,3 +126,14 @@ options: default: "no" type: string description: "Use SSL for Keystone itself. Set to 'yes' to enable it." + ssl_cert: + type: string + description: | + SSL certificate to install and use for API ports. Setting this value + and ssl_key will enable reverse proxying, point Keystone's entry in the + Keystone catalog to use https, and override any certficiate and key + issued by Keystone (if it is configured to do so). + ssl_key: + type: string + description: SSL key to use with certificate specified as ssl_cert. + From 82a9739edc6e4aa5f083714ecb3b8a1694deb5dd Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 26 Feb 2014 18:54:04 +0000 Subject: [PATCH 11/57] Set protocols on identity-service relation --- hooks/keystone_utils.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index db5c8219..01fd68fb 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -608,6 +608,12 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): else: relation_data["auth_host"] = unit_private_ip() relation_data["service_host"] = unit_private_ip() + if https(): + relation_data["auth_protocol"] = "https" + relation_data["service_protocol"] = "https" + else: + relation_data["auth_protocol"] = "http" + relation_data["service_protocol"] = "http" relation_data["auth_port"] = config('admin-port') relation_data["service_port"] = config('service-port') if config('https-service-endpoints') in ['True', 'true']: @@ -724,7 +730,12 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): if is_clustered(): relation_data["auth_host"] = config('vip') relation_data["service_host"] = config('vip') - + if https(): + relation_data["auth_protocol"] = "https" + relation_data["service_protocol"] = "https" + else: + relation_data["auth_protocol"] = "http" + relation_data["service_protocol"] = "http" # 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) From 05f2008e9527ae09c52633071ab5ed410252f8b9 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:24:33 +0000 Subject: [PATCH 12/57] Resync with ssl-everywhere charm-helpers, add db ssl data to connection strings --- charm-helpers.yaml | 2 +- .../charmhelpers/contrib/hahelpers/apache.py | 17 ++-- .../charmhelpers/contrib/openstack/context.py | 86 ++++++++++++++----- hooks/charmhelpers/contrib/unison/__init__.py | 2 +- templates/essex/etc_keystone_keystone.conf | 2 +- templates/folsom/etc_keystone_keystone.conf | 2 +- templates/grizzly/etc_keystone_keystone.conf | 2 +- templates/havana/etc_keystone_keystone.conf | 2 +- 8 files changed, 80 insertions(+), 35 deletions(-) diff --git a/charm-helpers.yaml b/charm-helpers.yaml index 2ba71da0..9bb0ecc1 100644 --- a/charm-helpers.yaml +++ b/charm-helpers.yaml @@ -1,4 +1,4 @@ -branch: lp:charm-helpers +branch: lp:~openstack-charmers/charm-helpers/ssl-everywhere destination: hooks/charmhelpers include: - core diff --git a/hooks/charmhelpers/contrib/hahelpers/apache.py b/hooks/charmhelpers/contrib/hahelpers/apache.py index 3208a85c..8d5fb8ba 100644 --- a/hooks/charmhelpers/contrib/hahelpers/apache.py +++ b/hooks/charmhelpers/contrib/hahelpers/apache.py @@ -39,14 +39,15 @@ def get_cert(): def get_ca_cert(): - ca_cert = None - log("Inspecting identity-service relations for CA SSL certificate.", - level=INFO) - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - if not ca_cert: - ca_cert = relation_get('ca_cert', - rid=r_id, unit=unit) + ca_cert = config_get('ssl_ca') + if ca_cert is None: + log("Inspecting identity-service relations for CA SSL certificate.", + level=INFO) + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + if ca_cert is None: + ca_cert = relation_get('ca_cert', + rid=r_id, unit=unit) return ca_cert diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 5785b6a1..11ddc0b0 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -1,5 +1,6 @@ import json import os +import time from base64 import b64decode @@ -113,7 +114,8 @@ class OSContextGenerator(object): class SharedDBContext(OSContextGenerator): interfaces = ['shared-db'] - def __init__(self, database=None, user=None, relation_prefix=None): + def __init__(self, + database=None, user=None, relation_prefix=None, ssl_dir=None): ''' Allows inspecting relation for settings prefixed with relation_prefix. This is useful for parsing access for multiple databases returned via @@ -122,6 +124,7 @@ class SharedDBContext(OSContextGenerator): self.relation_prefix = relation_prefix self.database = database self.user = user + self.ssl_dir = ssl_dir def __call__(self): self.database = self.database or config('database') @@ -139,19 +142,44 @@ class SharedDBContext(OSContextGenerator): for rid in relation_ids('shared-db'): for unit in related_units(rid): - passwd = relation_get(password_setting, rid=rid, unit=unit) + rdata = relation_get(rid=rid, unit=unit) ctxt = { - 'database_host': relation_get('db_host', rid=rid, - unit=unit), + 'database_host': rdata.get('db_host'), 'database': self.database, 'database_user': self.user, - 'database_password': passwd, + 'database_password': rdata.get(password_setting) } if context_complete(ctxt): + db_ssl(rdata, ctxt, self.ssl_dir) return ctxt return {} +def db_ssl(rdata, ctxt, ssl_dir): + if 'ssl_ca' in rdata and ssl_dir: + ca_path = os.path.join(ssl_dir, 'db-client.ca') + with open(ca_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_ca'])) + ctxt['database_ssl_ca'] = ca_path + elif 'ssl_ca' in rdata: + log("Charm not setup for ssl support but ssl ca found") + return ctxt + if 'ssl_cert' in rdata: + cert_path = os.path.join( + ssl_dir, 'db-client.cert') + if not os.path.exists(cert_path): + log("Waiting 1m for ssl client cert validity") + time.sleep(60) + with open(cert_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_cert'])) + ctxt['database_ssl_cert'] = cert_path + key_path = os.path.join(ssl_dir, 'db-client.key') + with open(key_path, 'w') as fh: + fh.write(b64decode(rdata['ssl_key'])) + ctxt['database_ssl_key'] = key_path + return ctxt + + class IdentityServiceContext(OSContextGenerator): interfaces = ['identity-service'] @@ -161,22 +189,19 @@ class IdentityServiceContext(OSContextGenerator): for rid in relation_ids('identity-service'): for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) ctxt = { - 'service_port': relation_get('service_port', rid=rid, - unit=unit), - 'service_host': relation_get('service_host', rid=rid, - unit=unit), - 'auth_host': relation_get('auth_host', rid=rid, unit=unit), - 'auth_port': relation_get('auth_port', rid=rid, unit=unit), - 'admin_tenant_name': relation_get('service_tenant', - rid=rid, unit=unit), - 'admin_user': relation_get('service_username', rid=rid, - unit=unit), - 'admin_password': relation_get('service_password', rid=rid, - unit=unit), - # XXX: Hard-coded http. - 'service_protocol': 'http', - 'auth_protocol': 'http', + 'service_port': rdata.get('service_port'), + 'service_host': rdata.get('service_host'), + 'auth_host': rdata.get('auth_host'), + 'auth_port': rdata.get('auth_port'), + 'admin_tenant_name': rdata.get('service_tenant'), + 'admin_user': rdata.get('service_username'), + 'admin_password': rdata.get('service_password'), + 'service_protocol': + rdata.get('service_protocol') or 'http', + 'auth_protocol': + rdata.get('auth_protocol') or 'http', } if context_complete(ctxt): return ctxt @@ -186,6 +211,9 @@ class IdentityServiceContext(OSContextGenerator): class AMQPContext(OSContextGenerator): interfaces = ['amqp'] + def __init__(self, ssl_dir=None): + self.ssl_dir = ssl_dir + def __call__(self): log('Generating template context for amqp') conf = config() @@ -196,7 +224,6 @@ class AMQPContext(OSContextGenerator): log('Could not generate shared_db context. ' 'Missing required charm config options: %s.' % e) raise OSContextError - ctxt = {} for rid in relation_ids('amqp'): for unit in related_units(rid): @@ -213,7 +240,24 @@ class AMQPContext(OSContextGenerator): unit=unit), 'rabbitmq_virtual_host': vhost, }) + ssl_port = relation_get('ssl_port', rid=rid, unit=unit) + if ssl_port: + ctxt['rabbit_ssl_port'] = ssl_port + ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit) + if ssl_ca: + ctxt['rabbit_ssl_ca'] = ssl_ca + if context_complete(ctxt): + if 'rabbit_ssl_ca' in ctxt: + if not self.ssl_dir: + log(("Charm not setup for ssl support " + "but ssl ca found")) + break + ca_path = os.path.join( + self.ssl_dir, 'rabbit-client-ca.pem') + with open(ca_path, 'w') as fh: + fh.write(b64decode(ctxt['rabbit_ssl_ca'])) + ctxt['rabbit_ssl_ca'] = ca_path # Sufficient information found = break out! break # Used for active/active rabbitmq >= grizzly diff --git a/hooks/charmhelpers/contrib/unison/__init__.py b/hooks/charmhelpers/contrib/unison/__init__.py index 06956085..c9cd0b10 100644 --- a/hooks/charmhelpers/contrib/unison/__init__.py +++ b/hooks/charmhelpers/contrib/unison/__init__.py @@ -253,5 +253,5 @@ def sync_to_peer(host, user, paths=[], verbose=False): def sync_to_peers(peer_interface, user, paths=[], verbose=False): '''Sync all hosts to an specific path''' - for host in collect_authed_hosts(peer_interface): + for host in collect_authed_hosts(): sync_to_peer(host, user, paths, verbose) diff --git a/templates/essex/etc_keystone_keystone.conf b/templates/essex/etc_keystone_keystone.conf index a5f94780..9580f959 100644 --- a/templates/essex/etc_keystone_keystone.conf +++ b/templates/essex/etc_keystone_keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/folsom/etc_keystone_keystone.conf b/templates/folsom/etc_keystone_keystone.conf index 220b10dc..8d1c560c 100644 --- a/templates/folsom/etc_keystone_keystone.conf +++ b/templates/folsom/etc_keystone_keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/grizzly/etc_keystone_keystone.conf b/templates/grizzly/etc_keystone_keystone.conf index a5c821be..0ffb2bfa 100644 --- a/templates/grizzly/etc_keystone_keystone.conf +++ b/templates/grizzly/etc_keystone_keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/havana/etc_keystone_keystone.conf b/templates/havana/etc_keystone_keystone.conf index 4c30e04b..ca28d9b0 100644 --- a/templates/havana/etc_keystone_keystone.conf +++ b/templates/havana/etc_keystone_keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} +connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} From e2e5fb98da029504c41aa6cbf028d8851bcddbc4 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:34:15 +0000 Subject: [PATCH 13/57] Reorder calls to fixup hook failure --- hooks/keystone_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 01fd68fb..edebf918 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -672,8 +672,8 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): # by ensuring each possible endpiont has appropriate fields # ['service', 'region', 'public_url', 'admin_url', 'internal_url'] if single.issubset(endpoints[ep]): - ensure_valid_service(ep['service']) ep = endpoints[ep] + ensure_valid_service(ep['service']) add_endpoint(region=ep['region'], service=ep['service'], publicurl=ep['public_url'], adminurl=ep['admin_url'], From cbdd573f12dd2ef777362756c7c59a8b91d8cac8 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:41:06 +0000 Subject: [PATCH 14/57] Temp patch for charm-helpers to switch use-https to boolean option --- config.yaml | 4 ++-- hooks/charmhelpers/contrib/hahelpers/cluster.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index 9b37eff0..5874f767 100644 --- a/config.yaml +++ b/config.yaml @@ -123,8 +123,8 @@ options: type: string description: "Manage SSL certificates for all service endpoints." use-https: - default: "no" - type: string + default: False + type: boolean description: "Use SSL for Keystone itself. Set to 'yes' to enable it." ssl_cert: type: string diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index bf832f7d..ceabcb25 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -91,7 +91,8 @@ def https(): . returns: boolean ''' - if config_get('use-https') == "yes": + if (config_get('use-https') is True or + config_get('use-https') in ["yes", 'Yes', 'True', 'true']): return True if config_get('ssl_cert') and config_get('ssl_key'): return True From e0f2b9017f487fc821bb852c0f90e110996ae04e Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:47:40 +0000 Subject: [PATCH 15/57] Add ssl_ca config option --- config.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index 5874f767..2afb17e2 100644 --- a/config.yaml +++ b/config.yaml @@ -136,4 +136,8 @@ options: ssl_key: type: string description: SSL key to use with certificate specified as ssl_cert. - + ssl_ca: + type: string + description: | + SSL CA to use with the certificate and key provided - this is only + required if you are providing a privately signed ssl_cert and ssl_key. From f632a29cea9697b2194f50073f04c51e95143246 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:55:38 +0000 Subject: [PATCH 16/57] Fixup SSL mysql handling --- hooks/keystone_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index edebf918..ab988a32 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -72,6 +72,7 @@ API_PORTS = { } KEYSTONE_CONF = "/etc/keystone/keystone.conf" +KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF) STORED_PASSWD = "/var/lib/keystone/keystone.passwd" STORED_TOKEN = "/var/lib/keystone/keystone.token" SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd' @@ -89,7 +90,7 @@ BASE_RESOURCE_MAP = OrderedDict([ (KEYSTONE_CONF, { 'services': BASE_SERVICES, 'contexts': [keystone_context.KeystoneContext(), - context.SharedDBContext(), + context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR), context.SyslogContext(), keystone_context.HAProxyContext()], }), From 1e1b35b197bfbf0920b1216f33b04e30735a73af Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Feb 2014 10:56:27 +0000 Subject: [PATCH 17/57] Drop surplus restart --- hooks/keystone_hooks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 6fe66918..074b1896 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -87,9 +87,6 @@ def config_changed(): save_script_rc() configure_https() CONFIGS.write_all() - service_restart('keystone') - time.sleep(10) - if eligible_leader(CLUSTER_RES): migrate_database() ensure_initial_admin(config) From a7c8d88dbbd8e52bea4b66340c3e61b79a75e50f Mon Sep 17 00:00:00 2001 From: Ante Karamatic Date: Sat, 1 Mar 2014 18:39:21 +0100 Subject: [PATCH 18/57] Fix unison: https://code.launchpad.net/~ivoks/charm-helpers/fix-unison/+merge/208937 --- hooks/charmhelpers/contrib/unison/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/charmhelpers/contrib/unison/__init__.py b/hooks/charmhelpers/contrib/unison/__init__.py index c9cd0b10..06956085 100644 --- a/hooks/charmhelpers/contrib/unison/__init__.py +++ b/hooks/charmhelpers/contrib/unison/__init__.py @@ -253,5 +253,5 @@ def sync_to_peer(host, user, paths=[], verbose=False): def sync_to_peers(peer_interface, user, paths=[], verbose=False): '''Sync all hosts to an specific path''' - for host in collect_authed_hosts(): + for host in collect_authed_hosts(peer_interface): sync_to_peer(host, user, paths, verbose) From 38bfc0e54ffdf92588db30ac626e06de3b2fe50d Mon Sep 17 00:00:00 2001 From: Ante Karamatic Date: Sat, 1 Mar 2014 19:09:28 +0100 Subject: [PATCH 19/57] Use tcp instead of http for LB --- templates/haproxy.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/haproxy.cfg b/templates/haproxy.cfg index fccf1c9d..548766f8 100644 --- a/templates/haproxy.cfg +++ b/templates/haproxy.cfg @@ -8,8 +8,8 @@ global defaults log global - mode http - option httplog + mode tcp + option tcplog option dontlognull retries 3 timeout queue 1000 @@ -19,6 +19,7 @@ defaults listen stats :8888 mode http + option httplog stats enable stats hide-version stats realm Haproxy\ Statistics @@ -29,7 +30,6 @@ listen stats :8888 {% for service, ports in service_ports.iteritems() -%} listen {{ service }} 0.0.0.0:{{ ports[0] }} balance roundrobin - option tcplog {% for unit, address in units.iteritems() -%} server {{ unit }} {{ address }}:{{ ports[1] }} check {% endfor %} From 5772e674674c273a939e618012875be82408f3b3 Mon Sep 17 00:00:00 2001 From: Ante Karamatic Date: Sun, 2 Mar 2014 09:56:04 +0100 Subject: [PATCH 20/57] Use VIP for Apache on ha-changed relation --- hooks/charmhelpers/contrib/openstack/context.py | 3 +++ hooks/keystone_hooks.py | 1 + revision | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 11ddc0b0..b0233445 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -432,6 +432,9 @@ class ApacheSSLContext(OSContextGenerator): 'private_address': unit_get('private-address'), 'endpoints': [] } + vip = config('vip') + if is_clustered() and vip: + ctxt['private_address'] = vip for api_port in self.external_ports: ext_port = determine_apache_port(api_port) int_port = determine_api_port(api_port) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 074b1896..f84010c0 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -186,6 +186,7 @@ def ha_changed(): if (clustered is not None and is_leader(CLUSTER_RES)): ensure_initial_admin(config) + CONFIGS.write_all() log('Cluster configured, notifying other services and updating ' 'keystone endpoint configuration') for rid in relation_ids('identity-service'): diff --git a/revision b/revision index dcb6b5ba..71d936fd 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -230 +231 From 09ddcbc3135099f266735649b1e7f2a8280dc356 Mon Sep 17 00:00:00 2001 From: Ante Karamatic Date: Sun, 2 Mar 2014 10:16:17 +0100 Subject: [PATCH 21/57] Redux --- hooks/charmhelpers/contrib/openstack/context.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index b0233445..249e896c 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -432,9 +432,8 @@ class ApacheSSLContext(OSContextGenerator): 'private_address': unit_get('private-address'), 'endpoints': [] } - vip = config('vip') - if is_clustered() and vip: - ctxt['private_address'] = vip + if is_clustered(): + ctxt['private_address'] = config('vip') for api_port in self.external_ports: ext_port = determine_apache_port(api_port) int_port = determine_api_port(api_port) From f258309df213c83e4ab95e678f78e709b77c5126 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 3 Mar 2014 09:09:17 +0000 Subject: [PATCH 22/57] Resync helpers --- hooks/charmhelpers/contrib/hahelpers/cluster.py | 3 +-- hooks/charmhelpers/contrib/openstack/context.py | 2 +- hooks/charmhelpers/contrib/openstack/neutron.py | 17 +++++++++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index ceabcb25..bf832f7d 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -91,8 +91,7 @@ def https(): . returns: boolean ''' - if (config_get('use-https') is True or - config_get('use-https') in ["yes", 'Yes', 'True', 'true']): + if config_get('use-https') == "yes": return True if config_get('ssl_cert') and config_get('ssl_key'): return True diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 249e896c..2403ce16 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -512,7 +512,7 @@ class NeutronContext(OSContextGenerator): host = config('vip') else: host = unit_get('private-address') - url = '%s://%s:%s' % (proto, host, '9292') + url = '%s://%s:%s' % (proto, host, '9696') ctxt = { 'network_manager': self.network_manager, 'neutron_url': url, diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 8d32bd00..a11c08b1 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -17,8 +17,11 @@ def headers_package(): kver = check_output(['uname', '-r']).strip() return 'linux-headers-%s' % kver +QUANTUM_CONF_DIR = '/etc/quantum' # legacy + + def quantum_plugins(): from charmhelpers.contrib.openstack import context return { @@ -30,7 +33,8 @@ def quantum_plugins(): 'contexts': [ context.SharedDBContext(user=config('neutron-database-user'), database=config('neutron-database'), - relation_prefix='neutron')], + relation_prefix='neutron', + ssl_dir=QUANTUM_CONF_DIR)], 'services': ['quantum-plugin-openvswitch-agent'], 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], ['quantum-plugin-openvswitch-agent']], @@ -45,7 +49,8 @@ def quantum_plugins(): 'contexts': [ context.SharedDBContext(user=config('neutron-database-user'), database=config('neutron-database'), - relation_prefix='neutron')], + relation_prefix='neutron', + ssl_dir=QUANTUM_CONF_DIR)], 'services': [], 'packages': [], 'server_packages': ['quantum-server', @@ -54,6 +59,8 @@ def quantum_plugins(): } } +NEUTRON_CONF_DIR = '/etc/neutron' + def neutron_plugins(): from charmhelpers.contrib.openstack import context @@ -66,7 +73,8 @@ def neutron_plugins(): 'contexts': [ context.SharedDBContext(user=config('neutron-database-user'), database=config('neutron-database'), - relation_prefix='neutron')], + relation_prefix='neutron', + ssl_dir=NEUTRON_CONF_DIR)], 'services': ['neutron-plugin-openvswitch-agent'], 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], ['quantum-plugin-openvswitch-agent']], @@ -81,7 +89,8 @@ def neutron_plugins(): 'contexts': [ context.SharedDBContext(user=config('neutron-database-user'), database=config('neutron-database'), - relation_prefix='neutron')], + relation_prefix='neutron', + ssl_dir=NEUTRON_CONF_DIR)], 'services': [], 'packages': [], 'server_packages': ['neutron-server', From 90888b24777aa5dadcdc3b9055d80447b1c02118 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 3 Mar 2014 09:13:00 +0000 Subject: [PATCH 23/57] Restart on change and write_all configs for all ha_changed hooked executions --- hooks/keystone_hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index f84010c0..1599e36d 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -181,12 +181,13 @@ def ha_joined(): @hooks.hook('ha-relation-changed') +@restart_on_change(restart_map()) def ha_changed(): clustered = relation_get('clustered') + CONFIGS.write_all() if (clustered is not None and is_leader(CLUSTER_RES)): ensure_initial_admin(config) - CONFIGS.write_all() log('Cluster configured, notifying other services and updating ' 'keystone endpoint configuration') for rid in relation_ids('identity-service'): From 9fc294e196c69a726457fbf9fe791968b8000e5a Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 3 Mar 2014 09:14:09 +0000 Subject: [PATCH 24/57] Pep8 formatting --- hooks/keystone_hooks.py | 5 ++--- hooks/keystone_ssl.py | 7 ++++--- hooks/keystone_utils.py | 34 +++++++++++++++++----------------- hooks/manager.py | 1 + 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 1599e36d..2ab440c1 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -22,7 +22,6 @@ from charmhelpers.core.hookenv import ( from charmhelpers.core.host import ( mkdir, restart_on_change, - service_restart, ) from charmhelpers.fetch import ( @@ -77,7 +76,7 @@ def config_changed(): unison.ensure_user(user=SSH_USER, group='keystone') homedir = unison.get_homedir(SSH_USER) if not os.path.isdir(homedir): - mkdir(homedir, SSH_USER, 'keystone', 0775) + mkdir(homedir, SSH_USER, 'keystone', 0o775) if openstack_upgrade_available('keystone'): do_openstack_upgrade(configs=CONFIGS) @@ -186,7 +185,7 @@ def ha_changed(): clustered = relation_get('clustered') CONFIGS.write_all() if (clustered is not None and - is_leader(CLUSTER_RES)): + is_leader(CLUSTER_RES)): ensure_initial_admin(config) log('Cluster configured, notifying other services and updating ' 'keystone endpoint configuration') diff --git a/hooks/keystone_ssl.py b/hooks/keystone_ssl.py index b39b227e..45e0029d 100755 --- a/hooks/keystone_ssl.py +++ b/hooks/keystone_ssl.py @@ -113,7 +113,7 @@ def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT): if not os.path.exists(d): print 'Creating %s.' % d os.mkdir(d) - os.chmod(os.path.join(ca_dir, 'private'), 0710) + os.chmod(os.path.join(ca_dir, 'private'), 0o710) if not os.path.isfile(os.path.join(ca_dir, 'serial')): with open(os.path.join(ca_dir, 'serial'), 'wb') as out: @@ -161,7 +161,7 @@ def intermediate_ca_csr_key(ca_dir): def sign_int_csr(ca_dir, csr, common_name): print 'Signing certificate request %s.' % csr crt = os.path.join(ca_dir, 'certs', - '%s.crt' % os.path.basename(csr).split('.')[0]) + '%s.crt' % os.path.basename(csr).split('.')[0]) subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name) cmd = ['openssl', 'ca', '-batch', '-config', os.path.join(ca_dir, 'ca.cnf'), @@ -238,6 +238,7 @@ def tar_directory(path): class JujuCA(object): + def __init__(self, name, ca_dir, root_ca_dir, user, group): root_crt, root_key = init_root_ca(root_ca_dir, '%s Certificate Authority' % name) @@ -288,7 +289,7 @@ class JujuCA(object): key = open(key, 'r').read() except: print 'Could not load ssl private key for %s from %s' %\ - (common_name, key) + (common_name, key) exit(1) return crt, key crt, key = self._create_certificate(common_name, common_name) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index ab988a32..79eaa6c3 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -13,7 +13,7 @@ from charmhelpers.contrib.hahelpers.cluster import( determine_api_port, https, is_clustered - ) +) from charmhelpers.contrib.openstack import context, templating @@ -252,13 +252,13 @@ def migrate_database(): time.sleep(10) -## OLD +# OLD def get_local_endpoint(): """ Returns the URL for the local end-point bypassing haproxy/ssl """ local_endpoint = 'http://localhost:{}/v2.0/'.format( determine_api_port(api_port('keystone-admin')) - ) + ) return local_endpoint @@ -317,7 +317,7 @@ def create_service_entry(service_name, service_type, service_desc, owner=None): log("Created new service entry '%s'" % service_name) -def create_endpoint_template(region, service, publicurl, adminurl, +def create_endpoint_template(region, service, publicurl, adminurl, internalurl): """ Create a new endpoint template for service if one does not already exist matching name *and* region """ @@ -328,7 +328,7 @@ def create_endpoint_template(region, service, publicurl, adminurl, for ep in [e._info for e in manager.api.endpoints.list()]: if ep['service_id'] == service_id and ep['region'] == region: log("Endpoint template already exists for '%s' in '%s'" - % (service, region)) + % (service, region)) up_to_date = True for k in ['publicurl', 'adminurl', 'internalurl']: @@ -405,7 +405,7 @@ def create_role(name, user=None, tenant=None): if None in [user_id, role_id, tenant_id]: error_out("Could not resolve [%s, %s, %s]" % - (user_id, role_id, tenant_id)) + (user_id, role_id, tenant_id)) grant_role(user, name, tenant) @@ -415,7 +415,7 @@ def grant_role(user, role, tenant): import manager manager = manager.KeystoneManager(endpoint=get_local_endpoint(), token=get_admin_token()) - log("Granting user '%s' role '%s' on tenant '%s'" % \ + log("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) @@ -426,10 +426,10 @@ def grant_role(user, role, tenant): manager.api.roles.add_user_role(user=user_id, role=role_id, tenant=tenant_id) - log("Granted user '%s' role '%s' on tenant '%s'" % \ + log("Granted user '%s' role '%s' on tenant '%s'" % (user, role, tenant)) else: - log("User '%s' already has role '%s' on tenant '%s'" % \ + log("User '%s' already has role '%s' on tenant '%s'" % (user, role, tenant)) @@ -453,8 +453,8 @@ def ensure_initial_admin(config): log("Loading stored passwd from %s" % STORED_PASSWD) passwd = open(STORED_PASSWD, 'r').readline().strip('\n') if passwd == "": - log("Generating new passwd for user: %s" % \ - config("admin-user")) + log("Generating new passwd for user: %s" % + config("admin-user")) cmd = ['pwgen', '-c', '16', '1'] passwd = str(subprocess.check_output(cmd)).strip() open(STORED_PASSWD, 'w+').writelines("%s\n" % passwd) @@ -506,8 +506,8 @@ def update_user_password(username, password): error_out("Could not resolve user id for '%s'" % username) manager.api.users.update_password(user=user_id, password=password) - log("Successfully updated password for user '%s'" % \ - username) + log("Successfully updated password for user '%s'" % + username) def load_stored_passwords(path=SERVICE_PASSWD_PATH): @@ -545,7 +545,7 @@ def synchronize_service_credentials(): broadcasted by peer, depending on hook context. ''' if (not eligible_leader(CLUSTER_RES) or - not os.path.isfile(SERVICE_PASSWD_PATH)): + not os.path.isfile(SERVICE_PASSWD_PATH)): return log('Synchronizing service passwords to all peers.') if is_clustered(): @@ -569,7 +569,7 @@ def get_ca(user='keystone', group='keystone'): ca_dir=os.path.join(SSL_DIR, '%s_intermediate_ca' % d_name), root_ca_dir=os.path.join(SSL_DIR, - '%s_root_ca' % d_name)) + '%s_root_ca' % d_name)) # SSL_DIR is synchronized via all peers over unison+ssh, need # to ensure permissions. subprocess.check_output(['chown', '-R', '%s.%s' % (user, group), @@ -583,7 +583,7 @@ def relation_list(rid): cmd = [ 'relation-list', '-r', rid, - ] + ] result = str(subprocess.check_output(cmd)).split() if result == "": return None @@ -773,7 +773,7 @@ def add_endpoint(region, service, publicurl, adminurl, internalurl): def get_requested_roles(settings): ''' Retrieve any valid requested_roles from dict settings ''' if ('requested_roles' in settings and - settings['requested_roles'] not in ['None', None]): + settings['requested_roles'] not in ['None', None]): return settings['requested_roles'].split(',') else: return [] diff --git a/hooks/manager.py b/hooks/manager.py index e7058820..8c8968d3 100644 --- a/hooks/manager.py +++ b/hooks/manager.py @@ -3,6 +3,7 @@ from keystoneclient.v2_0 import client class KeystoneManager(object): + def __init__(self, endpoint, token): self.api = client.Client(endpoint=endpoint, token=token) From 45ca9775cd265894e1d5a478a75cf6df391a5a2f Mon Sep 17 00:00:00 2001 From: Ante Karamatic Date: Mon, 3 Mar 2014 20:39:38 +0100 Subject: [PATCH 25/57] Judging by hooks/charmhelpers/contrib/hahelpers/cluster.py, use-https is a string, not a bool. --- config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index 2afb17e2..a2ce62ac 100644 --- a/config.yaml +++ b/config.yaml @@ -123,8 +123,8 @@ options: type: string description: "Manage SSL certificates for all service endpoints." use-https: - default: False - type: boolean + default: "no" + type: string description: "Use SSL for Keystone itself. Set to 'yes' to enable it." ssl_cert: type: string From 41e6992ea703ccfdb37ff9f9c63e0e0ff2162c73 Mon Sep 17 00:00:00 2001 From: Kapil Thangavelu Date: Tue, 4 Mar 2014 08:56:18 -0500 Subject: [PATCH 26/57] keystone ssl ca sync whenver we sync passwords --- hooks/keystone_ssl.py | 2 ++ hooks/keystone_utils.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hooks/keystone_ssl.py b/hooks/keystone_ssl.py index 45e0029d..1cbdfad7 100755 --- a/hooks/keystone_ssl.py +++ b/hooks/keystone_ssl.py @@ -1,10 +1,12 @@ #!/usr/bin/python +import base64 import os import shutil import subprocess import tarfile import tempfile +import zipfile CA_EXPIRY = '365' ORG_NAME = 'Ubuntu' diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 79eaa6c3..19cdb4a9 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -552,6 +552,9 @@ def synchronize_service_credentials(): unison.sync_to_peers(peer_interface='cluster', paths=[SERVICE_PASSWD_PATH], user=SSH_USER, verbose=True) + if config('http-service-endpoints') in ['True', 'true']: + unison.sync_to_peers(peer_interface='cluster', + paths=[SSL_DIR], user=SSH_USER, verbose=True) CA = [] @@ -746,9 +749,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): relation_data['ssl_key'] = b64encode(key) relation_data['ca_cert'] = b64encode(ca_bundle) relation_data['https_keystone'] = 'True' - if is_clustered(): - unison.sync_to_peers(peer_interface='cluster', - paths=[SSL_DIR], user=SSH_USER, verbose=True) relation_set(relation_id=relation_id, **relation_data) From b6f2640f0062d6a98622689da4d143712be52bf7 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 4 Mar 2014 17:00:56 +0000 Subject: [PATCH 27/57] Sleep after restart --- hooks/keystone_hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 3e21bba7..803f12f4 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -440,6 +440,7 @@ def config_changed(): if config_dirty(): utils.restart('keystone') + time.sleep(10) if cluster.eligible_leader(CLUSTER_RES): utils.juju_log('INFO', From df99b92164c4ab2c141ff22c3ae8c3f21f005e34 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 6 Mar 2014 12:38:13 +0000 Subject: [PATCH 28/57] Use upgrade for upgrades --- hooks/keystone_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 3752917c..a898a365 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -455,7 +455,7 @@ def do_openstack_upgrade(install_src, packages): execute('apt-get update', die=True, echo=True) os.environ['DEBIAN_FRONTEND'] = 'noninteractive' cmd = 'apt-get --option Dpkg::Options::=--force-confnew -y '\ - 'install %s' % packages + 'upgrade' execute(cmd, echo=True, die=True) # we have new, fresh config files that need updating. From 54200dbaab81765e12cd3e17ea1e18db57c453af Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 6 Mar 2014 13:41:06 +0000 Subject: [PATCH 29/57] Use dist-upgrade --- hooks/keystone_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index a898a365..f754368e 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -455,7 +455,7 @@ def do_openstack_upgrade(install_src, packages): execute('apt-get update', die=True, echo=True) os.environ['DEBIAN_FRONTEND'] = 'noninteractive' cmd = 'apt-get --option Dpkg::Options::=--force-confnew -y '\ - 'upgrade' + 'dist-upgrade' execute(cmd, echo=True, die=True) # we have new, fresh config files that need updating. From c15ac4415082d849cea3281b7e9334246ef899f4 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 09:36:34 +0000 Subject: [PATCH 30/57] Fixup incorrect use of config --- hooks/keystone_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 19cdb4a9..888c882c 100755 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -552,7 +552,7 @@ def synchronize_service_credentials(): unison.sync_to_peers(peer_interface='cluster', paths=[SERVICE_PASSWD_PATH], user=SSH_USER, verbose=True) - if config('http-service-endpoints') in ['True', 'true']: + if config('https-service-endpoints') in ['True', 'true']: unison.sync_to_peers(peer_interface='cluster', paths=[SSL_DIR], user=SSH_USER, verbose=True) From 50da41087105736de4b8ac76c9c7c46ecb1abc9c Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 11:29:56 +0000 Subject: [PATCH 31/57] Sortout perms --- hooks/keystone_hooks.py | 0 hooks/keystone_ssl.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 hooks/keystone_hooks.py mode change 100755 => 100644 hooks/keystone_ssl.py diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py old mode 100644 new mode 100755 diff --git a/hooks/keystone_ssl.py b/hooks/keystone_ssl.py old mode 100755 new mode 100644 From ce52adaad1c7a03cca9489f96d05c56ba76f0303 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 11:36:12 +0000 Subject: [PATCH 32/57] Fixup early execution of haproxy config generation --- templates/haproxy.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/haproxy.cfg b/templates/haproxy.cfg index cfe30d81..a9fa1391 100644 --- a/templates/haproxy.cfg +++ b/templates/haproxy.cfg @@ -26,6 +26,7 @@ listen stats :8888 stats uri / stats auth admin:password +{% if units %} {% for service, ports in service_ports.iteritems() -%} listen {{ service }} 0.0.0.0:{{ ports[0] }} balance roundrobin @@ -33,3 +34,5 @@ listen {{ service }} 0.0.0.0:{{ ports[0] }} server {{ unit }} {{ address }}:{{ ports[1] }} check {% endfor %} {% endfor %} +{% endif %} + From 9243b5c0e4977deee4dcaaec744502d42a74e196 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 13:49:16 +0000 Subject: [PATCH 33/57] Run db-sync as keystone --- hooks/keystone_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 888c882c..c0eaf76f 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -246,7 +246,10 @@ def migrate_database(): '''Runs keystone-manage to initialize a new database or migrate existing''' log('Migrating the keystone database.', level=INFO) service_stop('keystone') - cmd = ['keystone-manage', 'db_sync'] + # NOTE(jamespage) > icehouse creates a log file as root so use + # sudo to execute as keystone otherwise keystone won't start + # afterwards. + cmd = ['sudo', '-u', 'keystone', 'keystone-manage', 'db_sync'] subprocess.check_output(cmd) service_start('keystone') time.sleep(10) From 5113db2cdd017e1bccbb3c97cc3a0d0b03a8aacb Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 22:00:08 +0000 Subject: [PATCH 34/57] reexec changed on db_change for migration --- hooks/keystone_hooks.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 2ab440c1..bc1dba2c 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -16,6 +16,7 @@ from charmhelpers.core.hookenv import ( relation_get, relation_ids, relation_set, + related_units, unit_get, ) @@ -115,6 +116,11 @@ def db_changed(): if eligible_leader(CLUSTER_RES): migrate_database() ensure_initial_admin(config) + # Ensure any existing service entries are updated in the + # new database backend + for rid in relation_ids('identity-service'): + for unit in related_units(rid=rid): + identity_changed(relation_id=rid, remote_unit=unit) @hooks.hook('identity-service-relation-joined') From 5d8579294d6e2e5252a37c1174d7b7e64edcfa0e Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 27 Mar 2014 22:02:13 +0000 Subject: [PATCH 35/57] fix related_unit call --- hooks/keystone_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index bc1dba2c..dd366bf1 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -119,7 +119,7 @@ def db_changed(): # Ensure any existing service entries are updated in the # new database backend for rid in relation_ids('identity-service'): - for unit in related_units(rid=rid): + for unit in related_units(rid): identity_changed(relation_id=rid, remote_unit=unit) From 543e694969bac75aa9f7dd8cd7aea7fca2fa3d4c Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 28 Mar 2014 10:39:49 +0000 Subject: [PATCH 36/57] Move password storage to peer storage --- charm-helpers.yaml | 1 + .../contrib/peerstorage/__init__.py | 83 +++++++++++++++++++ hooks/keystone_hooks.py | 8 +- hooks/keystone_utils.py | 40 +++++---- 4 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 hooks/charmhelpers/contrib/peerstorage/__init__.py diff --git a/charm-helpers.yaml b/charm-helpers.yaml index 2ba71da0..2cdc3628 100644 --- a/charm-helpers.yaml +++ b/charm-helpers.yaml @@ -10,3 +10,4 @@ include: - cluster - contrib.unison - payload.execd + - contrib.peerstorage diff --git a/hooks/charmhelpers/contrib/peerstorage/__init__.py b/hooks/charmhelpers/contrib/peerstorage/__init__.py new file mode 100644 index 00000000..e0eeab5b --- /dev/null +++ b/hooks/charmhelpers/contrib/peerstorage/__init__.py @@ -0,0 +1,83 @@ +from charmhelpers.core.hookenv import ( + relation_ids, + relation_get, + local_unit, + relation_set, +) + +""" +This helper provides functions to support use of a peer relation +for basic key/value storage, with the added benefit that all storage +can be replicated across peer units, so this is really useful for +services that issue usernames/passwords to remote services. + +def shared_db_changed() + # Only the lead unit should create passwords + if not is_leader(): + return + username = relation_get('username') + key = '{}.password'.format(username) + # Attempt to retrieve any existing password for this user + password = peer_retrieve(key) + if password is None: + # New user, create password and store + password = pwgen(length=64) + peer_store(key, password) + create_access(username, password) + relation_set(password=password) + + +def cluster_changed() + # Echo any relation data other that *-address + # back onto the peer relation so all units have + # all *.password keys stored on their local relation + # for later retrieval. + peer_echo() + +""" + + +def peer_retrieve(key, relation_name='cluster'): + """ Retrieve a named key from peer relation relation_name """ + cluster_rels = relation_ids(relation_name) + if len(cluster_rels) > 0: + cluster_rid = cluster_rels[0] + return relation_get(attribute=key, rid=cluster_rid, + unit=local_unit()) + else: + raise ValueError('Unable to detect' + 'peer relation {}'.format(relation_name)) + + +def peer_store(key, value, relation_name='cluster'): + """ Store the key/value pair on the named peer relation relation_name """ + cluster_rels = relation_ids(relation_name) + if len(cluster_rels) > 0: + cluster_rid = cluster_rels[0] + relation_set(relation_id=cluster_rid, + relation_settings={key: value}) + else: + raise ValueError('Unable to detect ' + 'peer relation {}'.format(relation_name)) + + +def peer_echo(includes=None): + """Echo filtered attributes back onto the same relation for storage + + Note that this helper must only be called within a peer relation + changed hook + """ + rdata = relation_get() + echo_data = {} + if includes is None: + echo_data = rdata.copy() + for ex in ['private-address', 'public-address']: + if ex in echo_data: + echo_data.pop(ex) + else: + for attribute, value in rdata.iteritems(): + for include in includes: + if include in attribute: + echo_data[attribute] = value + if len(echo_data) > 0: + relation_set(relation_settings=echo_data) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index dd366bf1..1a0e3789 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -42,7 +42,7 @@ from keystone_utils import ( ensure_initial_admin, migrate_database, save_script_rc, - synchronize_service_credentials, + synchronize_ca, register_configs, relation_list, restart_map, @@ -58,6 +58,7 @@ from charmhelpers.contrib.hahelpers.cluster import ( ) from charmhelpers.payload.execd import execd_preinstall +from charmhelpers.contrib.peerstorage import peer_echo hooks = Hooks() CONFIGS = register_configs() @@ -133,7 +134,7 @@ def identity_joined(): def identity_changed(relation_id=None, remote_unit=None): if eligible_leader(CLUSTER_RES): add_service_to_keystone(relation_id, remote_unit) - synchronize_service_credentials() + synchronize_ca() else: log('Deferring identity_changed() to service leader.') @@ -150,11 +151,12 @@ def cluster_joined(): 'cluster-relation-departed') @restart_on_change(restart_map(), stopstart=True) def cluster_changed(): + peer_echo() unison.ssh_authorized_peers(user=SSH_USER, group='keystone', peer_interface='cluster', ensure_local_user=True) - synchronize_service_credentials() + synchronize_ca() CONFIGS.write_all() diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index c0eaf76f..01c4d680 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -43,6 +43,12 @@ from charmhelpers.fetch import ( from charmhelpers.core.host import ( service_stop, service_start, + pwgen +) + +from charmhelpers.contrib.peerstorage import ( + peer_store, + peer_retrieve, ) import keystone_context @@ -525,36 +531,34 @@ def load_stored_passwords(path=SERVICE_PASSWD_PATH): 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 _migrate_service_passwords(): + ''' Migrate on-disk service passwords to peer storage ''' + if os.path.exists(SERVICE_PASSWD_PATH): + log('Migrating on-disk stored passwords to peer storage') + creds = load_stored_passwords() + for k, v in creds.iteritems(): + peer_store(key=k, value=v) + os.unlink(SERVICE_PASSWD_PATH) def get_service_password(service_username): - creds = load_stored_passwords() - if service_username in creds: - return creds[service_username] - - passwd = subprocess.check_output(['pwgen', '-c', '32', '1']).strip() - creds[service_username] = passwd - save_stored_passwords(**creds) - + _migrate_service_passwords() + passwd = peer_retrieve(service_username) + if passwd is None: + passwd = pwgen(length=64) + peer_store(key=service_username, value=passwd) return passwd -def synchronize_service_credentials(): +def synchronize_ca(): ''' Broadcast service credentials to peers or consume those that have been broadcasted by peer, depending on hook context. ''' - if (not eligible_leader(CLUSTER_RES) or - not os.path.isfile(SERVICE_PASSWD_PATH)): + if not eligible_leader(CLUSTER_RES): return - log('Synchronizing service passwords to all peers.') + log('Synchronizing CA to all peers.') if is_clustered(): - unison.sync_to_peers(peer_interface='cluster', - paths=[SERVICE_PASSWD_PATH], user=SSH_USER, - verbose=True) if config('https-service-endpoints') in ['True', 'true']: unison.sync_to_peers(peer_interface='cluster', paths=[SSL_DIR], user=SSH_USER, verbose=True) From 1335b85687c9f5c173662e36edc27ded60b3d27e Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 28 Mar 2014 10:43:32 +0000 Subject: [PATCH 37/57] Don't call cluster_changed in upgrade hook --- hooks/keystone_hooks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 1a0e3789..88f6cef0 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -223,7 +223,11 @@ def configure_https(): @restart_on_change(restart_map(), stopstart=True) def upgrade_charm(): apt_install(filter_installed_packages(determine_packages())) - cluster_changed() + unison.ssh_authorized_peers(user=SSH_USER, + group='keystone', + peer_interface='cluster', + ensure_local_user=True) + synchronize_ca() if eligible_leader(CLUSTER_RES): log('Cluster leader - ensuring endpoint configuration' ' is up to date') From 1e7d06b86ef8e5211ed066a089ff4436a8e0f5a0 Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 28 Mar 2014 11:04:08 +0000 Subject: [PATCH 38/57] Fixup peer echo - limit to passwd's only --- hooks/keystone_hooks.py | 3 ++- hooks/keystone_utils.py | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 88f6cef0..5eb49aac 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -151,7 +151,8 @@ def cluster_joined(): 'cluster-relation-departed') @restart_on_change(restart_map(), stopstart=True) def cluster_changed(): - peer_echo() + # NOTE(jamespage) re-echo passwords for peer storage + peer_echo(includes=['_passwd']) unison.ssh_authorized_peers(user=SSH_USER, group='keystone', peer_interface='cluster', diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 01c4d680..f922804e 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -537,16 +537,18 @@ def _migrate_service_passwords(): log('Migrating on-disk stored passwords to peer storage') creds = load_stored_passwords() for k, v in creds.iteritems(): - peer_store(key=k, value=v) + peer_store(key="{}_passwd".format(k), value=v) os.unlink(SERVICE_PASSWD_PATH) def get_service_password(service_username): _migrate_service_passwords() - passwd = peer_retrieve(service_username) + peer_key = "{}_passwd".format(service_username) + passwd = peer_retrieve(peer_key) if passwd is None: passwd = pwgen(length=64) - peer_store(key=service_username, value=passwd) + peer_store(key=peer_key, + value=passwd) return passwd From 9d3cbaf54dd88f22a22e08e1ca3d15e2dcc23295 Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 28 Mar 2014 11:34:38 +0000 Subject: [PATCH 39/57] Ensure configured admin-token is used if provided --- hooks/keystone_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_context.py b/hooks/keystone_context.py index 3badbe78..d067af8b 100644 --- a/hooks/keystone_context.py +++ b/hooks/keystone_context.py @@ -96,7 +96,7 @@ class KeystoneContext(context.OSContextGenerator): def __call__(self): from keystone_utils import api_port, set_admin_token ctxt = {} - ctxt['token'] = set_admin_token() + ctxt['token'] = set_admin_token(config('admin-token')) ctxt['admin_port'] = determine_api_port(api_port('keystone-admin')) ctxt['public_port'] = determine_api_port(api_port('keystone-public')) ctxt['debug'] = config('debug') in ['yes', 'true', 'True'] From 57ee8728c0e9e942d2072eef86013ba45e9f27e5 Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 28 Mar 2014 11:45:58 +0000 Subject: [PATCH 40/57] Refactor token generation, add upgrade step --- hooks/keystone_utils.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index f922804e..54f0d00e 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -38,6 +38,7 @@ from charmhelpers.core.hookenv import ( from charmhelpers.fetch import ( apt_install, apt_update, + apt_upgrade, ) from charmhelpers.core.host import ( @@ -237,7 +238,7 @@ def do_openstack_upgrade(configs): '--option', 'Dpkg::Options::=--force-confnew', '--option', 'Dpkg::Options::=--force-confdef', ] - + apt_upgrade(options=dpkg_opts, fatal=True, dist=True) apt_install(packages=determine_packages(), options=dpkg_opts, fatal=True) # set CONFIGS to load templates from new release and regenerate config @@ -284,15 +285,12 @@ def set_admin_token(admin_token='None'): msg = 'Loading a previously generated' \ ' admin token from %s' % STORED_TOKEN log(msg) - f = open(STORED_TOKEN, 'r') - token = f.read().strip() - f.close() + with open(STORED_TOKEN, 'r') as f: + token = f.read().strip() else: - cmd = ['pwgen', '-c', '32', '1'] - token = str(subprocess.check_output(cmd)).strip() - out = open(STORED_TOKEN, 'w') - out.write('%s\n' % token) - out.close() + token = pwgen(length=64) + with open(STORED_TOKEN, 'w') as out: + out.write('%s\n' % token) return(token) From 593ebdd59e3bba048c2e348f6ba8cc357081cb7e Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Mon, 31 Mar 2014 10:35:19 +0200 Subject: [PATCH 41/57] added postgresql support --- .../charmhelpers/contrib/openstack/context.py | 32 ++++- hooks/keystone_context.pyc | Bin 0 -> 4240 bytes hooks/keystone_hooks.py | 38 ++++++ hooks/keystone_hooks.pyc | Bin 0 -> 9520 bytes hooks/keystone_ssl.py | 2 - hooks/keystone_ssl.pyc | Bin 0 -> 11714 bytes hooks/keystone_utils.py | 2 + hooks/keystone_utils.pyc | Bin 0 -> 24196 bytes hooks/lib/apache_utils.py | 8 +- hooks/lib/cluster_utils.py | 12 +- hooks/lib/haproxy_utils.py | 6 +- hooks/lib/unison.py | 15 +-- hooks/lib/utils.py | 55 +++----- hooks/pgsql-db-relation-changed | 1 + hooks/pgsql-db-relation-joined | 1 + metadata.yaml | 2 + revision | 2 +- templates/essex/keystone.conf | 2 +- templates/folsom/keystone.conf | 2 +- templates/grizzly/keystone.conf | 2 +- templates/havana/keystone.conf | 2 +- unit_tests/__init__.py | 0 unit_tests/test_keystone_hooks.py | 120 ++++++++++++++++++ unit_tests/test_utils.py | 119 +++++++++++++++++ 24 files changed, 354 insertions(+), 69 deletions(-) create mode 100644 hooks/keystone_context.pyc create mode 100644 hooks/keystone_hooks.pyc create mode 100644 hooks/keystone_ssl.pyc create mode 100644 hooks/keystone_utils.pyc create mode 120000 hooks/pgsql-db-relation-changed create mode 120000 hooks/pgsql-db-relation-joined create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/test_keystone_hooks.py create mode 100644 unit_tests/test_utils.py diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index d254de18..e4ad6c2a 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -147,7 +147,8 @@ class SharedDBContext(OSContextGenerator): 'database_host': rdata.get('db_host'), 'database': self.database, 'database_user': self.user, - 'database_password': rdata.get(password_setting) + 'database_password': rdata.get(password_setting), + 'database_type': 'mysql', } if context_complete(ctxt): db_ssl(rdata, ctxt, self.ssl_dir) @@ -155,6 +156,35 @@ class SharedDBContext(OSContextGenerator): return {} +class PostgresqlDBContext(OSContextGenerator): + interfaces = ['pgsql-db'] + + def __init__(self, database=None): + self.database = database + + def __call__(self): + self.database = self.database or config('database') + if self.database is None: + log('Could not generate postgresql_db context. ' + 'Missing required charm config options. ' + '(database name)') + raise OSContextError + ctxt = {} + + for rid in relation_ids(self.interfaces[0]): + for unit in related_units(rid): + ctxt = { + 'database_host': relation_get('host', rid=rid, unit=unit), + 'database': self.database, + 'database_user': relation_get('user', rid=rid, unit=unit), + 'database_password': relation_get('password', rid=rid, unit=unit), + 'database_type': 'postgresql', + } + if context_complete(ctxt): + return ctxt + return {} + + def db_ssl(rdata, ctxt, ssl_dir): if 'ssl_ca' in rdata and ssl_dir: ca_path = os.path.join(ssl_dir, 'db-client.ca') diff --git a/hooks/keystone_context.pyc b/hooks/keystone_context.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f88afe16f2cbb7d4aebd35003607d54228e96c59 GIT binary patch literal 4240 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 literal 0 HcmV?d00001 diff --git a/hooks/keystone_ssl.py b/hooks/keystone_ssl.py index 1cbdfad7..45e0029d 100644 --- a/hooks/keystone_ssl.py +++ b/hooks/keystone_ssl.py @@ -1,12 +1,10 @@ #!/usr/bin/python -import base64 import os import shutil import subprocess import tarfile import tempfile -import zipfile CA_EXPIRY = '365' ORG_NAME = 'Ubuntu' diff --git a/hooks/keystone_ssl.pyc b/hooks/keystone_ssl.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcdbbf62d7826da464cacbb152502ae1d7d50872 GIT binary patch literal 11714 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 literal 0 HcmV?d00001 diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 54f0d00e..efc97407 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -64,6 +64,7 @@ BASE_PACKAGES = [ 'openssl', 'python-keystoneclient', 'python-mysqldb', + 'python-psycopg2', 'pwgen', 'unison', 'uuid', @@ -98,6 +99,7 @@ BASE_RESOURCE_MAP = OrderedDict([ 'services': BASE_SERVICES, 'contexts': [keystone_context.KeystoneContext(), context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR), + context.PostgresqlDBContext(), context.SyslogContext(), keystone_context.HAProxyContext()], }), diff --git a/hooks/keystone_utils.pyc b/hooks/keystone_utils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec566613d27ae489f13bc4bcd66cd59de9d6bd37 GIT binary patch literal 24196 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 literal 0 HcmV?d00001 diff --git a/hooks/lib/apache_utils.py b/hooks/lib/apache_utils.py index 131f8ac0..3890582c 100644 --- a/hooks/lib/apache_utils.py +++ b/hooks/lib/apache_utils.py @@ -17,8 +17,7 @@ from lib.utils import ( config_get, install, get_host_ip, - restart - ) + restart) from lib.cluster_utils import https import os @@ -136,8 +135,7 @@ def enable_https(port_maps, namespace, cert, key, ca_cert=None): "ext": ext_port, "int": int_port, "namespace": namespace, - "private_address": get_host_ip() - } + "private_address": get_host_ip()} fsite.write(render_template(SITE_TEMPLATE, context)) @@ -160,7 +158,7 @@ def disable_https(port_maps, namespace): juju_log('INFO', 'Ensuring HTTPS disabled for {}'.format(port_maps)) if (not os.path.exists('/etc/apache2') or - not os.path.exists(os.path.join('/etc/apache2/ssl', namespace))): + not os.path.exists(os.path.join('/etc/apache2/ssl', namespace))): return http_restart = False diff --git a/hooks/lib/cluster_utils.py b/hooks/lib/cluster_utils.py index b7d00f8b..1405d6fb 100644 --- a/hooks/lib/cluster_utils.py +++ b/hooks/lib/cluster_utils.py @@ -14,8 +14,7 @@ from lib.utils import ( relation_list, relation_get, get_unit_hostname, - config_get - ) + config_get) import subprocess import os @@ -34,8 +33,7 @@ def is_clustered(): def is_leader(resource): cmd = [ "crm", "resource", - "show", resource - ] + "show", resource] try: status = subprocess.check_output(cmd) except subprocess.CalledProcessError: @@ -91,9 +89,9 @@ def https(): for r_id in relation_ids('identity-service'): for unit in relation_list(r_id): if (relation_get('https_keystone', rid=r_id, unit=unit) and - relation_get('ssl_cert', rid=r_id, unit=unit) and - relation_get('ssl_key', rid=r_id, unit=unit) and - relation_get('ca_cert', rid=r_id, unit=unit)): + relation_get('ssl_cert', rid=r_id, unit=unit) and + relation_get('ssl_key', rid=r_id, unit=unit) and + relation_get('ca_cert', rid=r_id, unit=unit)): return True return False diff --git a/hooks/lib/haproxy_utils.py b/hooks/lib/haproxy_utils.py index 721bb7f2..f14a20c1 100644 --- a/hooks/lib/haproxy_utils.py +++ b/hooks/lib/haproxy_utils.py @@ -14,8 +14,7 @@ from lib.utils import ( relation_get, unit_get, reload, - render_template - ) + render_template) import os HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' @@ -44,8 +43,7 @@ def configure_haproxy(service_ports): unit=unit) context = { 'units': cluster_hosts, - 'service_ports': service_ports - } + 'service_ports': service_ports} with open(HAPROXY_CONF, 'w') as f: f.write(render_template(os.path.basename(HAPROXY_CONF), context)) diff --git a/hooks/lib/unison.py b/hooks/lib/unison.py index 06dd8b4c..fdef449d 100755 --- a/hooks/lib/unison.py +++ b/hooks/lib/unison.py @@ -73,15 +73,14 @@ def get_keypair(user): pub_key = '%s.pub' % priv_key if not os.path.isfile(pub_key): - utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' % \ + utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' % pub_key) cmd = ['ssh-keygen', '-y', '-f', priv_key] p = subprocess.check_output(cmd).strip() with open(pub_key, 'wb') as out: out.write(p) subprocess.check_call(['chown', '-R', user, ssh_dir]) - return open(priv_key, 'r').read().strip(), \ - open(pub_key, 'r').read().strip() + return open(priv_key, 'r').read().strip(), open(pub_key, 'r').read().strip() def write_authorized_keys(user, keys): @@ -149,7 +148,7 @@ def ssh_authorized_peers(peer_interface, user, group=None, ensure_local_user=Fal hosts.append(settings['private-address']) else: utils.juju_log('INFO', - 'ssh_authorized_peers(): ssh_pub_key '\ + 'ssh_authorized_peers(): ssh_pub_key ' 'missing for unit %s, skipping.' % unit) write_authorized_keys(user, keys) write_known_hosts(user, hosts) @@ -204,8 +203,7 @@ def sync_to_peers(peer_interface, user, paths=[], verbose=False): hosts.append(settings['private-address']) else: print 'unison sync_to_peers: peer (%s) has not authorized '\ - '*this* host yet, skipping.' %\ - settings['private-address'] + '*this* host yet, skipping.' % settings['private-address'] for path in paths: # removing trailing slash from directory paths, unison @@ -214,7 +212,6 @@ def sync_to_peers(peer_interface, user, paths=[], verbose=False): path = path[:(len(path) - 1)] for host in hosts: cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)] - utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' %\ - (path, user, host, path)) - print ' '.join(cmd) + utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' % + (path, user, host, path)) run_as_user(user, cmd) diff --git a/hooks/lib/utils.py b/hooks/lib/utils.py index 8095e86a..018ac9e4 100644 --- a/hooks/lib/utils.py +++ b/hooks/lib/utils.py @@ -32,8 +32,7 @@ def install(*pkgs): cmd = [ 'apt-get', '-y', - 'install' - ] + 'install'] for pkg in pkgs: cmd.append(pkg) subprocess.check_call(cmd) @@ -54,16 +53,14 @@ except ImportError: def render_template(template_name, context, template_dir=TEMPLATES_DIR): - templates = jinja2.Environment( - loader=jinja2.FileSystemLoader(template_dir) - ) + templates = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) template = templates.get_template(template_name) return template.render(context) CLOUD_ARCHIVE = \ -""" # Ubuntu Cloud Archive -deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main -""" + """ # Ubuntu Cloud Archive + deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main + """ CLOUD_ARCHIVE_POCKETS = { 'folsom': 'precise-updates/folsom', @@ -77,8 +74,7 @@ CLOUD_ARCHIVE_POCKETS = { 'havana/proposed': 'precise-proposed/havana', 'icehouse': 'precise-updates/icehouse', 'icehouse/updates': 'precise-updates/icehouse', - 'icehouse/proposed': 'precise-proposed/icehouse', - } + 'icehouse/proposed': 'precise-proposed/icehouse'} def configure_source(): @@ -88,8 +84,7 @@ def configure_source(): if source.startswith('ppa:'): cmd = [ 'add-apt-repository', - source - ] + source] subprocess.check_call(cmd) if source.startswith('cloud:'): # CA values should be formatted as cloud:ubuntu-openstack/pocket, eg: @@ -106,8 +101,7 @@ def configure_source(): cmd = [ 'apt-key', 'adv', '--keyserver keyserver.ubuntu.com', - '--recv-keys', key - ] + '--recv-keys', key] subprocess.check_call(cmd) elif l == 1: apt_line = source @@ -116,8 +110,7 @@ def configure_source(): apt.write(apt_line + "\n") cmd = [ 'apt-get', - 'update' - ] + 'update'] subprocess.check_call(cmd) # Protocols @@ -128,8 +121,7 @@ UDP = 'UDP' def expose(port, protocol='TCP'): cmd = [ 'open-port', - '{}/{}'.format(port, protocol) - ] + '{}/{}'.format(port, protocol)] subprocess.check_call(cmd) @@ -137,8 +129,7 @@ def juju_log(severity, message): cmd = [ 'juju-log', '--log-level', severity, - message - ] + message] subprocess.check_call(cmd) @@ -162,8 +153,7 @@ def cached(func): def relation_ids(relation): cmd = [ 'relation-ids', - relation - ] + relation] result = str(subprocess.check_output(cmd)).split() if result == "": return None @@ -175,8 +165,7 @@ def relation_ids(relation): def relation_list(rid): cmd = [ 'relation-list', - '-r', rid, - ] + '-r', rid] result = str(subprocess.check_output(cmd)).split() if result == "": return None @@ -187,8 +176,7 @@ def relation_list(rid): @cached def relation_get(attribute, unit=None, rid=None): cmd = [ - 'relation-get', - ] + 'relation-get'] if rid: cmd.append('-r') cmd.append(rid) @@ -206,8 +194,7 @@ def relation_get(attribute, unit=None, rid=None): def relation_get_dict(relation_id=None, remote_unit=None): """Obtain all relation data as dict by way of JSON""" cmd = [ - 'relation-get', '--format=json' - ] + 'relation-get', '--format=json'] if relation_id: cmd.append('-r') cmd.append(relation_id) @@ -225,8 +212,7 @@ def relation_get_dict(relation_id=None, remote_unit=None): def relation_set(**kwargs): cmd = [ - 'relation-set' - ] + 'relation-set'] args = [] for k, v in kwargs.items(): if k == 'rid': @@ -243,8 +229,7 @@ def relation_set(**kwargs): def unit_get(attribute): cmd = [ 'unit-get', - attribute - ] + attribute] value = subprocess.check_output(cmd).strip() # IGNORE:E1103 if value == "": return None @@ -257,8 +242,7 @@ def config_get(attribute): cmd = [ 'config-get', '--format', - 'json', - ] + 'json'] out = subprocess.check_output(cmd).strip() # IGNORE:E1103 cfg = json.loads(out) @@ -321,8 +305,7 @@ def running(service): except subprocess.CalledProcessError: return False else: - if ("start/running" in output or - "is running" in output): + if ("start/running" in output or "is running" in output): return True else: return False diff --git a/hooks/pgsql-db-relation-changed b/hooks/pgsql-db-relation-changed new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/pgsql-db-relation-changed @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/hooks/pgsql-db-relation-joined b/hooks/pgsql-db-relation-joined new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/pgsql-db-relation-joined @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 42d82ab2..498b197b 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -12,6 +12,8 @@ provides: requires: shared-db: interface: mysql-shared + pgsql-db: + interface: pgsql ha: interface: hacluster scope: container diff --git a/revision b/revision index bf18240e..dcb6b5ba 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -229 +230 diff --git a/templates/essex/keystone.conf b/templates/essex/keystone.conf index 9580f959..f514d9bb 100644 --- a/templates/essex/keystone.conf +++ b/templates/essex/keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/folsom/keystone.conf b/templates/folsom/keystone.conf index 8d1c560c..1daa88ec 100644 --- a/templates/folsom/keystone.conf +++ b/templates/folsom/keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/grizzly/keystone.conf b/templates/grizzly/keystone.conf index 0ffb2bfa..370f78a4 100644 --- a/templates/grizzly/keystone.conf +++ b/templates/grizzly/keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/templates/havana/keystone.conf b/templates/havana/keystone.conf index ca28d9b0..f53310e2 100644 --- a/templates/havana/keystone.conf +++ b/templates/havana/keystone.conf @@ -14,7 +14,7 @@ verbose = {{ verbose }} [sql] {% if database_host -%} -connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} {% else -%} connection = sqlite:////var/lib/keystone/keystone.db {% endif -%} diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py new file mode 100644 index 00000000..9e2de575 --- /dev/null +++ b/unit_tests/test_keystone_hooks.py @@ -0,0 +1,120 @@ +from mock import call, patch, MagicMock +import os + +from test_utils import CharmTestCase + +os.environ['JUJU_UNIT_NAME'] = 'keystone' +with patch('charmhelpers.core.hookenv.config') as config: + config.return_value = 'keystone' + import keystone_utils as utils + +_reg = utils.register_configs +_map = utils.restart_map + +utils.register_configs = MagicMock() +utils.restart_map = MagicMock() + +import keystone_hooks as hooks + +utils.register_configs = _reg +utils.restart_map = _map + +TO_PATCH = [ + # charmhelpers.core.hookenv + 'Hooks', + 'config', + 'is_relation_made', + 'log', + 'relation_ids', + 'relation_set', + 'relation_get', + 'unit_get', + # charmhelpers.core.host + 'apt_install', + 'apt_update', + 'restart_on_change', + # charmhelpers.contrib.openstack.utils + 'configure_installation_source', + # charmhelpers.contrib.hahelpers.cluster_utils + 'eligible_leader', + # keystone_utils + 'restart_map', + 'register_configs', + 'do_openstack_upgrade', + 'migrate_database', + # other + 'check_call', + 'execd_preinstall', + 'mkdir' +] + + +class KeystoneRelationTests(CharmTestCase): + + def setUp(self): + super(KeystoneRelationTests, self).setUp(hooks, TO_PATCH) + self.config.side_effect = self.test_config.get + + + def test_db_joined(self): + self.unit_get.return_value = 'keystone.foohost.com' + self.is_relation_made.return_value = False + hooks.db_joined() + self.relation_set.assert_called_with(database='keystone', + username='keystone', + hostname='keystone.foohost.com') + self.unit_get.assert_called_with('private-address') + + def test_postgresql_db_joined(self): + self.unit_get.return_value = 'keystone.foohost.com' + self.is_relation_made.return_value = False + hooks.pgsql_db_joined() + self.relation_set.assert_called_with(database='keystone'), + + def test_db_joined_with_postgresql(self): + self.is_relation_made.return_value = True + + with self.assertRaises(Exception) as context: + hooks.db_joined() + self.assertEqual(context.exception.message, + 'Attempting to associate a mysql database when there ' + 'is already associated a postgresql one') + + def test_postgresql_joined_with_db(self): + self.is_relation_made.return_value = True + + with self.assertRaises(Exception) as context: + hooks.pgsql_db_joined() + self.assertEqual(context.exception.message, + 'Attempting to associate a postgresql database when there ' + 'is already associated a mysql one') + + @patch.object(hooks, 'CONFIGS') + def test_db_changed_missing_relation_data(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = [] + hooks.db_changed() + self.log.assert_called_with( + 'shared-db relation incomplete. Peer not ready?' + ) + + @patch.object(hooks, 'CONFIGS') + def test_postgresql_db_changed_missing_relation_data(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = [] + hooks.pgsql_db_changed() + self.log.assert_called_with( + 'pgsql-db relation incomplete. Peer not ready?' + ) + + def _shared_db_test(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = ['shared-db'] + configs.write = MagicMock() + hooks.db_changed() + + def _postgresql_db_test(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = ['pgsql-db'] + configs.write = MagicMock() + hooks.pgsql_db_changed() diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py new file mode 100644 index 00000000..e1e346b1 --- /dev/null +++ b/unit_tests/test_utils.py @@ -0,0 +1,119 @@ +import logging +import os +import unittest +import yaml + +from contextlib import contextmanager +from mock import patch, MagicMock + + +def load_config(): + '''Walk backwords from __file__ looking for config.yaml, + load and return the 'options' section' + ''' + config = None + f = __file__ + while config is None: + d = os.path.dirname(f) + if os.path.isfile(os.path.join(d, 'config.yaml')): + config = os.path.join(d, 'config.yaml') + break + f = d + + if not config: + logging.error('Could not find config.yaml in any parent directory ' + 'of %s. ' % file) + raise Exception + + return yaml.safe_load(open(config).read())['options'] + + +def get_default_config(): + '''Load default charm config from config.yaml return as a dict. + If no default is set in config.yaml, its value is None. + ''' + default_config = {} + config = load_config() + for k, v in config.iteritems(): + if 'default' in v: + default_config[k] = v['default'] + else: + default_config[k] = None + return default_config + + +class CharmTestCase(unittest.TestCase): + + def setUp(self, obj, patches): + super(CharmTestCase, self).setUp() + self.patches = patches + self.obj = obj + self.test_config = TestConfig() + self.test_relation = TestRelation() + self.patch_all() + + def patch(self, method): + _m = patch.object(self.obj, method) + mock = _m.start() + self.addCleanup(_m.stop) + return mock + + def patch_all(self): + for method in self.patches: + setattr(self, method, self.patch(method)) + + +class TestConfig(object): + + def __init__(self): + self.config = get_default_config() + + def get(self, attr=None): + if not attr: + return self.get_all() + try: + return self.config[attr] + except KeyError: + return None + + def get_all(self): + return self.config + + def set(self, attr, value): + if attr not in self.config: + raise KeyError + self.config[attr] = value + + +class TestRelation(object): + + def __init__(self, relation_data={}): + self.relation_data = relation_data + + def set(self, relation_data): + self.relation_data = relation_data + + def get(self, attr=None, unit=None, rid=None): + if attr is None: + return self.relation_data + elif attr in self.relation_data: + return self.relation_data[attr] + return None + + +@contextmanager +def patch_open(): + '''Patch open() to allow mocking both open() itself and the file that is + yielded. + Yields the mock for "open" and "file", respectively. + ''' + mock_open = MagicMock(spec=open) + mock_file = MagicMock(spec=file) + + @contextmanager + def stub_open(*args, **kwargs): + mock_open(*args, **kwargs) + yield mock_file + + with patch('__builtin__.open', stub_open): + yield mock_open, mock_file From 57e9e8e032d2e1519e361f1efd350c706325b0ed Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 31 Mar 2014 11:18:06 +0100 Subject: [PATCH 42/57] Add trivial test config --- .coveragerc | 6 ++++++ setup.cfg | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 .coveragerc create mode 100644 setup.cfg diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..972ccd41 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + if __name__ == .__main__.: +include= + hooks/keystone_* diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..37083b62 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[nosetests] +verbosity=2 +with-coverage=1 +cover-erase=1 +cover-package=hooks From 0aa7abf6529f3e8b40f0b342691f6c92ce03daf2 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 31 Mar 2014 15:03:24 +0100 Subject: [PATCH 43/57] Fixup signing section until PKI is fully implemented --- templates/folsom/keystone.conf | 8 +--- templates/grizzly/keystone.conf | 8 +--- templates/havana/keystone.conf | 8 +--- templates/icehouse/keystone.conf | 70 ++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 templates/icehouse/keystone.conf diff --git a/templates/folsom/keystone.conf b/templates/folsom/keystone.conf index 1daa88ec..5c09801f 100644 --- a/templates/folsom/keystone.conf +++ b/templates/folsom/keystone.conf @@ -36,16 +36,10 @@ driver = keystone.policy.backends.rules.Policy [ec2] driver = keystone.contrib.ec2.backends.sql.Ec2 -{% if signing -%} [signing] token_format = UUID -certfile = /etc/keystone/ssl/certs/signing_cert.pem -keyfile = /etc/keystone/ssl/private/signing_key.pem -ca_certs = /etc/keystone/ssl/certs/ca.pem -key_size = 1024 +key_size = 2048 valid_days = 3650 -ca_password = None -{% endif -%} [filter:debug] paste.filter_factory = keystone.common.wsgi:Debug.factory diff --git a/templates/grizzly/keystone.conf b/templates/grizzly/keystone.conf index 370f78a4..968c7d8a 100644 --- a/templates/grizzly/keystone.conf +++ b/templates/grizzly/keystone.conf @@ -39,16 +39,10 @@ driver = keystone.policy.backends.rules.Policy [ec2] driver = keystone.contrib.ec2.backends.sql.Ec2 -{% if signing -%} [signing] token_format = UUID -certfile = /etc/keystone/ssl/certs/signing_cert.pem -keyfile = /etc/keystone/ssl/private/signing_key.pem -ca_certs = /etc/keystone/ssl/certs/ca.pem -key_size = 1024 +key_size = 2048 valid_days = 3650 -ca_password = None -{% endif -%} [auth] methods = password,token diff --git a/templates/havana/keystone.conf b/templates/havana/keystone.conf index f53310e2..fade7ed0 100644 --- a/templates/havana/keystone.conf +++ b/templates/havana/keystone.conf @@ -52,16 +52,10 @@ driver = keystone.contrib.ec2.backends.kvs.Ec2 [oauth1] -{% if signing -%} [signing] token_format = UUID -certfile = /etc/keystone/ssl/certs/signing_cert.pem -keyfile = /etc/keystone/ssl/private/signing_key.pem -ca_certs = /etc/keystone/ssl/certs/ca.pem -key_size = 1024 +key_size = 2048 valid_days = 3650 -ca_password = None -{% endif -%} [auth] methods = external,password,token,oauth1 diff --git a/templates/icehouse/keystone.conf b/templates/icehouse/keystone.conf new file mode 100644 index 00000000..45eb5951 --- /dev/null +++ b/templates/icehouse/keystone.conf @@ -0,0 +1,70 @@ +# havana +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +[DEFAULT] +admin_token = {{ token }} +admin_port = {{ admin_port }} +public_port = {{ public_port }} +use_syslog = {{ use_syslog }} +log_config = /etc/keystone/logging.conf +debug = {{ debug }} +verbose = {{ verbose }} + +[database] +{% if database_host -%} +connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +{% else -%} +connection = sqlite:////var/lib/keystone/keystone.db +{% endif -%} +idle_timeout = 200 + +[identity] +driver = keystone.identity.backends.sql.Identity + +[credential] +driver = keystone.credential.backends.sql.Credential + +[trust] +driver = keystone.trust.backends.sql.Trust + +[os_inherit] + +[catalog] +driver = keystone.catalog.backends.sql.Catalog + +[endpoint_filter] + +[token] +driver = keystone.token.backends.sql.Token +expiration = 86400 + +[cache] + +[policy] +driver = keystone.policy.backends.rules.Policy + +[ec2] +driver = keystone.contrib.ec2.backends.kvs.Ec2 + +[assignment] + +[oauth1] + +[signing] +token_format = UUID +key_size = 2048 +valid_days = 3650 + +[auth] +methods = external,password,token,oauth1 +password = keystone.auth.plugins.password.Password +token = keystone.auth.plugins.token.Token +oauth1 = keystone.auth.plugins.oauth1.OAuth + +[paste_deploy] +config_file = keystone-paste.ini + +[extra_headers] +Distribution = Ubuntu From b00bbdbcb14f073f0c950668d3668aef0f7fe937 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 31 Mar 2014 16:00:14 +0100 Subject: [PATCH 44/57] Fixup relation_set post clustering --- hooks/keystone_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index f35c25dc..a0015cd2 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -237,7 +237,7 @@ def ha_changed(): log('Cluster configured, notifying other services and updating ' 'keystone endpoint configuration') for rid in relation_ids('identity-service'): - relation_set(rid=rid, + relation_set(relation_id=rid, auth_host=config('vip'), service_host=config('vip')) From 2f4f5dbb9a798292d9504e344a68ae24b359df78 Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Tue, 1 Apr 2014 14:38:11 +0200 Subject: [PATCH 45/57] added unit testing --- hooks/keystone_context.pyc | Bin 3896 -> 0 bytes hooks/keystone_hooks.pyc | Bin 8832 -> 0 bytes hooks/keystone_ssl.pyc | Bin 11026 -> 0 bytes hooks/keystone_utils.pyc | Bin 22820 -> 0 bytes unit_tests/test_keystone_hooks.py | 254 +++++++++++++++++++++++++++++- 5 files changed, 249 insertions(+), 5 deletions(-) delete mode 100644 hooks/keystone_context.pyc delete mode 100644 hooks/keystone_hooks.pyc delete mode 100644 hooks/keystone_ssl.pyc delete mode 100644 hooks/keystone_utils.pyc diff --git a/hooks/keystone_context.pyc b/hooks/keystone_context.pyc deleted file mode 100644 index 68739dd0f60d83769e0ff1fc2bb7d61a7dec25f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3896 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 IH(8qc4`2l=VE_OC diff --git a/hooks/keystone_utils.pyc b/hooks/keystone_utils.pyc deleted file mode 100644 index 1c67882b869635b311b5b12d5c8aa170e7da92cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22820 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 a17u`qcxdm?zJbEn(V>yCuMG_j4gX(C4#y7w diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 9e2de575..6a031e83 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -15,6 +15,7 @@ utils.register_configs = MagicMock() utils.restart_map = MagicMock() import keystone_hooks as hooks +from charmhelpers.contrib import unison utils.register_configs = _reg utils.restart_map = _map @@ -26,9 +27,12 @@ TO_PATCH = [ 'is_relation_made', 'log', 'relation_ids', + 'relation_list', 'relation_set', 'relation_get', + 'related_units', 'unit_get', + 'peer_echo', # charmhelpers.core.host 'apt_install', 'apt_update', @@ -41,11 +45,19 @@ TO_PATCH = [ 'restart_map', 'register_configs', 'do_openstack_upgrade', + 'openstack_upgrade_available', + 'save_script_rc', 'migrate_database', + 'ensure_initial_admin', + 'add_service_to_keystone', + 'synchronize_ca', + 'get_hacluster_config', + 'is_leader', # other 'check_call', 'execd_preinstall', - 'mkdir' + 'mkdir', + 'os', ] @@ -54,7 +66,18 @@ class KeystoneRelationTests(CharmTestCase): def setUp(self): super(KeystoneRelationTests, self).setUp(hooks, TO_PATCH) self.config.side_effect = self.test_config.get + self.ssh_user = 'juju_keystone' + def test_install_hook(self): + repo = 'cloud:precise-grizzly' + self.test_config.set('openstack-origin', repo) + hooks.install() + self.configure_installation_source.assert_called_with(repo) + self.assertTrue(self.apt_update.called) + self.apt_install.assert_called_with(['haproxy', 'unison', 'python-keystoneclient', + 'uuid', 'python-mysqldb', 'openssl', 'apache2', + 'pwgen', 'keystone', 'python-psycopg2'], fatal=True) + self.assertTrue(self.execd_preinstall.called) def test_db_joined(self): self.unit_get.return_value = 'keystone.foohost.com' @@ -76,18 +99,20 @@ class KeystoneRelationTests(CharmTestCase): with self.assertRaises(Exception) as context: hooks.db_joined() - self.assertEqual(context.exception.message, + self.assertEqual( + context.exception.message, 'Attempting to associate a mysql database when there ' - 'is already associated a postgresql one') + 'is already associated a postgresql one') def test_postgresql_joined_with_db(self): self.is_relation_made.return_value = True with self.assertRaises(Exception) as context: hooks.pgsql_db_joined() - self.assertEqual(context.exception.message, + self.assertEqual( + context.exception.message, 'Attempting to associate a postgresql database when there ' - 'is already associated a mysql one') + 'is already associated a mysql one') @patch.object(hooks, 'CONFIGS') def test_db_changed_missing_relation_data(self, configs): @@ -118,3 +143,222 @@ class KeystoneRelationTests(CharmTestCase): configs.complete_contexts.return_value = ['pgsql-db'] configs.write = MagicMock() hooks.pgsql_db_changed() + + @patch.object(hooks, 'CONFIGS') + @patch.object(hooks, 'identity_changed') + def test_db_changed(self, identity_changed, configs): + self.relation_ids.return_value = ['identity-service:0'] + self.related_units.return_value = ['unit/0'] + + self._shared_db_test(configs) + self.assertEquals([call('/etc/keystone/keystone.conf')], + configs.write.call_args_list) + self.migrate_database.assert_called_with() + self.ensure_initial_admin.assert_called() + identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + + @patch.object(hooks, 'CONFIGS') + @patch.object(hooks, 'identity_changed') + def test_postgresql_db_changed(self, identity_changed, configs): + self.relation_ids.return_value = ['identity-service:0'] + self.related_units.return_value = ['unit/0'] + + self._postgresql_db_test(configs) + self.assertEquals([call('/etc/keystone/keystone.conf')], + configs.write.call_args_list) + self.migrate_database.assert_called_with() + self.ensure_initial_admin.assert_called() + identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + + @patch.object(unison, 'ensure_user') + @patch.object(unison, 'get_homedir') + @patch.object(hooks, 'CONFIGS') + @patch.object(hooks, 'identity_changed') + @patch.object(hooks, 'configure_https') + def test_config_changed_no_openstack_upgrade_leader(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + self.openstack_upgrade_available.return_value = False + self.eligible_leader.return_value = True + self.relation_ids.return_value = ['identity-service:0'] + self.relation_list.return_value = ['unit/0'] + + hooks.config_changed() + ensure_user.assert_called_with(user=self.ssh_user, group='keystone') + get_homedir.assert_called_with(self.ssh_user) + + self.save_script_rc.assert_called_with() + configure_https.assert_called_with() + self.assertTrue(configs.write_all.called) + + self.migrate_database.assert_called_with() + self.ensure_initial_admin.assert_called() + self.log.assert_called_with('Firing identity_changed hook for all related services.') + identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + + @patch.object(unison, 'ensure_user') + @patch.object(unison, 'get_homedir') + @patch.object(hooks, 'CONFIGS') + @patch.object(hooks, 'identity_changed') + @patch.object(hooks, 'configure_https') + def test_config_changed_no_openstack_upgrade_not_leader(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + self.openstack_upgrade_available.return_value = False + self.eligible_leader.return_value = False + + hooks.config_changed() + ensure_user.assert_called_with(user=self.ssh_user, group='keystone') + get_homedir.assert_called_with(self.ssh_user) + + self.save_script_rc.assert_called_with() + configure_https.assert_called_with() + self.assertTrue(configs.write_all.called) + + self.assertFalse(self.migrate_database.called) + self.assertFalse(self.ensure_initial_admin.called) + self.assertFalse(identity_changed.called) + + @patch.object(unison, 'ensure_user') + @patch.object(unison, 'get_homedir') + @patch.object(hooks, 'CONFIGS') + @patch.object(hooks, 'identity_changed') + @patch.object(hooks, 'configure_https') + def test_config_changed_with_openstack_upgrade(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + self.openstack_upgrade_available.return_value = True + self.eligible_leader.return_value = True + self.relation_ids.return_value = ['identity-service:0'] + self.relation_list.return_value = ['unit/0'] + + hooks.config_changed() + ensure_user.assert_called_with(user=self.ssh_user, group='keystone') + get_homedir.assert_called_with(self.ssh_user) + + self.do_openstack_upgrade.assert_called() + + self.save_script_rc.assert_called_with() + configure_https.assert_called_with() + self.assertTrue(configs.write_all.called) + + self.migrate_database.assert_called_with() + self.ensure_initial_admin.assert_called() + self.log.assert_called_with('Firing identity_changed hook for all related services.') + identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + + def test_identity_changed_leader(self): + self.eligible_leader.return_value = True + hooks.identity_changed(relation_id='identity-service:0', remote_unit='unit/0') + self.add_service_to_keystone.assert_called_with('identity-service:0', 'unit/0') + self.synchronize_ca.assert_called() + + def test_identity_changed_no_leader(self): + self.eligible_leader.return_value = False + hooks.identity_changed(relation_id='identity-service:0', remote_unit='unit/0') + self.assertFalse(self.add_service_to_keystone.called) + self.log.assert_called_with('Deferring identity_changed() to service leader.') + + @patch.object(unison, 'ssh_authorized_peers') + def test_cluster_joined(self, ssh_authorized_peers): + hooks.cluster_joined() + ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='juju_keystone', + peer_interface='cluster', ensure_local_user=True) + + @patch.object(unison, 'ssh_authorized_peers') + @patch.object(hooks, 'CONFIGS') + def test_cluster_changed(self, configs, ssh_authorized_peers): + hooks.cluster_changed() + self.peer_echo.assert_called_with(includes=['_passwd']) + ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) + self.synchronize_ca.assert_called() + self.assertTrue(configs.write_all.called) + + def test_ha_joined(self): + self.get_hacluster_config.return_value = { + 'ha-bindiface': 'em0', + 'ha-mcastport': '8080', + 'vip': '10.10.10.10', + 'vip_iface': 'em1', + 'vip_cidr': '24' + } + hooks.ha_joined() + self.get_hacluster_config.assert_called() + args = { + 'corosync_bindiface': 'em0', + 'corosync_mcastport': '8080', + 'init_services': {'res_ks_haproxy': 'haproxy'}, + 'resources': {'res_ks_vip': 'ocf:heartbeat:IPaddr2', + 'res_ks_haproxy': 'lsb:haproxy'}, + 'resource_params': { + 'res_ks_vip': 'params ip="10.10.10.10"' + ' cidr_netmask="24" nic="em1"', + 'res_ks_haproxy': 'op monitor interval="5s"'}, + 'clones': {'cl_ks_haproxy': 'res_ks_haproxy'} + } + self.relation_set.assert_called_with(**args) + + @patch.object(hooks, 'CONFIGS') + def test_ha_relation_changed_not_clustered_not_leader(self, configs): + self.relation_get.return_value = False + self.is_leader.return_value = False + + hooks.ha_changed() + self.assertTrue(configs.write_all.called) + + @patch.object(hooks, 'CONFIGS') + def test_ha_relation_changed_clustered_leader(self, configs): + self.relation_get.return_value = True + self.is_leader.return_value = True + self.relation_ids.return_value = ['identity-service:0'] + self.test_config.set('vip', '10.10.10.10') + + hooks.ha_changed() + self.assertTrue(configs.write_all.called) + self.log.assert_called_with('Cluster configured, notifying other services and updating ' + 'keystone endpoint configuration') + self.relation_set.assert_called_with(relation_id='identity-service:0', + auth_host='10.10.10.10', + service_host='10.10.10.10') + + @patch.object(hooks, 'CONFIGS') + def test_configure_https_enable(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = ['https'] + configs.write = MagicMock() + + hooks.configure_https() + self.assertTrue(configs.write_all.called) + cmd = ['a2ensite', 'openstack_https_frontend'] + self.check_call.assert_called_with(cmd) + + @patch.object(hooks, 'CONFIGS') + def test_configure_https_disable(self, configs): + configs.complete_contexts = MagicMock() + configs.complete_contexts.return_value = [''] + configs.write = MagicMock() + + hooks.configure_https() + self.assertTrue(configs.write_all.called) + cmd = ['a2dissite', 'openstack_https_frontend'] + self.check_call.assert_called_with(cmd) + + @patch.object(unison, 'ssh_authorized_peers') + def test_upgrade_charm_leader(self, ssh_authorized_peers): + self.eligible_leader.return_value = True + hooks.upgrade_charm() + self.apt_install.assert_called_with(['haproxy', 'unison', 'uuid', 'python-mysqldb', + 'pwgen', 'keystone', 'python-psycopg2']) + ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) + self.synchronize_ca.assert_called() + self.log.assert_called_with('Cluster leader - ensuring endpoint configuration' + ' is up to date') + self.ensure_initial_admin.assert_called() + + @patch.object(unison, 'ssh_authorized_peers') + def test_upgrade_charm_not_leader(self, ssh_authorized_peers): + self.eligible_leader.return_value = False + hooks.upgrade_charm() + self.apt_install.assert_called_with(['haproxy', 'unison', 'uuid', 'python-mysqldb', + 'pwgen', 'keystone', 'python-psycopg2']) + ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) + self.synchronize_ca.assert_called() + self.assertFalse(self.log.called) + self.assertFalse(self.ensure_initial_admin.called) From bd60af6583fe6a9b61566f994d820e2cf27347f3 Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Wed, 2 Apr 2014 10:36:05 +0200 Subject: [PATCH 46/57] added testing for contexts --- unit_tests/test_keystone_contexts.py | 70 ++++++++++++++++++++++++++++ unit_tests/test_keystone_hooks.py | 1 + 2 files changed, 71 insertions(+) create mode 100644 unit_tests/test_keystone_contexts.py diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py new file mode 100644 index 00000000..c30f812c --- /dev/null +++ b/unit_tests/test_keystone_contexts.py @@ -0,0 +1,70 @@ +import keystone_context as context +from mock import patch + +with patch('charmhelpers.core.hookenv.config') as config: + config.return_value = 'keystone' + import keystone_utils as utils + +from test_utils import ( + CharmTestCase +) + +TO_PATCH = [ + 'determine_apache_port', + 'determine_api_port', +] + + +class TestKeystoneContexts(CharmTestCase): + + def setUp(self): + super(TestKeystoneContexts, self).setUp(context, TO_PATCH) + + @patch('charmhelpers.contrib.openstack.context.is_clustered') + @patch('charmhelpers.contrib.openstack.context.determine_apache_port') + @patch('charmhelpers.contrib.openstack.context.determine_api_port') + @patch('charmhelpers.contrib.openstack.context.unit_get') + @patch('charmhelpers.contrib.openstack.context.https') + def test_apache_ssl_context_service_enabled(self, mock_https, + mock_unit_get, + mock_determine_api_port, + mock_determine_apache_port, + mock_is_clustered): + mock_https.return_value = True + mock_unit_get.return_value = '1.2.3.4' + mock_determine_api_port.return_value = '12' + mock_determine_apache_port.return_value = '34' + mock_is_clustered.return_value = False + + ctxt = context.ApacheSSLContext() + with patch.object(ctxt, 'enable_modules') as mock_enable_modules: + with patch.object(ctxt, 'configure_cert') as mock_configure_cert: + self.assertEquals(ctxt(), {'endpoints': [(34, 12)], + 'private_address': '1.2.3.4', + 'namespace': 'keystone'}) + self.assertTrue(mock_https.called) + mock_unit_get.assert_called_with('private-address') + + @patch('charmhelpers.contrib.openstack.context.relation_ids') + @patch('charmhelpers.contrib.openstack.context.unit_get') + @patch('charmhelpers.contrib.openstack.context.related_units') + @patch('charmhelpers.contrib.openstack.context.relation_get') + @patch('charmhelpers.contrib.openstack.context.log') + @patch('__builtin__.open') + def test_haproxy_context_service_enabled(self, mock_open, mock_log, mock_relation_get, mock_related_units, + mock_unit_get, mock_relation_ids): + mock_relation_ids.return_value = ['identity-service:0',] + mock_unit_get.return_value = '1.2.3.4' + mock_relation_get.return_value = '10.0.0.0' + mock_related_units.return_value = ['unit/0',] + self.determine_apache_port.return_value = '34' + + ctxt = context.HAProxyContext() + + self.assertEquals(ctxt(), {'listen_ports': {'admin_port': 'keystone', 'public_port': 'keystone'}, + 'service_ports': {'admin-port': ['keystone', '34'], + 'public-port': ['keystone', '34']}, + 'units': {'keystone': '1.2.3.4', 'unit-0':'10.0.0.0'}}) + mock_unit_get.assert_called_with('private-address') + mock_relation_get.assert_called_with('private-address', rid='identity-service:0', unit='unit/0') + mock_open.assert_called_with('/etc/default/haproxy', 'w') diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 6a031e83..50f3dc28 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -58,6 +58,7 @@ TO_PATCH = [ 'execd_preinstall', 'mkdir', 'os', + 'time', ] From 5621512adf095c453e4a52d7c307c79aee80de13 Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Wed, 2 Apr 2014 12:45:02 +0200 Subject: [PATCH 47/57] added testing for utils --- unit_tests/test_keystone_utils.py | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 unit_tests/test_keystone_utils.py diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py new file mode 100644 index 00000000..d695cc6c --- /dev/null +++ b/unit_tests/test_keystone_utils.py @@ -0,0 +1,74 @@ +from mock import patch, call, MagicMock +from test_utils import CharmTestCase +from copy import deepcopy + +from collections import OrderedDict +import os + +os.environ['JUJU_UNIT_NAME'] = 'keystone' +with patch('charmhelpers.core.hookenv.config') as config: + import keystone_utils as utils + +import keystone_context +from charmhelpers.contrib.openstack import context + +TO_PATCH = [ + 'config', + 'os_release', + 'log', +] + +class TestKeystoneUtils(CharmTestCase): + + def setUp(self): + super(TestKeystoneUtils, self).setUp(utils, TO_PATCH) + self.config.side_effect = self.test_config.get + + self.ctxt = MagicMock() + self.rsc_map = { + '/etc/keystone/keystone.conf': { + 'services': ['keystone'], + 'contexts': [self.ctxt], + }, + '/etc/apache2/sites-available/openstack_https_frontend': { + 'services': ['apache2'], + 'contexts': [self.ctxt], + }, + '/etc/apache2/sites-available/openstack_https_frontend.conf': { + 'services': ['apache2'], + 'contexts': [self.ctxt], + } + } + + @patch('charmhelpers.contrib.openstack.templating.OSConfigRenderer') + @patch('os.path.exists') + @patch.object(utils, 'resource_map') + def test_register_configs_apache(self, resource_map, exists, renderer): + exists.return_value = False + self.os_release.return_value = 'havana' + fake_renderer = MagicMock() + fake_renderer.register = MagicMock() + renderer.return_value = fake_renderer + + resource_map.return_value = self.rsc_map + utils.register_configs() + renderer.assert_called_with( + openstack_release='havana', templates_dir='templates/') + + ex_reg = [ + call('/etc/keystone/keystone.conf', [self.ctxt]), + call('/etc/apache2/sites-available/openstack_https_frontend', [self.ctxt]), + call('/etc/apache2/sites-available/openstack_https_frontend.conf', [self.ctxt]), + ] + self.assertEquals(fake_renderer.register.call_args_list, ex_reg) + + def test_determine_ports(self): + self.test_config.set('admin-port','80') + self.test_config.set('service-port','81') + result = utils.determine_ports() + self.assertEquals(result, ['80', '81']) + + def test_determine_packages(self): + result = utils.determine_packages() + ex = utils.BASE_PACKAGES + ['keystone', 'haproxy', 'apache2'] + self.assertEquals(set(ex), set(result)) From 0a8a57b7b3beb864e6851cddb7040ce8881380de Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 2 Apr 2014 12:23:40 +0100 Subject: [PATCH 48/57] Add tenant_id to identity-service relations --- hooks/keystone_hooks.pyc | Bin 8832 -> 8832 bytes hooks/keystone_utils.py | 7 ++++++- hooks/keystone_utils.pyc | Bin 22820 -> 22961 bytes 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hooks/keystone_hooks.pyc b/hooks/keystone_hooks.pyc index 1f6cf0071265c892b75dddcd71a8f6be6711a746..0408b7866d85296c3a5f61f73d36a5354f1e56ef 100644 GIT binary patch delta 24 fcmZp0ZE$5{{>;nurfDOam(*kdL6*%SQaT&}WL5_> delta 22 dcmZp0ZE$5{{>;n8>adZ`ON!BbbD)$y2LMK<1)Bf> diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index efc97407..e63d10c6 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -604,6 +604,9 @@ def relation_list(rid): def add_service_to_keystone(relation_id=None, remote_unit=None): + import manager + manager = manager.KeystoneManager(endpoint=get_local_endpoint(), + token=get_admin_token()) settings = relation_get(rid=relation_id, unit=remote_unit) # the minimum settings needed per endpoint single = set(['service', 'region', 'public_url', 'admin_url', @@ -724,6 +727,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): # 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 + service_tenant = config('service-tenant') relation_data = { "admin_token": token, "service_host": unit_private_ip(), @@ -732,7 +736,8 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): "auth_port": config("admin-port"), "service_username": service_username, "service_password": service_password, - "service_tenant": config('service-tenant'), + "service_tenant": service_tenant, + "service_tenant_id": manager.resolve_tenant_id(service_tenant), "https_keystone": "False", "ssl_cert": "", "ssl_key": "", diff --git a/hooks/keystone_utils.pyc b/hooks/keystone_utils.pyc index 1c67882b869635b311b5b12d5c8aa170e7da92cc..642ecdac0cab4b7a28a22f523343f272327b321f 100644 GIT binary patch delta 1720 zcmb7EO>7%g5dPk7*6aT`w&TRjPZMaH5DIPii9nb`X~YSdvPCk7hLrN6%C0cjSx}I6 zS*bZ95b6qvGgL^3UWz3Sm3rs}35f$I#Gw)gBqVM~J#m6>cGr%rfCQFz-@KVOGjG3{ z`EGurZugaKJ}6|%51zYi-*|Dd2B5&_9G~4gR`|=aPY2q9r?3yhH{n{)DQFuy4V{6` z;;Myx1%CqG1llQhrfhp>44uPO$hK|M7aLn;Tr%KW@OdbO*9~;v$F&rC_f!k;ZMbPU z*ZU4GmC(YJmCr8IEStx5pap&mI*OFcwk zqfTXF(nljHP2Ex?WhgC2QkIUeRC3VI!L6ZP8=6?nJiI*GGa__?7oq|cu=kD}8I<$N zNI5OEXQ3a80D2g8zbKn_9p0$n7>@F~J|QW>E8v=q-e(3&PnjCQhHVjV>fR(8-3_D3 z4)TK%_|d${-q&K@*!luYkaSz*D=_r#8i_BB0ub*o4iFUyK(R>xqLctcDFKMm0H6}u za|lL45tZG`W&&4oa2&4gWwU$N;J=wbP!d##@qd_yeN*P)b5m;U<*<7zk^+|_MF6Iv zMmY{z;zpSlQG%D@vVU^$D6subh;rX#E3t7{T&h+?HD=p;X0#92{;dz`AB_2RixbVE z!mpsk3b21yu^vyME>dE~Xnb^Ss4m3nXv%aI>kO38w@R$h1H)ejdo>x)AD zPC-RTk%xEQdfVOEez((dog1`-o14DjOorcYzLvYsJyc$mRX#j%;nV57GL@~est`Rk zWm}c{KU_$u)C}VpX87|fU#a8ab?1^bOGPI9)%nd>P~q9-)s$1CrV{>naY@ztM_yev dd9cy^q;!mGN8BB8acceh&2!4SnXxn3zX3ZoL;U~% delta 1657 zcmb7E-)kII6#nka&Q5kWoBfqEyD@DVTXmbpA5V*79;^nRB&nOoT1X77H->It>2B`B zR%RG5;9Ef)#Fv5(3L*-&UGTvdpM4NSqaz#RcYiPDy_a6N?|=AeGz*}}_Z7ZJ?|Og%v_inuL7;F9Cv;#Gv$_F#I0!QcJp>-!a?szzVHSgr)lDFbvM?Dj9NdMW z@0_>^7$J|fQ~LtedJ8aFu^sCt8!C-&y*z}Tw)!I0QZ&Ppv(Tl9I6)LMK%~p#h?OK_ zb-b@7`&rrRXY8G~m6?K`hv7A$LLY&TZe9Y1l+W1+bsj4qm>RojFg!3^h2h04OY6Jm z5awWt&V6kfl?aSGG%d~T{NtHMe-qn3>p-sD!pit zN7-46vP9=B@_7)RN)5w2OchbLI?*wmym$fKnt${(V0n%!-XN87A?4(Dj6oPn%(*#kF5|pTfAfl9@h%jY{Xv|Ov-T9NQNXk*mC0)W+ zYImBge#`C04xdg+K}lF4$N$j}ku=0LR~lmIN|J*-j^4A#)NNsvZJ|64Dv~;B2o&cP zm<7QB0bA#S2#;M>5(|gPrRbuFPU(ivpRB_bMB_sITOFnjCO-SWxSgA1y$t;XT1gw~ z0uiIwj?uM=IG>1ZW9bT7^rfHVhXwmoqOxvv6D=KGg0Azs0)098tR|lZTH$0i+{;Lu zf1M2+T*(CZG4p>WKt#Vuhd*mO{2lKR9li+tv@`~LeuuNHim-O7^QGS>W8-qt|23gr z2G#06=fPi-qPNIo>YxkF+5H(9=siSH%~z(zP0tMv#8=L%gt7CnuxD#U5dY}c%!>-mX-HO&d=rl1au-VQ2+n{ From bf6b84790658acaff8df7526a78a1cf19040e09f Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 2 Apr 2014 12:23:58 +0100 Subject: [PATCH 49/57] Drop pyc files --- hooks/keystone_context.pyc | Bin 3896 -> 0 bytes hooks/keystone_hooks.pyc | Bin 8832 -> 0 bytes hooks/keystone_ssl.pyc | Bin 11026 -> 0 bytes hooks/keystone_utils.pyc | Bin 22961 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 hooks/keystone_context.pyc delete mode 100644 hooks/keystone_hooks.pyc delete mode 100644 hooks/keystone_ssl.pyc delete mode 100644 hooks/keystone_utils.pyc diff --git a/hooks/keystone_context.pyc b/hooks/keystone_context.pyc deleted file mode 100644 index 68739dd0f60d83769e0ff1fc2bb7d61a7dec25f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3896 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 diff --git a/hooks/keystone_ssl.pyc b/hooks/keystone_ssl.pyc deleted file mode 100644 index 41ae310ac56d3bce8f4ec46010c84362c0253e09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11026 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 IH(8qc4`2l=VE_OC diff --git a/hooks/keystone_utils.pyc b/hooks/keystone_utils.pyc deleted file mode 100644 index 642ecdac0cab4b7a28a22f523343f272327b321f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22961 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 From eed04f99f3c78cf75206034d878edc86df96b6e8 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 2 Apr 2014 12:28:40 +0100 Subject: [PATCH 50/57] Deal with upgrades --- hooks/keystone_hooks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index a0015cd2..1f806b82 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -272,6 +272,11 @@ def upgrade_charm(): ' is up to date') time.sleep(10) ensure_initial_admin(config) + # Deal with interface changes for icehouse + for r_id in relation_ids('identity-service'): + for unit in relation_list(r_id): + identity_changed(relation_id=r_id, + remote_unit=unit) CONFIGS.write_all() From fdfa082ed636582d52da0859081a37622e4d6eb3 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 2 Apr 2014 22:26:48 +0100 Subject: [PATCH 51/57] Align templates with sql --- templates/essex/keystone.conf | 2 +- templates/folsom/keystone.conf | 2 +- templates/grizzly/keystone.conf | 6 +----- templates/havana/keystone.conf | 7 ++----- templates/icehouse/keystone.conf | 10 +++------- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/templates/essex/keystone.conf b/templates/essex/keystone.conf index f514d9bb..310a46f3 100644 --- a/templates/essex/keystone.conf +++ b/templates/essex/keystone.conf @@ -31,7 +31,7 @@ driver = keystone.token.backends.sql.Token expiration = 86400 [policy] -driver = keystone.policy.backends.rules.Policy +driver = keystone.policy.backends.sql.Policy [ec2] driver = keystone.contrib.ec2.backends.sql.Ec2 diff --git a/templates/folsom/keystone.conf b/templates/folsom/keystone.conf index 5c09801f..0d9241fd 100644 --- a/templates/folsom/keystone.conf +++ b/templates/folsom/keystone.conf @@ -31,7 +31,7 @@ driver = keystone.token.backends.sql.Token expiration = 86400 [policy] -driver = keystone.policy.backends.rules.Policy +driver = keystone.policy.backends.sql.Policy [ec2] driver = keystone.contrib.ec2.backends.sql.Ec2 diff --git a/templates/grizzly/keystone.conf b/templates/grizzly/keystone.conf index 968c7d8a..3a31be58 100644 --- a/templates/grizzly/keystone.conf +++ b/templates/grizzly/keystone.conf @@ -31,18 +31,14 @@ driver = keystone.catalog.backends.sql.Catalog [token] driver = keystone.token.backends.sql.Token -expiration = 86400 [policy] -driver = keystone.policy.backends.rules.Policy +driver = keystone.policy.backends.sql.Policy [ec2] driver = keystone.contrib.ec2.backends.sql.Ec2 [signing] -token_format = UUID -key_size = 2048 -valid_days = 3650 [auth] methods = password,token diff --git a/templates/havana/keystone.conf b/templates/havana/keystone.conf index fade7ed0..a7661ade 100644 --- a/templates/havana/keystone.conf +++ b/templates/havana/keystone.conf @@ -43,19 +43,16 @@ expiration = 86400 [cache] [policy] -driver = keystone.policy.backends.rules.Policy +driver = keystone.policy.backends.sql.Policy [ec2] -driver = keystone.contrib.ec2.backends.kvs.Ec2 +driver = keystone.contrib.ec2.backends.sql.Ec2 [assignment] [oauth1] [signing] -token_format = UUID -key_size = 2048 -valid_days = 3650 [auth] methods = external,password,token,oauth1 diff --git a/templates/icehouse/keystone.conf b/templates/icehouse/keystone.conf index 45eb5951..fc39d342 100644 --- a/templates/icehouse/keystone.conf +++ b/templates/icehouse/keystone.conf @@ -1,4 +1,4 @@ -# havana +# icehouse ############################################################################### # [ WARNING ] # Configuration file maintained by Juju. Local changes may be overwritten. @@ -38,24 +38,20 @@ driver = keystone.catalog.backends.sql.Catalog [token] driver = keystone.token.backends.sql.Token -expiration = 86400 [cache] [policy] -driver = keystone.policy.backends.rules.Policy +driver = keystone.policy.backends.sql.Policy [ec2] -driver = keystone.contrib.ec2.backends.kvs.Ec2 +driver = keystone.contrib.ec2.backends.sql.Ec2 [assignment] [oauth1] [signing] -token_format = UUID -key_size = 2048 -valid_days = 3650 [auth] methods = external,password,token,oauth1 From 754d3082c4695761c695a1783283d3b9fe54b56b Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Thu, 3 Apr 2014 14:21:01 +0200 Subject: [PATCH 52/57] added more utils testing --- unit_tests/test_keystone_utils.py | 173 ++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index d695cc6c..d9277343 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -10,12 +10,41 @@ with patch('charmhelpers.core.hookenv.config') as config: import keystone_utils as utils import keystone_context +import keystone_hooks as hooks from charmhelpers.contrib.openstack import context TO_PATCH = [ + 'api_port', 'config', + 'create_user', 'os_release', 'log', + 'get_ca', + 'create_role', + 'create_service_entry', + 'create_endpoint_template', + 'get_admin_token', + 'get_requested_roles', + 'get_service_password', + 'get_os_codename_install_source', + 'grant_role', + 'configure_installation_source', + 'eligible_leader', + 'https', + 'is_clustered', + 'service_stop', + 'service_start', + 'relation_get', + 'relation_set', + 'https', + 'unit_private_ip', + # generic + 'apt_update', + 'apt_upgrade', + 'apt_install', + 'subprocess', + 'time', + 'pwgen', ] class TestKeystoneUtils(CharmTestCase): @@ -72,3 +101,147 @@ class TestKeystoneUtils(CharmTestCase): result = utils.determine_packages() ex = utils.BASE_PACKAGES + ['keystone', 'haproxy', 'apache2'] self.assertEquals(set(ex), set(result)) + + @patch.object(hooks, 'CONFIGS') + @patch.object(utils, 'determine_packages') + @patch.object(utils, 'migrate_database') + def test_openstack_upgrade_leader(self, migrate_database, determine_packages, configs): + self.test_config.set('openstack-origin', 'precise') + determine_packages.return_value = [] + self.eligible_leader.return_value = True + + utils.do_openstack_upgrade(configs) + + self.get_os_codename_install_source.assert_called_with('precise') + self.configure_installation_source.assert_called_with('precise') + self.apt_update.assert_called() + + dpkg_opts = [ + '--option', 'Dpkg::Options::=--force-confnew', + '--option', 'Dpkg::Options::=--force-confdef', + ] + self.apt_upgrade.assert_called_with(options=dpkg_opts, fatal=True, dist=True) + self.apt_install.assert_called_with(packages=[], options=dpkg_opts, fatal=True) + + self.assertTrue(configs.set_release.called) + self.assertTrue(configs.write_all.called) + + migrate_database.assert_called() + + def test_migrate_database(self): + utils.migrate_database() + + self.service_stop.assert_called_with('keystone') + cmd = ['sudo', '-u', 'keystone', 'keystone-manage', 'db_sync'] + self.subprocess.check_output.assert_called_with(cmd) + self.service_start.assert_called_wkth('keystone') + + @patch.object(utils, 'b64encode') + def test_add_service_to_keystone_clustered_https_none_values(self, b64encode): + relation_id = 'identity-service:0' + remote_unit = 'unit/0' + self.is_clustered.return_value = True + self.https.return_value = True + self.test_config.set('https-service-endpoints', 'True') + self.test_config.set('vip', '10.10.10.10') + self.test_config.set('admin-port', 80) + self.test_config.set('service-port', 81) + b64encode.return_value = 'certificate' + self.get_requested_roles.return_value = ['role1',] + + self.relation_get.return_value = {'service': 'keystone', + 'region': 'RegionOne', + 'public_url': 'None', + 'admin_url': '10.0.0.2', + 'internal_url': '192.168.1.2'} + + utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + self.is_clustered.assert_called() + self.https.assert_called() + self.create_role.assert_called() + + relation_data = {'auth_host': '10.10.10.10', + 'service_host': '10.10.10.10', + 'auth_protocol': 'https', + 'service_protocol': 'https', + 'auth_port': 80, + 'service_port': 81, + 'https_keystone': 'True', + 'ca_cert': 'certificate'} + self.relation_set.assert_called_with(relation_id=relation_id, **relation_data) + + @patch.object(utils, 'ensure_valid_service') + @patch.object(utils, 'add_endpoint') + def test_add_service_to_keystone_no_clustered_no_https_complete_values(self, add_endpoint, ensure_valid_service): + relation_id = 'identity-service:0' + remote_unit = 'unit/0' + self.get_admin_token.return_value = 'token' + self.get_service_password.return_value = 'password' + self.test_config.set('service-tenant', 'tenant') + self.test_config.set('admin-role', 'admin') + self.get_requested_roles.return_value = ['role1',] + self.unit_private_ip.return_value = '10.0.0.3' + self.test_config.set('admin-port', 80) + self.test_config.set('service-port', 81) + self.is_clustered.return_value = False + self.https.return_value = False + self.test_config.set('https-service-endpoints', 'False') + + self.relation_get.return_value = {'service': 'keystone', + 'region': 'RegionOne', + 'public_url': '10.0.0.1', + 'admin_url': '10.0.0.2', + 'internal_url': '192.168.1.2'} + + utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + ensure_valid_service.assert_called_with('keystone') + add_endpoint.assert_called_with(region='RegionOne', service='keystone', + publicurl='10.0.0.1', adminurl='10.0.0.2', + internalurl='192.168.1.2') + self.get_admin_token.assert_called() + self.get_service_password.assert_called_with('keystone') + self.create_user.assert_called_with('keystone', 'password', 'tenant') + self.grant_role.assert_called_with('keystone', 'admin', 'tenant') + self.create_role.assert_called_with('role1', 'keystone', 'tenant') + + self.is_clustered.assert_called() + relation_data = {'admin_token': 'token', 'service_port':81, + 'auth_port':80, 'service_username':'keystone', + 'service_password': 'password', 'service_tenant': 'tenant', + 'https_keystone': 'False', 'ssl_cert': '', 'ssl_key': '', 'ca_cert': '', + 'auth_host':'10.0.0.3', 'service_host': '10.0.0.3', + 'auth_protocol': 'http', 'service_protocol': 'http'} + self.relation_set.assert_called_with(relation_id=relation_id, **relation_data) + + @patch.object(utils, 'ensure_valid_service') + @patch.object(utils, 'add_endpoint') + def test_add_service_to_keystone_nosubset(self, add_endpoint, ensure_valid_service): + relation_id = 'identity-service:0' + remote_unit = 'unit/0' + + self.relation_get.return_value = {'ec2_service': 'nova', + 'ec2_region': 'RegionOne', + 'ec2_public_url': '10.0.0.1', + 'ec2_admin_url': '10.0.0.2', + 'ec2_internal_url': '192.168.1.2'} + + utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + ensure_valid_service.assert_called_with('nova') + add_endpoint.assert_called_with(region='RegionOne', service='nova', + publicurl='10.0.0.1', adminurl='10.0.0.2', + internalurl='192.168.1.2') + + def test_ensure_valid_service_incorrect(self): + utils.ensure_valid_service('fakeservice') + self.log.assert_called_with("Invalid service requested: 'fakeservice'") + self.relation_set.assert_called_with(admin_token=-1) + + def test_add_endpoint(self): + publicurl = '10.0.0.1' + adminurl = '10.0.0.2' + internalurl = '10.0.0.3' + utils.add_endpoint('RegionOne', 'nova', publicurl, adminurl, internalurl) + self.create_service_entry.assert_called_with('nova', 'compute', 'Nova Compute Service') + self.create_endpoint_template.asssert_called_with(region='RegionOne', service='nova', + publicurl=publicurl, adminurl=adminurl, + internalurl=internalurl) From ff1e28adeb43421e5e6c7d06293a4d89baf3fdec Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Fri, 4 Apr 2014 10:18:04 +0200 Subject: [PATCH 53/57] mocking extra calls --- unit_tests/test_keystone_hooks.py | 9 +++++---- unit_tests/test_keystone_utils.py | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 50f3dc28..6ff875f3 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -26,6 +26,7 @@ TO_PATCH = [ 'config', 'is_relation_made', 'log', + 'filter_installed_packages', 'relation_ids', 'relation_list', 'relation_set', @@ -342,9 +343,9 @@ class KeystoneRelationTests(CharmTestCase): @patch.object(unison, 'ssh_authorized_peers') def test_upgrade_charm_leader(self, ssh_authorized_peers): self.eligible_leader.return_value = True + self.filter_installed_packages.return_value = [] hooks.upgrade_charm() - self.apt_install.assert_called_with(['haproxy', 'unison', 'uuid', 'python-mysqldb', - 'pwgen', 'keystone', 'python-psycopg2']) + self.apt_install.assert_called() ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', peer_interface='cluster', ensure_local_user=True) self.synchronize_ca.assert_called() @@ -355,9 +356,9 @@ class KeystoneRelationTests(CharmTestCase): @patch.object(unison, 'ssh_authorized_peers') def test_upgrade_charm_not_leader(self, ssh_authorized_peers): self.eligible_leader.return_value = False + self.filter_installed_packages.return_value = [] hooks.upgrade_charm() - self.apt_install.assert_called_with(['haproxy', 'unison', 'uuid', 'python-mysqldb', - 'pwgen', 'keystone', 'python-psycopg2']) + self.apt_install.assert_called() ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', peer_interface='cluster', ensure_local_user=True) self.synchronize_ca.assert_called() diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index d9277343..c97241b5 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -24,6 +24,7 @@ TO_PATCH = [ 'create_service_entry', 'create_endpoint_template', 'get_admin_token', + 'get_local_endpoint', 'get_requested_roles', 'get_service_password', 'get_os_codename_install_source', From 9166631f6bcbc92434ba0381bba38cf7e8bd7d5d Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Fri, 4 Apr 2014 12:11:10 +0200 Subject: [PATCH 54/57] mocking keystone manager --- unit_tests/test_keystone_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index c97241b5..4a60a215 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -11,6 +11,7 @@ with patch('charmhelpers.core.hookenv.config') as config: import keystone_context import keystone_hooks as hooks +import manager from charmhelpers.contrib.openstack import context TO_PATCH = [ @@ -173,7 +174,8 @@ class TestKeystoneUtils(CharmTestCase): @patch.object(utils, 'ensure_valid_service') @patch.object(utils, 'add_endpoint') - def test_add_service_to_keystone_no_clustered_no_https_complete_values(self, add_endpoint, ensure_valid_service): + @patch.object(manager, 'KeystoneManager') + def test_add_service_to_keystone_no_clustered_no_https_complete_values(self, manager, add_endpoint, ensure_valid_service): relation_id = 'identity-service:0' remote_unit = 'unit/0' self.get_admin_token.return_value = 'token' @@ -187,6 +189,7 @@ class TestKeystoneUtils(CharmTestCase): self.is_clustered.return_value = False self.https.return_value = False self.test_config.set('https-service-endpoints', 'False') + manager.resolve_tenant_id.return_value = 'tenant_id' self.relation_get.return_value = {'service': 'keystone', 'region': 'RegionOne', From 31c33da34586028499af358ce6181bab04969305 Mon Sep 17 00:00:00 2001 From: "yolanda.robla@canonical.com" <> Date: Fri, 4 Apr 2014 14:25:37 +0200 Subject: [PATCH 55/57] changing assert_called --- unit_tests/test_keystone_hooks.py | 26 +++++++++++++------------- unit_tests/test_keystone_utils.py | 15 +++++++-------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 6ff875f3..245d105d 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -156,7 +156,7 @@ class KeystoneRelationTests(CharmTestCase): self.assertEquals([call('/etc/keystone/keystone.conf')], configs.write.call_args_list) self.migrate_database.assert_called_with() - self.ensure_initial_admin.assert_called() + self.assertTrue(self.ensure_initial_admin.called) identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') @patch.object(hooks, 'CONFIGS') @@ -169,7 +169,7 @@ class KeystoneRelationTests(CharmTestCase): self.assertEquals([call('/etc/keystone/keystone.conf')], configs.write.call_args_list) self.migrate_database.assert_called_with() - self.ensure_initial_admin.assert_called() + self.assertTrue(self.ensure_initial_admin.called) identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') @patch.object(unison, 'ensure_user') @@ -192,7 +192,7 @@ class KeystoneRelationTests(CharmTestCase): self.assertTrue(configs.write_all.called) self.migrate_database.assert_called_with() - self.ensure_initial_admin.assert_called() + self.assertTrue(self.ensure_initial_admin.called) self.log.assert_called_with('Firing identity_changed hook for all related services.') identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') @@ -232,14 +232,14 @@ class KeystoneRelationTests(CharmTestCase): ensure_user.assert_called_with(user=self.ssh_user, group='keystone') get_homedir.assert_called_with(self.ssh_user) - self.do_openstack_upgrade.assert_called() + self.assertTrue(self.do_openstack_upgrade.called) self.save_script_rc.assert_called_with() configure_https.assert_called_with() self.assertTrue(configs.write_all.called) self.migrate_database.assert_called_with() - self.ensure_initial_admin.assert_called() + self.assertTrue(self.ensure_initial_admin.called) self.log.assert_called_with('Firing identity_changed hook for all related services.') identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') @@ -247,7 +247,7 @@ class KeystoneRelationTests(CharmTestCase): self.eligible_leader.return_value = True hooks.identity_changed(relation_id='identity-service:0', remote_unit='unit/0') self.add_service_to_keystone.assert_called_with('identity-service:0', 'unit/0') - self.synchronize_ca.assert_called() + self.assertTrue(self.synchronize_ca.called) def test_identity_changed_no_leader(self): self.eligible_leader.return_value = False @@ -268,7 +268,7 @@ class KeystoneRelationTests(CharmTestCase): self.peer_echo.assert_called_with(includes=['_passwd']) ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', peer_interface='cluster', ensure_local_user=True) - self.synchronize_ca.assert_called() + self.assertTrue(self.synchronize_ca.called) self.assertTrue(configs.write_all.called) def test_ha_joined(self): @@ -280,7 +280,7 @@ class KeystoneRelationTests(CharmTestCase): 'vip_cidr': '24' } hooks.ha_joined() - self.get_hacluster_config.assert_called() + self.assertTrue(self.get_hacluster_config.called) args = { 'corosync_bindiface': 'em0', 'corosync_mcastport': '8080', @@ -345,22 +345,22 @@ class KeystoneRelationTests(CharmTestCase): self.eligible_leader.return_value = True self.filter_installed_packages.return_value = [] hooks.upgrade_charm() - self.apt_install.assert_called() + self.assertTrue(self.apt_install.called) ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', peer_interface='cluster', ensure_local_user=True) - self.synchronize_ca.assert_called() + self.assertTrue(self.synchronize_ca.called) self.log.assert_called_with('Cluster leader - ensuring endpoint configuration' ' is up to date') - self.ensure_initial_admin.assert_called() + self.assertTrue(self.ensure_initial_admin.called) @patch.object(unison, 'ssh_authorized_peers') def test_upgrade_charm_not_leader(self, ssh_authorized_peers): self.eligible_leader.return_value = False self.filter_installed_packages.return_value = [] hooks.upgrade_charm() - self.apt_install.assert_called() + self.assertTrue(self.apt_install.called) ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', peer_interface='cluster', ensure_local_user=True) - self.synchronize_ca.assert_called() + self.assertTrue(self.synchronize_ca.called) self.assertFalse(self.log.called) self.assertFalse(self.ensure_initial_admin.called) diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index 86e02d6a..42af3356 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -116,7 +116,7 @@ class TestKeystoneUtils(CharmTestCase): self.get_os_codename_install_source.assert_called_with('precise') self.configure_installation_source.assert_called_with('precise') - self.apt_update.assert_called() + self.assertTrue(self.apt_update.called) dpkg_opts = [ '--option', 'Dpkg::Options::=--force-confnew', @@ -127,8 +127,7 @@ class TestKeystoneUtils(CharmTestCase): self.assertTrue(configs.set_release.called) self.assertTrue(configs.write_all.called) - - migrate_database.assert_called() + self.assertTrue(migrate_database.called) def test_migrate_database(self): utils.migrate_database() @@ -158,9 +157,9 @@ class TestKeystoneUtils(CharmTestCase): 'internal_url': '192.168.1.2'} utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) - self.is_clustered.assert_called() - self.https.assert_called() - self.create_role.assert_called() + self.assertTrue(self.is_clustered.called) + self.assertTrue(self.https.called) + self.assertTrue(self.create_role.called) relation_data = {'auth_host': '10.10.10.10', 'service_host': '10.10.10.10', @@ -206,13 +205,13 @@ class TestKeystoneUtils(CharmTestCase): add_endpoint.assert_called_with(region='RegionOne', service='keystone', publicurl='10.0.0.1', adminurl='10.0.0.2', internalurl='192.168.1.2') - self.get_admin_token.assert_called() + self.assertTrue(self.get_admin_token.called) self.get_service_password.assert_called_with('keystone') self.create_user.assert_called_with('keystone', 'password', 'tenant') self.grant_role.assert_called_with('keystone', 'admin', 'tenant') self.create_role.assert_called_with('role1', 'keystone', 'tenant') - self.is_clustered.assert_called() + self.assertTrue(self.is_clustered.called) relation_data = {'admin_token': 'token', 'service_port':81, 'auth_port':80, 'service_username':'keystone', 'service_password': 'password', 'service_tenant': 'tenant', From 7cc221cd08466897084dab2ee85b71a5ecc1c38c Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 10 Apr 2014 16:51:03 +0100 Subject: [PATCH 56/57] Final sync --- hooks/charmhelpers/contrib/openstack/context.py | 8 ++++++-- hooks/charmhelpers/contrib/openstack/utils.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index e4ad6c2a..e92b1cc8 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -148,7 +148,7 @@ class SharedDBContext(OSContextGenerator): 'database': self.database, 'database_user': self.user, 'database_password': rdata.get(password_setting), - 'database_type': 'mysql', + 'database_type': 'mysql' } if context_complete(ctxt): db_ssl(rdata, ctxt, self.ssl_dir) @@ -181,7 +181,7 @@ class PostgresqlDBContext(OSContextGenerator): 'database_type': 'postgresql', } if context_complete(ctxt): - return ctxt + return ctxt return {} @@ -234,6 +234,10 @@ class IdentityServiceContext(OSContextGenerator): rdata.get('auth_protocol') or 'http', } if context_complete(ctxt): + # NOTE(jamespage) this is required for >= icehouse + # so a missing value just indicates keystone needs + # upgrading + ctxt['admin_tenant_id'] = rdata.get('service_tenant_id') return ctxt return {} diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 3261adf4..ac261fd7 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -65,6 +65,7 @@ SWIFT_CODENAMES = OrderedDict([ ('1.10.0', 'havana'), ('1.9.1', 'havana'), ('1.9.0', 'havana'), + ('1.13.1', 'icehouse'), ('1.13.0', 'icehouse'), ('1.12.0', 'icehouse'), ('1.11.0', 'icehouse'), From a3f42101cc19a73b77ccaa5cfff850e676704251 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 10 Apr 2014 17:00:28 +0100 Subject: [PATCH 57/57] Final tidy --- Makefile | 3 +- hooks/keystone_hooks.py | 4 +- hooks/lib/__init__.py | 0 hooks/lib/apache_utils.py | 194 ---------------- hooks/lib/cluster_utils.py | 128 ----------- hooks/lib/haproxy_utils.py | 53 ----- hooks/lib/openstack_common.py | 239 -------------------- hooks/lib/unison.py | 217 ------------------ hooks/lib/utils.py | 319 --------------------------- hooks/start | 1 + hooks/stop | 1 + unit_tests/test_keystone_contexts.py | 33 +-- unit_tests/test_keystone_hooks.py | 86 +++++--- unit_tests/test_keystone_utils.py | 106 ++++++--- 14 files changed, 151 insertions(+), 1233 deletions(-) delete mode 100644 hooks/lib/__init__.py delete mode 100644 hooks/lib/apache_utils.py delete mode 100644 hooks/lib/cluster_utils.py delete mode 100644 hooks/lib/haproxy_utils.py delete mode 100644 hooks/lib/openstack_common.py delete mode 100755 hooks/lib/unison.py delete mode 100644 hooks/lib/utils.py create mode 120000 hooks/start create mode 120000 hooks/stop diff --git a/Makefile b/Makefile index d91e4b49..3c053acd 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,7 @@ PYTHON := /usr/bin/env python lint: - @flake8 --exclude hooks/charmhelpers hooks - @flake8 --exclude hooks/charmhelpers unit_tests + @flake8 --exclude hooks/charmhelpers hooks unit_tests @charm proof test: diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 1f806b82..05c4bcda 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -120,8 +120,8 @@ def db_joined(): def pgsql_db_joined(): if is_relation_made('shared-db'): # raise error - e = ('Attempting to associate a postgresql database when there is already ' - 'associated a mysql one') + e = ('Attempting to associate a postgresql database when there' + ' is already associated a mysql one') log(e, level=ERROR) raise Exception(e) diff --git a/hooks/lib/__init__.py b/hooks/lib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/hooks/lib/apache_utils.py b/hooks/lib/apache_utils.py deleted file mode 100644 index 3890582c..00000000 --- a/hooks/lib/apache_utils.py +++ /dev/null @@ -1,194 +0,0 @@ -# -# Copyright 2012 Canonical Ltd. -# -# This file is sourced from lp:openstack-charm-helpers -# -# Authors: -# James Page -# Adam Gandelman -# - -from lib.utils import ( - relation_ids, - relation_list, - relation_get, - render_template, - juju_log, - config_get, - install, - get_host_ip, - restart) -from lib.cluster_utils import https - -import os -import subprocess -from base64 import b64decode - -APACHE_SITE_DIR = "/etc/apache2/sites-available" -SITE_TEMPLATE = "apache2_site.tmpl" -RELOAD_CHECK = "To activate the new configuration" - - -def get_cert(): - cert = config_get('ssl_cert') - key = config_get('ssl_key') - if not (cert and key): - juju_log('INFO', - "Inspecting identity-service relations for SSL certificate.") - cert = key = None - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - if not cert: - cert = relation_get('ssl_cert', - rid=r_id, unit=unit) - if not key: - key = relation_get('ssl_key', - rid=r_id, unit=unit) - return (cert, key) - - -def get_ca_cert(): - ca_cert = None - juju_log('INFO', - "Inspecting identity-service relations for CA SSL certificate.") - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - if not ca_cert: - ca_cert = relation_get('ca_cert', - rid=r_id, unit=unit) - return ca_cert - - -def install_ca_cert(ca_cert): - if ca_cert: - with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt', - 'w') as crt: - crt.write(ca_cert) - subprocess.check_call(['update-ca-certificates', '--fresh']) - - -def enable_https(port_maps, namespace, cert, key, ca_cert=None): - ''' - For a given number of port mappings, configures apache2 - HTTPs local reverse proxying using certficates and keys provided in - either configuration data (preferred) or relation data. Assumes ports - are not in use (calling charm should ensure that). - - port_maps: dict: external to internal port mappings - namespace: str: name of charm - ''' - def _write_if_changed(path, new_content): - content = None - if os.path.exists(path): - with open(path, 'r') as f: - content = f.read().strip() - if content != new_content: - with open(path, 'w') as f: - f.write(new_content) - return True - else: - return False - - juju_log('INFO', "Enabling HTTPS for port mappings: {}".format(port_maps)) - http_restart = False - - if cert: - cert = b64decode(cert) - if key: - key = b64decode(key) - if ca_cert: - ca_cert = b64decode(ca_cert) - - if not cert and not key: - juju_log('ERROR', - "Expected but could not find SSL certificate data, not " - "configuring HTTPS!") - return False - - install('apache2') - if RELOAD_CHECK in subprocess.check_output(['a2enmod', 'ssl', - 'proxy', 'proxy_http']): - http_restart = True - - ssl_dir = os.path.join('/etc/apache2/ssl', namespace) - if not os.path.exists(ssl_dir): - os.makedirs(ssl_dir) - - if (_write_if_changed(os.path.join(ssl_dir, 'cert'), cert)): - http_restart = True - if (_write_if_changed(os.path.join(ssl_dir, 'key'), key)): - http_restart = True - os.chmod(os.path.join(ssl_dir, 'key'), 0600) - - install_ca_cert(ca_cert) - - sites_dir = '/etc/apache2/sites-available' - for ext_port, int_port in port_maps.items(): - juju_log('INFO', - 'Creating apache2 reverse proxy vhost' - ' for {}:{}'.format(ext_port, - int_port)) - site = "{}_{}".format(namespace, ext_port) - site_path = os.path.join(sites_dir, site) - with open(site_path, 'w') as fsite: - context = { - "ext": ext_port, - "int": int_port, - "namespace": namespace, - "private_address": get_host_ip()} - fsite.write(render_template(SITE_TEMPLATE, - context)) - - if RELOAD_CHECK in subprocess.check_output(['a2ensite', site]): - http_restart = True - - if http_restart: - restart('apache2') - - return True - - -def disable_https(port_maps, namespace): - ''' - Ensure HTTPS reverse proxying is disables for given port mappings - - port_maps: dict: of ext -> int port mappings - namespace: str: name of chamr - ''' - juju_log('INFO', 'Ensuring HTTPS disabled for {}'.format(port_maps)) - - if (not os.path.exists('/etc/apache2') or - not os.path.exists(os.path.join('/etc/apache2/ssl', namespace))): - return - - http_restart = False - for ext_port in port_maps.keys(): - if os.path.exists(os.path.join(APACHE_SITE_DIR, - "{}_{}".format(namespace, - ext_port))): - juju_log('INFO', - "Disabling HTTPS reverse proxy" - " for {} {}.".format(namespace, - ext_port)) - if (RELOAD_CHECK in - subprocess.check_output(['a2dissite', - '{}_{}'.format(namespace, - ext_port)])): - http_restart = True - - if http_restart: - restart(['apache2']) - - -def setup_https(port_maps, namespace, cert, key, ca_cert=None): - ''' - Ensures HTTPS is either enabled or disabled for given port - mapping. - - port_maps: dict: of ext -> int port mappings - namespace: str: name of charm - ''' - if not https: - disable_https(port_maps, namespace) - else: - enable_https(port_maps, namespace, cert, key, ca_cert) diff --git a/hooks/lib/cluster_utils.py b/hooks/lib/cluster_utils.py deleted file mode 100644 index 1405d6fb..00000000 --- a/hooks/lib/cluster_utils.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# Copyright 2012 Canonical Ltd. -# -# This file is sourced from lp:openstack-charm-helpers -# -# Authors: -# James Page -# Adam Gandelman -# - -from lib.utils import ( - juju_log, - relation_ids, - relation_list, - relation_get, - get_unit_hostname, - config_get) -import subprocess -import os - - -def is_clustered(): - for r_id in (relation_ids('ha') or []): - for unit in (relation_list(r_id) or []): - clustered = relation_get('clustered', - rid=r_id, - unit=unit) - if clustered: - return True - return False - - -def is_leader(resource): - cmd = [ - "crm", "resource", - "show", resource] - try: - status = subprocess.check_output(cmd) - except subprocess.CalledProcessError: - return False - else: - if get_unit_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 = int(os.getenv('JUJU_UNIT_NAME').split('/')[1]) - for peer in peers: - remote_unit_no = int(peer.split('/')[1]) - if remote_unit_no < local_unit_no: - return False - return True - - -def eligible_leader(resource): - if is_clustered(): - if not is_leader(resource): - juju_log('INFO', 'Deferring action to CRM leader.') - return False - else: - peers = peer_units() - if peers and not oldest_peer(peers): - juju_log('INFO', 'Deferring action to oldest service unit.') - return False - return True - - -def https(): - ''' - Determines whether enough data has been provided in configuration - or relation data to configure HTTPS - . - returns: boolean - ''' - if config_get('use-https') == "yes": - return True - if config_get('ssl_cert') and config_get('ssl_key'): - return True - for r_id in relation_ids('identity-service'): - for unit in relation_list(r_id): - if (relation_get('https_keystone', rid=r_id, unit=unit) and - relation_get('ssl_cert', rid=r_id, unit=unit) and - relation_get('ssl_key', rid=r_id, unit=unit) and - relation_get('ca_cert', rid=r_id, unit=unit)): - return True - return False - - -def determine_api_port(public_port): - ''' - Determine correct API server listening port based on - existence of HTTPS reverse proxy and/or haproxy. - - public_port: int: standard public port for given service - - returns: int: the correct listening port for the API service - ''' - i = 0 - if len(peer_units()) > 0 or is_clustered(): - i += 1 - if https(): - i += 1 - return public_port - (i * 10) - - -def determine_haproxy_port(public_port): - ''' - Description: Determine correct proxy listening port based on public IP + - existence of HTTPS reverse proxy. - - public_port: int: standard public port for given service - - returns: int: the correct listening port for the HAProxy service - ''' - i = 0 - if https(): - i += 1 - return public_port - (i * 10) diff --git a/hooks/lib/haproxy_utils.py b/hooks/lib/haproxy_utils.py deleted file mode 100644 index f14a20c1..00000000 --- a/hooks/lib/haproxy_utils.py +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright 2012 Canonical Ltd. -# -# This file is sourced from lp:openstack-charm-helpers -# -# Authors: -# James Page -# Adam Gandelman -# - -from lib.utils import ( - relation_ids, - relation_list, - relation_get, - unit_get, - reload, - render_template) -import os - -HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' -HAPROXY_DEFAULT = '/etc/default/haproxy' - - -def configure_haproxy(service_ports): - ''' - Configure HAProxy based on the current peers in the service - cluster using the provided port map: - - "swift": [ 8080, 8070 ] - - HAproxy will also be reloaded/started if required - - service_ports: dict: dict of lists of [ frontend, backend ] - ''' - cluster_hosts = {} - cluster_hosts[os.getenv('JUJU_UNIT_NAME').replace('/', '-')] = \ - unit_get('private-address') - for r_id in relation_ids('cluster'): - for unit in relation_list(r_id): - cluster_hosts[unit.replace('/', '-')] = \ - relation_get(attribute='private-address', - rid=r_id, - unit=unit) - context = { - 'units': cluster_hosts, - 'service_ports': service_ports} - with open(HAPROXY_CONF, 'w') as f: - f.write(render_template(os.path.basename(HAPROXY_CONF), - context)) - with open(HAPROXY_DEFAULT, 'w') as f: - f.write('ENABLED=1') - - reload('haproxy') diff --git a/hooks/lib/openstack_common.py b/hooks/lib/openstack_common.py deleted file mode 100644 index a990dc2a..00000000 --- a/hooks/lib/openstack_common.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/python - -# Common python helper functions used for OpenStack charms. - -import apt_pkg as apt -import subprocess -import os - -CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" -CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' - -ubuntu_openstack_release = { - 'oneiric': 'diablo', - 'precise': 'essex', - 'quantal': 'folsom', - 'raring': 'grizzly', - 'saucy': 'havana', - 'trusty': 'icehouse', -} - - -openstack_codenames = { - '2011.2': 'diablo', - '2012.1': 'essex', - '2012.2': 'folsom', - '2013.1': 'grizzly', - '2013.2': 'havana', - '2014.1': 'icehouse', -} - -# The ugly duckling -swift_codenames = { - '1.4.3': 'diablo', - '1.4.8': 'essex', - '1.7.4': 'folsom', - '1.7.6': 'grizzly', - '1.7.7': 'grizzly', - '1.8.0': 'grizzly', -} - - -def juju_log(msg): - subprocess.check_call(['juju-log', msg]) - - -def error_out(msg): - juju_log("FATAL ERROR: %s" % msg) - exit(1) - - -def lsb_release(): - '''Return /etc/lsb-release in a dict''' - lsb = open('/etc/lsb-release', 'r') - d = {} - for l in lsb: - k, v = l.split('=') - d[k.strip()] = v.strip() - return d - - -def get_os_codename_install_source(src): - '''Derive OpenStack release codename from a given installation source.''' - ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] - - rel = '' - if src == 'distro': - try: - rel = ubuntu_openstack_release[ubuntu_rel] - except KeyError: - e = 'Code not derive openstack release for '\ - 'this Ubuntu release: %s' % rel - error_out(e) - return rel - - if src.startswith('cloud:'): - ca_rel = src.split(':')[1] - ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0] - return ca_rel - - # Best guess match based on deb string provided - if src.startswith('deb') or src.startswith('ppa'): - for k, v in openstack_codenames.iteritems(): - if v in src: - return v - - -def get_os_codename_version(vers): - '''Determine OpenStack codename from version number.''' - try: - return openstack_codenames[vers] - except KeyError: - e = 'Could not determine OpenStack codename for version %s' % vers - error_out(e) - - -def get_os_version_codename(codename): - '''Determine OpenStack version number from codename.''' - for k, v in openstack_codenames.iteritems(): - if v == codename: - return k - e = 'Code not derive OpenStack version for '\ - 'codename: %s' % codename - error_out(e) - - -def get_os_codename_package(pkg): - '''Derive OpenStack release codename from an installed package.''' - apt.init() - cache = apt.Cache() - try: - pkg = cache[pkg] - except: - e = 'Could not determine version of installed package: %s' % pkg - error_out(e) - - vers = apt.upstream_version(pkg.current_ver.ver_str) - - try: - if 'swift' in pkg.name: - vers = vers[:5] - return swift_codenames[vers] - else: - vers = vers[:6] - return openstack_codenames[vers] - except KeyError: - e = 'Could not determine OpenStack codename for version %s' % vers - error_out(e) - - -def get_os_version_package(pkg): - '''Derive OpenStack version number from an installed package.''' - codename = get_os_codename_package(pkg) - - if 'swift' in pkg: - vers_map = swift_codenames - else: - vers_map = openstack_codenames - - for version, cname in vers_map.iteritems(): - if cname == codename: - return version - e = "Could not determine OpenStack version for package: %s" % pkg - error_out(e) - - -def configure_installation_source(rel): - '''Configure apt installation source.''' - - def _import_key(keyid): - cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \ - "--recv-keys %s" % keyid - try: - subprocess.check_call(cmd.split(' ')) - except subprocess.CalledProcessError: - error_out("Error importing repo key %s" % keyid) - - if rel == 'distro': - return - elif rel[:4] == "ppa:": - src = rel - subprocess.check_call(["add-apt-repository", "-y", src]) - elif rel[:3] == "deb": - l = len(rel.split('|')) - if l == 2: - src, key = rel.split('|') - juju_log("Importing PPA key from keyserver for %s" % src) - _import_key(key) - elif l == 1: - src = rel - else: - error_out("Invalid openstack-release: %s" % rel) - - with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f: - f.write(src) - elif rel[:6] == 'cloud:': - ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] - rel = rel.split(':')[1] - u_rel = rel.split('-')[0] - ca_rel = rel.split('-')[1] - - if u_rel != ubuntu_rel: - e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\ - 'version (%s)' % (ca_rel, ubuntu_rel) - error_out(e) - - if 'staging' in ca_rel: - # staging is just a regular PPA. - os_rel = ca_rel.split('/')[0] - ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel - cmd = 'add-apt-repository -y %s' % ppa - subprocess.check_call(cmd.split(' ')) - return - - # map charm config options to actual archive pockets. - pockets = { - 'folsom': 'precise-updates/folsom', - 'folsom/updates': 'precise-updates/folsom', - 'folsom/proposed': 'precise-proposed/folsom', - 'grizzly': 'precise-updates/grizzly', - 'grizzly/updates': 'precise-updates/grizzly', - 'grizzly/proposed': 'precise-proposed/grizzly', - 'havana': 'precise-updates/havana', - 'havana/updates': 'precise-updates/havana', - 'havana/proposed': 'precise-proposed/havana', - 'icehouse': 'precise-updates/icehouse', - 'icehouse/updates': 'precise-updates/icehouse', - 'icehouse/proposed': 'precise-proposed/icehouse', - } - - try: - pocket = pockets[ca_rel] - except KeyError: - e = 'Invalid Cloud Archive release specified: %s' % rel - error_out(e) - - src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket) - _import_key(CLOUD_ARCHIVE_KEY_ID) - - with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f: - f.write(src) - else: - error_out("Invalid openstack-release specified: %s" % rel) - - -def save_script_rc(script_path="scripts/scriptrc", **env_vars): - """ - Write an rc file in the charm-delivered directory containing - exported environment variables provided by env_vars. Any charm scripts run - outside the juju hook environment can source this scriptrc to obtain - updated config information necessary to perform health checks or - service changes. - """ - charm_dir = os.getenv('CHARM_DIR') - juju_rc_path = "%s/%s" % (charm_dir, script_path) - with open(juju_rc_path, 'wb') as rc_script: - rc_script.write( - "#!/bin/bash\n") - [rc_script.write('export %s=%s\n' % (u, p)) - for u, p in env_vars.iteritems() if u != "script_path"] diff --git a/hooks/lib/unison.py b/hooks/lib/unison.py deleted file mode 100755 index fdef449d..00000000 --- a/hooks/lib/unison.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/python -# -# Easy file synchronization among peer units using ssh + unison. -# -# From *both* peer relation -joined and -changed, add a call to -# ssh_authorized_peers() describing the peer relation and the desired -# user + group. After all peer relations have settled, all hosts should -# be able to connect to on another via key auth'd ssh as the specified user. -# -# Other hooks are then free to synchronize files and directories using -# sync_to_peers(). -# -# For a peer relation named 'cluster', for example: -# -# cluster-relation-joined: -# ... -# ssh_authorized_peers(peer_interface='cluster', -# user='juju_ssh', group='juju_ssh', -# ensure_user=True) -# ... -# -# cluster-relation-changed: -# ... -# ssh_authorized_peers(peer_interface='cluster', -# user='juju_ssh', group='juju_ssh', -# ensure_user=True) -# ... -# -# Hooks are now free to sync files as easily as: -# -# files = ['/etc/fstab', '/etc/apt.conf.d/'] -# sync_to_peers(peer_interface='cluster', -# user='juju_ssh, paths=[files]) -# -# It is assumed the charm itself has setup permissions on each unit -# such that 'juju_ssh' has read + write permissions. Also assumed -# that the calling charm takes care of leader delegation. -# -# TODO: Currently depends on the utils.py shipped with the keystone charm. -# Either copy required functionality to this library or depend on -# something more generic. - -import os -import sys -import lib.utils as utils -import subprocess -import grp -import pwd - - -def get_homedir(user): - try: - user = pwd.getpwnam(user) - return user.pw_dir - except KeyError: - utils.juju_log('INFO', - 'Could not get homedir for user %s: user exists?') - sys.exit(1) - - -def get_keypair(user): - home_dir = get_homedir(user) - ssh_dir = os.path.join(home_dir, '.ssh') - if not os.path.isdir(ssh_dir): - os.mkdir(ssh_dir) - - priv_key = os.path.join(ssh_dir, 'id_rsa') - if not os.path.isfile(priv_key): - utils.juju_log('INFO', 'Generating new ssh key for user %s.' % user) - cmd = ['ssh-keygen', '-q', '-N', '', '-t', 'rsa', '-b', '2048', - '-f', priv_key] - subprocess.check_call(cmd) - - pub_key = '%s.pub' % priv_key - if not os.path.isfile(pub_key): - utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' % - pub_key) - cmd = ['ssh-keygen', '-y', '-f', priv_key] - p = subprocess.check_output(cmd).strip() - with open(pub_key, 'wb') as out: - out.write(p) - subprocess.check_call(['chown', '-R', user, ssh_dir]) - return open(priv_key, 'r').read().strip(), open(pub_key, 'r').read().strip() - - -def write_authorized_keys(user, keys): - home_dir = get_homedir(user) - ssh_dir = os.path.join(home_dir, '.ssh') - auth_keys = os.path.join(ssh_dir, 'authorized_keys') - utils.juju_log('INFO', 'Syncing authorized_keys @ %s.' % auth_keys) - with open(auth_keys, 'wb') as out: - for k in keys: - out.write('%s\n' % k) - - -def write_known_hosts(user, hosts): - home_dir = get_homedir(user) - ssh_dir = os.path.join(home_dir, '.ssh') - known_hosts = os.path.join(ssh_dir, 'known_hosts') - khosts = [] - for host in hosts: - cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host] - remote_key = subprocess.check_output(cmd).strip() - khosts.append(remote_key) - utils.juju_log('INFO', 'Syncing known_hosts @ %s.' % known_hosts) - with open(known_hosts, 'wb') as out: - for host in khosts: - out.write('%s\n' % host) - - -def ensure_user(user, group=None): - # need to ensure a bash shell'd user exists. - try: - pwd.getpwnam(user) - except KeyError: - utils.juju_log('INFO', 'Creating new user %s.%s.' % (user, group)) - cmd = ['adduser', '--system', '--shell', '/bin/bash', user] - if group: - try: - grp.getgrnam(group) - except KeyError: - subprocess.check_call(['addgroup', group]) - cmd += ['--ingroup', group] - subprocess.check_call(cmd) - - -def ssh_authorized_peers(peer_interface, user, group=None, ensure_local_user=False): - """ - Main setup function, should be called from both peer -changed and -joined - hooks with the same parameters. - """ - if ensure_local_user: - ensure_user(user, group) - priv_key, pub_key = get_keypair(user) - hook = os.path.basename(sys.argv[0]) - if hook == '%s-relation-joined' % peer_interface: - utils.relation_set(ssh_pub_key=pub_key) - print 'joined' - elif hook == '%s-relation-changed' % peer_interface: - hosts = [] - keys = [] - for r_id in utils.relation_ids(peer_interface): - for unit in utils.relation_list(r_id): - settings = utils.relation_get_dict(relation_id=r_id, - remote_unit=unit) - if 'ssh_pub_key' in settings: - keys.append(settings['ssh_pub_key']) - hosts.append(settings['private-address']) - else: - utils.juju_log('INFO', - 'ssh_authorized_peers(): ssh_pub_key ' - 'missing for unit %s, skipping.' % unit) - write_authorized_keys(user, keys) - write_known_hosts(user, hosts) - authed_hosts = ':'.join(hosts) - utils.relation_set(ssh_authorized_hosts=authed_hosts) - - -def _run_as_user(user): - try: - user = pwd.getpwnam(user) - except KeyError: - utils.juju_log('INFO', 'Invalid user: %s' % user) - sys.exit(1) - uid, gid = user.pw_uid, user.pw_gid - os.environ['HOME'] = user.pw_dir - - def _inner(): - os.setgid(gid) - os.setuid(uid) - return _inner - - -def run_as_user(user, cmd): - return subprocess.check_output(cmd, preexec_fn=_run_as_user(user), cwd='/') - - -def sync_to_peers(peer_interface, user, paths=[], verbose=False): - base_cmd = ['unison', '-auto', '-batch=true', '-confirmbigdel=false', - '-fastcheck=true', '-group=false', '-owner=false', - '-prefer=newer', '-times=true'] - if not verbose: - base_cmd.append('-silent') - - hosts = [] - for r_id in (utils.relation_ids(peer_interface) or []): - for unit in utils.relation_list(r_id): - settings = utils.relation_get_dict(relation_id=r_id, - remote_unit=unit) - try: - authed_hosts = settings['ssh_authorized_hosts'].split(':') - except KeyError: - print 'unison sync_to_peers: peer has not authorized *any* '\ - 'hosts yet.' - return - - unit_hostname = utils.unit_get('private-address') - add_host = None - for authed_host in authed_hosts: - if unit_hostname == authed_host: - add_host = settings['private-address'] - if add_host: - hosts.append(settings['private-address']) - else: - print 'unison sync_to_peers: peer (%s) has not authorized '\ - '*this* host yet, skipping.' % settings['private-address'] - - for path in paths: - # removing trailing slash from directory paths, unison - # doesn't like these. - if path.endswith('/'): - path = path[:(len(path) - 1)] - for host in hosts: - cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)] - utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' % - (path, user, host, path)) - run_as_user(user, cmd) diff --git a/hooks/lib/utils.py b/hooks/lib/utils.py deleted file mode 100644 index 018ac9e4..00000000 --- a/hooks/lib/utils.py +++ /dev/null @@ -1,319 +0,0 @@ -# -# Copyright 2012 Canonical Ltd. -# -# This file is sourced from lp:openstack-charm-helpers -# -# Authors: -# James Page -# Paul Collins -# Adam Gandelman -# - -import json -import os -import subprocess -import socket -import sys - - -def do_hooks(hooks): - hook = os.path.basename(sys.argv[0]) - - try: - hook_func = hooks[hook] - except KeyError: - juju_log('INFO', - "This charm doesn't know how to handle '{}'.".format(hook)) - else: - hook_func() - - -def install(*pkgs): - cmd = [ - 'apt-get', - '-y', - 'install'] - for pkg in pkgs: - cmd.append(pkg) - subprocess.check_call(cmd) - -TEMPLATES_DIR = 'templates' - -try: - import jinja2 -except ImportError: - install('python-jinja2') - import jinja2 - -try: - import dns.resolver -except ImportError: - install('python-dnspython') - import dns.resolver - - -def render_template(template_name, context, template_dir=TEMPLATES_DIR): - templates = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) - template = templates.get_template(template_name) - return template.render(context) - -CLOUD_ARCHIVE = \ - """ # Ubuntu Cloud Archive - deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main - """ - -CLOUD_ARCHIVE_POCKETS = { - 'folsom': 'precise-updates/folsom', - 'folsom/updates': 'precise-updates/folsom', - 'folsom/proposed': 'precise-proposed/folsom', - 'grizzly': 'precise-updates/grizzly', - 'grizzly/updates': 'precise-updates/grizzly', - 'grizzly/proposed': 'precise-proposed/grizzly', - 'havana': 'precise-updates/havana', - 'havana/updates': 'precise-updates/havana', - 'havana/proposed': 'precise-proposed/havana', - 'icehouse': 'precise-updates/icehouse', - 'icehouse/updates': 'precise-updates/icehouse', - 'icehouse/proposed': 'precise-proposed/icehouse'} - - -def configure_source(): - source = str(config_get('openstack-origin')) - if not source: - return - if source.startswith('ppa:'): - cmd = [ - 'add-apt-repository', - source] - subprocess.check_call(cmd) - if source.startswith('cloud:'): - # CA values should be formatted as cloud:ubuntu-openstack/pocket, eg: - # cloud:precise-folsom/updates or cloud:precise-folsom/proposed - install('ubuntu-cloud-keyring') - pocket = source.split(':')[1] - pocket = pocket.split('-')[1] - with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: - apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket])) - if source.startswith('deb'): - l = len(source.split('|')) - if l == 2: - (apt_line, key) = source.split('|') - cmd = [ - 'apt-key', - 'adv', '--keyserver keyserver.ubuntu.com', - '--recv-keys', key] - subprocess.check_call(cmd) - elif l == 1: - apt_line = source - - with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt: - apt.write(apt_line + "\n") - cmd = [ - 'apt-get', - 'update'] - subprocess.check_call(cmd) - -# Protocols -TCP = 'TCP' -UDP = 'UDP' - - -def expose(port, protocol='TCP'): - cmd = [ - 'open-port', - '{}/{}'.format(port, protocol)] - subprocess.check_call(cmd) - - -def juju_log(severity, message): - cmd = [ - 'juju-log', - '--log-level', severity, - message] - subprocess.check_call(cmd) - - -cache = {} - - -def cached(func): - def wrapper(*args, **kwargs): - global cache - key = str((func, args, kwargs)) - try: - return cache[key] - except KeyError: - res = func(*args, **kwargs) - cache[key] = res - return res - return wrapper - - -@cached -def relation_ids(relation): - cmd = [ - 'relation-ids', - relation] - result = str(subprocess.check_output(cmd)).split() - if result == "": - return None - else: - return result - - -@cached -def relation_list(rid): - cmd = [ - 'relation-list', - '-r', rid] - result = str(subprocess.check_output(cmd)).split() - if result == "": - return None - else: - return result - - -@cached -def relation_get(attribute, unit=None, rid=None): - cmd = [ - 'relation-get'] - if rid: - cmd.append('-r') - cmd.append(rid) - cmd.append(attribute) - if unit: - cmd.append(unit) - value = subprocess.check_output(cmd).strip() # IGNORE:E1103 - if value == "": - return None - else: - return value - - -@cached -def relation_get_dict(relation_id=None, remote_unit=None): - """Obtain all relation data as dict by way of JSON""" - cmd = [ - 'relation-get', '--format=json'] - if relation_id: - cmd.append('-r') - cmd.append(relation_id) - if remote_unit: - cmd.append('-') - cmd.append(remote_unit) - j = subprocess.check_output(cmd) - d = json.loads(j) - settings = {} - # convert unicode to strings - for k, v in d.iteritems(): - settings[str(k)] = str(v) - return settings - - -def relation_set(**kwargs): - cmd = [ - 'relation-set'] - args = [] - for k, v in kwargs.items(): - if k == 'rid': - if v: - cmd.append('-r') - cmd.append(v) - else: - args.append('{}={}'.format(k, v)) - cmd += args - subprocess.check_call(cmd) - - -@cached -def unit_get(attribute): - cmd = [ - 'unit-get', - attribute] - value = subprocess.check_output(cmd).strip() # IGNORE:E1103 - if value == "": - return None - else: - return value - - -@cached -def config_get(attribute): - cmd = [ - 'config-get', - '--format', - 'json'] - out = subprocess.check_output(cmd).strip() # IGNORE:E1103 - cfg = json.loads(out) - - try: - return cfg[attribute] - except KeyError: - return None - - -@cached -def get_unit_hostname(): - return socket.gethostname() - - -@cached -def get_host_ip(hostname=unit_get('private-address')): - try: - # Test to see if already an IPv4 address - socket.inet_aton(hostname) - return hostname - except socket.error: - answers = dns.resolver.query(hostname, 'A') - if answers: - return answers[0].address - return None - - -def _svc_control(service, action): - subprocess.check_call(['service', service, action]) - - -def restart(*services): - for service in services: - _svc_control(service, 'restart') - - -def stop(*services): - for service in services: - _svc_control(service, 'stop') - - -def start(*services): - for service in services: - _svc_control(service, 'start') - - -def reload(*services): - for service in services: - try: - _svc_control(service, 'reload') - except subprocess.CalledProcessError: - # Reload failed - either service does not support reload - # or it was not running - restart will fixup most things - _svc_control(service, 'restart') - - -def running(service): - try: - output = subprocess.check_output(['service', service, 'status']) - except subprocess.CalledProcessError: - return False - else: - if ("start/running" in output or "is running" in output): - return True - else: - return False - - -def is_relation_made(relation, key='private-address'): - for r_id in (relation_ids(relation) or []): - for unit in (relation_list(r_id) or []): - if relation_get(key, rid=r_id, unit=unit): - return True - return False diff --git a/hooks/start b/hooks/start new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/start @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/hooks/stop b/hooks/stop new file mode 120000 index 00000000..dd3b3eff --- /dev/null +++ b/hooks/stop @@ -0,0 +1 @@ +keystone_hooks.py \ No newline at end of file diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py index c30f812c..6fea20c3 100644 --- a/unit_tests/test_keystone_contexts.py +++ b/unit_tests/test_keystone_contexts.py @@ -1,10 +1,6 @@ import keystone_context as context from mock import patch -with patch('charmhelpers.core.hookenv.config') as config: - config.return_value = 'keystone' - import keystone_utils as utils - from test_utils import ( CharmTestCase ) @@ -37,8 +33,8 @@ class TestKeystoneContexts(CharmTestCase): mock_is_clustered.return_value = False ctxt = context.ApacheSSLContext() - with patch.object(ctxt, 'enable_modules') as mock_enable_modules: - with patch.object(ctxt, 'configure_cert') as mock_configure_cert: + with patch.object(ctxt, 'enable_modules'): + with patch.object(ctxt, 'configure_cert'): self.assertEquals(ctxt(), {'endpoints': [(34, 12)], 'private_address': '1.2.3.4', 'namespace': 'keystone'}) @@ -51,20 +47,27 @@ class TestKeystoneContexts(CharmTestCase): @patch('charmhelpers.contrib.openstack.context.relation_get') @patch('charmhelpers.contrib.openstack.context.log') @patch('__builtin__.open') - def test_haproxy_context_service_enabled(self, mock_open, mock_log, mock_relation_get, mock_related_units, - mock_unit_get, mock_relation_ids): - mock_relation_ids.return_value = ['identity-service:0',] + def test_haproxy_context_service_enabled( + self, mock_open, mock_log, mock_relation_get, mock_related_units, + mock_unit_get, mock_relation_ids): + mock_relation_ids.return_value = ['identity-service:0', ] mock_unit_get.return_value = '1.2.3.4' mock_relation_get.return_value = '10.0.0.0' - mock_related_units.return_value = ['unit/0',] + mock_related_units.return_value = ['unit/0', ] self.determine_apache_port.return_value = '34' ctxt = context.HAProxyContext() - self.assertEquals(ctxt(), {'listen_ports': {'admin_port': 'keystone', 'public_port': 'keystone'}, - 'service_ports': {'admin-port': ['keystone', '34'], - 'public-port': ['keystone', '34']}, - 'units': {'keystone': '1.2.3.4', 'unit-0':'10.0.0.0'}}) + self.assertEquals( + ctxt(), + {'listen_ports': {'admin_port': 'keystone', + 'public_port': 'keystone'}, + 'service_ports': {'admin-port': ['keystone', '34'], + 'public-port': ['keystone', '34']}, + 'units': {'keystone': '1.2.3.4', 'unit-0': '10.0.0.0'}}) mock_unit_get.assert_called_with('private-address') - mock_relation_get.assert_called_with('private-address', rid='identity-service:0', unit='unit/0') + mock_relation_get.assert_called_with( + 'private-address', + rid='identity-service:0', + unit='unit/0') mock_open.assert_called_with('/etc/default/haproxy', 'w') diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 245d105d..5ba9a33b 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -76,9 +76,10 @@ class KeystoneRelationTests(CharmTestCase): hooks.install() self.configure_installation_source.assert_called_with(repo) self.assertTrue(self.apt_update.called) - self.apt_install.assert_called_with(['haproxy', 'unison', 'python-keystoneclient', - 'uuid', 'python-mysqldb', 'openssl', 'apache2', - 'pwgen', 'keystone', 'python-psycopg2'], fatal=True) + self.apt_install.assert_called_with( + ['haproxy', 'unison', 'python-keystoneclient', + 'uuid', 'python-mysqldb', 'openssl', 'apache2', + 'pwgen', 'keystone', 'python-psycopg2'], fatal=True) self.assertTrue(self.execd_preinstall.called) def test_db_joined(self): @@ -157,7 +158,9 @@ class KeystoneRelationTests(CharmTestCase): configs.write.call_args_list) self.migrate_database.assert_called_with() self.assertTrue(self.ensure_initial_admin.called) - identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + identity_changed.assert_called_with( + relation_id='identity-service:0', + remote_unit='unit/0') @patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'identity_changed') @@ -170,14 +173,18 @@ class KeystoneRelationTests(CharmTestCase): configs.write.call_args_list) self.migrate_database.assert_called_with() self.assertTrue(self.ensure_initial_admin.called) - identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + identity_changed.assert_called_with( + relation_id='identity-service:0', + remote_unit='unit/0') @patch.object(unison, 'ensure_user') @patch.object(unison, 'get_homedir') @patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'identity_changed') @patch.object(hooks, 'configure_https') - def test_config_changed_no_openstack_upgrade_leader(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + def test_config_changed_no_openstack_upgrade_leader( + self, configure_https, identity_changed, + configs, get_homedir, ensure_user): self.openstack_upgrade_available.return_value = False self.eligible_leader.return_value = True self.relation_ids.return_value = ['identity-service:0'] @@ -193,15 +200,20 @@ class KeystoneRelationTests(CharmTestCase): self.migrate_database.assert_called_with() self.assertTrue(self.ensure_initial_admin.called) - self.log.assert_called_with('Firing identity_changed hook for all related services.') - identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + self.log.assert_called_with( + 'Firing identity_changed hook for all related services.') + identity_changed.assert_called_with( + relation_id='identity-service:0', + remote_unit='unit/0') @patch.object(unison, 'ensure_user') @patch.object(unison, 'get_homedir') @patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'identity_changed') @patch.object(hooks, 'configure_https') - def test_config_changed_no_openstack_upgrade_not_leader(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + def test_config_changed_no_openstack_upgrade_not_leader( + self, configure_https, identity_changed, + configs, get_homedir, ensure_user): self.openstack_upgrade_available.return_value = False self.eligible_leader.return_value = False @@ -222,7 +234,9 @@ class KeystoneRelationTests(CharmTestCase): @patch.object(hooks, 'CONFIGS') @patch.object(hooks, 'identity_changed') @patch.object(hooks, 'configure_https') - def test_config_changed_with_openstack_upgrade(self, configure_https, identity_changed, configs, get_homedir, ensure_user): + def test_config_changed_with_openstack_upgrade( + self, configure_https, identity_changed, + configs, get_homedir, ensure_user): self.openstack_upgrade_available.return_value = True self.eligible_leader.return_value = True self.relation_ids.return_value = ['identity-service:0'] @@ -240,34 +254,46 @@ class KeystoneRelationTests(CharmTestCase): self.migrate_database.assert_called_with() self.assertTrue(self.ensure_initial_admin.called) - self.log.assert_called_with('Firing identity_changed hook for all related services.') - identity_changed.assert_called_with(relation_id='identity-service:0', remote_unit='unit/0') + self.log.assert_called_with( + 'Firing identity_changed hook for all related services.') + identity_changed.assert_called_with( + relation_id='identity-service:0', + remote_unit='unit/0') def test_identity_changed_leader(self): self.eligible_leader.return_value = True - hooks.identity_changed(relation_id='identity-service:0', remote_unit='unit/0') - self.add_service_to_keystone.assert_called_with('identity-service:0', 'unit/0') + hooks.identity_changed( + relation_id='identity-service:0', + remote_unit='unit/0') + self.add_service_to_keystone.assert_called_with( + 'identity-service:0', + 'unit/0') self.assertTrue(self.synchronize_ca.called) def test_identity_changed_no_leader(self): self.eligible_leader.return_value = False - hooks.identity_changed(relation_id='identity-service:0', remote_unit='unit/0') + hooks.identity_changed( + relation_id='identity-service:0', + remote_unit='unit/0') self.assertFalse(self.add_service_to_keystone.called) - self.log.assert_called_with('Deferring identity_changed() to service leader.') + self.log.assert_called_with( + 'Deferring identity_changed() to service leader.') @patch.object(unison, 'ssh_authorized_peers') def test_cluster_joined(self, ssh_authorized_peers): hooks.cluster_joined() - ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='juju_keystone', - peer_interface='cluster', ensure_local_user=True) + ssh_authorized_peers.assert_called_with( + user=self.ssh_user, group='juju_keystone', + peer_interface='cluster', ensure_local_user=True) @patch.object(unison, 'ssh_authorized_peers') @patch.object(hooks, 'CONFIGS') def test_cluster_changed(self, configs, ssh_authorized_peers): hooks.cluster_changed() self.peer_echo.assert_called_with(includes=['_passwd']) - ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', - peer_interface='cluster', ensure_local_user=True) + ssh_authorized_peers.assert_called_with( + user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) self.assertTrue(self.synchronize_ca.called) self.assertTrue(configs.write_all.called) @@ -312,8 +338,9 @@ class KeystoneRelationTests(CharmTestCase): hooks.ha_changed() self.assertTrue(configs.write_all.called) - self.log.assert_called_with('Cluster configured, notifying other services and updating ' - 'keystone endpoint configuration') + self.log.assert_called_with( + 'Cluster configured, notifying other services and updating ' + 'keystone endpoint configuration') self.relation_set.assert_called_with(relation_id='identity-service:0', auth_host='10.10.10.10', service_host='10.10.10.10') @@ -346,11 +373,13 @@ class KeystoneRelationTests(CharmTestCase): self.filter_installed_packages.return_value = [] hooks.upgrade_charm() self.assertTrue(self.apt_install.called) - ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', - peer_interface='cluster', ensure_local_user=True) + ssh_authorized_peers.assert_called_with( + user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) self.assertTrue(self.synchronize_ca.called) - self.log.assert_called_with('Cluster leader - ensuring endpoint configuration' - ' is up to date') + self.log.assert_called_with( + 'Cluster leader - ensuring endpoint configuration' + ' is up to date') self.assertTrue(self.ensure_initial_admin.called) @patch.object(unison, 'ssh_authorized_peers') @@ -359,8 +388,9 @@ class KeystoneRelationTests(CharmTestCase): self.filter_installed_packages.return_value = [] hooks.upgrade_charm() self.assertTrue(self.apt_install.called) - ssh_authorized_peers.assert_called_with(user=self.ssh_user, group='keystone', - peer_interface='cluster', ensure_local_user=True) + ssh_authorized_peers.assert_called_with( + user=self.ssh_user, group='keystone', + peer_interface='cluster', ensure_local_user=True) self.assertTrue(self.synchronize_ca.called) self.assertFalse(self.log.called) self.assertFalse(self.ensure_initial_admin.called) diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index 42af3356..beab606d 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -1,8 +1,5 @@ from mock import patch, call, MagicMock from test_utils import CharmTestCase -from copy import deepcopy - -from collections import OrderedDict import os import manager @@ -10,9 +7,7 @@ os.environ['JUJU_UNIT_NAME'] = 'keystone' with patch('charmhelpers.core.hookenv.config') as config: import keystone_utils as utils -import keystone_context import keystone_hooks as hooks -from charmhelpers.contrib.openstack import context TO_PATCH = [ 'api_port', @@ -49,6 +44,7 @@ TO_PATCH = [ 'pwgen', ] + class TestKeystoneUtils(CharmTestCase): def setUp(self): @@ -88,14 +84,18 @@ class TestKeystoneUtils(CharmTestCase): ex_reg = [ call('/etc/keystone/keystone.conf', [self.ctxt]), - call('/etc/apache2/sites-available/openstack_https_frontend', [self.ctxt]), - call('/etc/apache2/sites-available/openstack_https_frontend.conf', [self.ctxt]), + call( + '/etc/apache2/sites-available/openstack_https_frontend', + [self.ctxt]), + call( + '/etc/apache2/sites-available/openstack_https_frontend.conf', + [self.ctxt]), ] self.assertEquals(fake_renderer.register.call_args_list, ex_reg) def test_determine_ports(self): - self.test_config.set('admin-port','80') - self.test_config.set('service-port','81') + self.test_config.set('admin-port', '80') + self.test_config.set('service-port', '81') result = utils.determine_ports() self.assertEquals(result, ['80', '81']) @@ -107,14 +107,15 @@ class TestKeystoneUtils(CharmTestCase): @patch.object(hooks, 'CONFIGS') @patch.object(utils, 'determine_packages') @patch.object(utils, 'migrate_database') - def test_openstack_upgrade_leader(self, migrate_database, determine_packages, configs): + def test_openstack_upgrade_leader( + self, migrate_database, determine_packages, configs): self.test_config.set('openstack-origin', 'precise') determine_packages.return_value = [] self.eligible_leader.return_value = True utils.do_openstack_upgrade(configs) - self.get_os_codename_install_source.assert_called_with('precise') + self.get_os_codename_install_source.assert_called_with('precise') self.configure_installation_source.assert_called_with('precise') self.assertTrue(self.apt_update.called) @@ -122,8 +123,14 @@ class TestKeystoneUtils(CharmTestCase): '--option', 'Dpkg::Options::=--force-confnew', '--option', 'Dpkg::Options::=--force-confdef', ] - self.apt_upgrade.assert_called_with(options=dpkg_opts, fatal=True, dist=True) - self.apt_install.assert_called_with(packages=[], options=dpkg_opts, fatal=True) + self.apt_upgrade.assert_called_with( + options=dpkg_opts, + fatal=True, + dist=True) + self.apt_install.assert_called_with( + packages=[], + options=dpkg_opts, + fatal=True) self.assertTrue(configs.set_release.called) self.assertTrue(configs.write_all.called) @@ -138,7 +145,8 @@ class TestKeystoneUtils(CharmTestCase): self.service_start.assert_called_wkth('keystone') @patch.object(utils, 'b64encode') - def test_add_service_to_keystone_clustered_https_none_values(self, b64encode): + def test_add_service_to_keystone_clustered_https_none_values( + self, b64encode): relation_id = 'identity-service:0' remote_unit = 'unit/0' self.is_clustered.return_value = True @@ -148,7 +156,7 @@ class TestKeystoneUtils(CharmTestCase): self.test_config.set('admin-port', 80) self.test_config.set('service-port', 81) b64encode.return_value = 'certificate' - self.get_requested_roles.return_value = ['role1',] + self.get_requested_roles.return_value = ['role1', ] self.relation_get.return_value = {'service': 'keystone', 'region': 'RegionOne', @@ -156,7 +164,9 @@ class TestKeystoneUtils(CharmTestCase): 'admin_url': '10.0.0.2', 'internal_url': '192.168.1.2'} - utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + utils.add_service_to_keystone( + relation_id=relation_id, + remote_unit=remote_unit) self.assertTrue(self.is_clustered.called) self.assertTrue(self.https.called) self.assertTrue(self.create_role.called) @@ -169,19 +179,22 @@ class TestKeystoneUtils(CharmTestCase): 'service_port': 81, 'https_keystone': 'True', 'ca_cert': 'certificate'} - self.relation_set.assert_called_with(relation_id=relation_id, **relation_data) + self.relation_set.assert_called_with( + relation_id=relation_id, + **relation_data) @patch.object(utils, 'ensure_valid_service') @patch.object(utils, 'add_endpoint') @patch.object(manager, 'KeystoneManager') - def test_add_service_to_keystone_no_clustered_no_https_complete_values(self, KeystoneManager, add_endpoint, ensure_valid_service): + def test_add_service_to_keystone_no_clustered_no_https_complete_values( + self, KeystoneManager, add_endpoint, ensure_valid_service): relation_id = 'identity-service:0' remote_unit = 'unit/0' self.get_admin_token.return_value = 'token' self.get_service_password.return_value = 'password' self.test_config.set('service-tenant', 'tenant') self.test_config.set('admin-role', 'admin') - self.get_requested_roles.return_value = ['role1',] + self.get_requested_roles.return_value = ['role1', ] self.unit_private_ip.return_value = '10.0.0.3' self.test_config.set('admin-port', 80) self.test_config.set('service-port', 81) @@ -200,10 +213,13 @@ class TestKeystoneUtils(CharmTestCase): 'admin_url': '10.0.0.2', 'internal_url': '192.168.1.2'} - utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + utils.add_service_to_keystone( + relation_id=relation_id, + remote_unit=remote_unit) ensure_valid_service.assert_called_with('keystone') add_endpoint.assert_called_with(region='RegionOne', service='keystone', - publicurl='10.0.0.1', adminurl='10.0.0.2', + publicurl='10.0.0.1', + adminurl='10.0.0.2', internalurl='192.168.1.2') self.assertTrue(self.get_admin_token.called) self.get_service_password.assert_called_with('keystone') @@ -212,19 +228,25 @@ class TestKeystoneUtils(CharmTestCase): self.create_role.assert_called_with('role1', 'keystone', 'tenant') self.assertTrue(self.is_clustered.called) - relation_data = {'admin_token': 'token', 'service_port':81, - 'auth_port':80, 'service_username':'keystone', - 'service_password': 'password', 'service_tenant': 'tenant', - 'https_keystone': 'False', 'ssl_cert': '', 'ssl_key': '', - 'ca_cert': '', 'auth_host':'10.0.0.3', 'service_host': '10.0.0.3', + relation_data = {'admin_token': 'token', 'service_port': 81, + 'auth_port': 80, 'service_username': 'keystone', + 'service_password': 'password', + 'service_tenant': 'tenant', + 'https_keystone': 'False', + 'ssl_cert': '', 'ssl_key': '', + 'ca_cert': '', 'auth_host': '10.0.0.3', + 'service_host': '10.0.0.3', 'auth_protocol': 'http', 'service_protocol': 'http', 'service_tenant_id': 'tenant_id'} - self.relation_set.assert_called_with(relation_id=relation_id, **relation_data) + self.relation_set.assert_called_with( + relation_id=relation_id, + **relation_data) @patch.object(utils, 'ensure_valid_service') @patch.object(utils, 'add_endpoint') @patch.object(manager, 'KeystoneManager') - def test_add_service_to_keystone_nosubset(self, KeystoneManager, add_endpoint, ensure_valid_service): + def test_add_service_to_keystone_nosubset( + self, KeystoneManager, add_endpoint, ensure_valid_service): relation_id = 'identity-service:0' remote_unit = 'unit/0' @@ -236,11 +258,14 @@ class TestKeystoneUtils(CharmTestCase): self.get_local_endpoint.return_value = 'http://localhost:80/v2.0/' KeystoneManager.resolve_tenant_id.return_value = 'tenant_id' - utils.add_service_to_keystone(relation_id=relation_id, remote_unit=remote_unit) + utils.add_service_to_keystone( + relation_id=relation_id, + remote_unit=remote_unit) ensure_valid_service.assert_called_with('nova') add_endpoint.assert_called_with(region='RegionOne', service='nova', - publicurl='10.0.0.1', adminurl='10.0.0.2', - internalurl='192.168.1.2') + publicurl='10.0.0.1', + adminurl='10.0.0.2', + internalurl='192.168.1.2') def test_ensure_valid_service_incorrect(self): utils.ensure_valid_service('fakeservice') @@ -251,8 +276,17 @@ class TestKeystoneUtils(CharmTestCase): publicurl = '10.0.0.1' adminurl = '10.0.0.2' internalurl = '10.0.0.3' - utils.add_endpoint('RegionOne', 'nova', publicurl, adminurl, internalurl) - self.create_service_entry.assert_called_with('nova', 'compute', 'Nova Compute Service') - self.create_endpoint_template.asssert_called_with(region='RegionOne', service='nova', - publicurl=publicurl, adminurl=adminurl, - internalurl=internalurl) + utils.add_endpoint( + 'RegionOne', + 'nova', + publicurl, + adminurl, + internalurl) + self.create_service_entry.assert_called_with( + 'nova', + 'compute', + 'Nova Compute Service') + self.create_endpoint_template.asssert_called_with( + region='RegionOne', service='nova', + publicurl=publicurl, adminurl=adminurl, + internalurl=internalurl)