Merge "Update quotas to handle domain acting as project"

This commit is contained in:
Jenkins 2016-03-07 20:06:15 +00:00 committed by Gerrit Code Review
commit bb75d7b07a
3 changed files with 83 additions and 3 deletions

View File

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

View File

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

View File

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