diff --git a/hooks/keystone-fid-service-provider-relation-broken b/hooks/keystone-fid-service-provider-relation-broken
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/keystone-fid-service-provider-relation-broken
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/keystone-fid-service-provider-relation-changed b/hooks/keystone-fid-service-provider-relation-changed
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/keystone-fid-service-provider-relation-changed
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/keystone-fid-service-provider-relation-departed b/hooks/keystone-fid-service-provider-relation-departed
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/keystone-fid-service-provider-relation-departed
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/keystone-fid-service-provider-relation-joined b/hooks/keystone-fid-service-provider-relation-joined
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/keystone-fid-service-provider-relation-joined
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/keystone_context.py b/hooks/keystone_context.py
index edf138f6..6ff8aa00 100644
--- a/hooks/keystone_context.py
+++ b/hooks/keystone_context.py
@@ -14,6 +14,7 @@
import hashlib
import os
+import json
from base64 import b64decode
@@ -39,6 +40,9 @@ from charmhelpers.core.hookenv import (
leader_get,
DEBUG,
INFO,
+ related_units,
+ relation_ids,
+ relation_get,
)
from charmhelpers.core.strutils import (
@@ -405,3 +409,46 @@ class TokenFlushContext(context.OSContextGenerator):
'token_flush': is_elected_leader(DC_RESOURCE_NAME)
}
return ctxt
+
+
+class KeystoneFIDServiceProviderContext(context.OSContextGenerator):
+ interfaces = ['keystone-fid-service-provider']
+
+ def __call__(self):
+ fid_sp_keys = ['protocol-name', 'remote-id-attribute']
+ fid_sps = []
+ for rid in relation_ids("keystone-fid-service-provider"):
+ for unit in related_units(rid):
+ rdata = relation_get(unit=unit, rid=rid)
+ if set(rdata).issuperset(set(fid_sp_keys)):
+ fid_sps.append({
+ k: json.loads(v) for k, v in rdata.items()
+ if k in fid_sp_keys
+ })
+ # populate the context with data from one or more
+ # service providers
+ ctxt = ({'fid_sps': fid_sps}
+ if fid_sps else {})
+ return ctxt
+
+
+class WebSSOTrustedDashboardContext(context.OSContextGenerator):
+ interfaces = ['websso-trusted-dashboard']
+
+ def __call__(self):
+ trusted_dashboard_keys = ['scheme', 'hostname', 'path']
+ trusted_dashboards = set()
+ for rid in relation_ids("websso-trusted-dashboard"):
+ for unit in related_units(rid):
+ rdata = relation_get(unit=unit, rid=rid)
+ if set(rdata).issuperset(set(trusted_dashboard_keys)):
+ scheme = rdata.get('scheme')
+ hostname = rdata.get('hostname')
+ path = rdata.get('path')
+ url = '{}{}{}'.format(scheme, hostname, path)
+ trusted_dashboards.add(url)
+ # populate the context with data from one or more
+ # service providers
+ ctxt = ({'trusted_dashboards': trusted_dashboards}
+ if trusted_dashboards else {})
+ return ctxt
diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py
index ca56c1ec..d7407f60 100755
--- a/hooks/keystone_hooks.py
+++ b/hooks/keystone_hooks.py
@@ -40,6 +40,7 @@ from charmhelpers.core.hookenv import (
status_set,
open_port,
is_leader,
+ relation_id,
)
from charmhelpers.core.host import (
@@ -121,7 +122,7 @@ from keystone_utils import (
ADMIN_DOMAIN,
ADMIN_PROJECT,
create_or_show_domain,
- keystone_service,
+ restart_keystone,
)
from charmhelpers.contrib.hahelpers.cluster import (
@@ -272,6 +273,7 @@ def config_changed_postupgrade():
update_all_identity_relation_units()
update_all_domain_backends()
+ update_all_fid_backends()
# Ensure sync request is sent out (needed for any/all ssl change)
send_ssl_sync_request()
@@ -381,6 +383,17 @@ def update_all_domain_backends():
domain_backend_changed(relation_id=rid, unit=unit)
+def update_all_fid_backends():
+ if CompareOpenStackReleases(os_release('keystone-common')) < 'ocata':
+ log('Ignoring keystone-fid-service-provider relation as it is'
+ ' not supported on releases older than Ocata')
+ return
+ """If there are any config changes, e.g. for domain or service port
+ make sure to update those for all relation-level buckets"""
+ for rid in relation_ids('keystone-fid-service-provider'):
+ update_keystone_fid_service_provider(relation_id=rid)
+
+
def leader_init_db_if_ready(use_current_context=False):
""" Initialise the keystone db if it is ready and mark it as initialised.
@@ -784,11 +797,7 @@ def domain_backend_changed(relation_id=None, unit=None):
domain_nonce_key = 'domain-restart-nonce-{}'.format(domain_name)
db = unitdata.kv()
if restart_nonce != db.get(domain_nonce_key):
- if not is_unit_paused_set():
- if snap_install_requested():
- service_restart('snap.keystone.*')
- else:
- service_restart(keystone_service())
+ restart_keystone()
db.set(domain_nonce_key, restart_nonce)
db.flush()
@@ -869,6 +878,80 @@ def update_nrpe_config():
nrpe_setup.write()
+@hooks.hook('keystone-fid-service-provider-relation-joined',
+ 'keystone-fid-service-provider-relation-changed')
+def keystone_fid_service_provider_changed():
+ if get_api_version() < 3:
+ log('Identity federation is only supported with keystone v3')
+ return
+ if CompareOpenStackReleases(os_release('keystone-common')) < 'ocata':
+ log('Ignoring keystone-fid-service-provider relation as it is'
+ ' not supported on releases older than Ocata')
+ return
+ # for the join case a keystone public-facing hostname and service
+ # port need to be set
+ update_keystone_fid_service_provider(relation_id=relation_id())
+
+ # handle relation data updates (if any), e.g. remote_id_attribute
+ # and a restart will be handled via a nonce, not restart_on_change
+ CONFIGS.write(KEYSTONE_CONF)
+
+ # The relation is container-scoped so this keystone unit's unitdata
+ # will only contain a nonce of a single fid subordinate for a given
+ # fid backend (relation id)
+ restart_nonce = relation_get('restart-nonce')
+ if restart_nonce:
+ nonce = json.loads(restart_nonce)
+ # multiplex by relation id for multiple federated identity
+ # provider charms
+ fid_nonce_key = 'fid-restart-nonce-{}'.format(relation_id())
+ db = unitdata.kv()
+ if restart_nonce != db.get(fid_nonce_key):
+ restart_keystone()
+ db.set(fid_nonce_key, nonce)
+ db.flush()
+
+
+@hooks.hook('keystone-fid-service-provider-relation-broken')
+def keystone_fid_service_provider_broken():
+ if CompareOpenStackReleases(os_release('keystone-common')) < 'ocata':
+ log('Ignoring keystone-fid-service-provider relation as it is'
+ ' not supported on releases older than Ocata')
+ return
+
+ restart_keystone()
+
+
+@hooks.hook('websso-trusted-dashboard-relation-joined',
+ 'websso-trusted-dashboard-relation-changed',
+ 'websso-trusted-dashboard-relation-broken')
+@restart_on_change(restart_map(), restart_functions=restart_function_map())
+def websso_trusted_dashboard_changed():
+ if get_api_version() < 3:
+ log('WebSSO is only supported with keystone v3')
+ return
+ if CompareOpenStackReleases(os_release('keystone-common')) < 'ocata':
+ log('Ignoring WebSSO relation as it is not supported on'
+ ' releases older than Ocata')
+ return
+ CONFIGS.write(KEYSTONE_CONF)
+
+
+def update_keystone_fid_service_provider(relation_id=None):
+ tls_enabled = (config('ssl_cert') is not None and
+ config('ssl_key') is not None)
+ # reactive endpoints implementation on the other side, hence
+ # json-encoded values
+ fid_settings = {
+ 'hostname': json.dumps(config('os-public-hostname')),
+ 'port': json.dumps(config('service-port')),
+ 'tls-enabled': json.dumps(tls_enabled),
+ }
+
+ relation_set(relation_id=relation_id,
+ relation_settings=fid_settings)
+
+
def main():
try:
hooks.execute(sys.argv)
diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py
index ec7e077e..0002e19a 100644
--- a/hooks/keystone_utils.py
+++ b/hooks/keystone_utils.py
@@ -72,6 +72,7 @@ from charmhelpers.contrib.openstack.utils import (
install_os_snaps,
get_snaps_install_info_from_origin,
enable_memcache,
+ is_unit_paused_set,
)
from charmhelpers.core.strutils import (
@@ -245,7 +246,9 @@ BASE_RESOURCE_MAP = OrderedDict([
keystone_context.HAProxyContext(),
context.BindHostContext(),
context.WorkerConfigContext(),
- context.MemcacheContext(package='keystone')],
+ context.MemcacheContext(package='keystone'),
+ keystone_context.KeystoneFIDServiceProviderContext(),
+ keystone_context.WebSSOTrustedDashboardContext()],
}),
(KEYSTONE_LOGGER_CONF, {
'contexts': [keystone_context.KeystoneLoggingContext()],
@@ -2574,3 +2577,11 @@ def post_snap_install():
if os.path.exists(PASTE_SRC):
log("Perfoming post snap install tasks", INFO)
shutil.copy(PASTE_SRC, PASTE_DST)
+
+
+def restart_keystone():
+ if not is_unit_paused_set():
+ if snap_install_requested():
+ service_restart('snap.keystone.*')
+ else:
+ service_restart(keystone_service())
diff --git a/hooks/websso-trusted-dashboard-relation-broken b/hooks/websso-trusted-dashboard-relation-broken
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/websso-trusted-dashboard-relation-broken
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/websso-trusted-dashboard-relation-changed b/hooks/websso-trusted-dashboard-relation-changed
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/websso-trusted-dashboard-relation-changed
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/websso-trusted-dashboard-relation-departed b/hooks/websso-trusted-dashboard-relation-departed
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/websso-trusted-dashboard-relation-departed
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/hooks/websso-trusted-dashboard-relation-joined b/hooks/websso-trusted-dashboard-relation-joined
new file mode 120000
index 00000000..dd3b3eff
--- /dev/null
+++ b/hooks/websso-trusted-dashboard-relation-joined
@@ -0,0 +1 @@
+keystone_hooks.py
\ No newline at end of file
diff --git a/metadata.yaml b/metadata.yaml
index 3dadf6af..2e80061d 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -39,6 +39,11 @@ requires:
domain-backend:
interface: keystone-domain-backend
scope: container
+ keystone-fid-service-provider:
+ interface: keystone-fid-service-provider
+ scope: container
+ websso-trusted-dashboard:
+ interface: websso-trusted-dashboard
peers:
cluster:
interface: keystone-ha
diff --git a/templates/ocata/keystone.conf b/templates/ocata/keystone.conf
index 4ce530da..6fc6bd98 100644
--- a/templates/ocata/keystone.conf
+++ b/templates/ocata/keystone.conf
@@ -67,7 +67,7 @@ driver = {{ assignment_backend }}
[oauth1]
[auth]
-methods = external,password,token,oauth1
+methods = external,password,token,oauth1,mapped,openid
password = keystone.auth.plugins.password.Password
token = keystone.auth.plugins.token.Token
oauth1 = keystone.auth.plugins.oauth1.OAuth
@@ -115,3 +115,5 @@ group_allow_delete = False
admin_project_domain_name = {{ admin_domain_name }}
admin_project_name = admin
{% endif -%}
+
+{% include "parts/section-federation" %}
diff --git a/templates/openstack_https_frontend.conf b/templates/openstack_https_frontend.conf
new file mode 100644
index 00000000..e0e42296
--- /dev/null
+++ b/templates/openstack_https_frontend.conf
@@ -0,0 +1,30 @@
+{% if endpoints -%}
+{% for ext_port in ext_ports -%}
+Listen {{ ext_port }}
+{% endfor -%}
+{% for address, endpoint, ext, int in endpoints -%}
+
+ ServerName {{ endpoint }}
+ SSLEngine on
+ SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
+ SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM
+ SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
+ # See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8
+ SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
+ SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
+ ProxyPass / http://localhost:{{ int }}/
+ ProxyPassReverse / http://localhost:{{ int }}/
+ ProxyPreserveHost on
+ RequestHeader set X-Forwarded-Proto "https"
+ IncludeOptional /etc/apache2/mellon*/sp-location*.conf
+
+{% endfor -%}
+
+ Order deny,allow
+ Allow from all
+
+
+ Order allow,deny
+ Allow from all
+
+{% endif -%}
diff --git a/templates/parts/section-federation b/templates/parts/section-federation
new file mode 100644
index 00000000..65ee99ed
--- /dev/null
+++ b/templates/parts/section-federation
@@ -0,0 +1,10 @@
+{% if trusted_dashboards %}
+[federation]
+{% for dashboard_url in trusted_dashboards -%}
+trusted_dashboard = {{ dashboard_url }}
+{% endfor -%}
+{% endif %}
+{% for sp in fid_sps -%}
+[{{ sp['protocol-name'] }}]
+remote_id_attribute = {{ sp['remote-id-attribute'] }}
+{% endfor -%}
diff --git a/templates/wsgi-openstack-api.conf b/templates/wsgi-openstack-api.conf
new file mode 100644
index 00000000..942e2b29
--- /dev/null
+++ b/templates/wsgi-openstack-api.conf
@@ -0,0 +1,94 @@
+# Configuration file maintained by Juju. Local changes may be overwritten.
+
+{% if port -%}
+Listen {{ port }}
+{% endif -%}
+
+{% if admin_port -%}
+Listen {{ admin_port }}
+{% endif -%}
+
+{% if public_port -%}
+Listen {{ public_port }}
+{% endif -%}
+
+{% if port -%}
+
+ WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
+ display-name=%{GROUP}
+ WSGIProcessGroup {{ service_name }}
+ WSGIScriptAlias / {{ script }}
+ WSGIApplicationGroup %{GLOBAL}
+ WSGIPassAuthorization On
+ = 2.4>
+ ErrorLogFormat "%{cu}t %M"
+
+ ErrorLog /var/log/apache2/{{ service_name }}_error.log
+ CustomLog /var/log/apache2/{{ service_name }}_access.log combined
+
+
+ = 2.4>
+ Require all granted
+
+
+ Order allow,deny
+ Allow from all
+
+
+ IncludeOptional /etc/apache2/mellon*/sp-location*.conf
+
+{% endif -%}
+
+{% if admin_port -%}
+
+ WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
+ display-name=%{GROUP}
+ WSGIProcessGroup {{ service_name }}-admin
+ WSGIScriptAlias / {{ admin_script }}
+ WSGIApplicationGroup %{GLOBAL}
+ WSGIPassAuthorization On
+ = 2.4>
+ ErrorLogFormat "%{cu}t %M"
+
+ ErrorLog /var/log/apache2/{{ service_name }}_error.log
+ CustomLog /var/log/apache2/{{ service_name }}_access.log combined
+
+
+ = 2.4>
+ Require all granted
+
+
+ Order allow,deny
+ Allow from all
+
+
+ IncludeOptional /etc/apache2/mellon*/sp-location*.conf
+
+{% endif -%}
+
+{% if public_port -%}
+
+ WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
+ display-name=%{GROUP}
+ WSGIProcessGroup {{ service_name }}-public
+ WSGIScriptAlias / {{ public_script }}
+ WSGIApplicationGroup %{GLOBAL}
+ WSGIPassAuthorization On
+ = 2.4>
+ ErrorLogFormat "%{cu}t %M"
+
+ ErrorLog /var/log/apache2/{{ service_name }}_error.log
+ CustomLog /var/log/apache2/{{ service_name }}_access.log combined
+
+
+ = 2.4>
+ Require all granted
+
+
+ Order allow,deny
+ Allow from all
+
+
+ IncludeOptional /etc/apache2/mellon*/sp-location*.conf
+
+{% endif -%}
diff --git a/unit_tests/test_keystone_contexts.py b/unit_tests/test_keystone_contexts.py
index d8d256e0..42a7b7e5 100644
--- a/unit_tests/test_keystone_contexts.py
+++ b/unit_tests/test_keystone_contexts.py
@@ -217,3 +217,204 @@ class TestKeystoneContexts(CharmTestCase):
mock_is_elected_leader.return_value = True
self.assertEqual({'token_flush': True}, ctxt())
+
+ @patch.object(context, 'relation_ids')
+ @patch.object(context, 'related_units')
+ @patch.object(context, 'relation_get')
+ def test_keystone_fid_service_provider_rdata(
+ self, mock_relation_get, mock_related_units,
+ mock_relation_ids):
+ os.environ['JUJU_UNIT_NAME'] = 'keystone'
+
+ def relation_ids_side_effect(rname):
+ return {
+ 'keystone-fid-service-provider': {
+ 'keystone-fid-service-provider:0',
+ 'keystone-fid-service-provider:1',
+ 'keystone-fid-service-provider:2'
+ }
+ }[rname]
+
+ mock_relation_ids.side_effect = relation_ids_side_effect
+
+ def related_units_side_effect(rid):
+ return {
+ 'keystone-fid-service-provider:0': ['sp-mellon/0'],
+ 'keystone-fid-service-provider:1': ['sp-shib/0'],
+ 'keystone-fid-service-provider:2': ['sp-oidc/0'],
+ }[rid]
+ mock_related_units.side_effect = related_units_side_effect
+
+ def relation_get_side_effect(unit, rid):
+ # one unit only as the relation is container-scoped
+ return {
+ "keystone-fid-service-provider:0": {
+ "sp-mellon/0": {
+ "ingress-address": '10.0.0.10',
+ "protocol-name": '"saml2"',
+ "remote-id-attribute": '"MELLON_IDP"',
+ },
+ },
+ "keystone-fid-service-provider:1": {
+ "sp-shib/0": {
+ "ingress-address": '10.0.0.10',
+ "protocol-name": '"mapped"',
+ "remote-id-attribute": '"Shib-Identity-Provider"',
+ },
+ },
+ "keystone-fid-service-provider:2": {
+ "sp-oidc/0": {
+ "ingress-address": '10.0.0.10',
+ "protocol-name": '"oidc"',
+ "remote-id-attribute": '"HTTP_OIDC_ISS"',
+ },
+ },
+ }[rid][unit]
+
+ mock_relation_get.side_effect = relation_get_side_effect
+ ctxt = context.KeystoneFIDServiceProviderContext()
+
+ self.maxDiff = None
+ self.assertItemsEqual(
+ ctxt(),
+ {
+ "fid_sps": [
+ {
+ "protocol-name": "saml2",
+ "remote-id-attribute": "MELLON_IDP",
+ },
+ {
+ "protocol-name": "mapped",
+ "remote-id-attribute": "Shib-Identity-Provider",
+ },
+ {
+ "protocol-name": "oidc",
+ "remote-id-attribute": "HTTP_OIDC_ISS",
+ },
+ ]
+ }
+ )
+
+ @patch.object(context, 'relation_ids')
+ def test_keystone_fid_service_provider_empty(
+ self, mock_relation_ids):
+ os.environ['JUJU_UNIT_NAME'] = 'keystone'
+
+ def relation_ids_side_effect(rname):
+ return {
+ 'keystone-fid-service-provider': {}
+ }[rname]
+
+ mock_relation_ids.side_effect = relation_ids_side_effect
+ ctxt = context.KeystoneFIDServiceProviderContext()
+
+ self.maxDiff = None
+ self.assertItemsEqual(ctxt(), {})
+
+ @patch.object(context, 'relation_ids')
+ @patch.object(context, 'related_units')
+ @patch.object(context, 'relation_get')
+ def test_websso_trusted_dashboard_urls_generated(
+ self, mock_relation_get, mock_related_units,
+ mock_relation_ids):
+ os.environ['JUJU_UNIT_NAME'] = 'keystone'
+
+ def relation_ids_side_effect(rname):
+ return {
+ 'websso-trusted-dashboard': {
+ 'websso-trusted-dashboard:0',
+ 'websso-trusted-dashboard:1',
+ 'websso-trusted-dashboard:2'
+ }
+ }[rname]
+
+ mock_relation_ids.side_effect = relation_ids_side_effect
+
+ def related_units_side_effect(rid):
+ return {
+ 'websso-trusted-dashboard:0': ['dashboard-blue/0',
+ 'dashboard-blue/1'],
+ 'websso-trusted-dashboard:1': ['dashboard-red/0',
+ 'dashboard-red/1'],
+ 'websso-trusted-dashboard:2': ['dashboard-green/0',
+ 'dashboard-green/1']
+ }[rid]
+ mock_related_units.side_effect = related_units_side_effect
+
+ def relation_get_side_effect(unit, rid):
+ return {
+ "websso-trusted-dashboard:0": {
+ "dashboard-blue/0": { # dns-ha
+ "ingress-address": '10.0.0.10',
+ "scheme": "https://",
+ "hostname": "horizon.intranet.test",
+ "path": "/auth/websso/",
+ },
+ "dashboard-blue/1": { # dns-ha
+ "ingress-address": '10.0.0.11',
+ "scheme": "https://",
+ "hostname": "horizon.intranet.test",
+ "path": "/auth/websso/",
+ },
+ },
+ "websso-trusted-dashboard:1": {
+ "dashboard-red/0": { # vip
+ "ingress-address": '10.0.0.12',
+ "scheme": "https://",
+ "hostname": "10.0.0.100",
+ "path": "/auth/websso/",
+ },
+ "dashboard-red/1": { # vip
+ "ingress-address": '10.0.0.13',
+ "scheme": "https://",
+ "hostname": "10.0.0.100",
+ "path": "/auth/websso/",
+ },
+ },
+ "websso-trusted-dashboard:2": {
+ "dashboard-green/0": { # vip-less, dns-ha-less
+ "ingress-address": '10.0.0.14',
+ "scheme": "http://",
+ "hostname": "10.0.0.14",
+ "path": "/auth/websso/",
+ },
+ "dashboard-green/1": {
+ "ingress-address": '10.0.0.15',
+ "scheme": "http://",
+ "hostname": "10.0.0.15",
+ "path": "/auth/websso/",
+ },
+ },
+ }[rid][unit]
+
+ mock_relation_get.side_effect = relation_get_side_effect
+ ctxt = context.WebSSOTrustedDashboardContext()
+
+ self.maxDiff = None
+ self.assertEqual(
+ ctxt(),
+ {
+ 'trusted_dashboards': set([
+ 'https://horizon.intranet.test/auth/websso/',
+ 'https://10.0.0.100/auth/websso/',
+ 'http://10.0.0.14/auth/websso/',
+ 'http://10.0.0.15/auth/websso/',
+ ])
+ }
+ )
+
+ @patch.object(context, 'relation_ids')
+ def test_websso_trusted_dashboard_empty(
+ self, mock_relation_ids):
+ os.environ['JUJU_UNIT_NAME'] = 'keystone'
+
+ def relation_ids_side_effect(rname):
+ return {
+ 'websso-trusted-dashboard': {}
+ }[rname]
+
+ mock_relation_ids.side_effect = relation_ids_side_effect
+ ctxt = context.WebSSOTrustedDashboardContext()
+
+ self.maxDiff = None
+ self.assertItemsEqual(ctxt(), {})
diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py
index b3c8c8d8..a214b757 100644
--- a/unit_tests/test_keystone_hooks.py
+++ b/unit_tests/test_keystone_hooks.py
@@ -93,7 +93,6 @@ TO_PATCH = [
'update_nrpe_config',
'ensure_ssl_dirs',
'is_db_ready',
- 'keystone_service',
'create_or_show_domain',
'get_api_version',
# other
@@ -441,6 +440,7 @@ class KeystoneRelationTests(CharmTestCase):
self.assertTrue(update.called)
self.assertTrue(mock_update_domains.called)
+ @patch.object(hooks, 'os_release')
@patch.object(hooks, 'run_in_apache')
@patch.object(hooks, 'initialise_pki')
@patch.object(hooks, 'is_db_initialised')
@@ -460,7 +460,9 @@ class KeystoneRelationTests(CharmTestCase):
ensure_ssl_dir,
mock_db_init,
mock_initialise_pki,
- mock_run_in_apache):
+ mock_run_in_apache,
+ os_release):
+ os_release.return_value = 'ocata'
self.enable_memcache.return_value = False
mock_run_in_apache.return_value = False
ensure_ssl_cert.return_value = False
@@ -1087,9 +1089,14 @@ class KeystoneRelationTests(CharmTestCase):
@patch.object(hooks, 'is_unit_paused_set')
@patch.object(hooks, 'is_db_initialised')
+ @patch.object(utils, 'run_in_apache')
+ @patch.object(utils, 'service_restart')
def test_domain_backend_changed_complete(self,
+ service_restart,
+ run_in_apache,
is_db_initialised,
is_unit_paused_set):
+ run_in_apache.return_value = True
self.get_api_version.return_value = 3
self.relation_get.side_effect = ['mydomain', 'nonce2']
self.is_leader.return_value = True
@@ -1099,7 +1106,6 @@ class KeystoneRelationTests(CharmTestCase):
mock_kv.get.return_value = None
self.unitdata.kv.return_value = mock_kv
is_unit_paused_set.return_value = False
- self.keystone_service.return_value = 'apache2'
hooks.domain_backend_changed()
@@ -1113,16 +1119,21 @@ class KeystoneRelationTests(CharmTestCase):
rid=None),
])
self.create_or_show_domain.assert_called_with('mydomain')
- self.service_restart.assert_called_with('apache2')
+ service_restart.assert_called_with('apache2')
mock_kv.set.assert_called_with('domain-restart-nonce-mydomain',
'nonce2')
self.assertTrue(mock_kv.flush.called)
@patch.object(hooks, 'is_unit_paused_set')
@patch.object(hooks, 'is_db_initialised')
+ @patch.object(utils, 'run_in_apache')
+ @patch.object(utils, 'service_restart')
def test_domain_backend_changed_complete_follower(self,
+ service_restart,
+ run_in_apache,
is_db_initialised,
is_unit_paused_set):
+ run_in_apache.return_value = True
self.get_api_version.return_value = 3
self.relation_get.side_effect = ['mydomain', 'nonce2']
self.is_leader.return_value = False
@@ -1132,7 +1143,6 @@ class KeystoneRelationTests(CharmTestCase):
mock_kv.get.return_value = None
self.unitdata.kv.return_value = mock_kv
is_unit_paused_set.return_value = False
- self.keystone_service.return_value = 'apache2'
hooks.domain_backend_changed()
@@ -1147,7 +1157,84 @@ class KeystoneRelationTests(CharmTestCase):
])
# Only lead unit will create the domain
self.assertFalse(self.create_or_show_domain.called)
- self.service_restart.assert_called_with('apache2')
+ service_restart.assert_called_with('apache2')
mock_kv.set.assert_called_with('domain-restart-nonce-mydomain',
'nonce2')
self.assertTrue(mock_kv.flush.called)
+
+ @patch.object(hooks, 'os_release')
+ @patch.object(hooks, 'relation_id')
+ @patch.object(hooks, 'is_unit_paused_set')
+ @patch.object(hooks, 'is_db_initialised')
+ @patch.object(utils, 'run_in_apache')
+ @patch.object(utils, 'service_restart')
+ def test_fid_service_provider_changed_complete(
+ self,
+ service_restart,
+ run_in_apache,
+ is_db_initialised,
+ is_unit_paused_set,
+ relation_id, os_release):
+ os_release.return_value = 'ocata'
+ rel = 'keystone-fid-service-provider:0'
+ relation_id.return_value = rel
+ run_in_apache.return_value = True
+ self.get_api_version.return_value = 3
+ self.relation_get.side_effect = ['"nonce2"']
+ self.is_leader.return_value = True
+ self.is_db_ready.return_value = True
+ is_db_initialised.return_value = True
+ mock_kv = MagicMock()
+ mock_kv.get.return_value = None
+ self.unitdata.kv.return_value = mock_kv
+ is_unit_paused_set.return_value = False
+
+ hooks.keystone_fid_service_provider_changed()
+
+ self.assertTrue(self.get_api_version.called)
+ self.relation_get.assert_has_calls([
+ call('restart-nonce'),
+ ])
+ service_restart.assert_called_with('apache2')
+ mock_kv.set.assert_called_with(
+ 'fid-restart-nonce-{}'.format(rel), 'nonce2')
+ self.assertTrue(mock_kv.flush.called)
+
+ @patch.object(hooks, 'os_release')
+ @patch.object(hooks, 'relation_id')
+ @patch.object(hooks, 'is_unit_paused_set')
+ @patch.object(hooks, 'is_db_initialised')
+ @patch.object(utils, 'run_in_apache')
+ @patch.object(utils, 'service_restart')
+ def test_fid_service_provider_changed_complete_follower(
+ self,
+ service_restart,
+ run_in_apache,
+ is_db_initialised,
+ is_unit_paused_set,
+ relation_id, os_release):
+ os_release.return_value = 'ocata'
+ rel = 'keystone-fid-service-provider:0'
+ relation_id.return_value = rel
+ run_in_apache.return_value = True
+ self.get_api_version.return_value = 3
+ self.relation_get.side_effect = ['"nonce2"']
+ self.is_leader.return_value = False
+ self.is_db_ready.return_value = True
+ is_db_initialised.return_value = True
+ mock_kv = MagicMock()
+ mock_kv.get.return_value = None
+ self.unitdata.kv.return_value = mock_kv
+ is_unit_paused_set.return_value = False
+
+ hooks.keystone_fid_service_provider_changed()
+
+ self.assertTrue(self.get_api_version.called)
+ self.relation_get.assert_has_calls([
+ call('restart-nonce'),
+ ])
+ service_restart.assert_called_with('apache2')
+ mock_kv.set.assert_called_with(
+ 'fid-restart-nonce-{}'.format(rel),
+ 'nonce2')
+ self.assertTrue(mock_kv.flush.called)