Add support for Keystone API version 3

This change enables the charm to configure the Openstack Dashboard
to support Keystone v3 integration. Mitaka is the earliest release
to support Dashboard and v3 integration so v3 integration should
only be enabled on Mitaka or above.

A new identity policy template now ships with the charm which is
specifically for v3 integration.

Both the local settings file and the new v3 policy file need the
admin domain id. This is now passed to the charm from Keystone via
the identity service relation.

The openstack-dashboard package uses
django.contrib.sessions.backends.signed_cookies for session
management but cookies are not large enough to store domain scoped
tokens so a different session management engine is needed. This patch
adds the option to relate the charm to a database backend. If the
relation is present then the charm uses the
django.contrib.sessions.backends.cached_db session engine. This
stores the session information in the database and also caches the
session information locally using memcache.

For details on Dashboard and v3 integration see
https://wiki.openstack.org/wiki/Horizon/DomainWorkFlow

Change-Id: I24f514e29811752d7c0c5347a1157d9778297738
Partial-Bug: 1595685
This commit is contained in:
Liam Young 2016-06-28 08:01:43 +00:00
parent 78e54d7546
commit 94df23fbc5
14 changed files with 450 additions and 32 deletions

View File

@ -70,6 +70,17 @@ If both 'vip' and 'dns-ha' are set as they are mutually exclusive
If 'dns-ha' is set and none of the os-{admin,internal,public}-hostname(s) are
set
Whichever method has been used to cluster the charm the 'secret' option
should be set to ensure that the Django secret is consistent accross all units.
Keystone V3
===========
If the charm is being deployed into a keystone v3 enabled environment then the
charm needs to be related to a database to store session information. This is
only supported for Mitaka or later.
Use with a Load Balancing Proxy
===============================

View File

@ -290,3 +290,11 @@ options:
description: |
If True, redirects plain http requests to https port 443. For this option
to have an effect, SSL must be configured.
database-user:
default: horizon
type: string
description: Username for Horizon database access (if enabled)
database:
default: horizon
type: string
description: Database name for Horizon (if enabled)

View File

@ -110,9 +110,14 @@ class IdentityServiceContext(OSContextGenerator):
'service_port': rdata.get('service_port'),
'service_host': serv_host,
'service_protocol':
rdata.get('service_protocol') or 'http'
rdata.get('service_protocol') or 'http',
'api_version': rdata.get('api_version', '2')
}
# If using keystone v3 the context is incomplete without the
# admin domain id
if local_ctxt['api_version'] == '3':
local_ctxt['admin_domain_id'] = rdata.get(
'admin_domain_id')
if not context_complete(local_ctxt):
continue

View File

