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
This commit is contained in:
Rodrigo Duarte Sousa
2015-01-26 12:47:00 -03:00
parent 32c18a83e2
commit 14ace4a5de
4 changed files with 144 additions and 10 deletions

View File

@@ -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)

View File

@@ -21,6 +21,8 @@ Exception definitions.
.. py:exception:: HttpError
.. py:exception:: ValidationError
.. py:exception:: Unauthorized
"""

View File

@@ -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

View File

@@ -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)
"""
# According to the API spec, the query params are key only
query = ''
if subtree_as_list:
query = '?subtree_as_list'
if parents_as_list:
query = query + '&parents_as_list' if query else '?parents_as_list'
: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_params = []
if subtree_as_list:
query_params.append('subtree_as_list')
if subtree_as_ids:
query_params.append('subtree_as_ids')
if 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,