diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py index 3af09a00fe..e16a565f8a 100644 --- a/tempest/lib/common/cred_client.py +++ b/tempest/lib/common/cred_client.py @@ -39,11 +39,15 @@ class CredsClient(object): self.projects_client = projects_client self.roles_client = roles_client - def create_user(self, username, password, project, email): + def create_user(self, username, password, project=None, email=None): params = {'name': username, - 'password': password, - self.project_id_param: project['id'], - 'email': email} + 'password': password} + # with keystone v3, a default project is not required + if project: + params[self.project_id_param] = project['id'] + # email is not a first-class attribute of a user + if email: + params['email'] = email user = self.users_client.create_user(**params) if 'user' in user: user = user['user'] @@ -160,6 +164,15 @@ class V3CredsClient(CredsClient): def delete_project(self, project_id): self.projects_client.delete_project(project_id) + def create_domain(self, name, description): + domain = self.domains_client.create_domain( + name=name, description=description)['domain'] + return domain + + def delete_domain(self, domain_id): + self.domains_client.update_domain(domain_id, enabled=False) + self.domains_client.delete_domain(domain_id) + def get_credentials( self, user, project, password, domain=None, system=None): # User, project and domain already include both ID and name here, @@ -215,6 +228,23 @@ class V3CredsClient(CredsClient): LOG.debug("Role %s already assigned on domain %s for user %s", role['id'], domain['id'], user['id']) + def assign_user_role_on_system(self, user, role_name): + """Assign the specified role on the system + + :param user: a user dict + :param role_name: name of the role to be assigned + """ + role = self._check_role_exists(role_name) + if not role: + msg = 'No "%s" role found' % role_name + raise lib_exc.NotFound(msg) + try: + self.roles_client.create_user_role_on_system( + user['id'], role['id']) + except lib_exc.Conflict: + LOG.debug("Role %s already assigned on the system for user %s", + role['id'], user['id']) + def get_creds_client(identity_client, projects_client, diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py index 0a2817b203..038528f8f4 100644 --- a/tempest/lib/common/dynamic_creds.py +++ b/tempest/lib/common/dynamic_creds.py @@ -164,62 +164,98 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): os.network.PortsClient(), os.network.SecurityGroupsClient()) - def _create_creds(self, admin=False, roles=None): + def _create_creds(self, admin=False, roles=None, scope='project'): """Create credentials with random name. - Creates project and user. When admin flag is True create user - with admin role. Assign user with additional roles (for example - _member_) and roles requested by caller. + Creates user and role assignments on a project, domain, or system. When + the admin flag is True, creates user with the admin role on the + resource. If roles are provided, assigns those roles on the resource. + Otherwise, assigns the user the 'member' role on the resource. :param admin: Flag if to assign to the user admin role :type admin: bool :param roles: Roles to assign for the user :type roles: list + :param str scope: The scope for the role assignment, may be one of + 'project', 'domain', or 'system'. :return: Readonly Credentials with network resources + :raises: Exception if scope is invalid """ + if not roles: + roles = [] root = self.name - project_name = data_utils.rand_name(root, prefix=self.resource_prefix) - project_desc = project_name + "-desc" - project = self.creds_client.create_project( - name=project_name, description=project_desc) + cred_params = { + 'project': None, + 'domain': None, + 'system': None + } + if scope == 'project': + project_name = data_utils.rand_name( + root, prefix=self.resource_prefix) + project_desc = project_name + '-desc' + project = self.creds_client.create_project( + name=project_name, description=project_desc) - # NOTE(andreaf) User and project can be distinguished from the context, - # having the same ID in both makes it easier to match them and debug. - username = project_name - user_password = data_utils.rand_password() - email = data_utils.rand_name( - root, prefix=self.resource_prefix) + "@example.com" - user = self.creds_client.create_user( - username, user_password, project, email) - role_assigned = False + # NOTE(andreaf) User and project can be distinguished from the + # context, having the same ID in both makes it easier to match them + # and debug. + username = project_name + '-project' + cred_params['project'] = project + elif scope == 'domain': + domain_name = data_utils.rand_name( + root, prefix=self.resource_prefix) + domain_desc = domain_name + '-desc' + domain = self.creds_client.create_domain( + name=domain_name, description=domain_desc) + username = domain_name + '-domain' + cred_params['domain'] = domain + elif scope == 'system': + prefix = data_utils.rand_name(root, prefix=self.resource_prefix) + username = prefix + '-system' + cred_params['system'] = 'all' + else: + raise lib_exc.InvalidScopeType(scope=scope) if admin: - self.creds_client.assign_user_role(user, project, self.admin_role) - role_assigned = True + username += '-admin' + elif roles and len(roles) == 1: + username += '-' + roles[0] + user_password = data_utils.rand_password() + cred_params['password'] = user_password + user = self.creds_client.create_user( + username, user_password) + cred_params['user'] = user + roles_to_assign = [r for r in roles] + if admin: + roles_to_assign.append(self.admin_role) + self.creds_client.assign_user_role( + user, project, self.identity_admin_role) if (self.identity_version == 'v3' and self.identity_admin_domain_scope): self.creds_client.assign_user_role_on_domain( user, self.identity_admin_role) # Add roles specified in config file - for conf_role in self.extra_roles: - self.creds_client.assign_user_role(user, project, conf_role) - role_assigned = True - # Add roles requested by caller - if roles: - for role in roles: - self.creds_client.assign_user_role(user, project, role) - role_assigned = True + roles_to_assign.extend(self.extra_roles) + # If there are still no roles, default to 'member' # NOTE(mtreinish) For a user to have access to a project with v3 auth # it must beassigned a role on the project. So we need to ensure that # our newly created user has a role on the newly created project. - if self.identity_version == 'v3' and not role_assigned: + if not roles_to_assign and self.identity_version == 'v3': + roles_to_assign = ['member'] try: self.creds_client.create_user_role('member') except lib_exc.Conflict: LOG.warning('member role already exists, ignoring conflict.') - self.creds_client.assign_user_role(user, project, 'member') + for role in roles_to_assign: + if scope == 'project': + self.creds_client.assign_user_role(user, project, role) + elif scope == 'domain': + self.creds_client.assign_user_role_on_domain( + user, role, domain) + elif scope == 'system': + self.creds_client.assign_user_role_on_system(user, role) - creds = self.creds_client.get_credentials(user, project, user_password) + creds = self.creds_client.get_credentials(**cred_params) return cred_provider.TestResources(creds) def _create_network_resources(self, tenant_id): @@ -334,16 +370,26 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): self.routers_admin_client.add_router_interface(router_id, subnet_id=subnet_id) - def get_credentials(self, credential_type): - if self._creds.get(str(credential_type)): + def get_credentials(self, credential_type, scope=None): + if not scope and self._creds.get(str(credential_type)): credentials = self._creds[str(credential_type)] + elif scope and self._creds.get("%s_%s" % (scope, credential_type[0])): + credentials = self._creds["%s_%s" % (scope, credential_type[0])] else: if credential_type in ['primary', 'alt', 'admin']: is_admin = (credential_type == 'admin') credentials = self._create_creds(admin=is_admin) else: - credentials = self._create_creds(roles=credential_type) - self._creds[str(credential_type)] = credentials + if scope: + credentials = self._create_creds( + roles=credential_type, scope=scope) + else: + credentials = self._create_creds(roles=credential_type) + if scope: + self._creds["%s_%s" % + (scope, credential_type[0])] = credentials + else: + self._creds[str(credential_type)] = credentials # Maintained until tests are ported LOG.info("Acquired dynamic creds:\n" " credentials: %s", credentials) @@ -365,6 +411,33 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): def get_alt_creds(self): return self.get_credentials('alt') + def get_system_admin_creds(self): + return self.get_credentials(['admin'], scope='system') + + def get_system_member_creds(self): + return self.get_credentials(['member'], scope='system') + + def get_system_reader_creds(self): + return self.get_credentials(['reader'], scope='system') + + def get_domain_admin_creds(self): + return self.get_credentials(['admin'], scope='domain') + + def get_domain_member_creds(self): + return self.get_credentials(['member'], scope='domain') + + def get_domain_reader_creds(self): + return self.get_credentials(['reader'], scope='domain') + + def get_project_admin_creds(self): + return self.get_credentials(['admin'], scope='project') + + def get_project_member_creds(self): + return self.get_credentials(['member'], scope='project') + + def get_project_reader_creds(self): + return self.get_credentials(['reader'], scope='project') + def get_creds_by_roles(self, roles, force_new=False): roles = list(set(roles)) # The roles list as a str will become the index as the dict key for @@ -472,6 +545,16 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): except lib_exc.NotFound: LOG.warning("tenant with name: %s not found for delete", creds.tenant_name) + + # if cred is domain scoped, delete ephemeral domain + # do not delete default domain + if (hasattr(creds, 'domain_id') and + creds.domain_id != creds.project_domain_id): + try: + self.creds_client.delete_domain(creds.domain_id) + except lib_exc.NotFound: + LOG.warning("domain with name: %s not found for delete", + creds.domain_name) self._creds = {} def is_multi_user(self): diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py index 84b7ee6ebb..abe68d22c8 100644 --- a/tempest/lib/exceptions.py +++ b/tempest/lib/exceptions.py @@ -294,3 +294,7 @@ class ConsistencyGroupException(TempestException): class ConsistencyGroupSnapshotException(TempestException): message = ("Consistency group snapshot %(cgsnapshot_id)s failed and is " "in ERROR status") + + +class InvalidScopeType(TempestException): + message = "Invalid scope %(scope)s" diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py index 90debd9103..d3289562fd 100644 --- a/tempest/lib/services/clients.py +++ b/tempest/lib/services/clients.py @@ -257,7 +257,7 @@ class ServiceClients(object): # class should only be used by tests hosted in Tempest. @removals.removed_kwarg('client_parameters') - def __init__(self, credentials, identity_uri, region=None, scope='project', + def __init__(self, credentials, identity_uri, region=None, scope=None, disable_ssl_certificate_validation=True, ca_certs=None, trace_requests='', client_parameters=None, proxy_url=None): """Service Clients provider @@ -348,6 +348,14 @@ class ServiceClients(object): self.ca_certs = ca_certs self.trace_requests = trace_requests self.proxy_url = proxy_url + if self.credentials.project_id or self.credentials.project_name: + scope = 'project' + elif self.credentials.system: + scope = 'system' + elif self.credentials.domain_id or self.credentials.domain_name: + scope = 'domain' + else: + scope = 'project' # Creates an auth provider for the credentials self.auth_provider = auth_provider_class( self.credentials, self.identity_uri, scope=scope,