Merge "Use scope in v3 identity client"

Jenkins 7 years ago committed by Gerrit Code Review
commit b15c5d5649

@ -26,6 +26,11 @@ can be used to:
- Run tests for admin APIs
- Generate test credentials on the fly (see `Dynamic Credentials`_)
When keystone uses a policy that requires domain scoped tokens for admin
actions, the flag ``admin_domain_scope`` must be set to ``True``.
The admin user configured, if any, must have a role assigned to the domain to
be usable.
Tempest allows for configuring pre-provisioned test credentials as well.
This can be done using the accounts.yaml file (see
`Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
@ -87,6 +92,14 @@ list of role names each of which will be assigned to each of the users created
by dynamic credentials. This option will not have any effect when Tempest is not
configured to use dynamic credentials.
When the ``admin_domain_scope`` option is set to ``True``, provisioned admin
accounts will be assigned a role on domain configured in
``default_credentials_domain_name``. This will make the accounts provisioned
usable in a cloud where domain scoped tokens are required by keystone for
admin operations. Note that the the initial pre-provision admin accounts,
configured in tempest.conf, must have a role on the same domain as well, for
Dynamic Credentials to work.
Pre-Provisioned Credentials
@ -124,6 +137,18 @@ should have a unique project. This is required to provide proper isolation
to the tests using the credentials, and failure to do this will likely cause
unexpected failures in some tests.
When the keystone in the target cloud requires domain scoped tokens to
perform admin actions, all pre-provisioned admin users must have a role
assigned on the domain where test accounts a provisioned.
The option ``admin_domain_scope`` is used to tell tempest that domain scoped
tokens shall be used. ``default_credentials_domain_name`` is the domain where
test accounts are expected to be provisioned if no domain is specified.
Note that if credentials are pre-provisioned via ``tempest account-generator``
the role on the domain will be assigned automatically for you, as long as
``admin_domain_scope`` as ``default_credentials_domain_name`` are configured
properly in tempest.conf.
Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.

@ -98,7 +98,8 @@ class BaseTrustsV3Test(base.BaseIdentityV3AdminTest):
os = clients.Manager(credentials=creds)
self.trustor_client = os.trusts_client
@ -266,7 +267,18 @@ class TrustsV3TestJSON(BaseTrustsV3Test):
def test_get_trusts_all(self):
# Simple function that can be used for cleanup
def set_scope(auth_provider, scope):
auth_provider.scope = scope
# Listing trusts can be done by trustor, by trustee, or without
# any filter if scoped to a project, so we must ensure token scope is
# project for this test.
original_scope = self.os_adm.auth_provider.scope
set_scope(self.os_adm.auth_provider, 'project')
self.addCleanup(set_scope, self.os_adm.auth_provider, original_scope)
trusts_get = self.trusts_client.list_trusts()['trusts']
trusts = [t for t in trusts_get
if t['id'] == self.trust_id]

@ -162,6 +162,12 @@ class BaseIdentityV3AdminTest(BaseIdentityV3Test):
cls.creds_client = cls.os_adm.credentials_client
cls.groups_client = cls.os_adm.groups_client
cls.projects_client = cls.os_adm.projects_client
if CONF.identity.admin_domain_scope:
# NOTE(andreaf) When keystone policy requires it, the identity
# admin clients for these tests shall use 'domain' scoped tokens.
# As the client manager is already created by the base class,
# we set the scope for the inner auth provider.
cls.os_adm.auth_provider.scope = 'domain'
def resource_setup(cls):

@ -193,17 +193,21 @@ def get_configured_admin_credentials(fill_in=True, identity_version=None):
# Wrapper around auth.get_credentials to use the configured identity version
# is none is specified
# if none is specified
def get_credentials(fill_in=True, identity_version=None, **kwargs):
params = dict(DEFAULT_PARAMS, **kwargs)
identity_version = identity_version or CONF.identity.auth_version
# In case of "v3" add the domain from config if not specified
# To honour the "default_credentials_domain_name", if not domain
# field is specified at all, add it the credential dict.
if identity_version == 'v3':
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
if 'domain' in x)
if not domain_fields.intersection(kwargs.keys()):
domain_name = CONF.auth.default_credentials_domain_name
params['user_domain_name'] = domain_name
# NOTE(andreaf) Setting domain_name implicitly sets user and
# project domain names, if they are None
params['domain_name'] = domain_name
auth_url = CONF.identity.uri_v3

@ -96,8 +96,15 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
os.networks_client, os.routers_client, os.subnets_client,
os.ports_client, os.security_groups_client)
return (os.identity_v3_client, os.projects_client,
os.users_v3_client, os.roles_v3_client, os.domains_client,
# We use a dedicated client manager for identity client in case we
# need a different token scope for them.
scope = 'domain' if CONF.identity.admin_domain_scope else 'project'
identity_os = clients.Manager(self.default_admin_creds,
return (identity_os.identity_v3_client,
identity_os.users_v3_client, identity_os.roles_v3_client,
os.networks_client, os.routers_client,
os.subnets_client, os.ports_client,
@ -136,7 +143,8 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
self.creds_client.assign_user_role(user, project,
role_assigned = True
if self.identity_version == 'v3':
if (self.identity_version == 'v3' and
user, CONF.identity.admin_role)
# Add roles specified in config file

@ -166,6 +166,10 @@ IdentityGroup = [
help="ID of the default domain"),
help="Whether keystone identity v3 policy required "
"a domain scoped token to use admin APIs")
identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',

@ -532,6 +532,7 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
endpoint_type = endpoint_type.replace('URL', '')
_base_url = None
catalog = _auth_data.get('catalog', [])
# Select entries with matching service type
service_catalog = [ep for ep in catalog if ep['type'] == service]
if len(service_catalog) > 0:
@ -549,10 +550,20 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
# NOTE(andreaf) If there's no catalog at all and the service
# is identity, it's a valid use case. Having a non-empty
# catalog with no identity in it is not valid instead.
msg = ('Got an empty catalog. Scope: %s. '
'Falling back to configured URL for %s: %s')
LOG.debug(msg, self.scope, service, self.auth_url)
return apply_url_filters(self.auth_url, filters)
# No matching service
raise exceptions.EndpointNotFound(service)
msg = ('No matching service found in the catalog.\n'
'Scope: %s, Credentials: %s\n'
'Auth data: %s\n'
'Service: %s, Region: %s, endpoint_type: %s\n'
'Catalog: %s')
raise exceptions.EndpointNotFound(msg % (
self.scope, self.credentials, _auth_data, service, region,
endpoint_type, catalog))
# Filter by endpoint type (interface)
filtered_catalog = [ep for ep in service_catalog if
ep['interface'] == endpoint_type]