@ -12,6 +12,9 @@ from charmhelpers.core.hookenv import (
relation_ids,
unit_get,
status_set,
network_get_primary_address,
is_leader,
local_unit,
)
from charmhelpers.fetch import (
apt_update, apt_install,
@ -28,6 +31,7 @@ from charmhelpers.contrib.openstack.utils import (
openstack_upgrade_available,
os_release,
save_script_rc,
sync_db_with_multi_ipv6_addresses,
)
from charmhelpers.contrib.openstack.ha.utils import (
update_dns_ha_resource_params,
@ -46,6 +50,7 @@ from horizon_utils import (
INSTALL_DIR,
restart_on_change,
assess_status,
db_migration,
)
from charmhelpers.contrib.network.ip import (
get_iface_for_address,
@ -157,7 +162,7 @@ def keystone_joined(rel_id=None):
@hooks.hook('identity-service-relation-changed')
@restart_on_change(restart_map(), stopstart=True, sleep=3)
def keystone_changed():
CONFIGS.write(LOCAL_SETTINGS)
CONFIGS.write_all()
if relation_get('ca_cert'):
install_ca_cert(b64decode(relation_get('ca_cert')))
@ -289,6 +294,44 @@ def update_status():
log('Updating status.')
@hooks.hook('shared-db-relation-joined')
def db_joined():
if config('prefer-ipv6'):
sync_db_with_multi_ipv6_addresses(config('database'),
config('database-user'))
else:
host = None
try:
# NOTE: try to use network spaces
host = network_get_primary_address('shared-db')
except NotImplementedError:
# NOTE: fallback to private-address
host = unit_get('private-address')
relation_set(database=config('database'),
username=config('database-user'),
hostname=host)
@hooks.hook('shared-db-relation-changed')
@restart_on_change(restart_map(), stopstart=True, sleep=3)
def db_changed():
if 'shared-db' not in CONFIGS.complete_contexts():
log('shared-db relation incomplete. Peer not ready?')
return
CONFIGS.write_all()
if is_leader():
allowed_units = relation_get('allowed_units')
if allowed_units and local_unit() in allowed_units.split():
db_migration()
else:
log('Not running neutron database migration, either no'
' allowed_units or this unit is not present')
return
else:
log('Not running neutron database migration, not leader')
def main():
try:
hooks.execute(sys.argv)

View File

@ -93,6 +93,7 @@ GIT_PACKAGE_BLACKLIST = [
APACHE_CONF_DIR = "/etc/apache2"
LOCAL_SETTINGS = "/etc/openstack-dashboard/local_settings.py"
DASHBOARD_CONF_DIR = "/etc/openstack-dashboard/"
HAPROXY_CONF = "/etc/haproxy/haproxy.cfg"
APACHE_CONF = "%s/conf.d/openstack-dashboard.conf" % (APACHE_CONF_DIR)
APACHE_24_CONF = "%s/conf-available/openstack-dashboard.conf" \
@ -103,9 +104,10 @@ APACHE_24_DEFAULT = "%s/sites-available/000-default.conf" % (APACHE_CONF_DIR)
APACHE_SSL = "%s/sites-available/default-ssl" % (APACHE_CONF_DIR)
APACHE_DEFAULT = "%s/sites-available/default" % (APACHE_CONF_DIR)
INSTALL_DIR = "/usr/share/openstack-dashboard"
ROUTER_SETTING = \
"/usr/share/openstack-dashboard/openstack_dashboard/enabled/_40_router.py"
ROUTER_SETTING = ('/usr/share/openstack-dashboard/openstack_dashboard/enabled/'
'_40_router.py')
KEYSTONEV3_POLICY = ('/usr/share/openstack-dashboard/openstack_dashboard/conf/'
'keystonev3_policy.json')
TEMPLATES = 'templates'
CONFIG_FILES = OrderedDict([
@ -159,6 +161,10 @@ CONFIG_FILES = OrderedDict([
'hook_contexts': [horizon_contexts.RouterSettingContext()],
'services': ['apache2'],
}),
(KEYSTONEV3_POLICY, {
'hook_contexts': [horizon_contexts.IdentityServiceContext()],
'services': ['apache2'],
}),
])
@ -172,6 +178,15 @@ def register_configs():
HAPROXY_CONF,
PORTS_CONF]
if release >= 'mitaka':
configs.register(KEYSTONEV3_POLICY,
CONFIG_FILES[KEYSTONEV3_POLICY]['hook_contexts'])
CONFIG_FILES[LOCAL_SETTINGS]['hook_contexts'].append(
context.SharedDBContext(
user=config('database-user'),
database=config('database'),
ssl_dir=DASHBOARD_CONF_DIR))
for conf in confs:
configs.register(conf, CONFIG_FILES[conf]['hook_contexts'])
@ -197,6 +212,7 @@ def register_configs():
if os.path.exists(os.path.dirname(ROUTER_SETTING)):
configs.register(ROUTER_SETTING,
CONFIG_FILES[ROUTER_SETTING]['hook_contexts'])
return configs
@ -243,6 +259,10 @@ def determine_packages():
for p in GIT_PACKAGE_BLACKLIST:
packages.remove(p)
release = get_os_codename_install_source(config('openstack-origin'))
# Really should be handled as a dep in the openstack-dashboard package
if release >= 'mitaka':
packages.append('python-pymysql')
return list(set(packages))
@ -546,3 +566,8 @@ def _pause_resume_helper(f, configs):
f(assess_status_func(configs),
services=services(),
ports=None)
def db_migration():
cmd = ['/usr/share/openstack-dashboard/manage.py', 'syncdb', '--noinput']
subprocess.call(cmd)

View File

@ -0,0 +1 @@
horizon_hooks.py

View File

@ -0,0 +1 @@
horizon_hooks.py

View File

@ -0,0 +1 @@
horizon_hooks.py

View File

@ -22,6 +22,8 @@ requires:
ha:
interface: hacluster
scope: container
shared-db:
interface: mysql-shared
peers:
cluster:
interface: openstack-dashboard-ha

View File

@ -0,0 +1,223 @@
{
"admin_required": "role:Admin",
"cloud_admin": "rule:admin_required and domain_id:{{ admin_domain_id }}",
"service_role": "role:service",
"service_or_admin": "rule:admin_required or rule:service_role",
"owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s",
"admin_or_owner": "(rule:admin_required and domain_id:%(target.token.user.domain.id)s) or rule:owner",
"admin_and_matching_domain_id": "rule:admin_required and domain_id:%(domain_id)s",
"service_admin_or_owner": "rule:service_or_admin or rule:owner",
"default": "rule:admin_required",
"identity:get_region": "",
"identity:list_regions": "",
"identity:create_region": "rule:cloud_admin",
"identity:update_region": "rule:cloud_admin",
"identity:delete_region": "rule:cloud_admin",
"identity:get_service": "rule:admin_required",
"identity:list_services": "rule:admin_required",
"identity:create_service": "rule:cloud_admin",
"identity:update_service": "rule:cloud_admin",
"identity:delete_service": "rule:cloud_admin",
"identity:get_endpoint": "rule:admin_required",
"identity:list_endpoints": "rule:admin_required",
"identity:create_endpoint": "rule:cloud_admin",
"identity:update_endpoint": "rule:cloud_admin",
"identity:delete_endpoint": "rule:cloud_admin",
"identity:get_domain": "rule:cloud_admin or rule:admin_and_matching_domain_id",
"identity:list_domains": "rule:cloud_admin",
"identity:create_domain": "rule:cloud_admin",
"identity:update_domain": "rule:cloud_admin",
"identity:delete_domain": "rule:cloud_admin",
"admin_and_matching_target_project_domain_id": "rule:admin_required and domain_id:%(target.project.domain_id)s",
"admin_and_matching_project_domain_id": "rule:admin_required and domain_id:%(project.domain_id)s",
"identity:get_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id or project_id:%(target.project.id)s",
"identity:list_projects": "rule:cloud_admin or rule:admin_and_matching_domain_id",
"identity:list_user_projects": "rule:owner or rule:admin_and_matching_domain_id",
"identity:create_project": "rule:cloud_admin or rule:admin_and_matching_project_domain_id",
"identity:update_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id",
"identity:delete_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id",
"admin_and_matching_target_user_domain_id": "rule:admin_required and domain_id:%(target.user.domain_id)s",
"admin_and_matching_user_domain_id": "rule:admin_required and domain_id:%(user.domain_id)s",
"identity:get_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
"identity:list_users": "rule:cloud_admin or rule:admin_and_matching_domain_id",
"identity:create_user": "rule:cloud_admin or rule:admin_and_matching_user_domain_id",
"identity:update_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
"identity:delete_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id",
"admin_and_matching_target_group_domain_id": "rule:admin_required and domain_id:%(target.group.domain_id)s",
"admin_and_matching_group_domain_id": "rule:admin_required and domain_id:%(group.domain_id)s",
"identity:get_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:list_groups": "rule:cloud_admin or rule:admin_and_matching_domain_id",
"identity:list_groups_for_user": "rule:owner or rule:admin_and_matching_target_user_domain_id",
"identity:create_group": "rule:cloud_admin or rule:admin_and_matching_group_domain_id",
"identity:update_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:delete_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:list_users_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:remove_user_from_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:check_user_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:add_user_to_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id",
"identity:get_credential": "rule:admin_required",
"identity:list_credentials": "rule:admin_required or user_id:%(user_id)s",
"identity:create_credential": "rule:admin_required",
"identity:update_credential": "rule:admin_required",
"identity:delete_credential": "rule:admin_required",
"identity:ec2_get_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
"identity:ec2_list_credentials": "rule:admin_required or rule:owner",
"identity:ec2_create_credential": "rule:admin_required or rule:owner",
"identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)",
"identity:get_role": "rule:admin_required",
"identity:list_roles": "rule:admin_required",
"identity:create_role": "rule:cloud_admin",
"identity:update_role": "rule:cloud_admin",
"identity:delete_role": "rule:cloud_admin",
"identity:get_domain_role": "rule:cloud_admin or rule:get_domain_roles",
"identity:list_domain_roles": "rule:cloud_admin or rule:list_domain_roles",
"identity:create_domain_role": "rule:cloud_admin or rule:domain_admin_matches_domain_role",
"identity:update_domain_role": "rule:cloud_admin or rule:domain_admin_matches_target_domain_role",
"identity:delete_domain_role": "rule:cloud_admin or rule:domain_admin_matches_target_domain_role",
"domain_admin_matches_domain_role": "rule:admin_required and domain_id:%(role.domain_id)s",
"get_domain_roles": "rule:domain_admin_matches_target_domain_role or rule:project_admin_matches_target_domain_role",
"domain_admin_matches_target_domain_role": "rule:admin_required and domain_id:%(target.role.domain_id)s",
"project_admin_matches_target_domain_role": "rule:admin_required and project_domain_id:%(target.role.domain_id)s",
"list_domain_roles": "rule:domain_admin_matches_filter_on_list_domain_roles or rule:project_admin_matches_filter_on_list_domain_roles",
"domain_admin_matches_filter_on_list_domain_roles": "rule:admin_required and domain_id:%(domain_id)s",
"project_admin_matches_filter_on_list_domain_roles": "rule:admin_required and project_domain_id:%(domain_id)s",
"identity:get_implied_role": "rule:cloud_admin",
"identity:list_implied_roles": "rule:cloud_admin",
"identity:create_implied_role": "rule:cloud_admin",
"identity:delete_implied_role": "rule:cloud_admin",
"identity:list_role_inference_rules": "rule:cloud_admin",
"identity:check_implied_role": "rule:cloud_admin",
"identity:check_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
"identity:list_grants": "rule:cloud_admin or rule:domain_admin_for_list_grants or rule:project_admin_for_list_grants",
"identity:create_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
"identity:revoke_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants",
"domain_admin_for_grants": "rule:domain_admin_for_global_role_grants or rule:domain_admin_for_domain_role_grants",
"domain_admin_for_global_role_grants": "rule:admin_required and None:%(target.role.domain_id)s and rule:domain_admin_grant_match",
"domain_admin_for_domain_role_grants": "rule:admin_required and domain_id:%(target.role.domain_id)s and rule:domain_admin_grant_match",
"domain_admin_grant_match": "domain_id:%(domain_id)s or domain_id:%(target.project.domain_id)s",
"project_admin_for_grants": "rule:project_admin_for_global_role_grants or rule:project_admin_for_domain_role_grants",
"project_admin_for_global_role_grants": "rule:admin_required and None:%(target.role.domain_id)s and project_id:%(project_id)s",
"project_admin_for_domain_role_grants": "rule:admin_required and project_domain_id:%(target.role.domain_id)s and project_id:%(project_id)s",
"domain_admin_for_list_grants": "rule:admin_required and rule:domain_admin_grant_match",
"project_admin_for_list_grants": "rule:admin_required and project_id:%(project_id)s",
"admin_on_domain_filter" : "rule:admin_required and domain_id:%(scope.domain.id)s",
"admin_on_project_filter" : "rule:admin_required and project_id:%(scope.project.id)s",
"admin_on_domain_of_project_filter" : "rule:admin_required and domain_id:%(target.project.domain_id)s",
"identity:list_role_assignments": "rule:cloud_admin or rule:admin_on_domain_filter or rule:admin_on_project_filter",
"identity:list_role_assignments_for_tree": "rule:cloud_admin or rule:admin_on_domain_of_project_filter",
"identity:get_policy": "rule:cloud_admin",
"identity:list_policies": "rule:cloud_admin",
"identity:create_policy": "rule:cloud_admin",
"identity:update_policy": "rule:cloud_admin",
"identity:delete_policy": "rule:cloud_admin",
"identity:change_password": "rule:owner",
"identity:check_token": "rule:admin_or_owner",
"identity:validate_token": "rule:service_admin_or_owner",
"identity:validate_token_head": "rule:service_or_admin",
"identity:revocation_list": "rule:service_or_admin",
"identity:revoke_token": "rule:admin_or_owner",
"identity:create_trust": "user_id:%(trust.trustor_user_id)s",
"identity:list_trusts": "",
"identity:list_roles_for_trust": "",
"identity:get_role_for_trust": "",
"identity:delete_trust": "",
"identity:create_consumer": "rule:admin_required",
"identity:get_consumer": "rule:admin_required",
"identity:list_consumers": "rule:admin_required",
"identity:delete_consumer": "rule:admin_required",
"identity:update_consumer": "rule:admin_required",
"identity:authorize_request_token": "rule:admin_required",
"identity:list_access_token_roles": "rule:admin_required",
"identity:get_access_token_role": "rule:admin_required",
"identity:list_access_tokens": "rule:admin_required",
"identity:get_access_token": "rule:admin_required",
"identity:delete_access_token": "rule:admin_required",
"identity:list_projects_for_endpoint": "rule:admin_required",
"identity:add_endpoint_to_project": "rule:admin_required",
"identity:check_endpoint_in_project": "rule:admin_required",
"identity:list_endpoints_for_project": "rule:admin_required",
"identity:remove_endpoint_from_project": "rule:admin_required",
"identity:create_endpoint_group": "rule:admin_required",
"identity:list_endpoint_groups": "rule:admin_required",
"identity:get_endpoint_group": "rule:admin_required",
"identity:update_endpoint_group": "rule:admin_required",
"identity:delete_endpoint_group": "rule:admin_required",
"identity:list_projects_associated_with_endpoint_group": "rule:admin_required",
"identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required",
"identity:get_endpoint_group_in_project": "rule:admin_required",
"identity:list_endpoint_groups_for_project": "rule:admin_required",
"identity:add_endpoint_group_to_project": "rule:admin_required",
"identity:remove_endpoint_group_from_project": "rule:admin_required",
"identity:create_identity_provider": "rule:cloud_admin",
"identity:list_identity_providers": "rule:cloud_admin",
"identity:get_identity_providers": "rule:cloud_admin",
"identity:update_identity_provider": "rule:cloud_admin",
"identity:delete_identity_provider": "rule:cloud_admin",
"identity:create_protocol": "rule:cloud_admin",
"identity:update_protocol": "rule:cloud_admin",
"identity:get_protocol": "rule:cloud_admin",
"identity:list_protocols": "rule:cloud_admin",
"identity:delete_protocol": "rule:cloud_admin",
"identity:create_mapping": "rule:cloud_admin",
"identity:get_mapping": "rule:cloud_admin",
"identity:list_mappings": "rule:cloud_admin",
"identity:delete_mapping": "rule:cloud_admin",
"identity:update_mapping": "rule:cloud_admin",
"identity:create_service_provider": "rule:cloud_admin",
"identity:list_service_providers": "rule:cloud_admin",
"identity:get_service_provider": "rule:cloud_admin",
"identity:update_service_provider": "rule:cloud_admin",
"identity:delete_service_provider": "rule:cloud_admin",
"identity:get_auth_catalog": "",
"identity:get_auth_projects": "",
"identity:get_auth_domains": "",
"identity:list_projects_for_groups": "",
"identity:list_domains_for_groups": "",
"identity:list_revoke_events": "",
"identity:create_policy_association_for_endpoint": "rule:cloud_admin",
"identity:check_policy_association_for_endpoint": "rule:cloud_admin",
"identity:delete_policy_association_for_endpoint": "rule:cloud_admin",
"identity:create_policy_association_for_service": "rule:cloud_admin",
"identity:check_policy_association_for_service": "rule:cloud_admin",
"identity:delete_policy_association_for_service": "rule:cloud_admin",
"identity:create_policy_association_for_region_and_service": "rule:cloud_admin",
"identity:check_policy_association_for_region_and_service": "rule:cloud_admin",
"identity:delete_policy_association_for_region_and_service": "rule:cloud_admin",
"identity:get_policy_for_endpoint": "rule:cloud_admin",
"identity:list_endpoints_for_policy": "rule:cloud_admin",
"identity:create_domain_config": "rule:cloud_admin",
"identity:get_domain_config": "rule:cloud_admin",
"identity:update_domain_config": "rule:cloud_admin",
"identity:delete_domain_config": "rule:cloud_admin",
"identity:get_domain_config_default": "rule:cloud_admin"
}

View File

@ -137,7 +137,25 @@ CACHES = {
'LOCATION': '127.0.0.1:11211',
},
}
{% if database_host -%}
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
DATABASES = {
'default': {
# Database configuration here
'ENGINE': 'django.db.backends.mysql',
'NAME': '{{ database }}',
'USER': '{{ database_user }}',
'PASSWORD': '{{ database_password }}',
'HOST': '{{ database_host }}',
'default-character-set': 'utf8'
}
}
{% else -%}
{% if api_version == "3" -%}
# Warning: Please add DB relation for Keystone v3 + HA deployments
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
{% endif -%}
{% endif -%}
#CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
@ -169,8 +187,16 @@ AVAILABLE_REGIONS = [
{% endif -%}
OPENSTACK_HOST = "{{ service_host }}"
OPENSTACK_KEYSTONE_URL = "{{ service_protocol }}://%s:{{ service_port }}/v2.0" % OPENSTACK_HOST
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "{{ default_role }}"
{% if api_version == "3" -%}
OPENSTACK_KEYSTONE_URL = "{{ service_protocol }}://%s:{{ service_port }}/v3" % OPENSTACK_HOST
OPENSTACK_API_VERSIONS = { "identity": 3, }
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "{{ admin_domain_id }}"
{% else -%}
OPENSTACK_KEYSTONE_URL = "{{ service_protocol }}://%s:{{ service_port }}/v2.0" % OPENSTACK_HOST
{% endif -%}
# Enables keystone web single-sign-on if set to True.
#WEBSSO_ENABLED = False
@ -435,15 +461,17 @@ TIME_ZONE = "UTC"
# policy.v3cloudsample.json
# Having matching policy files on the Horizon and Keystone servers is essential
# for normal operation. This holds true for all services and their policy files.
#POLICY_FILES = {
# 'identity': 'keystone_policy.json',
# 'compute': 'nova_policy.json',
# 'volume': 'cinder_policy.json',
# 'image': 'glance_policy.json',
# 'orchestration': 'heat_policy.json',
# 'network': 'neutron_policy.json',
# 'telemetry': 'ceilometer_policy.json',
#}
{% if api_version == "3" -%}
POLICY_FILES = {
'identity': 'keystonev3_policy.json',
'compute': 'nova_policy.json',
'volume': 'cinder_policy.json',
'image': 'glance_policy.json',
'orchestration': 'heat_policy.json',
'network': 'neutron_policy.json',
'telemetry': 'ceilometer_policy.json',
}
{% endif -%}
# TODO: (david-lyle) remove when plugins support adding settings.
# Note: Only used when trove-dashboard plugin is configured to be used by

View File

@ -210,7 +210,7 @@ class TestHorizonContexts(CharmTestCase):
self.context_complete.return_value = True
self.assertEquals(horizon_contexts.IdentityServiceContext()(),
{'service_host': 'foo', 'service_port': 5000,
'service_protocol': 'http'})
'api_version': '2', 'service_protocol': 'http'})
@patch("horizon_contexts.format_ipv6_addr")
def test_IdentityServiceContext_single_region(self, mock_format_ipv6_addr):
@ -223,7 +223,7 @@ class TestHorizonContexts(CharmTestCase):
self.context_complete.return_value = True
self.assertEquals(horizon_contexts.IdentityServiceContext()(),
{'service_host': 'foo', 'service_port': 5000,
'service_protocol': 'http'})
'api_version': '2', 'service_protocol': 'http'})
@patch("horizon_contexts.format_ipv6_addr")
def test_IdentityServiceContext_multi_region(self, mock_format_ipv6_addr):
@ -236,12 +236,46 @@ class TestHorizonContexts(CharmTestCase):
self.context_complete.return_value = True
self.assertEqual(horizon_contexts.IdentityServiceContext()(),
{'service_host': 'foo', 'service_port': 5000,
'service_protocol': 'http',
'service_protocol': 'http', 'api_version': '2',
'regions': [{'endpoint': 'http://foo:5000/v2.0',
'title': 'regionOne'},
{'endpoint': 'http://foo:5000/v2.0',
'title': 'regionTwo'}]})
@patch("horizon_contexts.format_ipv6_addr")
def test_IdentityServiceContext_api3(self, mock_format_ipv6_addr):
mock_format_ipv6_addr.return_value = "foo"
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar', 'baz']
self.relation_get.side_effect = self.test_relation.get
self.test_relation.set({
'service_host': 'foo',
'service_port': 5000,
'region': 'regionOne',
'api_version': '3',
'admin_domain_id': 'admindomainid'})
self.context_complete.return_value = True
self.assertEquals(horizon_contexts.IdentityServiceContext()(), {
'service_host': 'foo',
'service_port': 5000,
'api_version': '3',
'admin_domain_id': 'admindomainid',
'service_protocol': 'http'})
@patch("horizon_contexts.format_ipv6_addr")
def test_IdentityServiceContext_api3_missing(self, mock_format_ipv6_addr):
mock_format_ipv6_addr.return_value = "foo"
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar', 'baz']
self.relation_get.side_effect = self.test_relation.get
self.test_relation.set({
'service_host': 'foo',
'service_port': 5000,
'region': 'regionOne',
'api_version': '3'})
self.context_complete.return_value = False
self.assertEquals(horizon_contexts.IdentityServiceContext()(), {})
def test_IdentityServiceContext_endpoint_type(self):
self.test_config.set('endpoint-type', 'internalURL')
self.assertEqual(horizon_contexts.IdentityServiceContext()(),

View File

@ -72,8 +72,10 @@ class TestHorizonHooks(CharmTestCase):
hooks.hooks.execute([
'hooks/{}'.format(hookname)])
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'git_install_requested')
def test_install_hook(self, _git_requested):
def test_install_hook(self, _git_requested, _determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = False
self.filter_installed_packages.return_value = ['foo', 'bar']
self.os_release.return_value = 'icehouse'
@ -82,8 +84,10 @@ class TestHorizonHooks(CharmTestCase):
self.apt_update.assert_called_with(fatal=True)
self.apt_install.assert_called_with(['foo', 'bar'], fatal=True)
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'git_install_requested')
def test_install_hook_precise(self, _git_requested):
def test_install_hook_precise(self, _git_requested, _determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = False
self.filter_installed_packages.return_value = ['foo', 'bar']
self.os_release.return_value = 'icehouse'
@ -97,8 +101,11 @@ class TestHorizonHooks(CharmTestCase):
]
self.apt_install.assert_has_calls(calls)
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'git_install_requested')
def test_install_hook_icehouse_pkgs(self, _git_requested):
def test_install_hook_icehouse_pkgs(self, _git_requested,
_determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = False
self.os_release.return_value = 'icehouse'
self._call_hook('install.real')
@ -108,8 +115,11 @@ class TestHorizonHooks(CharmTestCase):
)
self.assertTrue(self.apt_install.called)
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'git_install_requested')
def test_install_hook_pre_icehouse_pkgs(self, _git_requested):
def test_install_hook_pre_icehouse_pkgs(self, _git_requested,
_determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = False
self.os_release.return_value = 'grizzly'
self._call_hook('install.real')
@ -119,8 +129,10 @@ class TestHorizonHooks(CharmTestCase):
)
self.assertTrue(self.apt_install.called)
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'git_install_requested')
def test_install_hook_git(self, _git_requested):
def test_install_hook_git(self, _git_requested, _determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = True
self.filter_installed_packages.return_value = ['foo', 'bar']
repo = 'cloud:trusty-juno'
@ -145,10 +157,13 @@ class TestHorizonHooks(CharmTestCase):
self.apt_install.assert_called_with(['foo', 'bar'], fatal=True)
self.git_install.assert_called_with(projects_yaml)
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'path_hash')
@patch.object(utils, 'service')
@patch.object(utils, 'git_install_requested')
def test_upgrade_charm_hook(self, _git_requested, _service, _hash):
def test_upgrade_charm_hook(self, _git_requested, _service, _hash,
_determine_packages):
_determine_packages.return_value = []
_git_requested.return_value = False
side_effects = []
[side_effects.append(None) for f in RESTART_MAP.keys()]
@ -337,17 +352,13 @@ class TestHorizonHooks(CharmTestCase):
def test_keystone_changed_no_cert(self):
self.relation_get.return_value = None
self._call_hook('identity-service-relation-changed')
self.CONFIGS.write.assert_called_with(
'/etc/openstack-dashboard/local_settings.py'
)
self.CONFIGS.write_all.assert_called_with()
self.install_ca_cert.assert_not_called()
def test_keystone_changed_cert(self):
self.relation_get.return_value = 'certificate'
self._call_hook('identity-service-relation-changed')
self.CONFIGS.write.assert_called_with(
'/etc/openstack-dashboard/local_settings.py'
)
self.CONFIGS.write_all.assert_called_with()
self.install_ca_cert.assert_called_with('certificate')
def test_cluster_departed(self):

