Merge "Update quotas to handle domain acting as project"
This commit is contained in:
commit
bb75d7b07a
@ -104,6 +104,19 @@ def get_volume_type_reservation(ctxt, volume, type_id,
|
|||||||
return reservations
|
return reservations
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_domain_id_from_parents(domain_id, tree):
|
||||||
|
"""Removes the domain_id from the tree if present"""
|
||||||
|
new_tree = None
|
||||||
|
if tree:
|
||||||
|
parent, children = next(iter(tree.items()))
|
||||||
|
# Don't add the domain id to the parents hierarchy
|
||||||
|
if parent != domain_id:
|
||||||
|
new_tree = {parent: _filter_domain_id_from_parents(domain_id,
|
||||||
|
children)}
|
||||||
|
|
||||||
|
return new_tree
|
||||||
|
|
||||||
|
|
||||||
def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
||||||
parents_as_ids=False):
|
parents_as_ids=False):
|
||||||
"""A Helper method to get the project hierarchy.
|
"""A Helper method to get the project hierarchy.
|
||||||
@ -111,6 +124,8 @@ def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
|||||||
Along with hierarchical multitenancy in keystone API v3, projects can be
|
Along with hierarchical multitenancy in keystone API v3, projects can be
|
||||||
hierarchically organized. Therefore, we need to know the project
|
hierarchically organized. Therefore, we need to know the project
|
||||||
hierarchy, if any, in order to do nested quota operations properly.
|
hierarchy, if any, in order to do nested quota operations properly.
|
||||||
|
If the domain is being used as the top most parent, it is filtered out from
|
||||||
|
the parent tree and parent_id.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
keystone = _keystone_client(context)
|
keystone = _keystone_client(context)
|
||||||
@ -119,11 +134,18 @@ def get_project_hierarchy(context, project_id, subtree_as_ids=False,
|
|||||||
project = keystone.projects.get(project_id,
|
project = keystone.projects.get(project_id,
|
||||||
subtree_as_ids=subtree_as_ids,
|
subtree_as_ids=subtree_as_ids,
|
||||||
parents_as_ids=parents_as_ids)
|
parents_as_ids=parents_as_ids)
|
||||||
|
|
||||||
|
generic_project.parent_id = None
|
||||||
|
if project.parent_id != project.domain_id:
|
||||||
generic_project.parent_id = project.parent_id
|
generic_project.parent_id = project.parent_id
|
||||||
|
|
||||||
generic_project.subtree = (
|
generic_project.subtree = (
|
||||||
project.subtree if subtree_as_ids else None)
|
project.subtree if subtree_as_ids else None)
|
||||||
generic_project.parents = (
|
|
||||||
project.parents if parents_as_ids else None)
|
generic_project.parents = None
|
||||||
|
if parents_as_ids:
|
||||||
|
generic_project.parents = _filter_domain_id_from_parents(
|
||||||
|
project.domain_id, project.parents)
|
||||||
except exceptions.NotFound:
|
except exceptions.NotFound:
|
||||||
msg = (_("Tenant ID: %s does not exist.") % project_id)
|
msg = (_("Tenant ID: %s does not exist.") % project_id)
|
||||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||||
|
@ -1387,6 +1387,7 @@ class NestedDbQuotaDriverBaseTestCase(DbQuotaDriverBaseTestCase):
|
|||||||
def __init__(self, parent_id):
|
def __init__(self, parent_id):
|
||||||
self.parent_id = parent_id
|
self.parent_id = parent_id
|
||||||
self.parents = {parent_id: None}
|
self.parents = {parent_id: None}
|
||||||
|
self.domain_id = 'default'
|
||||||
|
|
||||||
def fake_get_project(project_id, subtree_as_ids=False,
|
def fake_get_project(project_id, subtree_as_ids=False,
|
||||||
parents_as_ids=False):
|
parents_as_ids=False):
|
||||||
|
@ -35,6 +35,8 @@ class QuotaUtilsTest(test.TestCase):
|
|||||||
self.id = id
|
self.id = id
|
||||||
self.parent_id = parent_id
|
self.parent_id = parent_id
|
||||||
self.subtree = None
|
self.subtree = None
|
||||||
|
self.parents = None
|
||||||
|
self.domain_id = 'default'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(QuotaUtilsTest, self).setUp()
|
super(QuotaUtilsTest, self).setUp()
|
||||||
@ -92,6 +94,61 @@ class QuotaUtilsTest(test.TestCase):
|
|||||||
self.context.project_id, parents_as_ids=False, subtree_as_ids=True)
|
self.context.project_id, parents_as_ids=False, subtree_as_ids=True)
|
||||||
self.assertEqual(expected_project.__dict__, project.__dict__)
|
self.assertEqual(expected_project.__dict__, project.__dict__)
|
||||||
|
|
||||||
|
def _setup_mock_ksclient(self, mock_client, version='v3',
|
||||||
|
subtree=None, parents=None):
|
||||||
|
keystoneclient = mock_client.return_value
|
||||||
|
keystoneclient.version = version
|
||||||
|
proj = self.FakeProject(self.context.project_id)
|
||||||
|
proj.subtree = subtree
|
||||||
|
if parents:
|
||||||
|
proj.parents = parents
|
||||||
|
proj.parent_id = next(iter(parents.keys()))
|
||||||
|
keystoneclient.projects.get.return_value = proj
|
||||||
|
|
||||||
|
@mock.patch('keystoneclient.client.Client')
|
||||||
|
def test__filter_domain_id_from_parents_domain_as_parent(
|
||||||
|
self, mock_client):
|
||||||
|
# Test with a top level project (domain is direct parent)
|
||||||
|
self._setup_mock_ksclient(mock_client, parents={'default': None})
|
||||||
|
project = quota_utils.get_project_hierarchy(
|
||||||
|
self.context, self.context.project_id, parents_as_ids=True)
|
||||||
|
self.assertIsNone(project.parent_id)
|
||||||
|
self.assertIsNone(project.parents)
|
||||||
|
|
||||||
|
@mock.patch('keystoneclient.client.Client')
|
||||||
|
def test__filter_domain_id_from_parents_domain_as_grandparent(
|
||||||
|
self, mock_client):
|
||||||
|
# Test with a child project (domain is more than a parent)
|
||||||
|
self._setup_mock_ksclient(mock_client,
|
||||||
|
parents={'bar': {'default': None}})
|
||||||
|
project = quota_utils.get_project_hierarchy(
|
||||||
|
self.context, self.context.project_id, parents_as_ids=True)
|
||||||
|
self.assertEqual('bar', project.parent_id)
|
||||||
|
self.assertEqual({'bar': None}, project.parents)
|
||||||
|
|
||||||
|
@mock.patch('keystoneclient.client.Client')
|
||||||
|
def test__filter_domain_id_from_parents_no_domain_in_parents(
|
||||||
|
self, mock_client):
|
||||||
|
# Test that if top most parent is not a domain (to simulate an older
|
||||||
|
# keystone version) nothing gets removed from the tree
|
||||||
|
parents = {'bar': {'foo': None}}
|
||||||
|
self._setup_mock_ksclient(mock_client, parents=parents)
|
||||||
|
project = quota_utils.get_project_hierarchy(
|
||||||
|
self.context, self.context.project_id, parents_as_ids=True)
|
||||||
|
self.assertEqual('bar', project.parent_id)
|
||||||
|
self.assertEqual(parents, project.parents)
|
||||||
|
|
||||||
|
@mock.patch('keystoneclient.client.Client')
|
||||||
|
def test__filter_domain_id_from_parents_no_parents(
|
||||||
|
self, mock_client):
|
||||||
|
# Test that if top no parents are present (to simulate an older
|
||||||
|
# keystone version) things don't blow up
|
||||||
|
self._setup_mock_ksclient(mock_client)
|
||||||
|
project = quota_utils.get_project_hierarchy(
|
||||||
|
self.context, self.context.project_id, parents_as_ids=True)
|
||||||
|
self.assertIsNone(project.parent_id)
|
||||||
|
self.assertIsNone(project.parents)
|
||||||
|
|
||||||
@mock.patch('cinder.quota_utils._keystone_client')
|
@mock.patch('cinder.quota_utils._keystone_client')
|
||||||
def test_validate_nested_projects_with_keystone_v2(self, _keystone_client):
|
def test_validate_nested_projects_with_keystone_v2(self, _keystone_client):
|
||||||
_keystone_client.side_effect = exceptions.VersionNotAvailable
|
_keystone_client.side_effect = exceptions.VersionNotAvailable
|
||||||
|
Loading…
Reference in New Issue
Block a user