diff options
author | Rodrigo Duarte Sousa <rodrigods@lsd.ufcg.edu.br> | 2015-01-26 12:47:00 -0300 |
---|---|---|
committer | Rodrigo Duarte Sousa <rodrigods@lsd.ufcg.edu.br> | 2015-03-13 17:02:39 -0300 |
commit | 14ace4a5ded0bd4005928b42a4f337639bd90799 (patch) | |
tree | 2b71e008bbd775b160573ddd2713391772fd45de | |
parent | 32c18a83e2ffe80f559ca871eeddb2ef8848ff17 (diff) | |
download | python-keystoneclient-14ace4a5ded0bd4005928b42a4f337639bd90799.tar.gz |
Implements subtree_as_ids and parents_as_ids
This patch implements the new ways to get the project's hierarchy:
'subtree_as_ids': If True, returns projects IDs down the hierarchy
as a structured dictionay.
'parents_as_ids': If True, returns projects IDs up the hierarchy
as a structured dictionay.
Change-Id: Ia3afe994893dfca059cb8361f7ab1c14e28e1ad5
Implements: blueprint hierarchical-multitenancy-improvements
-rw-r--r-- | keystoneclient/base.py | 9 | ||||
-rw-r--r-- | keystoneclient/exceptions.py | 2 | ||||
-rw-r--r-- | keystoneclient/tests/unit/v3/test_projects.py | 86 | ||||
-rw-r--r-- | keystoneclient/v3/projects.py | 49 |
4 files changed, 140 insertions, 6 deletions
diff --git a/keystoneclient/base.py b/keystoneclient/base.py index 81d5e26..2af38b9 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -340,6 +340,15 @@ class CrudManager(Manager): def _build_query(self, params): return '?%s' % urllib.parse.urlencode(params) if params else '' + def build_key_only_query(self, params_list): + """Builds a query that does not include values, just keys. + + The Identity API has some calls that define queries without values, + this can not be accomplished by using urllib.parse.urlencode(). This + method builds a query using only the keys. + """ + return '?%s' % '&'.join(params_list) if params_list else '' + @filter_kwargs def list(self, fallback_to_auth=False, **kwargs): url = self.build_url(dict_args_in_out=kwargs) diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py index a76aa32..0150bf5 100644 --- a/keystoneclient/exceptions.py +++ b/keystoneclient/exceptions.py @@ -21,6 +21,8 @@ Exception definitions. .. py:exception:: HttpError +.. py:exception:: ValidationError + .. py:exception:: Unauthorized """ diff --git a/keystoneclient/tests/unit/v3/test_projects.py b/keystoneclient/tests/unit/v3/test_projects.py index 5d08bb2..61a5ef1 100644 --- a/keystoneclient/tests/unit/v3/test_projects.py +++ b/keystoneclient/tests/unit/v3/test_projects.py @@ -144,6 +144,75 @@ class ProjectTests(utils.TestCase, utils.CrudTests): return projects + def test_get_with_subtree_as_ids(self): + projects = self._create_projects_hierarchy() + ref = projects[0] + + # We will query for projects[0] subtree, it should include projects[1] + # and projects[2] structured like the following: + # { + # projects[1]: { + # projects[2]: None + # } + # } + ref['subtree'] = { + projects[1]['id']: { + projects[2]['id']: None + } + } + + self.stub_entity('GET', id=ref['id'], entity=ref) + + returned = self.manager.get(ref['id'], subtree_as_ids=True) + self.assertQueryStringIs('subtree_as_ids') + self.assertDictEqual(ref['subtree'], returned.subtree) + + def test_get_with_parents_as_ids(self): + projects = self._create_projects_hierarchy() + ref = projects[2] + + # We will query for projects[2] parents, it should include projects[1] + # and projects[0] structured like the following: + # { + # projects[1]: { + # projects[0]: None + # } + # } + ref['parents'] = { + projects[1]['id']: { + projects[0]['id']: None + } + } + + self.stub_entity('GET', id=ref['id'], entity=ref) + + returned = self.manager.get(ref['id'], parents_as_ids=True) + self.assertQueryStringIs('parents_as_ids') + self.assertDictEqual(ref['parents'], returned.parents) + + def test_get_with_parents_as_ids_and_subtree_as_ids(self): + ref = self.new_ref() + projects = self._create_projects_hierarchy() + ref = projects[1] + + # We will query for projects[1] subtree and parents. The subtree should + # include projects[2] and the parents should include projects[2]. + ref['parents'] = { + projects[0]['id']: None + } + ref['subtree'] = { + projects[2]['id']: None + } + + self.stub_entity('GET', id=ref['id'], entity=ref) + + returned = self.manager.get(ref['id'], + parents_as_ids=True, + subtree_as_ids=True) + self.assertQueryStringIs('subtree_as_ids&parents_as_ids') + self.assertDictEqual(ref['parents'], returned.parents) + self.assertDictEqual(ref['subtree'], returned.subtree) + def test_get_with_subtree_as_list(self): projects = self._create_projects_hierarchy() ref = projects[0] @@ -213,6 +282,23 @@ class ProjectTests(utils.TestCase, utils.CrudTests): projects[2][attr], 'Expected different %s' % attr) + def test_get_with_invalid_parameters_combination(self): + # subtree_as_list and subtree_as_ids can not be included at the + # same time in the call. + self.assertRaises(exceptions.ValidationError, + self.manager.get, + project=uuid.uuid4().hex, + subtree_as_list=True, + subtree_as_ids=True) + + # parents_as_list and parents_as_ids can not be included at the + # same time in the call. + self.assertRaises(exceptions.ValidationError, + self.manager.get, + project=uuid.uuid4().hex, + parents_as_list=True, + parents_as_ids=True) + def test_update_with_parent_project(self): ref = self.new_ref() ref['parent_id'] = uuid.uuid4().hex diff --git a/keystoneclient/v3/projects.py b/keystoneclient/v3/projects.py index 0a98991..5daaee5 100644 --- a/keystoneclient/v3/projects.py +++ b/keystoneclient/v3/projects.py @@ -15,6 +15,8 @@ # under the License. from keystoneclient import base +from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -103,8 +105,23 @@ class ProjectManager(base.CrudManager): fallback_to_auth=True, **kwargs) + def _check_not_parents_as_ids_and_parents_as_list(self, parents_as_ids, + parents_as_list): + if parents_as_ids and parents_as_list: + msg = _('Specify either parents_as_ids or parents_as_list ' + 'parameters, not both') + raise exceptions.ValidationError(msg) + + def _check_not_subtree_as_ids_and_subtree_as_list(self, subtree_as_ids, + subtree_as_list): + if subtree_as_ids and subtree_as_list: + msg = _('Specify either subtree_as_ids or subtree_as_list ' + 'parameters, not both') + raise exceptions.ValidationError(msg) + @utils.positional() - def get(self, project, subtree_as_list=False, parents_as_list=False): + def get(self, project, subtree_as_list=False, parents_as_list=False, + subtree_as_ids=False, parents_as_ids=False): """Get a project. :param project: project to be retrieved. @@ -115,17 +132,37 @@ class ProjectManager(base.CrudManager): :param boolean parents_as_list: retrieve projects above this project in the hierarchy as a flat list. (optional) + :param boolean subtree_as_ids: retrieve the IDs from the projects below + this project in the hierarchy as a + structured dictionary. (optional) + :param boolean parents_as_ids: retrieve the IDs from the projects above + this project in the hierarchy as a + structured dictionary. (optional) + + :raises keystoneclient.exceptions.ValidationError: if subtree_as_list + and subtree_as_ids or parents_as_list and parents_as_ids are + included at the same time in the call. """ + self._check_not_parents_as_ids_and_parents_as_list( + parents_as_ids, parents_as_list) + self._check_not_subtree_as_ids_and_subtree_as_list( + subtree_as_ids, subtree_as_list) + # According to the API spec, the query params are key only - query = '' + query_params = [] if subtree_as_list: - query = '?subtree_as_list' + query_params.append('subtree_as_list') + if subtree_as_ids: + query_params.append('subtree_as_ids') if parents_as_list: - query = query + '&parents_as_list' if query else '?parents_as_list' + query_params.append('parents_as_list') + if parents_as_ids: + query_params.append('parents_as_ids') + query = self.build_key_only_query(query_params) dict_args = {'project_id': base.getid(project)} - url = self.build_url(dict_args_in_out=dict_args) + query - return self._get(url, self.key) + url = self.build_url(dict_args_in_out=dict_args) + return self._get(url + query, self.key) @utils.positional(enforcement=utils.positional.WARN) def update(self, project, name=None, domain=None, description=None, |