View File

@ -35,6 +35,29 @@ class TestHorizohorizon_utils(CharmTestCase):
def setUp(self):
super(TestHorizohorizon_utils, self).setUp(horizon_utils, TO_PATCH)
@patch.object(horizon_utils, 'get_os_codename_install_source')
@patch.object(horizon_utils, 'git_install_requested')
def test_determine_packages(self, _git_install_requested,
_get_os_codename_install_source):
_git_install_requested.return_value = False
_get_os_codename_install_source.return_value = 'icehouse'
self.assertEqual(horizon_utils.determine_packages(), [
'haproxy',
'python-novaclient',
'python-keystoneclient',
'openstack-dashboard-ubuntu-theme',
'python-memcache',
'openstack-dashboard',
'memcached'])
@patch.object(horizon_utils, 'get_os_codename_install_source')
@patch.object(horizon_utils, 'git_install_requested')
def test_determine_packages_mitaka(self, _git_install_requested,
_get_os_codename_install_source):
_git_install_requested.return_value = False
_get_os_codename_install_source.return_value = 'mitaka'
self.assertTrue('python-pymysql' in horizon_utils.determine_packages())
@patch('subprocess.call')
def test_enable_ssl(self, _call):
horizon_utils.enable_ssl()
@ -57,6 +80,8 @@ class TestHorizohorizon_utils(CharmTestCase):
('/etc/haproxy/haproxy.cfg', ['haproxy']),
('/usr/share/openstack-dashboard/openstack_dashboard/enabled/'
'_40_router.py', ['apache2']),
('/usr/share/openstack-dashboard/openstack_dashboard/conf/'
'keystonev3_policy.json', ['apache2']),
])
self.assertEquals(horizon_utils.restart_map(), ex_map)