Merge "Use scope in v3 identity client"
This commit is contained in:
commit
b15c5d5649
|
@ -26,6 +26,11 @@ can be used to:
|
||||||
- Run tests for admin APIs
|
- Run tests for admin APIs
|
||||||
- Generate test credentials on the fly (see `Dynamic Credentials`_)
|
- 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.
|
Tempest allows for configuring pre-provisioned test credentials as well.
|
||||||
This can be done using the accounts.yaml file (see
|
This can be done using the accounts.yaml file (see
|
||||||
`Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
|
`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
|
by dynamic credentials. This option will not have any effect when Tempest is not
|
||||||
configured to use dynamic credentials.
|
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
|
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
|
to the tests using the credentials, and failure to do this will likely cause
|
||||||
unexpected failures in some tests.
|
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.
|
Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
|
||||||
|
|
||||||
Compute
|
Compute
|
||||||
|
|
|
@ -98,7 +98,8 @@ class BaseTrustsV3Test(base.BaseIdentityV3AdminTest):
|
||||||
password=self.trustor_password,
|
password=self.trustor_password,
|
||||||
user_domain_id='default',
|
user_domain_id='default',
|
||||||
tenant_name=self.trustor_project_name,
|
tenant_name=self.trustor_project_name,
|
||||||
project_domain_id='default')
|
project_domain_id='default',
|
||||||
|
domain_id='default')
|
||||||
os = clients.Manager(credentials=creds)
|
os = clients.Manager(credentials=creds)
|
||||||
self.trustor_client = os.trusts_client
|
self.trustor_client = os.trusts_client
|
||||||
|
|
||||||
|
@ -266,7 +267,18 @@ class TrustsV3TestJSON(BaseTrustsV3Test):
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
|
@test.idempotent_id('4773ebd5-ecbf-4255-b8d8-b63e6f72b65d')
|
||||||
def test_get_trusts_all(self):
|
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()
|
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_get = self.trusts_client.list_trusts()['trusts']
|
||||||
trusts = [t for t in trusts_get
|
trusts = [t for t in trusts_get
|
||||||
if t['id'] == self.trust_id]
|
if t['id'] == self.trust_id]
|
||||||
|
|
|
@ -162,6 +162,12 @@ class BaseIdentityV3AdminTest(BaseIdentityV3Test):
|
||||||
cls.creds_client = cls.os_adm.credentials_client
|
cls.creds_client = cls.os_adm.credentials_client
|
||||||
cls.groups_client = cls.os_adm.groups_client
|
cls.groups_client = cls.os_adm.groups_client
|
||||||
cls.projects_client = cls.os_adm.projects_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
|
@classmethod
|
||||||
def resource_setup(cls):
|
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
|
# 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):
|
def get_credentials(fill_in=True, identity_version=None, **kwargs):
|
||||||
params = dict(DEFAULT_PARAMS, **kwargs)
|
params = dict(DEFAULT_PARAMS, **kwargs)
|
||||||
identity_version = identity_version or CONF.identity.auth_version
|
identity_version = identity_version or CONF.identity.auth_version
|
||||||
# In case of "v3" add the domain from config if not specified
|
# 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':
|
if identity_version == 'v3':
|
||||||
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
|
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
|
||||||
if 'domain' in x)
|
if 'domain' in x)
|
||||||
if not domain_fields.intersection(kwargs.keys()):
|
if not domain_fields.intersection(kwargs.keys()):
|
||||||
domain_name = CONF.auth.default_credentials_domain_name
|
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
|
auth_url = CONF.identity.uri_v3
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -96,8 +96,15 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
|
||||||
os.networks_client, os.routers_client, os.subnets_client,
|
os.networks_client, os.routers_client, os.subnets_client,
|
||||||
os.ports_client, os.security_groups_client)
|
os.ports_client, os.security_groups_client)
|
||||||
else:
|
else:
|
||||||
return (os.identity_v3_client, os.projects_client,
|
# We use a dedicated client manager for identity client in case we
|
||||||
os.users_v3_client, os.roles_v3_client, os.domains_client,
|
# 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.networks_client, os.routers_client,
|
||||||
os.subnets_client, os.ports_client,
|
os.subnets_client, os.ports_client,
|
||||||
os.security_groups_client)
|
os.security_groups_client)
|
||||||
|
@ -136,7 +143,8 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider):
|
||||||
self.creds_client.assign_user_role(user, project,
|
self.creds_client.assign_user_role(user, project,
|
||||||
self.admin_role)
|
self.admin_role)
|
||||||
role_assigned = True
|
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(
|
self.creds_client.assign_user_role_on_domain(
|
||||||
user, CONF.identity.admin_role)
|
user, CONF.identity.admin_role)
|
||||||
# Add roles specified in config file
|
# Add roles specified in config file
|
||||||
|
|
|
@ -166,6 +166,10 @@ IdentityGroup = [
|
||||||
cfg.StrOpt('default_domain_id',
|
cfg.StrOpt('default_domain_id',
|
||||||
default='default',
|
default='default',
|
||||||
help="ID of the default domain"),
|
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',
|
identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',
|
||||||
|
|
|
@ -532,6 +532,7 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||||
endpoint_type = endpoint_type.replace('URL', '')
|
endpoint_type = endpoint_type.replace('URL', '')
|
||||||
_base_url = None
|
_base_url = None
|
||||||
catalog = _auth_data.get('catalog', [])
|
catalog = _auth_data.get('catalog', [])
|
||||||
|
|
||||||
# Select entries with matching service type
|
# Select entries with matching service type
|
||||||
service_catalog = [ep for ep in catalog if ep['type'] == service]
|
service_catalog = [ep for ep in catalog if ep['type'] == service]
|
||||||
if len(service_catalog) > 0:
|
if len(service_catalog) > 0:
|
||||||
|
@ -549,10 +550,20 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||||
# NOTE(andreaf) If there's no catalog at all and the service
|
# NOTE(andreaf) If there's no catalog at all and the service
|
||||||
# is identity, it's a valid use case. Having a non-empty
|
# is identity, it's a valid use case. Having a non-empty
|
||||||
# catalog with no identity in it is not valid instead.
|
# 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)
|
return apply_url_filters(self.auth_url, filters)
|
||||||
else:
|
else:
|
||||||
# No matching service
|
# 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)
|
# Filter by endpoint type (interface)
|
||||||
filtered_catalog = [ep for ep in service_catalog if
|
filtered_catalog = [ep for ep in service_catalog if
|
||||||
ep['interface'] == endpoint_type]
|
ep['interface'] == endpoint_type]
|
||||||
|
|
Loading…
Reference in New Issue