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:
@@ -340,6 +340,15 @@ class CrudManager(Manager):
|
|||||||
def _build_query(self, params):
|
def _build_query(self, params):
|
||||||
return '?%s' % urllib.parse.urlencode(params) if params else ''
|
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
|
@filter_kwargs
|
||||||
def list(self, fallback_to_auth=False, **kwargs):
|
def list(self, fallback_to_auth=False, **kwargs):
|
||||||
url = self.build_url(dict_args_in_out=kwargs)
|
url = self.build_url(dict_args_in_out=kwargs)
|
||||||
|
@@ -21,6 +21,8 @@ Exception definitions.
|
|||||||
|
|
||||||
.. py:exception:: HttpError
|
.. py:exception:: HttpError
|
||||||
|
|
||||||
|
.. py:exception:: ValidationError
|
||||||
|
|
||||||
.. py:exception:: Unauthorized
|
.. py:exception:: Unauthorized
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@@ -144,6 +144,75 @@ class ProjectTests(utils.TestCase, utils.CrudTests):
|
|||||||
|
|
||||||
return projects
|
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):
|
def test_get_with_subtree_as_list(self):
|
||||||
projects = self._create_projects_hierarchy()
|
projects = self._create_projects_hierarchy()
|
||||||
ref = projects[0]
|
ref = projects[0]
|
||||||
@@ -213,6 +282,23 @@ class ProjectTests(utils.TestCase, utils.CrudTests):
|
|||||||
projects[2][attr],
|
projects[2][attr],
|
||||||
'Expected different %s' % 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):
|
def test_update_with_parent_project(self):
|
||||||
ref = self.new_ref()
|
ref = self.new_ref()
|
||||||
ref['parent_id'] = uuid.uuid4().hex
|
ref['parent_id'] = uuid.uuid4().hex
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from keystoneclient import base
|
from keystoneclient import base
|
||||||
|
from keystoneclient import exceptions
|
||||||
|
from keystoneclient.i18n import _
|
||||||
from keystoneclient import utils
|
from keystoneclient import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -103,8 +105,23 @@ class ProjectManager(base.CrudManager):
|
|||||||
fallback_to_auth=True,
|
fallback_to_auth=True,
|
||||||
**kwargs)
|
**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()
|
@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.
|
"""Get a project.
|
||||||
|
|
||||||
:param project: project to be retrieved.
|
:param project: project to be retrieved.
|
||||||
@@ -115,17 +132,37 @@ class ProjectManager(base.CrudManager):
|
|||||||
:param boolean parents_as_list: retrieve projects above this project
|
:param boolean parents_as_list: retrieve projects above this project
|
||||||
in the hierarchy as a flat list.
|
in the hierarchy as a flat list.
|
||||||
(optional)
|
(optional)
|
||||||
"""
|
:param boolean subtree_as_ids: retrieve the IDs from the projects below
|
||||||
# According to the API spec, the query params are key only
|
this project in the hierarchy as a
|
||||||
query = ''
|
structured dictionary. (optional)
|
||||||
if subtree_as_list:
|
:param boolean parents_as_ids: retrieve the IDs from the projects above
|
||||||
query = '?subtree_as_list'
|
this project in the hierarchy as a
|
||||||
if parents_as_list:
|
structured dictionary. (optional)
|
||||||
query = query + '&parents_as_list' if query else '?parents_as_list'
|
|
||||||
|
|
||||||
|
: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)}
|
dict_args = {'project_id': base.getid(project)}
|
||||||
url = self.build_url(dict_args_in_out=dict_args) + query
|
url = self.build_url(dict_args_in_out=dict_args)
|
||||||
return self._get(url, self.key)
|
return self._get(url + query, self.key)
|
||||||
|
|
||||||
@utils.positional(enforcement=utils.positional.WARN)
|
@utils.positional(enforcement=utils.positional.WARN)
|
||||||
def update(self, project, name=None, domain=None, description=None,
|
def update(self, project, name=None, domain=None, description=None,
|
||||||
|
Reference in New Issue
Block a user