Use scope in v3 identity client

Certain identity v3 API calls require a domain scoped token when
policy.v3cloudsample.json is used.
Introduce a new config flag to tell Tempest that keystone in the
targe cloud requires domain scoped tokens for admin actions.
Modify the v3 client managers used to obtain v3 admin identity
clients to request the domain scope when the flag is turned on.

Co-authored by: Roxana Gherle <roxana.gherle@hp.com>
Change-Id: I91ca907992428a5a14fb8d48a4fad105d2906e27
This commit is contained in:
Andrea Frittoli (andreaf) 2016-05-05 23:34:52 +01:00
parent bd06f981a7
commit 100d18df69
7 changed files with 77 additions and 7 deletions

View File

@ -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.
Compute

View File

@ -98,7 +98,8 @@ class BaseTrustsV3Test(base.BaseIdentityV3AdminTest):
password=self.trustor_password,
user_domain_id='default',
tenant_name=self.trustor_project_name,
project_domain_id='default')
project_domain_id='default',
domain_id='default')
os = clients.Manager(credentials=creds)
self.trustor_client = os.trusts_client
@ -266,7 +267,18 @@ class TrustsV3TestJSON(BaseTrustsV3Test):
@test.attr(type='smoke')
@test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
def test_get_trusts_all(self):
# Simple function that can be used for cleanup
def set_scope(auth_provider, scope):
auth_provider.scope = scope
self.create_trust()
# 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]

View File

@ -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'
@classmethod
def resource_setup(cls):

View File

@ -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
else:

View File

@ -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)
else:
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,
scope=scope)
return (identity_os.identity_v3_client,
identity_os.projects_client,
identity_os.users_v3_client, identity_os.roles_v3_client,
identity_os.domains_client,
os.networks_client, os.routers_client,
os.subnets_client, os.ports_client,
os.security_groups_client)
@ -136,7 +143,8 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
self.creds_client.assign_user_role(user, project,
self.admin_role)
role_assigned = True
if self.identity_version == 'v3':
if (self.identity_version == 'v3' and
CONF.identity.admin_domain_scope):
self.creds_client.assign_user_role_on_domain(
user, CONF.identity.admin_role)
# Add roles specified in config file

View File

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

View File

@ -510,6 +510,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:
@ -519,10 +520,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)
else:
# 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]