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:
parent
bd06f981a7
commit
100d18df69
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue