# License for the specific language governing permissions and limitations
# under the License.
-from oslo_utils import strutils
import webob
+from keystoneclient import exceptions
+from keystoneclient.v3 import client
+
from cinder.api import extensions
from cinder.api.openstack import wsgi
from cinder.api import xmlutil
from cinder import quota
from cinder import utils
+from oslo_config import cfg
+from oslo_utils import strutils
+
+CONF = cfg.CONF
QUOTAS = quota.QUOTAS
NON_QUOTA_KEYS = ['tenant_id', 'id']
else:
return {k: v['limit'] for k, v in values.items()}
+ def _get_project(self, context, id, subtree_as_ids=False):
+ """A Helper method to get the project hierarchy.
+
+ Along with Hierachical Multitenancy, projects can be hierarchically
+ organized. Therefore, we need to know the project hierarchy, if any, in
+ order to do quota operations properly.
+ """
+ try:
+ keystone = client.Client(auth_url=CONF.keymgr.encryption_auth_url,
+ token=context.auth_token,
+ project_id=context.project_id)
+ project = keystone.projects.get(id, subtree_as_ids=subtree_as_ids)
+ except exceptions.NotFound:
+ msg = (_("Tenant ID: %s does not exist.") % id)
+ raise webob.exc.HTTPNotFound(explanation=msg)
+ return project
+
@wsgi.serializers(xml=QuotaTemplate)
def show(self, req, id):
context = req.environ['cinder.context']
def defaults(self, req, id):
context = req.environ['cinder.context']
authorize_show(context)
- return self._format_quota_set(id,
- QUOTAS.get_defaults(context,
- parent_project_id=
- None))
+ project = self._get_project(context, context.project_id)
+ return self._format_quota_set(id, QUOTAS.get_defaults(
+ context, parent_project_id=project.parent_id))
@wsgi.serializers(xml=QuotaTemplate)
def delete(self, req, id):
import mock
from lxml import etree
-import webob.exc
+import uuid
+import webob.exc
from cinder.api.contrib import quotas
from cinder import context
from cinder import test
from cinder.tests.unit import test_db_api
+from oslo_config import cfg
+
+
+CONF = cfg.CONF
+
def make_body(root=True, gigabytes=1000, snapshots=10,
volumes=10, backups=10, backup_gigabytes=1000,
return result
+def make_subproject_body(root=True, gigabytes=0, snapshots=0,
+ volumes=0, backups=0, backup_gigabytes=0,
+ tenant_id='foo', per_volume_gigabytes=0):
+ return make_body(root=root, gigabytes=gigabytes, snapshots=snapshots,
+ volumes=volumes, backups=backups,
+ backup_gigabytes=backup_gigabytes, tenant_id=tenant_id,
+ per_volume_gigabytes=per_volume_gigabytes)
+
+
class QuotaSetsControllerTest(test.TestCase):
+ class FakeProject(object):
+
+ def __init__(self, id='foo', parent_id=None):
+ self.id = id
+ self.parent_id = parent_id
+ self.subtree = None
+
def setUp(self):
super(QuotaSetsControllerTest, self).setUp()
self.controller = quotas.QuotaSetsController()
self.req = self.mox.CreateMockAnything()
self.req.environ = {'cinder.context': context.get_admin_context()}
self.req.environ['cinder.context'].is_admin = True
+ self.req.environ['cinder.context'].auth_token = uuid.uuid4().hex
+
+ self._create_project_hierarchy()
+ self.auth_url = CONF.keymgr.encryption_auth_url
+
+ def _create_project_hierarchy(self):
+ """Sets an environment used for nested quotas tests.
+
+ Create a project hierarchy such as follows:
+ +-----------+
+ | |
+ | A |
+ | / \ |
+ | B C |
+ | / |
+ | D |
+ +-----------+
+ """
+ self.A = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
+ self.B = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.A.id)
+ self.C = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.A.id)
+ self.D = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.B.id)
+
+ # update projects subtrees
+ self.B.subtree = {self.D.id: self.D.subtree}
+ self.A.subtree = {self.B.id: self.B.subtree, self.C.id: self.C.subtree}
+
+ # project_by_id attribute is used to recover a project based on its id.
+ self.project_by_id = {self.A.id: self.A, self.B.id: self.B,
+ self.C.id: self.C, self.D.id: self.D}
+
+ def _get_project(self, context, id, subtree_as_ids=False):
+ return self.project_by_id.get(id, self.FakeProject())
+
+ @mock.patch('keystoneclient.v3.client.Client')
+ def test_keystone_client_instantiation(self, ksclient_class):
+ context = self.req.environ['cinder.context']
+ self.controller._get_project(context, context.project_id)
+ ksclient_class.assert_called_once_with(auth_url=self.auth_url,
+ token=context.auth_token,
+ project_id=context.project_id)
+
+ @mock.patch('keystoneclient.v3.client.Client')
+ def test_get_project(self, ksclient_class):
+ context = self.req.environ['cinder.context']
+ keystoneclient = ksclient_class.return_value
+ self.controller._get_project(context, context.project_id)
+ keystoneclient.projects.get.assert_called_once_with(
+ context.project_id, subtree_as_ids=False)
def test_defaults(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
result = self.controller.defaults(self.req, 'foo')
self.assertDictMatch(result, make_body())
+ def test_subproject_defaults(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
+ context = self.req.environ['cinder.context']
+ context.project_id = self.B.id
+ result = self.controller.defaults(self.req, self.B.id)
+ expected = make_subproject_body(tenant_id=self.B.id)
+ self.assertDictMatch(result, expected)
+
def test_show(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
result = self.controller.show(self.req, 'foo')
self.assertDictMatch(result, make_body())
def test_show_not_authorized(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
self.req.environ['cinder.context'].is_admin = False
self.req.environ['cinder.context'].user_id = 'bad_user'
self.req.environ['cinder.context'].project_id = 'bad_project'
self.req, 'foo')
def test_update(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
body = make_body(gigabytes=2000, snapshots=15,
volumes=5, backups=5, tenant_id=None)
result = self.controller.update(self.req, 'foo', body)
self.req, 'foo', body)
def test_update_invalid_value_key_value(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
body = {'quota_set': {'gigabytes': "should_be_int"}}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
self.req, 'foo', body)
def test_update_invalid_type_key_value(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
body = {'quota_set': {'gigabytes': None}}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
self.req, 'foo', body)
def test_update_multi_value_with_bad_data(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
orig_quota = self.controller.show(self.req, 'foo')
body = make_body(gigabytes=2000, snapshots=15, volumes="should_be_int",
backups=5, tenant_id=None)
self.assertDictMatch(orig_quota, new_quota)
def test_update_bad_quota_limit(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
body = {'quota_set': {'gigabytes': -1000}}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
self.req, 'foo', body)
self.req, 'foo', body)
def test_update_no_admin(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
self.req.environ['cinder.context'].is_admin = False
self.req.environ['cinder.context'].project_id = 'foo'
self.req.environ['cinder.context'].user_id = 'foo_user'
result['quota_set']['volumes'])
def test_delete(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
result_show = self.controller.show(self.req, 'foo')
self.assertDictMatch(result_show, make_body())
self.assertDictMatch(result_show, result_show_after)
def test_delete_no_admin(self):
+ self.controller._get_project = mock.Mock()
+ self.controller._get_project.side_effect = self._get_project
self.req.environ['cinder.context'].is_admin = False
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
- self.req, 'test')
+ self.req, 'foo')
class QuotaSerializerTest(test.TestCase):