diff --git a/doc/source/keystone_scopes_and_roles_support.rst b/doc/source/keystone_scopes_and_roles_support.rst index f446f8ccd0..4d705651ed 100644 --- a/doc/source/keystone_scopes_and_roles_support.rst +++ b/doc/source/keystone_scopes_and_roles_support.rst @@ -203,6 +203,10 @@ Project scoped personas: cls.az_p_reader_client = ( cls.os_project_reader.availability_zone_client) + .. note:: + 'primary', 'project_admin', 'project_member', and 'project_reader' + credentials will be created under same project. + #. Project alternate Admin: This is supported and can be requested and used from the test as below: @@ -248,6 +252,10 @@ Project scoped personas: cls.az_p_alt_reader_client = ( cls.os_project_alt_reader.availability_zone_client) + .. note:: + 'alt', 'project_alt_admin', 'project_alt_member', and + 'project_alt_reader' credentials will be created under same project. + #. Project other roles: This is supported and can be requested and used from the test as below: @@ -269,6 +277,16 @@ Project scoped personas: cls.az_role2_client = ( cls.os_project_my_role2.availability_zone_client) + .. note:: + 'admin' credenatials is considered and kept as legacy admin and + will be created under new project. If any test want to test with + admin role in projectA and non-admin/admin in projectB then test + can request projectA admin using 'admin' or 'project_alt_admin' + and non-admin in projectB using 'primary', 'project_member', + or 'project_reader'/admin in projectB using 'project_admin'. Many + existing tests using the 'admin' with new project to assert on the + resource list so we are keeping 'admin' a kind of legacy admin. + Pre-Provisioned Credentials --------------------------- diff --git a/releasenotes/notes/fix-bug-1964509-b742f2c95d854980.yaml b/releasenotes/notes/fix-bug-1964509-b742f2c95d854980.yaml new file mode 100644 index 0000000000..db627de7d7 --- /dev/null +++ b/releasenotes/notes/fix-bug-1964509-b742f2c95d854980.yaml @@ -0,0 +1,19 @@ +--- +fixes: + - | + There was a bug (bug#1964509) in dynamic credentials creation where + project credentials with different roles are created with the new + projects. Credential of different role of projects must be created + within the same project. For exmaple, 'project_admin', 'project_member', + 'project_reader', and 'primary', credentials will be created in the + same projects. 'alt', 'project_alt_admin', 'project_alt_member', + 'project_alt_reader' will be created within the same project. + + 'admin' credenatials is considered and kept as legacy admin and + will be created under new project. If any test want to test with + admin role in projectA and non-admin/admin in projectB then test + can request projectA admin using 'admin' or 'project_alt_admin' + and non-admin in projectB using 'primary', 'project_member', + or 'project_reader'/admin in projectB using 'project_admin'. Many + existing tests using the 'admin' with new project to assert on the + resource list so we are keeping 'admin' a kind of legacy admin. diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py index f13d6d0334..69798a4b86 100644 --- a/tempest/lib/common/cred_client.py +++ b/tempest/lib/common/cred_client.py @@ -58,6 +58,10 @@ class CredsClient(object, metaclass=abc.ABCMeta): def create_project(self, name, description): pass + @abc.abstractmethod + def show_project(self, project_id): + pass + def _check_role_exists(self, role_name): try: roles = self._list_roles() @@ -118,6 +122,9 @@ class V2CredsClient(CredsClient): name=name, description=description)['tenant'] return tenant + def show_project(self, project_id): + return self.projects_client.show_tenant(project_id)['tenant'] + def delete_project(self, project_id): self.projects_client.delete_tenant(project_id) @@ -159,6 +166,9 @@ class V3CredsClient(CredsClient): domain_id=self.creds_domain['id'])['project'] return project + def show_project(self, project_id): + return self.projects_client.show_project(project_id)['project'] + def delete_project(self, project_id): self.projects_client.delete_project(project_id) diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py index d687eb5033..99647d49ce 100644 --- a/tempest/lib/common/dynamic_creds.py +++ b/tempest/lib/common/dynamic_creds.py @@ -163,7 +163,8 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): os.network.PortsClient(), os.network.SecurityGroupsClient()) - def _create_creds(self, admin=False, roles=None, scope='project'): + def _create_creds(self, admin=False, roles=None, scope='project', + project_id=None): """Create credentials with random name. Creates user and role assignments on a project, domain, or system. When @@ -177,6 +178,8 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): :type roles: list :param str scope: The scope for the role assignment, may be one of 'project', 'domain', or 'system'. + :param str project_id: The project id of already created project + for credentials under same project. :return: Readonly Credentials with network resources :raises: Exception if scope is invalid """ @@ -190,12 +193,20 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): '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) - + if not project_id: + 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) + else: + # NOTE(gmann) This is the case where creds are requested + # from the existing creds within same project. We should + # not create the new project in this case. + project = self.creds_client.show_project(project_id) + project_name = project['name'] + LOG.info("Using the existing project %s for scope %s and " + "roles: %s", project['id'], scope, roles) # 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. @@ -372,48 +383,78 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): self.routers_admin_client.add_router_interface(router_id, subnet_id=subnet_id) - 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, str(credential_type)))): - credentials = self._creds["%s_%s" % (scope, str(credential_type))] + def _get_project_id(self, credential_type, scope): + same_creds = [['admin'], ['member'], ['reader']] + same_alt_creds = [['alt_admin'], ['alt_member'], ['alt_reader']] + search_in = [] + if credential_type in same_creds: + search_in = same_creds + elif credential_type in same_alt_creds: + search_in = same_alt_creds + for cred in search_in: + found_cred = self._creds.get("%s_%s" % (scope, str(cred))) + if found_cred: + project_id = found_cred.get("%s_%s" % (scope, 'id')) + LOG.debug("Reusing existing project %s from creds: %s ", + project_id, found_cred) + return project_id + return None + + def get_credentials(self, credential_type, scope=None, by_role=False): + cred_prefix = '' + if by_role: + cred_prefix = 'role_' + if not scope and self._creds.get( + "%s%s" % (cred_prefix, str(credential_type))): + credentials = self._creds[ + "%s%s" % (cred_prefix, str(credential_type))] + elif scope and (self._creds.get( + "%s%s_%s" % (cred_prefix, scope, str(credential_type)))): + credentials = self._creds[ + "%s%s_%s" % (cred_prefix, scope, str(credential_type))] else: LOG.debug("Creating new dynamic creds for scope: %s and " "credential_type: %s", scope, credential_type) + project_id = None if scope: - if credential_type in [['admin'], ['alt_admin']]: + if scope == 'project': + project_id = self._get_project_id( + credential_type, 'project') + if by_role: credentials = self._create_creds( - admin=True, scope=scope) + roles=credential_type, scope=scope) + elif credential_type in [['admin'], ['alt_admin']]: + credentials = self._create_creds( + admin=True, scope=scope, project_id=project_id) elif credential_type in [['alt_member'], ['alt_reader']]: cred_type = credential_type[0][4:] if isinstance(cred_type, str): cred_type = [cred_type] credentials = self._create_creds( - roles=cred_type, scope=scope) - else: + roles=cred_type, scope=scope, project_id=project_id) + elif credential_type in [['member'], ['reader']]: credentials = self._create_creds( - roles=credential_type, scope=scope) + roles=credential_type, scope=scope, + project_id=project_id) elif 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) if scope: - self._creds["%s_%s" % - (scope, str(credential_type))] = credentials + self._creds["%s%s_%s" % ( + cred_prefix, scope, str(credential_type))] = credentials else: - self._creds[str(credential_type)] = credentials + self._creds[ + "%s%s" % (cred_prefix, str(credential_type))] = credentials # Maintained until tests are ported LOG.info("Acquired dynamic creds:\n" " credentials: %s", credentials) # NOTE(gmann): For 'domain' and 'system' scoped token, there is no # project_id so we are skipping the network creation for both - # scope. How these scoped token can create the network, Nova - # server or other project mapped resources is one of the open - # question and discussed a lot in Xena cycle PTG. Once we sort - # out that then if needed we can update the network creation here. - if (not scope or scope == 'project'): + # scope. + # We need to create nework resource once per project. + if (not project_id and (not scope or scope == 'project')): if (self.neutron_available and self.create_networks): network, subnet, router = self._create_network_resources( credentials.tenant_id) @@ -422,24 +463,22 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): LOG.info("Created isolated network resources for:\n" " credentials: %s", credentials) else: - LOG.info("Network resources are not created for scope: %s", - scope) + LOG.info("Network resources are not created for requested " + "scope: %s and credentials: %s", scope, credentials) return credentials # TODO(gmann): Remove this method in favor of get_project_member_creds() # after the deprecation phase. def get_primary_creds(self): - return self.get_credentials('primary') + return self.get_project_member_creds() - # TODO(gmann): Remove this method in favor of get_project_admin_creds() - # after the deprecation phase. def get_admin_creds(self): return self.get_credentials('admin') - # TODO(gmann): Replace this method with more appropriate name. - # like get_project_alt_member_creds() + # TODO(gmann): Remove this method in favor of + # get_project_alt_member_creds() after the deprecation phase. def get_alt_creds(self): - return self.get_credentials('alt') + return self.get_project_alt_member_creds() def get_system_admin_creds(self): return self.get_credentials(['admin'], scope='system') @@ -481,9 +520,9 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): roles = list(set(roles)) # The roles list as a str will become the index as the dict key for # the created credentials set in the dynamic_creds dict. - creds_name = str(roles) + creds_name = "role_%s" % str(roles) if scope: - creds_name = "%s_%s" % (scope, str(roles)) + creds_name = "role_%s_%s" % (scope, str(roles)) exist_creds = self._creds.get(creds_name) # If force_new flag is True 2 cred sets with the same roles are needed # handle this by creating a separate index for old one to store it @@ -492,7 +531,7 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): new_index = creds_name + '-' + str(len(self._creds)) self._creds[new_index] = exist_creds del self._creds[creds_name] - return self.get_credentials(roles, scope=scope) + return self.get_credentials(roles, scope=scope, by_role=True) def _clear_isolated_router(self, router_id, router_name): client = self.routers_admin_client @@ -553,31 +592,20 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): if not self._creds: return self._clear_isolated_net_resources() + project_ids = set() for creds in self._creds.values(): + # NOTE(gmann): With new RBAC personas, we can have single project + # and multiple user created under it, to avoid conflict let's + # cleanup the projects at the end. + # Adding project if id is not None, means leaving domain and + # system creds. + if creds.project_id: + project_ids.add(creds.project_id) try: self.creds_client.delete_user(creds.user_id) except lib_exc.NotFound: LOG.warning("user with name: %s not found for delete", creds.username) - if creds.tenant_id: - # NOTE(zhufl): Only when neutron's security_group ext is - # enabled, cleanup_default_secgroup will not raise error. But - # here cannot use test_utils.is_extension_enabled for it will - # cause "circular dependency". So here just use try...except to - # ensure tenant deletion without big changes. - try: - if self.neutron_available: - self.cleanup_default_secgroup( - self.security_groups_admin_client, creds.tenant_id) - except lib_exc.NotFound: - LOG.warning("failed to cleanup tenant %s's secgroup", - creds.tenant_name) - try: - self.creds_client.delete_project(creds.tenant_id) - 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 @@ -587,6 +615,28 @@ class DynamicCredentialProvider(cred_provider.CredentialProvider): except lib_exc.NotFound: LOG.warning("domain with name: %s not found for delete", creds.domain_name) + for project_id in project_ids: + # NOTE(zhufl): Only when neutron's security_group ext is + # enabled, cleanup_default_secgroup will not raise error. But + # here cannot use test_utils.is_extension_enabled for it will + # cause "circular dependency". So here just use try...except to + # ensure tenant deletion without big changes. + LOG.info("Deleting project and security group for project: %s", + project_id) + + try: + if self.neutron_available: + self.cleanup_default_secgroup( + self.security_groups_admin_client, project_id) + except lib_exc.NotFound: + LOG.warning("failed to cleanup tenant %s's secgroup", + project_id) + try: + self.creds_client.delete_project(project_id) + except lib_exc.NotFound: + LOG.warning("tenant with id: %s not found for delete", + project_id) + self._creds = {} def is_multi_user(self): diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py index b4b1b91127..d3d01c01af 100644 --- a/tempest/tests/lib/common/test_dynamic_creds.py +++ b/tempest/tests/lib/common/test_dynamic_creds.py @@ -60,6 +60,7 @@ class TestDynamicCredentialProvider(base.TestCase): fake_response = fake_identity._fake_v2_response tenants_client_class = tenants_client.TenantsClient delete_tenant = 'delete_tenant' + create_tenant = 'create_tenant' def setUp(self): super(TestDynamicCredentialProvider, self).setUp() @@ -140,7 +141,9 @@ class TestDynamicCredentialProvider(base.TestCase): return_value=(rest_client.ResponseBody (200, {'roles': [ {'id': '1', 'name': 'FakeRole'}, - {'id': '2', 'name': 'member'}]})))) + {'id': '2', 'name': 'member'}, + {'id': '3', 'name': 'reader'}, + {'id': '4', 'name': 'admin'}]})))) return roles_fix def _mock_list_ec2_credentials(self, user_id, tenant_id): @@ -191,6 +194,205 @@ class TestDynamicCredentialProvider(base.TestCase): self.assertEqual(primary_creds.tenant_id, '1234') self.assertEqual(primary_creds.user_id, '1234') + def _request_and_check_second_creds( + self, creds_obj, func, creds_to_compare, + show_mock, sm_count=1, sm_count_in_diff_project=0, + same_project_request=True, **func_kwargs): + self._mock_user_create('111', 'fake_user') + with mock.patch.object(creds_obj.creds_client, + 'create_project') as create_mock: + create_mock.return_value = {'id': '22', 'name': 'fake_project'} + new_creds = func(**func_kwargs) + if same_project_request: + # Check that with second creds request, create_project is not + # called and show_project is called. Which means new project is + # not created for the second requested creds instead new user is + # created under existing project. + self.assertEqual(len(create_mock.mock_calls), 0) + self.assertEqual(len(show_mock.mock_calls), sm_count) + # Verify project name and id is same as creds_to_compare + self.assertEqual(creds_to_compare.tenant_name, + new_creds.tenant_name) + self.assertEqual(creds_to_compare.tenant_id, + new_creds.tenant_id) + else: + # Check that with different project creds request, create_project + # is called and show_project is not called. Which means new project + # is created for this new creds request. + self.assertEqual(len(create_mock.mock_calls), 1) + self.assertEqual(len(show_mock.mock_calls), + sm_count_in_diff_project) + # Verify project name and id is not same as creds_to_compare + self.assertNotEqual(creds_to_compare.tenant_name, + new_creds.tenant_name) + self.assertNotEqual(creds_to_compare.tenant_id, + new_creds.tenant_id) + self.assertEqual(new_creds.tenant_name, 'fake_project') + self.assertEqual(new_creds.tenant_id, '22') + # Verify new user name and id + self.assertEqual(new_creds.username, 'fake_user') + self.assertEqual(new_creds.user_id, '111') + return new_creds + + @mock.patch('tempest.lib.common.rest_client.RestClient') + def _creds_within_same_project(self, MockRestClient, test_alt_creds=False): + creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) + if test_alt_creds: + admin_func = creds.get_project_alt_admin_creds + member_func = creds.get_project_alt_member_creds + reader_func = creds.get_project_alt_reader_creds + else: + admin_func = creds.get_project_admin_creds + member_func = creds.get_project_member_creds + reader_func = creds.get_project_reader_creds + self._mock_assign_user_role() + self._mock_list_role() + self._mock_user_create('11', 'fake_user1') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '21', 'name': 'fake_project1'} + with mock.patch.object(creds.creds_client, + 'create_project') as create_mock: + create_mock.return_value = {'id': '21', 'name': 'fake_project1'} + member_creds = member_func() + # Check that with first creds request, create_project is called and + # show_project is not called. Which means new project is created for + # the requested creds. + self.assertEqual(len(create_mock.mock_calls), 1) + self.assertEqual(len(show_mock.mock_calls), 0) + # Verify project, user name and IDs + self.assertEqual(member_creds.username, 'fake_user1') + self.assertEqual(member_creds.tenant_name, 'fake_project1') + self.assertEqual(member_creds.tenant_id, '21') + self.assertEqual(member_creds.user_id, '11') + + # Now request for the project reader creds which should not create new + # project instead should use the project_id of member_creds already + # created project. + self._request_and_check_second_creds( + creds, reader_func, member_creds, show_mock) + + # Now request for the project admin creds which should not create new + # project instead should use the project_id of member_creds already + # created project. + self._request_and_check_second_creds( + creds, admin_func, member_creds, show_mock, sm_count=2) + + def test_creds_within_same_project(self): + self._creds_within_same_project() + + def test_alt_creds_within_same_project(self): + self._creds_within_same_project(test_alt_creds=True) + + @mock.patch('tempest.lib.common.rest_client.RestClient') + def test_creds_in_different_project(self, MockRestClient): + creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) + self._mock_assign_user_role() + self._mock_list_role() + self._mock_user_create('11', 'fake_user1') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '21', 'name': 'fake_project1'} + with mock.patch.object(creds.creds_client, + 'create_project') as create_mock: + create_mock.return_value = {'id': '21', 'name': 'fake_project1'} + member_creds = creds.get_project_member_creds() + # Check that with first creds request, create_project is called and + # show_project is not called. Which means new project is created for + # the requested creds. + self.assertEqual(len(create_mock.mock_calls), 1) + self.assertEqual(len(show_mock.mock_calls), 0) + # Verify project, user name and IDs + self.assertEqual(member_creds.username, 'fake_user1') + self.assertEqual(member_creds.tenant_name, 'fake_project1') + self.assertEqual(member_creds.tenant_id, '21') + self.assertEqual(member_creds.user_id, '11') + + # Now request for the project alt reader creds which should create + # new project as this request is for alt creds. + alt_reader_creds = self._request_and_check_second_creds( + creds, creds.get_project_alt_reader_creds, + member_creds, show_mock, same_project_request=False) + + # Check that with second creds request, create_project is not called + # and show_project is called. Which means new project is not created + # for the second requested creds instead new user is created under + # existing project. + self._request_and_check_second_creds( + creds, creds.get_project_reader_creds, member_creds, show_mock) + + # Now request for the project alt member creds which should not create + # new project instead use the alt project already created for + # alt_reader creds. + show_mock.return_value = { + 'id': alt_reader_creds.tenant_id, + 'name': alt_reader_creds.tenant_name} + self._request_and_check_second_creds( + creds, creds.get_project_alt_member_creds, + alt_reader_creds, show_mock, sm_count=2, + same_project_request=True) + + @mock.patch('tempest.lib.common.rest_client.RestClient') + def test_creds_by_role_in_different_project(self, MockRestClient): + creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) + self._mock_assign_user_role() + self._mock_list_role() + self._mock_user_create('11', 'fake_user1') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '21', 'name': 'fake_project1'} + with mock.patch.object(creds.creds_client, + 'create_project') as create_mock: + create_mock.return_value = {'id': '21', 'name': 'fake_project1'} + member_creds = creds.get_project_member_creds() + # Check that with first creds request, create_project is called and + # show_project is not called. Which means new project is created for + # the requested creds. + self.assertEqual(len(create_mock.mock_calls), 1) + self.assertEqual(len(show_mock.mock_calls), 0) + # Verify project, user name and IDs + self.assertEqual(member_creds.username, 'fake_user1') + self.assertEqual(member_creds.tenant_name, 'fake_project1') + self.assertEqual(member_creds.tenant_id, '21') + self.assertEqual(member_creds.user_id, '11') + # Check that with second creds request, create_project is not called + # and show_project is called. Which means new project is not created + # for the second requested creds instead new user is created under + # existing project. + self._request_and_check_second_creds( + creds, creds.get_project_reader_creds, member_creds, show_mock) + # Now request the creds by role which should create new project. + self._request_and_check_second_creds( + creds, creds.get_creds_by_roles, member_creds, show_mock, + sm_count_in_diff_project=1, same_project_request=False, + roles=['member'], scope='project') + + @mock.patch('tempest.lib.common.rest_client.RestClient') + def test_legacy_admin_creds_in_different_project(self, MockRestClient): + creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) + self._mock_assign_user_role() + self._mock_list_role() + self._mock_user_create('11', 'fake_user1') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '21', 'name': 'fake_project1'} + with mock.patch.object(creds.creds_client, + 'create_project') as create_mock: + create_mock.return_value = {'id': '21', 'name': 'fake_project1'} + member_creds = creds.get_project_member_creds() + # Check that with first creds request, create_project is called and + # show_project is not called. Which means new project is created for + # the requested creds. + self.assertEqual(len(create_mock.mock_calls), 1) + self.assertEqual(len(show_mock.mock_calls), 0) + # Verify project, user name and IDs + self.assertEqual(member_creds.username, 'fake_user1') + self.assertEqual(member_creds.tenant_name, 'fake_project1') + self.assertEqual(member_creds.tenant_id, '21') + self.assertEqual(member_creds.user_id, '11') + + # Now request for the legacy admin creds which should create + # new project instead of using project member creds project. + self._request_and_check_second_creds( + creds, creds.get_admin_creds, + member_creds, show_mock, same_project_request=False) + @mock.patch('tempest.lib.common.rest_client.RestClient') def test_admin_creds(self, MockRestClient): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) @@ -321,7 +523,8 @@ class TestDynamicCredentialProvider(base.TestCase): @mock.patch('tempest.lib.common.rest_client.RestClient') def _test_get_same_role_creds_with_project_scope(self, MockRestClient, - scope=None): + scope=None, + force_new=False): creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params) self._mock_list_2_roles() self._mock_user_create('1234', 'fake_role_user') @@ -329,7 +532,7 @@ class TestDynamicCredentialProvider(base.TestCase): with mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') as user_mock: role_creds = creds.get_creds_by_roles( - roles=['role1', 'role2'], scope=scope) + roles=['role1', 'role2'], force_new=force_new, scope=scope) calls = user_mock.mock_calls # Assert that the role creation is called with the 2 specified roles self.assertEqual(len(calls), 2) @@ -338,13 +541,18 @@ class TestDynamicCredentialProvider(base.TestCase): with mock.patch.object(self.roles_client.RolesClient, 'create_user_role_on_project') as user_mock1: role_creds_new = creds.get_creds_by_roles( - roles=['role1', 'role2'], scope=scope) + roles=['role1', 'role2'], force_new=force_new, scope=scope) calls = user_mock1.mock_calls + # With force_new, assert that new creds are created + if force_new: + self.assertEqual(len(calls), 2) + self.assertNotEqual(role_creds, role_creds_new) # Assert that previously created creds are return and no call to - # role creation. - self.assertEqual(len(calls), 0) + # role creation # Check if previously created creds are returned. - self.assertEqual(role_creds, role_creds_new) + else: + self.assertEqual(len(calls), 0) + self.assertEqual(role_creds, role_creds_new) def test_get_same_role_creds_with_project_scope(self): self._test_get_same_role_creds_with_project_scope(scope='project') @@ -352,6 +560,13 @@ class TestDynamicCredentialProvider(base.TestCase): def test_get_same_role_creds_with_default_scope(self): self._test_get_same_role_creds_with_project_scope() + def test_get_same_role_creds_with_project_scope_force_new(self): + self._test_get_same_role_creds_with_project_scope( + scope='project', force_new=True) + + def test_get_same_role_creds_with_default_scope_force_new(self): + self._test_get_same_role_creds_with_project_scope(force_new=True) + @mock.patch('tempest.lib.common.rest_client.RestClient') def _test_get_different_role_creds_with_project_scope( self, MockRestClient, scope=None): @@ -391,8 +606,12 @@ class TestDynamicCredentialProvider(base.TestCase): self._mock_assign_user_role() self._mock_list_role() self._mock_tenant_create('1234', 'fake_prim_tenant') - self._mock_user_create('1234', 'fake_prim_user') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '1234', 'name': 'fake_prim_tenant'} + self._mock_user_create('1234', 'fake_project1_user') creds.get_primary_creds() + self._mock_user_create('12341', 'fake_project1_user') + creds.get_project_admin_creds() self._mock_tenant_create('12345', 'fake_alt_tenant') self._mock_user_create('12345', 'fake_alt_user') creds.get_alt_creds() @@ -407,10 +626,11 @@ class TestDynamicCredentialProvider(base.TestCase): creds.clear_creds() # Verify user delete calls calls = user_mock.mock_calls - self.assertEqual(len(calls), 3) + self.assertEqual(len(calls), 4) args = map(lambda x: x[1][0], calls) args = list(args) self.assertIn('1234', args) + self.assertIn('12341', args) self.assertIn('12345', args) self.assertIn('123456', args) # Verify tenant delete calls @@ -512,6 +732,9 @@ class TestDynamicCredentialProvider(base.TestCase): self._mock_list_role() self._mock_user_create('1234', 'fake_prim_user') self._mock_tenant_create('1234', 'fake_prim_tenant') + show_mock = self.patchobject(creds.creds_client, 'show_project') + show_mock.return_value = {'id': '1234', 'name': 'fake_prim_tenant'} + self._mock_user_create('12341', 'fake_project1_user') self._mock_network_create(creds, '1234', 'fake_net') self._mock_subnet_create(creds, '1234', 'fake_subnet') self._mock_router_create('1234', 'fake_router') @@ -519,6 +742,7 @@ class TestDynamicCredentialProvider(base.TestCase): 'tempest.lib.services.network.routers_client.RoutersClient.' 'add_router_interface') creds.get_primary_creds() + creds.get_project_admin_creds() router_interface_mock.assert_called_once_with('1234', subnet_id='1234') router_interface_mock.reset_mock() # Create alternate tenant and network @@ -779,6 +1003,7 @@ class TestDynamicCredentialProviderV3(TestDynamicCredentialProvider): fake_response = fake_identity._fake_v3_response tenants_client_class = tenants_client.ProjectsClient delete_tenant = 'delete_project' + create_tenant = 'create_project' def setUp(self): super(TestDynamicCredentialProviderV3, self).setUp()