From 8038c70abf5921dd70023c82cbec0de4b610a504 Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Fri, 29 Jun 2018 10:54:19 +0800 Subject: [PATCH] Add include_limits filter Add include_limits filter for get project to support fetching project hierarchy limits. This filter should be used together with "subtree_as_list" or "parents_as_list" filter bp: strict-two-level-model Change-Id: Ib602887c92b89be0ffec1394a3076f5dd5671511 --- keystone/resource/controllers.py | 6 +- keystone/resource/core.py | 22 ++++++- keystone/tests/unit/test_v3_resource.py | 64 +++++++++++++++++++ .../notes/bp-strict-two-level-model.yaml | 7 ++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py index 1725f42641..054cbc562a 100644 --- a/keystone/resource/controllers.py +++ b/keystone/resource/controllers.py @@ -213,6 +213,8 @@ class ProjectV3(controller.V3Controller): self.query_filter_is_true(params['subtree_as_list'])) subtree_as_ids = 'subtree_as_ids' in params and ( self.query_filter_is_true(params['subtree_as_ids'])) + include_limits = 'include_limits' in params and ( + self.query_filter_is_true(params['include_limits'])) # parents_as_list and parents_as_ids are mutually exclusive if parents_as_list and parents_as_ids: @@ -228,7 +230,7 @@ class ProjectV3(controller.V3Controller): if parents_as_list: parents = PROVIDERS.resource_api.list_project_parents( - ref['id'], request.context.user_id) + ref['id'], request.context.user_id, include_limits) ref['parents'] = [ProjectV3.wrap_member(context, p) for p in parents] elif parents_as_ids: @@ -238,7 +240,7 @@ class ProjectV3(controller.V3Controller): if subtree_as_list: subtree = PROVIDERS.resource_api.list_projects_in_subtree( - ref['id'], request.context.user_id) + ref['id'], request.context.user_id, include_limits) ref['subtree'] = [ProjectV3.wrap_member(context, p) for p in subtree] elif subtree_as_ids: diff --git a/keystone/resource/core.py b/keystone/resource/core.py index e4ec7e9756..76d8fb728c 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -526,13 +526,28 @@ class Manager(manager.Manager): # Check if project_id exists self.get_project(project_id) - def list_project_parents(self, project_id, user_id=None): + def _include_limits(self, projects): + """Modify a list of projects to include limit information. + + :param projects: a list of project references including an `id` + :type projects: list of dictionaries + """ + for project in projects: + hints = driver_hints.Hints() + hints.add_filter('project_id', project['id']) + limits = PROVIDERS.unified_limit_api.list_limits(hints) + project['limits'] = limits + + def list_project_parents(self, project_id, user_id=None, + include_limits=False): self._assert_valid_project_id(project_id) parents = self.driver.list_project_parents(project_id) # If a user_id was provided, the returned list should be filtered # against the projects this user has access to. if user_id: parents = self._filter_projects_list(parents, user_id) + if include_limits: + self._include_limits(parents) return parents def _build_parents_as_ids_dict(self, project, parents_by_id): @@ -578,13 +593,16 @@ class Manager(manager.Manager): project, {proj['id']: proj for proj in parents_list}) return parents_as_ids - def list_projects_in_subtree(self, project_id, user_id=None): + def list_projects_in_subtree(self, project_id, user_id=None, + include_limits=False): self._assert_valid_project_id(project_id) subtree = self.driver.list_projects_in_subtree(project_id) # If a user_id was provided, the returned list should be filtered # against the projects this user has access to. if user_id: subtree = self._filter_projects_list(subtree, user_id) + if include_limits: + self._include_limits(subtree) return subtree def _build_subtree_as_ids_dict(self, project_id, subtree_by_parent): diff --git a/keystone/tests/unit/test_v3_resource.py b/keystone/tests/unit/test_v3_resource.py index a1e8b4589a..b94e28822e 100644 --- a/keystone/tests/unit/test_v3_resource.py +++ b/keystone/tests/unit/test_v3_resource.py @@ -1090,6 +1090,70 @@ class ResourceTestCase(test_v3.RestfulTestCase, 'project_id': projects[1]['project']['id']}, expected_status=http_client.BAD_REQUEST) + def test_get_project_with_include_limits(self): + parent, project, subproject = self._create_projects_hierarchy(2) + # Assign a role for the user on all the created projects + for proj in (parent, project, subproject): + self.put(self.build_role_assignment_link( + role_id=self.role_id, user_id=self.user_id, + project_id=proj['project']['id'])) + # create a registered limit and three limits for each project. + reg_limit = unit.new_registered_limit_ref(service_id=self.service_id, + region_id=self.region_id, + resource_name='volume') + self.post( + '/registered_limits', + body={'registered_limits': [reg_limit]}, + expected_status=http_client.CREATED) + limit1 = unit.new_limit_ref(project_id=parent['project']['id'], + service_id=self.service_id, + region_id=self.region_id, + resource_name='volume') + limit2 = unit.new_limit_ref(project_id=project['project']['id'], + service_id=self.service_id, + region_id=self.region_id, + resource_name='volume') + limit3 = unit.new_limit_ref(project_id=subproject['project']['id'], + service_id=self.service_id, + region_id=self.region_id, + resource_name='volume') + self.post( + '/limits', + body={'limits': [limit1, limit2, limit3]}, + expected_status=http_client.CREATED) + # "include_limits" should work together with "parents_as_list" or + # "subtree_as_list". Only using "include_limits" really does nothing. + r = self.get('/projects/%(project_id)s?include_limits' % + {'project_id': subproject['project']['id']}) + + self.assertIsNone(r.result['project'].get('parents')) + self.assertIsNone(r.result['project'].get('subtree')) + self.assertIsNone(r.result['project'].get('limits')) + + # using "include_limits" with "parents_as_list" + r = self.get('/projects/%(project_id)s?include_limits&parents_as_list' + % {'project_id': subproject['project']['id']}) + + self.assertEqual(2, len(r.result['project']['parents'])) + for parent in r.result['project']['parents']: + self.assertEqual(1, len(parent['project']['limits'])) + self.assertEqual(parent['project']['id'], + parent['project']['limits'][0]['project_id']) + self.assertEqual(10, + parent['project']['limits'][0]['resource_limit']) + + # using "include_limits" with "subtree_as_list" + r = self.get('/projects/%(project_id)s?include_limits&subtree_as_list' + % {'project_id': parent['project']['id']}) + + self.assertEqual(2, len(r.result['project']['subtree'])) + for child in r.result['project']['subtree']: + self.assertEqual(1, len(child['project']['limits'])) + self.assertEqual(child['project']['id'], + child['project']['limits'][0]['project_id']) + self.assertEqual(10, + child['project']['limits'][0]['resource_limit']) + def test_list_project_is_domain_filter(self): """Call ``GET /projects?is_domain=True/False``.""" # Get the initial number of projects, both acting as a domain as well diff --git a/releasenotes/notes/bp-strict-two-level-model.yaml b/releasenotes/notes/bp-strict-two-level-model.yaml index 7af8c551b3..8f15057be8 100644 --- a/releasenotes/notes/bp-strict-two-level-model.yaml +++ b/releasenotes/notes/bp-strict-two-level-model.yaml @@ -19,3 +19,10 @@ features: The `project_id` filter is added for listing limits. This filter is used for system-scoped request only to fetch the specified project limits. Non system-scoped request will get empty response body instead. + + - > + [`blueprint strict-two-level-model `_] + The `include_limits` filter is added to `GET /v3/projects/{project_id}` API. + This filter should be used together with `parents_as_list` or + `subtree_as_list` filter to add parent/sub project's limit information the + response body.