From b2deeefc8b6b328f5a796b7860d894efea6648bf Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Thu, 21 Jan 2016 13:26:00 -0600 Subject: [PATCH] include keystonev2 role assignments This will require that user is set with project possibly being None, otherwise we would have to query over all combinations of users and projects to find all the assignments, which I would say is unreasonable. project is not required by keystoneclient, but based on what I was told by someone in #openstack-keystone, it is highly discouraged to create roles and users and assignments without the project being involved, so we won't be allowing it. Change-Id: Id5b7b9fb44a9dbecb7488eb8f0ef30773efed6d2 --- ...ignments-keystone-v2-b127b12b4860f50c.yaml | 3 + shade/_tasks.py | 5 ++ shade/operatorcloud.py | 43 ++++++++++-- shade/tests/functional/test_identity.py | 8 +++ shade/tests/unit/test_identity_roles.py | 66 ++++++++++++++++++- 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/list-role-assignments-keystone-v2-b127b12b4860f50c.yaml diff --git a/releasenotes/notes/list-role-assignments-keystone-v2-b127b12b4860f50c.yaml b/releasenotes/notes/list-role-assignments-keystone-v2-b127b12b4860f50c.yaml new file mode 100644 index 000000000..df0d96b3d --- /dev/null +++ b/releasenotes/notes/list-role-assignments-keystone-v2-b127b12b4860f50c.yaml @@ -0,0 +1,3 @@ +--- +features: + - Implement list_role_assignments for keystone v2, using roles_for_user. diff --git a/shade/_tasks.py b/shade/_tasks.py index 6f72bdd2a..66ef37028 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -687,6 +687,11 @@ class RoleAssignmentList(task_manager.Task): return client.keystone_client.role_assignments.list(**self.args) +class RolesForUser(task_manager.Task): + def main(self, client): + return client.keystone_client.roles.roles_for_user(**self.args) + + class StackList(task_manager.Task): def main(self, client): return client.heat_client.stacks.list() diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index c50202424..d83430837 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -1286,6 +1286,31 @@ class OperatorCloud(openstackcloud.OpenStackCloud): """ return _utils._get_entity(self.search_roles, name_or_id, filters) + def _keystone_v2_role_assignments(self, user, project=None, + role=None, **kwargs): + with _utils.shade_exceptions("Failed to list role assignments"): + roles = self.manager.submitTask( + _tasks.RolesForUser(user=user, tenant=project) + ) + ret = [] + for tmprole in roles: + if role is not None and role != tmprole.id: + continue + ret.append({ + 'role': { + 'id': tmprole.id + }, + 'scope': { + 'project': { + 'id': project, + } + }, + 'user': { + 'id': user, + } + }) + return ret + def list_role_assignments(self, filters=None): """List Keystone role assignments @@ -1304,6 +1329,9 @@ class OperatorCloud(openstackcloud.OpenStackCloud): 'user' and 'group' are mutually exclusive, as are 'domain' and 'project'. + NOTE: For keystone v2, only user, project, and role are used. + Project and user are both required in filters. + :returns: a list of dicts containing the role assignment description. Contains the following attributes:: @@ -1317,10 +1345,17 @@ class OperatorCloud(openstackcloud.OpenStackCloud): if not filters: filters = {} - with _utils.shade_exceptions("Failed to list role assignments"): - assignments = self.manager.submitTask( - _tasks.RoleAssignmentList(**filters) - ) + if self.cloud_config.get_api_version('identity').startswith('2'): + if filters.get('project') is None or filters.get('user') is None: + raise OpenStackCloudException( + "Must provide project and user for keystone v2" + ) + assignments = self._keystone_v2_role_assignments(**filters) + else: + with _utils.shade_exceptions("Failed to list role assignments"): + assignments = self.manager.submitTask( + _tasks.RoleAssignmentList(**filters) + ) return _utils.normalize_role_assignments(assignments) def create_flavor(self, name, ram, vcpus, disk, flavorid="auto", diff --git a/shade/tests/functional/test_identity.py b/shade/tests/functional/test_identity.py index d50be33af..1ad582540 100644 --- a/shade/tests/functional/test_identity.py +++ b/shade/tests/functional/test_identity.py @@ -89,3 +89,11 @@ class TestIdentity(base.TestCase): assignments = self.cloud.list_role_assignments() self.assertIsInstance(assignments, list) self.assertTrue(len(assignments) > 0) + + def test_list_role_assignments_v2(self): + user = self.cloud.get_user('demo') + project = self.cloud.get_project('demo') + assignments = self.cloud.list_role_assignments( + filters={'user': user.id, 'project': project.id}) + self.assertIsInstance(assignments, list) + self.assertTrue(len(assignments) > 0) diff --git a/shade/tests/unit/test_identity_roles.py b/shade/tests/unit/test_identity_roles.py index dad7a1678..7a7af3363 100644 --- a/shade/tests/unit/test_identity_roles.py +++ b/shade/tests/unit/test_identity_roles.py @@ -14,6 +14,7 @@ import mock import testtools +import os_client_config as occ import shade from shade import meta from shade import _utils @@ -82,8 +83,10 @@ class TestIdentityRoles(base.TestCase): self.assertTrue(self.cloud.delete_role('1234')) self.assertTrue(mock_keystone.roles.delete.called) + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') @mock.patch.object(shade.OpenStackCloud, 'keystone_client') - def test_list_role_assignments(self, mock_keystone): + def test_list_role_assignments(self, mock_keystone, mock_api_version): + mock_api_version.return_value = '3' mock_keystone.role_assignments.list.return_value = RAW_ROLE_ASSIGNMENTS ret = self.cloud.list_role_assignments() mock_keystone.role_assignments.list.assert_called_once_with() @@ -92,17 +95,74 @@ class TestIdentityRoles(base.TestCase): ) self.assertEqual(normalized_assignments, ret) + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') @mock.patch.object(shade.OpenStackCloud, 'keystone_client') - def test_list_role_assignments_filters(self, mock_keystone): + def test_list_role_assignments_filters(self, mock_keystone, + mock_api_version): + mock_api_version.return_value = '3' params = dict(user='123', domain='456', effective=True) self.cloud.list_role_assignments(filters=params) mock_keystone.role_assignments.list.assert_called_once_with(**params) + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') @mock.patch.object(shade.OpenStackCloud, 'keystone_client') - def test_list_role_assignments_exception(self, mock_keystone): + def test_list_role_assignments_exception(self, mock_keystone, + mock_api_version): + mock_api_version.return_value = '3' mock_keystone.role_assignments.list.side_effect = Exception() with testtools.ExpectedException( shade.OpenStackCloudException, "Failed to list role assignments" ): self.cloud.list_role_assignments() + + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_list_role_assignments_keystone_v2(self, mock_keystone, + mock_api_version): + fake_role = fakes.FakeRole(id='1234', name='fake_role') + mock_api_version.return_value = '2.0' + mock_keystone.roles.roles_for_user.return_value = [fake_role] + ret = self.cloud.list_role_assignments(filters={'user': '2222', + 'project': '3333'}) + self.assertEqual(ret, [{'id': fake_role.id, + 'project': '3333', + 'user': '2222'}]) + + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_list_role_assignments_keystone_v2_with_role(self, mock_keystone, + mock_api_version): + fake_role1 = fakes.FakeRole(id='1234', name='fake_role') + fake_role2 = fakes.FakeRole(id='4321', name='fake_role') + mock_api_version.return_value = '2.0' + mock_keystone.roles.roles_for_user.return_value = [fake_role1, + fake_role2] + ret = self.cloud.list_role_assignments(filters={'role': fake_role1.id, + 'user': '2222', + 'project': '3333'}) + self.assertEqual(ret, [{'id': fake_role1.id, + 'project': '3333', + 'user': '2222'}]) + + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_list_role_assignments_exception_v2(self, mock_keystone, + mock_api_version): + mock_api_version.return_value = '2.0' + with testtools.ExpectedException( + shade.OpenStackCloudException, + "Must provide project and user for keystone v2" + ): + self.cloud.list_role_assignments() + + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_list_role_assignments_exception_v2_no_project(self, mock_keystone, + mock_api_version): + mock_api_version.return_value = '2.0' + with testtools.ExpectedException( + shade.OpenStackCloudException, + "Must provide project and user for keystone v2" + ): + self.cloud.list_role_assignments(filters={'user': '12345'})