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
|
||||
|
||||
|
||||
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,
|
||||
parents_as_ids=False):
|
||||
"""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
|
||||
hierarchically organized. Therefore, we need to know the project
|
||||
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:
|
||||
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,
|
||||
subtree_as_ids=subtree_as_ids,
|
||||
parents_as_ids=parents_as_ids)
|
||||
generic_project.parent_id = project.parent_id
|
||||
|
||||
generic_project.parent_id = None
|
||||
if project.parent_id != project.domain_id:
|
||||
generic_project.parent_id = project.parent_id
|
||||
|
||||
generic_project.subtree = (
|
||||
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:
|
||||
msg = (_("Tenant ID: %s does not exist.") % project_id)
|
||||
raise webob.exc.HTTPNotFound(explanation=msg)
|
||||
|
@ -1387,6 +1387,7 @@ class NestedDbQuotaDriverBaseTestCase(DbQuotaDriverBaseTestCase):
|
||||
def __init__(self, parent_id):
|
||||
self.parent_id = parent_id
|
||||
self.parents = {parent_id: None}
|
||||
self.domain_id = 'default'
|
||||
|
||||
def fake_get_project(project_id, subtree_as_ids=False,
|
||||
parents_as_ids=False):
|
||||
|
@ -35,6 +35,8 @@ class QuotaUtilsTest(test.TestCase):
|
||||
self.id = id
|
||||
self.parent_id = parent_id
|
||||
self.subtree = None
|
||||
self.parents = None
|
||||
self.domain_id = 'default'
|
||||
|
||||
def setUp(self):
|
||||
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.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')
|
||||
def test_validate_nested_projects_with_keystone_v2(self, _keystone_client):
|
||||
_keystone_client.side_effect = exceptions.VersionNotAvailable
|
||||
|
Loading…
Reference in New Issue
Block a user