diff --git a/shade/_tasks.py b/shade/_tasks.py index 1ada75a21..304a1bb7c 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -64,7 +64,7 @@ class UserRemoveFromGroup(task_manager.Task): class ProjectList(task_manager.Task): def main(self, client): - return client._project_manager.list() + return client._project_manager.list(**self.args) class ProjectCreate(task_manager.Task): diff --git a/shade/_utils.py b/shade/_utils.py index ccc13b5f1..f65ca8819 100644 --- a/shade/_utils.py +++ b/shade/_utils.py @@ -118,7 +118,7 @@ def _filter_list(data, name_or_id, filters): return filtered -def _get_entity(func, name_or_id, filters): +def _get_entity(func, name_or_id, filters, **kwargs): """Return a single entity from the list returned by a given method. :param callable func: @@ -136,7 +136,7 @@ def _get_entity(func, name_or_id, filters): # object and just short-circuit return it. if hasattr(name_or_id, 'id'): return name_or_id - entities = func(name_or_id, filters) + entities = func(name_or_id, filters, **kwargs) if not entities: return None if len(entities) > 1: diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 380e9ab16..cd95a1839 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -442,53 +442,64 @@ class OpenStackCloud(object): return filtered @_utils.cache_on_arguments() - def list_projects(self): + def list_projects(self, domain_id=None): """List Keystone Projects. + :param string domain_id: domain id to scope the listed projects. + :returns: a list of dicts containing the project description. :raises: ``OpenStackCloudException``: if something goes wrong during the openstack API call. """ try: - projects = self.manager.submitTask(_tasks.ProjectList()) + if self.cloud_config.get_api_version('identity') == '3': + projects = self.manager.submitTask( + _tasks.ProjectList(domain=domain_id)) + else: + projects = self.manager.submitTask( + _tasks.ProjectList()) except Exception as e: self.log.debug("Failed to list projects", exc_info=True) raise OpenStackCloudException(str(e)) return projects - def search_projects(self, name_or_id=None, filters=None): + def search_projects(self, name_or_id=None, filters=None, domain_id=None): """Seach Keystone projects. :param name: project name or id. :param filters: a dict containing additional filters to use. + :param domain_id: domain id to scope the searched projects. :returns: a list of dict containing the projects :raises: ``OpenStackCloudException``: if something goes wrong during the openstack API call. """ - projects = self.list_projects() + projects = self.list_projects(domain_id=domain_id) return _utils._filter_list(projects, name_or_id, filters) - def get_project(self, name_or_id, filters=None): + def get_project(self, name_or_id, filters=None, domain_id=None): """Get exactly one Keystone project. :param id: project name or id. :param filters: a dict containing additional filters to use. + :param domain_id: domain id (keystone v3 only) :returns: a list of dicts containing the project description. :raises: ``OpenStackCloudException``: if something goes wrong during the openstack API call. """ - return _utils._get_entity(self.search_projects, name_or_id, filters) + return _utils._get_entity(self.search_projects, name_or_id, filters, + domain_id=domain_id) - def update_project(self, name_or_id, description=None, enabled=True): + def update_project(self, name_or_id, description=None, enabled=True, + domain_id=None): with _utils.shade_exceptions( "Error in updating project {project}".format( project=name_or_id)): - proj = self.get_project(name_or_id) + proj = self.get_project(name_or_id, domain_id=domain_id) if not proj: raise OpenStackCloudException( "Project %s not found." % name_or_id) @@ -523,10 +534,12 @@ class OpenStackCloud(object): self.list_projects.invalidate(self) return project - def delete_project(self, name_or_id): + def delete_project(self, name_or_id, domain_id=None): """Delete a project :param string name_or_id: Project name or id. + :param string domain_id: Domain id containing the project (keystone + v3 only). :returns: True if delete succeeded, False if the project was not found. @@ -537,7 +550,7 @@ class OpenStackCloud(object): with _utils.shade_exceptions( "Error in deleting project {project}".format( project=name_or_id)): - project = self.get_project(name_or_id) + project = self.get_project(name_or_id, domain_id=domain_id) if project is None: self.log.debug( "Project {0} not found for deleting".format(name_or_id)) diff --git a/shade/tests/functional/test_project.py b/shade/tests/functional/test_project.py index 029b14895..4e1e26d48 100644 --- a/shade/tests/functional/test_project.py +++ b/shade/tests/functional/test_project.py @@ -63,6 +63,25 @@ class TestProject(base.BaseFunctionalTestCase): self.assertEqual(project_name, project['name']) self.assertEqual('test_create_project', project['description']) + def test_update_project(self): + project_name = self.new_project_name + '_update' + + params = { + 'name': project_name, + 'description': 'test_update_project', + } + if self.identity_version == '3': + params['domain_id'] = \ + self.operator_cloud.get_domain('default')['id'] + + project = self.operator_cloud.create_project(**params) + updated_project = self.operator_cloud.update_project(project_name, + description='new') + self.assertIsNotNone(updated_project) + self.assertEqual(project['id'], updated_project['id']) + self.assertEqual(project['name'], updated_project['name']) + self.assertEqual(updated_project['description'], 'new') + def test_delete_project(self): project_name = self.new_project_name + '_delete' params = {'name': project_name} diff --git a/shade/tests/unit/test_project.py b/shade/tests/unit/test_project.py index a5507bc5e..6b8b1ad62 100644 --- a/shade/tests/unit/test_project.py +++ b/shade/tests/unit/test_project.py @@ -75,7 +75,7 @@ class TestProject(base.TestCase): mock_api_version.return_value = '2' mock_get.return_value = dict(id='123') self.assertTrue(self.cloud.delete_project('123')) - mock_get.assert_called_once_with('123') + mock_get.assert_called_once_with('123', domain_id=None) mock_keystone.tenants.delete.assert_called_once_with(tenant='123') @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') @@ -86,7 +86,7 @@ class TestProject(base.TestCase): mock_api_version.return_value = '3' mock_get.return_value = dict(id='123') self.assertTrue(self.cloud.delete_project('123')) - mock_get.assert_called_once_with('123') + mock_get.assert_called_once_with('123', domain_id=None) mock_keystone.projects.delete.assert_called_once_with(project='123') @mock.patch.object(shade.OpenStackCloud, 'get_project') @@ -119,3 +119,11 @@ class TestProject(base.TestCase): self.cloud.update_project('123', description='new', enabled=False) mock_keystone.projects.update.assert_called_once_with( description='new', enabled=False, project='123') + + @mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_list_projects_v3(self, mock_keystone, mock_api_version): + mock_api_version.return_value = '3' + self.cloud.list_projects('123') + mock_keystone.projects.list.assert_called_once_with( + domain='123')