From 100d18df69be8995f5baaa634c09fe6354656ec9 Mon Sep 17 00:00:00 2001 From: "Andrea Frittoli (andreaf)" Date: Thu, 5 May 2016 23:34:52 +0100 Subject: [PATCH] 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 Change-Id: I91ca907992428a5a14fb8d48a4fad105d2906e27 --- doc/source/configuration.rst | 25 ++++++++++++++++++++ tempest/api/identity/admin/v3/test_trusts.py | 14 ++++++++++- tempest/api/identity/base.py | 6 +++++ tempest/common/credentials_factory.py | 8 +++++-- tempest/common/dynamic_creds.py | 14 ++++++++--- tempest/config.py | 4 ++++ tempest/lib/auth.py | 13 +++++++++- 7 files changed, 77 insertions(+), 7 deletions(-) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 9a7ce15382..743b575160 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -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 diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py index 09ae468cd7..9c8f1f6ed0 100644 --- a/tempest/api/identity/admin/v3/test_trusts.py +++ b/tempest/api/identity/admin/v3/test_trusts.py @@ -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] diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py index 2d4ff17fe7..31420d11e7 100644 --- a/tempest/api/identity/base.py +++ b/tempest/api/identity/base.py @@ -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): diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py index 4873fcfa69..c22afc14fb 100644 --- a/tempest/common/credentials_factory.py +++ b/tempest/common/credentials_factory.py @@ -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: diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py index 3a3e3c2c29..0b92c15d01 100644 --- a/tempest/common/dynamic_creds.py +++ b/tempest/common/dynamic_creds.py @@ -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 diff --git a/tempest/config.py b/tempest/config.py index 3810cebcd4..1f88871a9d 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -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', diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py index 974ba82c84..213ab8ad47 100644 --- a/tempest/lib/auth.py +++ b/tempest/lib/auth.py @@ -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]