"Fossies" - the Fresh Open Source Software Archive

Member "cinder-13.0.7/cinder/tests/unit/api/contrib/test_quotas.py" (4 Oct 2019, 49365 Bytes) of package /linux/misc/openstack/cinder-13.0.7.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "test_quotas.py": 14.0.2_vs_15.0.0.

    1 #
    2 # Copyright 2013 OpenStack Foundation
    3 # All Rights Reserved.
    4 #
    5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 #    not use this file except in compliance with the License. You may obtain
    7 #    a copy of the License at
    8 #
    9 #         http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 #    Unless required by applicable law or agreed to in writing, software
   12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 #    License for the specific language governing permissions and limitations
   15 #    under the License.
   16 
   17 """
   18 Tests for cinder.api.contrib.quotas.py
   19 """
   20 
   21 
   22 import ddt
   23 import mock
   24 import uuid
   25 import webob.exc
   26 
   27 from cinder.api.contrib import quotas
   28 from cinder import context
   29 from cinder import db
   30 from cinder import exception
   31 from cinder import quota
   32 from cinder import test
   33 from cinder.tests.unit import fake_constants as fake
   34 from cinder.tests.unit import test_db_api
   35 
   36 
   37 from oslo_config import cfg
   38 from oslo_config import fixture as config_fixture
   39 
   40 
   41 CONF = cfg.CONF
   42 
   43 
   44 def make_body(root=True, gigabytes=1000, snapshots=10,
   45               volumes=10, backups=10, backup_gigabytes=1000,
   46               tenant_id=fake.PROJECT_ID, per_volume_gigabytes=-1, groups=10):
   47     resources = {'gigabytes': gigabytes,
   48                  'snapshots': snapshots,
   49                  'volumes': volumes,
   50                  'backups': backups,
   51                  'backup_gigabytes': backup_gigabytes,
   52                  'per_volume_gigabytes': per_volume_gigabytes,
   53                  'groups': groups}
   54     # need to consider preexisting volume types as well
   55     volume_types = db.volume_type_get_all(context.get_admin_context())
   56 
   57     for volume_type in volume_types:
   58         resources['gigabytes_' + volume_type] = -1
   59         resources['snapshots_' + volume_type] = -1
   60         resources['volumes_' + volume_type] = -1
   61 
   62     if tenant_id:
   63         resources['id'] = tenant_id
   64     if root:
   65         result = {'quota_set': resources}
   66     else:
   67         result = resources
   68     return result
   69 
   70 
   71 def make_subproject_body(root=True, gigabytes=0, snapshots=0,
   72                          volumes=0, backups=0, backup_gigabytes=0,
   73                          tenant_id=fake.PROJECT_ID, per_volume_gigabytes=0):
   74     return make_body(root=root, gigabytes=gigabytes, snapshots=snapshots,
   75                      volumes=volumes, backups=backups,
   76                      backup_gigabytes=backup_gigabytes, tenant_id=tenant_id,
   77                      per_volume_gigabytes=per_volume_gigabytes)
   78 
   79 
   80 class QuotaSetsControllerTestBase(test.TestCase):
   81 
   82     class FakeProject(object):
   83 
   84         def __init__(self, id=fake.PROJECT_ID, parent_id=None,
   85                      is_admin_project=False):
   86             self.id = id
   87             self.parent_id = parent_id
   88             self.subtree = None
   89             self.parents = None
   90             self.is_admin_project = is_admin_project
   91 
   92     def setUp(self):
   93         super(QuotaSetsControllerTestBase, self).setUp()
   94 
   95         self.controller = quotas.QuotaSetsController()
   96 
   97         self.req = mock.Mock()
   98         self.req.environ = {'cinder.context': context.get_admin_context()}
   99         self.req.environ['cinder.context'].is_admin = True
  100         self.req.params = {}
  101 
  102         self._create_project_hierarchy()
  103         self.req.environ['cinder.context'].project_id = self.A.id
  104 
  105         get_patcher = mock.patch('cinder.quota_utils.get_project_hierarchy',
  106                                  self._get_project)
  107         get_patcher.start()
  108         self.addCleanup(get_patcher.stop)
  109 
  110         def _list_projects(context):
  111             return self.project_by_id.values()
  112 
  113         list_patcher = mock.patch('cinder.quota_utils.get_all_projects',
  114                                   _list_projects)
  115         list_patcher.start()
  116         self.addCleanup(list_patcher.stop)
  117 
  118         self.auth_url = 'http://localhost:5000'
  119         self.fixture = self.useFixture(config_fixture.Config(CONF))
  120         self.fixture.config(auth_uri=self.auth_url, group='keystone_authtoken')
  121 
  122     def _create_project_hierarchy(self):
  123         r"""Sets an environment used for nested quotas tests.
  124 
  125         Create a project hierarchy such as follows:
  126         +-----------+
  127         |           |
  128         |     A     |
  129         |    / \    |
  130         |   B   C   |
  131         |  /        |
  132         | D         |
  133         +-----------+
  134         """
  135         self.A = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
  136         self.B = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.A.id)
  137         self.C = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.A.id)
  138         self.D = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.B.id)
  139 
  140         # update projects subtrees
  141         self.B.subtree = {self.D.id: self.D.subtree}
  142         self.A.subtree = {self.B.id: self.B.subtree, self.C.id: self.C.subtree}
  143 
  144         self.A.parents = None
  145         self.B.parents = {self.A.id: None}
  146         self.C.parents = {self.A.id: None}
  147         self.D.parents = {self.B.id: self.B.parents}
  148 
  149         # project_by_id attribute is used to recover a project based on its id.
  150         self.project_by_id = {self.A.id: self.A, self.B.id: self.B,
  151                               self.C.id: self.C, self.D.id: self.D}
  152 
  153     def _get_project(self, context, id, subtree_as_ids=False,
  154                      parents_as_ids=False, is_admin_project=False):
  155         return self.project_by_id.get(id, self.FakeProject())
  156 
  157     def _create_fake_quota_usages(self, usage_map):
  158         self._fake_quota_usages = {}
  159         for key, val in usage_map.items():
  160             self._fake_quota_usages[key] = {'in_use': val}
  161 
  162     def _fake_quota_usage_get_all_by_project(self, context, project_id):
  163         return {'volumes': self._fake_quota_usages[project_id]}
  164 
  165 
  166 class QuotaSetsControllerTest(QuotaSetsControllerTestBase):
  167     def test_defaults(self):
  168         result = self.controller.defaults(self.req, fake.PROJECT_ID)
  169         self.assertDictEqual(make_body(), result)
  170 
  171     def test_show(self):
  172         result = self.controller.show(self.req, fake.PROJECT_ID)
  173         self.assertDictEqual(make_body(), result)
  174 
  175     def test_show_not_authorized(self):
  176         self.req.environ['cinder.context'].is_admin = False
  177         self.req.environ['cinder.context'].user_id = fake.USER_ID
  178         self.req.environ['cinder.context'].project_id = fake.PROJECT_ID
  179         self.assertRaises(exception.PolicyNotAuthorized, self.controller.show,
  180                           self.req, fake.PROJECT2_ID)
  181 
  182     def test_show_non_admin_user(self):
  183         self.controller._get_quotas = mock.Mock(side_effect=
  184                                                 self.controller._get_quotas)
  185         result = self.controller.show(self.req, fake.PROJECT_ID)
  186         self.assertDictEqual(make_body(), result)
  187         self.controller._get_quotas.assert_called_with(
  188             self.req.environ['cinder.context'], fake.PROJECT_ID, False)
  189 
  190     def test_show_with_invalid_usage_param(self):
  191         self.req.params = {'usage': 'InvalidBool'}
  192         self.assertRaises(exception.InvalidParameterValue,
  193                           self.controller.show,
  194                           self.req, fake.PROJECT2_ID)
  195 
  196     def test_show_with_valid_usage_param(self):
  197         self.req.params = {'usage': 'false'}
  198         result = self.controller.show(self.req, fake.PROJECT_ID)
  199         self.assertDictEqual(make_body(), result)
  200 
  201     def test_update(self):
  202         body = make_body(gigabytes=2000, snapshots=15,
  203                          volumes=5, backups=5, tenant_id=None)
  204         result = self.controller.update(self.req, fake.PROJECT_ID, body=body)
  205         self.assertDictEqual(body, result)
  206 
  207         body = make_body(gigabytes=db.MAX_INT, tenant_id=None)
  208         result = self.controller.update(self.req, fake.PROJECT_ID, body=body)
  209         self.assertDictEqual(body, result)
  210 
  211     def test_update_subproject_not_in_hierarchy_non_nested(self):
  212         # When not using nested quotas, the hierarchy should not be considered
  213         # for an update
  214         E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
  215         F = self.FakeProject(id=uuid.uuid4().hex, parent_id=E.id)
  216         E.subtree = {F.id: F.subtree}
  217         self.project_by_id[E.id] = E
  218         self.project_by_id[F.id] = F
  219 
  220         # Update the project A quota.
  221         self.req.environ['cinder.context'].project_id = self.A.id
  222         body = make_body(gigabytes=2000, snapshots=15,
  223                          volumes=5, backups=5, tenant_id=None)
  224         result = self.controller.update(self.req, self.A.id, body=body)
  225         self.assertDictEqual(body, result)
  226         # Try to update the quota of F, it will be allowed even though
  227         # project E doesn't belong to the project hierarchy of A, because
  228         # we are NOT using the nested quota driver
  229         self.req.environ['cinder.context'].project_id = self.A.id
  230         body = make_body(gigabytes=2000, snapshots=15,
  231                          volumes=5, backups=5, tenant_id=None)
  232         self.controller.update(self.req, F.id, body=body)
  233 
  234     @mock.patch(
  235         'cinder.api.openstack.wsgi.Controller.validate_string_length')
  236     def test_update_limit(self, mock_validate):
  237         body = {'quota_set': {'volumes': 10}}
  238         result = self.controller.update(self.req, fake.PROJECT_ID, body=body)
  239 
  240         self.assertEqual(10, result['quota_set']['volumes'])
  241         self.assertTrue(mock_validate.called)
  242 
  243     def test_update_wrong_key(self):
  244         body = {'quota_set': {'bad': 'bad'}}
  245         self.assertRaises(exception.InvalidInput, self.controller.update,
  246                           self.req, fake.PROJECT_ID, body=body)
  247 
  248     def test_update_invalid_value_key_value(self):
  249         body = {'quota_set': {'gigabytes': "should_be_int"}}
  250         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  251                           self.req, fake.PROJECT_ID, body=body)
  252 
  253     def test_update_invalid_type_key_value(self):
  254         body = {'quota_set': {'gigabytes': None}}
  255         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  256                           self.req, fake.PROJECT_ID, body=body)
  257 
  258     def test_update_with_no_body(self):
  259         body = {}
  260         self.assertRaises(exception.ValidationError, self.controller.update,
  261                           self.req, fake.PROJECT_ID, body=body)
  262 
  263     def test_update_with_wrong_body(self):
  264         body = {'test': {}}
  265         self.assertRaises(exception.ValidationError, self.controller.update,
  266                           self.req, fake.PROJECT_ID, body=body)
  267 
  268     def test_update_multi_value_with_bad_data(self):
  269         orig_quota = self.controller.show(self.req, fake.PROJECT_ID)
  270         body = make_body(gigabytes=2000, snapshots=15, volumes="should_be_int",
  271                          backups=5, tenant_id=None)
  272         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  273                           self.req, fake.PROJECT_ID, body=body)
  274         # Verify that quota values are not updated in db
  275         new_quota = self.controller.show(self.req, fake.PROJECT_ID)
  276         self.assertDictEqual(orig_quota, new_quota)
  277 
  278     def test_update_bad_quota_limit(self):
  279         body = {'quota_set': {'gigabytes': -1000}}
  280         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  281                           self.req, fake.PROJECT_ID, body=body)
  282         body = {'quota_set': {'gigabytes': db.MAX_INT + 1}}
  283         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  284                           self.req, fake.PROJECT_ID, body=body)
  285 
  286     def test_update_no_admin(self):
  287         self.req.environ['cinder.context'].is_admin = False
  288         self.req.environ['cinder.context'].project_id = fake.PROJECT_ID
  289         self.req.environ['cinder.context'].user_id = 'foo_user'
  290         self.assertRaises(exception.PolicyNotAuthorized,
  291                           self.controller.update, self.req, fake.PROJECT_ID,
  292                           body=make_body(tenant_id=None))
  293 
  294     def test_update_without_quota_set_field(self):
  295         body = {'fake_quota_set': {'gigabytes': 100}}
  296         self.assertRaises(exception.ValidationError, self.controller.update,
  297                           self.req, fake.PROJECT_ID, body=body)
  298 
  299     def test_update_empty_body(self):
  300         body = {}
  301         self.assertRaises(exception.ValidationError, self.controller.update,
  302                           self.req, fake.PROJECT_ID, body=body)
  303 
  304     def _commit_quota_reservation(self):
  305         # Create simple quota and quota usage.
  306         ctxt = context.get_admin_context()
  307         res = test_db_api._quota_reserve(ctxt, fake.PROJECT_ID)
  308         db.reservation_commit(ctxt, res, fake.PROJECT_ID)
  309         expected = {'project_id': fake.PROJECT_ID,
  310                     'volumes': {'reserved': 0, 'in_use': 1},
  311                     'gigabytes': {'reserved': 0, 'in_use': 2},
  312                     }
  313         self.assertEqual(expected,
  314                          db.quota_usage_get_all_by_project(ctxt,
  315                                                            fake.PROJECT_ID))
  316 
  317     def test_update_lower_than_existing_resources(self):
  318         self._commit_quota_reservation()
  319         body = {'quota_set': {'volumes': 0}}
  320         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  321                           self.req, fake.PROJECT_ID, body=body)
  322         # Ensure that validation works even if some resources are valid
  323         body = {'quota_set': {'gigabytes': 1, 'volumes': 10}}
  324         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  325                           self.req, fake.PROJECT_ID, body=body)
  326 
  327     def test_delete(self):
  328         result_show = self.controller.show(self.req, fake.PROJECT_ID)
  329         self.assertDictEqual(make_body(), result_show)
  330 
  331         body = make_body(gigabytes=2000, snapshots=15,
  332                          volumes=5, backups=5,
  333                          backup_gigabytes=1000, tenant_id=None)
  334         result_update = self.controller.update(self.req, fake.PROJECT_ID,
  335                                                body=body)
  336         self.assertDictEqual(body, result_update)
  337 
  338         self.controller.delete(self.req, fake.PROJECT_ID)
  339 
  340         result_show_after = self.controller.show(self.req, fake.PROJECT_ID)
  341         self.assertDictEqual(result_show, result_show_after)
  342 
  343     def test_delete_with_allocated_quota_different_from_zero(self):
  344         self.req.environ['cinder.context'].project_id = self.A.id
  345 
  346         body = make_body(gigabytes=2000, snapshots=15,
  347                          volumes=5, backups=5,
  348                          backup_gigabytes=1000, tenant_id=None)
  349         result_update = self.controller.update(self.req, self.A.id, body=body)
  350         self.assertDictEqual(body, result_update)
  351 
  352         # Set usage param to True in order to see get allocated values.
  353         self.req.params = {'usage': 'True'}
  354         result_show = self.controller.show(self.req, self.A.id)
  355 
  356         result_update = self.controller.update(self.req, self.B.id, body=body)
  357         self.assertDictEqual(body, result_update)
  358 
  359         self.controller.delete(self.req, self.B.id)
  360 
  361         result_show_after = self.controller.show(self.req, self.A.id)
  362         self.assertDictEqual(result_show, result_show_after)
  363 
  364     def test_delete_no_admin(self):
  365         self.req.environ['cinder.context'].is_admin = False
  366         self.assertRaises(exception.PolicyNotAuthorized,
  367                           self.controller.delete, self.req, fake.PROJECT_ID)
  368 
  369     def test_subproject_show_not_using_nested_quotas(self):
  370         # Current roles say for non-nested quotas, an admin should be able to
  371         # see anyones quota
  372         self.req.environ['cinder.context'].project_id = self.B.id
  373         self.controller.show(self.req, self.C.id)
  374         self.controller.show(self.req, self.A.id)
  375 
  376 
  377 @ddt.ddt
  378 class QuotaSetControllerValidateNestedQuotaSetup(QuotaSetsControllerTestBase):
  379     """Validates the setup before using NestedQuota driver.
  380 
  381     Test case validates flipping on NestedQuota driver after using the
  382     non-nested quota driver for some time.
  383     """
  384 
  385     def _create_project_hierarchy(self):
  386         r"""Sets an environment used for nested quotas tests.
  387 
  388         Create a project hierarchy such as follows:
  389         +-----------------+
  390         |                 |
  391         |     A    G   E  |
  392         |    / \       \  |
  393         |   B   C       F |
  394         |  /              |
  395         | D               |
  396         +-----------------+
  397         """
  398         super(QuotaSetControllerValidateNestedQuotaSetup,
  399               self)._create_project_hierarchy()
  400         # Project A, B, C, D are already defined by parent test class
  401         self.E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
  402         self.F = self.FakeProject(id=uuid.uuid4().hex, parent_id=self.E.id)
  403         self.G = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
  404 
  405         self.E.subtree = {self.F.id: self.F.subtree}
  406 
  407         self.project_by_id.update({self.E.id: self.E, self.F.id: self.F,
  408                                    self.G.id: self.G})
  409 
  410     @ddt.data({'param': None, 'result': False},
  411               {'param': 'true', 'result': True},
  412               {'param': 'false', 'result': False})
  413     @ddt.unpack
  414     def test_validate_setup_for_nested_quota_use_with_param(self, param,
  415                                                             result):
  416         with mock.patch(
  417                 'cinder.quota_utils.validate_setup_for_nested_quota_use') as \
  418                 mock_quota_utils:
  419             if param:
  420                 self.req.params['fix_allocated_quotas'] = param
  421             self.controller.validate_setup_for_nested_quota_use(self.req)
  422             mock_quota_utils.assert_called_once_with(
  423                 self.req.environ['cinder.context'],
  424                 mock.ANY, mock.ANY,
  425                 fix_allocated_quotas=result)
  426 
  427     def test_validate_setup_for_nested_quota_use_with_invalid_param(self):
  428         self.req.params['fix_allocated_quotas'] = 'non_boolean'
  429         self.assertRaises(
  430             webob.exc.HTTPBadRequest,
  431             self.controller.validate_setup_for_nested_quota_use,
  432             self.req)
  433 
  434     def test_validate_nested_quotas_no_in_use_vols(self):
  435         # Update the project A quota.
  436         self.req.environ['cinder.context'].project_id = self.A.id
  437         quota = {'volumes': 5}
  438         body = {'quota_set': quota}
  439         self.controller.update(self.req, self.A.id, body=body)
  440 
  441         quota['volumes'] = 3
  442         self.controller.update(self.req, self.B.id, body=body)
  443         # Allocated value for quota A is borked, because update was done
  444         # without nested quota driver
  445         self.assertRaises(webob.exc.HTTPBadRequest,
  446                           self.controller.validate_setup_for_nested_quota_use,
  447                           self.req)
  448 
  449         # Fix the allocated values in DB
  450         self.req.params['fix_allocated_quotas'] = True
  451         self.controller.validate_setup_for_nested_quota_use(
  452             self.req)
  453 
  454         self.req.params['fix_allocated_quotas'] = False
  455         # Ensure that we've properly fixed the allocated quotas
  456         self.controller.validate_setup_for_nested_quota_use(self.req)
  457 
  458         # Over-allocate the quotas between children
  459         self.controller.update(self.req, self.C.id, body=body)
  460 
  461         # This is we should fail because the child limits are too big
  462         self.assertRaises(webob.exc.HTTPBadRequest,
  463                           self.controller.validate_setup_for_nested_quota_use,
  464                           self.req)
  465 
  466         quota['volumes'] = 1
  467         self.controller.update(self.req, self.C.id, body=body)
  468 
  469         # Make sure we're validating all hierarchy trees
  470         self.req.environ['cinder.context'].project_id = self.E.id
  471         quota['volumes'] = 1
  472         self.controller.update(self.req, self.E.id, body=body)
  473         quota['volumes'] = 3
  474         self.controller.update(self.req, self.F.id, body=body)
  475 
  476         self.assertRaises(
  477             webob.exc.HTTPBadRequest,
  478             self.controller.validate_setup_for_nested_quota_use,
  479             self.req)
  480 
  481         # Put quotas in a good state
  482         quota['volumes'] = 1
  483         self.controller.update(self.req, self.F.id, body=body)
  484         self.req.params['fix_allocated_quotas'] = True
  485         self.controller.validate_setup_for_nested_quota_use(self.req)
  486 
  487     @mock.patch('cinder.db.quota_usage_get_all_by_project')
  488     def test_validate_nested_quotas_in_use_vols(self, mock_usage):
  489         self._create_fake_quota_usages(
  490             {self.A.id: 1, self.B.id: 1, self.D.id: 0, self.C.id: 3,
  491              self.E.id: 0, self.F.id: 0, self.G.id: 0})
  492         mock_usage.side_effect = self._fake_quota_usage_get_all_by_project
  493 
  494         # Update the project A quota.
  495         self.req.environ['cinder.context'].project_id = self.A.id
  496         quota_limit = {'volumes': 7}
  497         body = {'quota_set': quota_limit}
  498         self.controller.update(self.req, self.A.id, body=body)
  499 
  500         quota_limit['volumes'] = 3
  501         self.controller.update(self.req, self.B.id, body=body)
  502 
  503         quota_limit['volumes'] = 3
  504         self.controller.update(self.req, self.C.id, body=body)
  505 
  506         self.req.params['fix_allocated_quotas'] = True
  507         self.controller.validate_setup_for_nested_quota_use(self.req)
  508 
  509         quota_limit['volumes'] = 6
  510         self.controller.update(self.req, self.A.id, body=body)
  511 
  512         # Should fail because the one in_use volume of 'A'
  513         self.assertRaises(
  514             webob.exc.HTTPBadRequest,
  515             self.controller.validate_setup_for_nested_quota_use,
  516             self.req)
  517 
  518     @mock.patch('cinder.db.quota_usage_get_all_by_project')
  519     def test_validate_nested_quotas_quota_borked(self, mock_usage):
  520         self._create_fake_quota_usages(
  521             {self.A.id: 1, self.B.id: 1, self.D.id: 0, self.C.id: 3,
  522              self.E.id: 0, self.F.id: 0, self.G.id: 0})
  523         mock_usage.side_effect = self._fake_quota_usage_get_all_by_project
  524 
  525         # Update the project A quota.
  526         self.req.environ['cinder.context'].project_id = self.A.id
  527         quota_limit = {'volumes': 7}
  528         body = {'quota_set': quota_limit}
  529         self.controller.update(self.req, self.A.id, body=body)
  530 
  531         # Other quotas would default to 0 but already have some limit being
  532         # used
  533         self.assertRaises(
  534             webob.exc.HTTPBadRequest,
  535             self.controller.validate_setup_for_nested_quota_use,
  536             self.req)
  537 
  538     @mock.patch('cinder.db.quota_usage_get_all_by_project')
  539     def test_validate_nested_quota_negative_limits(self, mock_usage):
  540         # TODO(mc_nair): this test case can be moved to Tempest once nested
  541         # quota coverage added
  542         self._create_fake_quota_usages(
  543             {self.A.id: 1, self.B.id: 3, self.C.id: 0, self.D.id: 2,
  544              self.E.id: 2, self.F.id: 0, self.G.id: 0})
  545         mock_usage.side_effect = self._fake_quota_usage_get_all_by_project
  546 
  547         # Setting E-F as children of D for this test case to flex the muscles
  548         # of more complex nesting
  549         self.D.subtree = {self.E.id: self.E.subtree}
  550         self.E.parent_id = self.D.id
  551         # Get B's subtree up to date with this change
  552         self.B.subtree[self.D.id] = self.D.subtree
  553 
  554         # Quota hierarchy now is
  555         #   / B - D - E - F
  556         # A
  557         #   \ C
  558         #
  559         # G
  560 
  561         self.req.environ['cinder.context'].project_id = self.A.id
  562         quota_limit = {'volumes': 10}
  563         body = {'quota_set': quota_limit}
  564         self.controller.update(self.req, self.A.id, body=body)
  565 
  566         quota_limit['volumes'] = 1
  567         self.controller.update(self.req, self.C.id, body=body)
  568 
  569         quota_limit['volumes'] = -1
  570         self.controller.update(self.req, self.B.id, body=body)
  571         self.controller.update(self.req, self.D.id, body=body)
  572         self.controller.update(self.req, self.F.id, body=body)
  573         quota_limit['volumes'] = 5
  574         self.controller.update(self.req, self.E.id, body=body)
  575 
  576         # Should fail because too much is allocated to children for A
  577         self.assertRaises(webob.exc.HTTPBadRequest,
  578                           self.controller.validate_setup_for_nested_quota_use,
  579                           self.req)
  580 
  581         # When root has -1 limit, children can allocate as much as they want
  582         quota_limit['volumes'] = -1
  583         self.controller.update(self.req, self.A.id, body=body)
  584         self.req.params['fix_allocated_quotas'] = True
  585         self.controller.validate_setup_for_nested_quota_use(self.req)
  586 
  587         # Not unlimited, but make children's allocated within bounds
  588         quota_limit['volumes'] = 10
  589         self.controller.update(self.req, self.A.id, body=body)
  590         quota_limit['volumes'] = 3
  591         self.controller.update(self.req, self.E.id, body=body)
  592         self.req.params['fix_allocated_quotas'] = True
  593         self.controller.validate_setup_for_nested_quota_use(self.req)
  594         self.req.params['fix_allocated_quotas'] = False
  595         self.controller.validate_setup_for_nested_quota_use(self.req)
  596 
  597 
  598 class QuotaSetsControllerNestedQuotasTest(QuotaSetsControllerTestBase):
  599     def setUp(self):
  600         super(QuotaSetsControllerNestedQuotasTest, self).setUp()
  601         driver = quota.NestedDbQuotaDriver()
  602         patcher = mock.patch('cinder.quota.VolumeTypeQuotaEngine._driver',
  603                              driver)
  604         patcher.start()
  605         self.addCleanup(patcher.stop)
  606 
  607     def test_subproject_defaults(self):
  608         context = self.req.environ['cinder.context']
  609         context.project_id = self.B.id
  610         result = self.controller.defaults(self.req, self.B.id)
  611         expected = make_subproject_body(tenant_id=self.B.id)
  612         self.assertDictEqual(expected, result)
  613 
  614     def test_subproject_show(self):
  615         self.req.environ['cinder.context'].project_id = self.A.id
  616         result = self.controller.show(self.req, self.B.id)
  617         expected = make_subproject_body(tenant_id=self.B.id)
  618         self.assertDictEqual(expected, result)
  619 
  620     def test_subproject_show_in_hierarchy(self):
  621         # A user scoped to a root project in a hierarchy can see its children
  622         # quotas.
  623         self.req.environ['cinder.context'].project_id = self.A.id
  624         result = self.controller.show(self.req, self.D.id)
  625         expected = make_subproject_body(tenant_id=self.D.id)
  626         self.assertDictEqual(expected, result)
  627         # A user scoped to a parent project can see its immediate children
  628         # quotas.
  629         self.req.environ['cinder.context'].project_id = self.B.id
  630         result = self.controller.show(self.req, self.D.id)
  631         expected = make_subproject_body(tenant_id=self.D.id)
  632         self.assertDictEqual(expected, result)
  633 
  634     def test_subproject_show_not_in_hierarchy_admin_context(self):
  635         E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None,
  636                              is_admin_project=True)
  637         self.project_by_id[E.id] = E
  638         self.req.environ['cinder.context'].project_id = E.id
  639         result = self.controller.show(self.req, self.B.id)
  640         expected = make_subproject_body(tenant_id=self.B.id)
  641         self.assertDictEqual(expected, result)
  642 
  643     def test_subproject_show_target_project_equals_to_context_project(
  644             self):
  645         self.req.environ['cinder.context'].project_id = self.B.id
  646         result = self.controller.show(self.req, self.B.id)
  647         expected = make_subproject_body(tenant_id=self.B.id)
  648         self.assertDictEqual(expected, result)
  649 
  650     def test_subproject_show_not_authorized(self):
  651         self.req.environ['cinder.context'].project_id = self.B.id
  652         self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
  653                           self.req, self.C.id)
  654         self.req.environ['cinder.context'].project_id = self.B.id
  655         self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
  656                           self.req, self.A.id)
  657 
  658     def test_update_subproject_not_in_hierarchy(self):
  659         # Create another project hierarchy
  660         E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None)
  661         F = self.FakeProject(id=uuid.uuid4().hex, parent_id=E.id)
  662         E.subtree = {F.id: F.subtree}
  663         self.project_by_id[E.id] = E
  664         self.project_by_id[F.id] = F
  665 
  666         # Update the project A quota.
  667         self.req.environ['cinder.context'].project_id = self.A.id
  668         body = make_body(gigabytes=2000, snapshots=15,
  669                          volumes=5, backups=5, tenant_id=None)
  670         result = self.controller.update(self.req, self.A.id, body=body)
  671         self.assertDictEqual(body, result)
  672         # Try to update the quota of F, it will not be allowed, since the
  673         # project E doesn't belongs to the project hierarchy of A.
  674         self.req.environ['cinder.context'].project_id = self.A.id
  675         body = make_body(gigabytes=2000, snapshots=15,
  676                          volumes=5, backups=5, tenant_id=None)
  677         self.assertRaises(webob.exc.HTTPForbidden,
  678                           self.controller.update, self.req, F.id, body=body)
  679 
  680     def test_update_subproject_not_in_hierarchy_admin_context(self):
  681         E = self.FakeProject(id=uuid.uuid4().hex, parent_id=None,
  682                              is_admin_project=True)
  683         self.project_by_id[E.id] = E
  684         self.req.environ['cinder.context'].project_id = E.id
  685         body = make_body(gigabytes=2000, snapshots=15,
  686                          volumes=5, backups=5, tenant_id=None)
  687         # Update the project A quota, not in the project hierarchy
  688         # of E but it will be allowed because E is the cloud admin.
  689         result = self.controller.update(self.req, self.A.id, body=body)
  690         self.assertDictEqual(body, result)
  691         # Update the quota of B to be equal to its parent A.
  692         result = self.controller.update(self.req, self.B.id, body=body)
  693         self.assertDictEqual(body, result)
  694         # Remove the admin role from project E
  695         E.is_admin_project = False
  696         # Now updating the quota of B will fail, because it is not
  697         # a member of E's hierarchy and E is no longer a cloud admin.
  698         self.assertRaises(webob.exc.HTTPForbidden,
  699                           self.controller.update, self.req, self.B.id,
  700                           body=body)
  701 
  702     def test_update_subproject(self):
  703         # Update the project A quota.
  704         self.req.environ['cinder.context'].project_id = self.A.id
  705         body = make_body(gigabytes=2000, snapshots=15,
  706                          volumes=5, backups=5, tenant_id=None)
  707         result = self.controller.update(self.req, self.A.id, body=body)
  708         self.assertDictEqual(body, result)
  709         # Update the quota of B to be equal to its parent quota
  710         self.req.environ['cinder.context'].project_id = self.A.id
  711         body = make_body(gigabytes=2000, snapshots=15,
  712                          volumes=5, backups=5, tenant_id=None)
  713         result = self.controller.update(self.req, self.B.id, body=body)
  714         self.assertDictEqual(body, result)
  715         # Try to update the quota of C, it will not be allowed, since the
  716         # project A doesn't have free quota available.
  717         self.req.environ['cinder.context'].project_id = self.A.id
  718         body = make_body(gigabytes=2000, snapshots=15,
  719                          volumes=5, backups=5, tenant_id=None)
  720         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  721                           self.req, self.C.id, body=body)
  722         # Successfully update the quota of D.
  723         self.req.environ['cinder.context'].project_id = self.A.id
  724         body = make_body(gigabytes=1000, snapshots=7,
  725                          volumes=3, backups=3, tenant_id=None)
  726         result = self.controller.update(self.req, self.D.id, body=body)
  727         self.assertDictEqual(body, result)
  728         # An admin of B can also update the quota of D, since D is its
  729         # immediate child.
  730         self.req.environ['cinder.context'].project_id = self.B.id
  731         body = make_body(gigabytes=1500, snapshots=10,
  732                          volumes=4, backups=4, tenant_id=None)
  733         self.controller.update(self.req, self.D.id, body=body)
  734 
  735     def test_update_subproject_repetitive(self):
  736         # Update the project A volumes quota.
  737         self.req.environ['cinder.context'].project_id = self.A.id
  738         body = make_body(gigabytes=2000, snapshots=15,
  739                          volumes=10, backups=5, tenant_id=None)
  740         result = self.controller.update(self.req, self.A.id, body=body)
  741         self.assertDictEqual(body, result)
  742         # Update the quota of B to be equal to its parent quota
  743         # three times should be successful, the quota will not be
  744         # allocated to 'allocated' value of parent project
  745         for i in range(0, 3):
  746             self.req.environ['cinder.context'].project_id = self.A.id
  747             body = make_body(gigabytes=2000, snapshots=15,
  748                              volumes=10, backups=5, tenant_id=None)
  749             result = self.controller.update(self.req, self.B.id, body=body)
  750             self.assertDictEqual(body, result)
  751 
  752     def test_update_subproject_with_not_root_context_project(self):
  753         # Update the project A quota.
  754         self.req.environ['cinder.context'].project_id = self.A.id
  755         body = make_body(gigabytes=2000, snapshots=15,
  756                          volumes=5, backups=5, tenant_id=None)
  757         result = self.controller.update(self.req, self.A.id, body=body)
  758         self.assertDictEqual(body, result)
  759         # Try to update the quota of B, it will not be allowed, since the
  760         # project in the context (B) is not a root project.
  761         self.req.environ['cinder.context'].project_id = self.B.id
  762         body = make_body(gigabytes=2000, snapshots=15,
  763                          volumes=5, backups=5, tenant_id=None)
  764         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
  765                           self.req, self.B.id, body=body)
  766 
  767     def test_update_subproject_quota_when_parent_has_default_quotas(self):
  768         # Since the quotas of the project A were not updated, it will have
  769         # default quotas.
  770         self.req.environ['cinder.context'].project_id = self.A.id
  771         # Update the project B quota.
  772         expected = make_body(gigabytes=1000, snapshots=10,
  773                              volumes=5, backups=5, tenant_id=None)
  774         result = self.controller.update(self.req, self.B.id, body=expected)
  775         self.assertDictEqual(expected, result)
  776 
  777     def _assert_quota_show(self, proj_id, resource, in_use=0, reserved=0,
  778                            allocated=0, limit=0):
  779         self.req.params = {'usage': 'True'}
  780         show_res = self.controller.show(self.req, proj_id)
  781         expected = {'in_use': in_use, 'reserved': reserved,
  782                     'allocated': allocated, 'limit': limit}
  783         self.assertEqual(expected, show_res['quota_set'][resource])
  784 
  785     def test_project_allocated_considered_on_reserve(self):
  786         def _reserve(project_id):
  787             quotas.QUOTAS._driver.reserve(
  788                 self.req.environ['cinder.context'], quotas.QUOTAS.resources,
  789                 {'volumes': 1}, project_id=project_id)
  790 
  791         # A's quota will default to 10 for volumes
  792         quota = {'volumes': 5}
  793         body = {'quota_set': quota}
  794         self.controller.update(self.req, self.B.id, body=body)
  795         self._assert_quota_show(self.A.id, 'volumes', allocated=5, limit=10)
  796         quota['volumes'] = 3
  797         self.controller.update(self.req, self.C.id, body=body)
  798         self._assert_quota_show(self.A.id, 'volumes', allocated=8, limit=10)
  799         _reserve(self.A.id)
  800         _reserve(self.A.id)
  801         self.assertRaises(exception.OverQuota, _reserve, self.A.id)
  802 
  803     def test_update_parent_project_lower_than_child(self):
  804         # A's quota will be default of 10
  805         quota = {'volumes': 10}
  806         body = {'quota_set': quota}
  807         self.controller.update(self.req, self.B.id, body=body)
  808         quota['volumes'] = 9
  809         self.assertRaises(webob.exc.HTTPBadRequest,
  810                           self.controller.update, self.req, self.A.id,
  811                           body=body)
  812 
  813     def test_project_delete_with_default_quota_less_than_in_use(self):
  814         quota = {'volumes': 11}
  815         body = {'quota_set': quota}
  816         self.controller.update(self.req, self.A.id, body=body)
  817         quotas.QUOTAS._driver.reserve(
  818             self.req.environ['cinder.context'], quotas.QUOTAS.resources,
  819             quota, project_id=self.A.id)
  820         # Should not be able to delete if it will cause the used values to go
  821         # over quota when nested quotas are used
  822         self.assertRaises(webob.exc.HTTPBadRequest,
  823                           self.controller.delete,
  824                           self.req,
  825                           self.A.id)
  826 
  827     def test_subproject_delete_with_default_quota_less_than_in_use(self):
  828         quota = {'volumes': 1}
  829         body = {'quota_set': quota}
  830         self.controller.update(self.req, self.B.id, body=body)
  831         quotas.QUOTAS._driver.reserve(
  832             self.req.environ['cinder.context'], quotas.QUOTAS.resources,
  833             quota, project_id=self.B.id)
  834 
  835         # Should not be able to delete if it will cause the used values to go
  836         # over quota when nested quotas are used
  837         self.assertRaises(webob.exc.HTTPBadRequest,
  838                           self.controller.delete,
  839                           self.req,
  840                           self.B.id)
  841 
  842     def test_subproject_delete(self):
  843         self.req.environ['cinder.context'].project_id = self.A.id
  844 
  845         body = make_body(gigabytes=2000, snapshots=15, volumes=5, backups=5,
  846                          backup_gigabytes=1000, tenant_id=None)
  847         result_update = self.controller.update(self.req, self.A.id, body=body)
  848         self.assertDictEqual(body, result_update)
  849 
  850         # Set usage param to True in order to see get allocated values.
  851         self.req.params = {'usage': 'True'}
  852         result_show = self.controller.show(self.req, self.A.id)
  853 
  854         result_update = self.controller.update(self.req, self.B.id, body=body)
  855         self.assertDictEqual(body, result_update)
  856 
  857         self.controller.delete(self.req, self.B.id)
  858 
  859         result_show_after = self.controller.show(self.req, self.A.id)
  860         self.assertDictEqual(result_show, result_show_after)
  861 
  862     def test_subproject_delete_not_considering_default_quotas(self):
  863         """Test delete subprojects' quotas won't consider default quotas.
  864 
  865         Test plan:
  866         - Update the volume quotas of project A
  867         - Update the volume quotas of project B
  868         - Delete the quotas of project B
  869 
  870         Resources with default quotas aren't expected to be considered when
  871         updating the allocated values of the parent project. Thus, the delete
  872         operation should succeed.
  873         """
  874         self.req.environ['cinder.context'].project_id = self.A.id
  875 
  876         body = {'quota_set': {'volumes': 5}}
  877         result = self.controller.update(self.req, self.A.id, body=body)
  878         self.assertEqual(body['quota_set']['volumes'],
  879                          result['quota_set']['volumes'])
  880 
  881         body = {'quota_set': {'volumes': 2}}
  882         result = self.controller.update(self.req, self.B.id, body=body)
  883         self.assertEqual(body['quota_set']['volumes'],
  884                          result['quota_set']['volumes'])
  885 
  886         self.controller.delete(self.req, self.B.id)
  887 
  888     def test_subproject_delete_with_child_present(self):
  889         # Update the project A quota.
  890         self.req.environ['cinder.context'].project_id = self.A.id
  891         body = make_body(volumes=5)
  892         self.controller.update(self.req, self.A.id, body=body)
  893 
  894         # Allocate some of that quota to a child project
  895         body = make_body(volumes=3)
  896         self.controller.update(self.req, self.B.id, body=body)
  897 
  898         # Deleting 'A' should be disallowed since 'B' is using some of that
  899         # quota
  900         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
  901                           self.req, self.A.id)
  902 
  903     def test_subproject_delete_with_child_updates_parent_allocated(self):
  904         quota = {'volumes': 5}
  905         body = {'quota_set': quota}
  906         self.controller.update(self.req, self.A.id, body=body)
  907 
  908         # Allocate some of that quota to a child project using hard limit
  909         quota['volumes'] = -1
  910         self.controller.update(self.req, self.B.id, body=body)
  911         quota['volumes'] = 2
  912         self.controller.update(self.req, self.D.id, body=body)
  913 
  914         res = 'volumes'
  915         self._assert_quota_show(self.A.id, res, allocated=2, limit=5)
  916         self._assert_quota_show(self.B.id, res, allocated=2, limit=-1)
  917         self.controller.delete(self.req, self.D.id)
  918         self._assert_quota_show(self.A.id, res, allocated=0, limit=5)
  919         self._assert_quota_show(self.B.id, res, allocated=0, limit=-1)
  920 
  921     def test_negative_child_limit_not_affecting_parents_free_quota(self):
  922         quota = {'volumes': -1}
  923         body = {'quota_set': quota}
  924         self.controller.update(self.req, self.C.id, body=body)
  925         self.controller.update(self.req, self.B.id, body=body)
  926 
  927         # Shouldn't be able to set greater than parent
  928         quota['volumes'] = 11
  929         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
  930                           self.req, self.B.id, body=body)
  931 
  932     def test_child_neg_limit_set_grandkid_zero_limit(self):
  933         cur_quota_a = self.controller.show(self.req, self.A.id)
  934         self.assertEqual(10, cur_quota_a['quota_set']['volumes'])
  935 
  936         quota = {'volumes': -1}
  937         body = {'quota_set': quota}
  938         self.controller.update(self.req, self.B.id, body=body)
  939 
  940         cur_quota_d = self.controller.show(self.req, self.D.id)
  941         # Default child value is 0
  942         self.assertEqual(0, cur_quota_d['quota_set']['volumes'])
  943         # Should be able to set D explicitly to 0 since that's already the val
  944         quota['volumes'] = 0
  945         self.controller.update(self.req, self.D.id, body=body)
  946 
  947     def test_grandkid_negative_one_limit_enforced(self):
  948         quota = {'volumes': 2, 'gigabytes': 2}
  949         body = {'quota_set': quota}
  950         self.controller.update(self.req, self.A.id, body=body)
  951 
  952         quota['volumes'] = -1
  953         quota['gigabytes'] = -1
  954         self.controller.update(self.req, self.B.id, body=body)
  955         self.controller.update(self.req, self.C.id, body=body)
  956         self.controller.update(self.req, self.D.id, body=body)
  957 
  958         def _reserve(project_id):
  959             quotas.QUOTAS._driver.reserve(
  960                 self.req.environ['cinder.context'], quotas.QUOTAS.resources,
  961                 {'volumes': 1, 'gigabytes': 1}, project_id=project_id)
  962 
  963         _reserve(self.C.id)
  964         _reserve(self.D.id)
  965         self.assertRaises(exception.OverQuota, _reserve, self.B.id)
  966         self.assertRaises(exception.OverQuota, _reserve, self.C.id)
  967         self.assertRaises(exception.OverQuota, _reserve, self.D.id)
  968 
  969         # Make sure the rollbacks went successfully for allocated for all res
  970         for res in quota.keys():
  971             self._assert_quota_show(self.A.id, res, allocated=2, limit=2)
  972             self._assert_quota_show(self.B.id, res, allocated=1, limit=-1)
  973             self._assert_quota_show(self.C.id, res, reserved=1, limit=-1)
  974             self._assert_quota_show(self.D.id, res, reserved=1, limit=-1)
  975 
  976     def test_child_update_affects_allocated_and_rolls_back(self):
  977         quota = {'gigabytes': -1, 'volumes': 3}
  978         body = {'quota_set': quota}
  979         self.controller.update(self.req, self.A.id, body=body)
  980         quota['volumes'] = -1
  981         self.controller.update(self.req, self.B.id, body=body)
  982         quota['volumes'] = 1
  983         self.controller.update(self.req, self.C.id, body=body)
  984 
  985         # Shouldn't be able to update to greater than the grandparent
  986         quota['volumes'] = 3
  987         quota['gigabytes'] = 1
  988         self.assertRaises(webob.exc.HTTPBadRequest,
  989                           self.controller.update, self.req, self.D.id,
  990                           body=body)
  991         # Validate we haven't updated either parents' allocated value for
  992         # any of the keys (even if some keys were valid)
  993         self._assert_quota_show(self.A.id, 'volumes', allocated=1, limit=3)
  994         self._assert_quota_show(self.A.id, 'gigabytes', limit=-1)
  995         self._assert_quota_show(self.B.id, 'volumes', limit=-1)
  996         self._assert_quota_show(self.B.id, 'gigabytes', limit=-1)
  997 
  998         quota['volumes'] = 2
  999         self.controller.update(self.req, self.D.id, body=body)
 1000         # Validate we have now updated the parent and grandparents'
 1001         self.req.params = {'usage': 'True'}
 1002         self._assert_quota_show(self.A.id, 'volumes', allocated=3, limit=3)
 1003         self._assert_quota_show(self.A.id, 'gigabytes', allocated=1, limit=-1)
 1004         self._assert_quota_show(self.B.id, 'volumes', allocated=2, limit=-1)
 1005         self._assert_quota_show(self.B.id, 'gigabytes', allocated=1, limit=-1)
 1006 
 1007     def test_negative_child_limit_reserve_and_rollback(self):
 1008         quota = {'volumes': 2, 'gigabytes': 2}
 1009         body = {'quota_set': quota}
 1010         self.controller.update(self.req, self.A.id, body=body)
 1011 
 1012         quota['volumes'] = -1
 1013         quota['gigabytes'] = -1
 1014         self.controller.update(self.req, self.B.id, body=body)
 1015         self.controller.update(self.req, self.C.id, body=body)
 1016         self.controller.update(self.req, self.D.id, body=body)
 1017 
 1018         res = quotas.QUOTAS._driver.reserve(
 1019             self.req.environ['cinder.context'], quotas.QUOTAS.resources,
 1020             {'volumes': 2, 'gigabytes': 2}, project_id=self.D.id)
 1021 
 1022         self.req.params = {'usage': 'True'}
 1023         quota_b = self.controller.show(self.req, self.B.id)
 1024         self.assertEqual(2, quota_b['quota_set']['volumes']['allocated'])
 1025         # A will be the next hard limit to set
 1026         quota_a = self.controller.show(self.req, self.A.id)
 1027         self.assertEqual(2, quota_a['quota_set']['volumes']['allocated'])
 1028         quota_d = self.controller.show(self.req, self.D.id)
 1029         self.assertEqual(2, quota_d['quota_set']['volumes']['reserved'])
 1030 
 1031         quotas.QUOTAS.rollback(self.req.environ['cinder.context'], res,
 1032                                self.D.id)
 1033         # After the rollback, A's limit should be properly set again
 1034         quota_a = self.controller.show(self.req, self.A.id)
 1035         self.assertEqual(0, quota_a['quota_set']['volumes']['allocated'])
 1036         quota_d = self.controller.show(self.req, self.D.id)
 1037         self.assertEqual(0, quota_d['quota_set']['volumes']['in_use'])
 1038 
 1039     @mock.patch('cinder.db.sqlalchemy.api._get_quota_usages')
 1040     @mock.patch('cinder.db.quota_usage_get_all_by_project')
 1041     def test_nested_quota_set_negative_limit(self, mock_usage, mock_get_usage):
 1042         # TODO(mc_nair): this test should be moved to Tempest once nested quota
 1043         # coverage is added
 1044         fake_usages = {self.A.id: 1, self.B.id: 1, self.D.id: 2, self.C.id: 0}
 1045         self._create_fake_quota_usages(fake_usages)
 1046         mock_usage.side_effect = self._fake_quota_usage_get_all_by_project
 1047 
 1048         class FakeUsage(object):
 1049                 def __init__(self, in_use, reserved):
 1050                     self.in_use = in_use
 1051                     self.reserved = reserved
 1052                     self.until_refresh = None
 1053                     self.total = self.reserved + self.in_use
 1054 
 1055         def _fake__get_quota_usages(context, session, project_id,
 1056                                     resources=None):
 1057             if not project_id:
 1058                 return {}
 1059             return {'volumes': FakeUsage(fake_usages[project_id], 0)}
 1060         mock_get_usage.side_effect = _fake__get_quota_usages
 1061 
 1062         # Update the project A quota.
 1063         quota_limit = {'volumes': 7}
 1064         body = {'quota_set': quota_limit}
 1065         self.controller.update(self.req, self.A.id, body=body)
 1066 
 1067         quota_limit['volumes'] = 4
 1068         self.controller.update(self.req, self.B.id, body=body)
 1069         quota_limit['volumes'] = -1
 1070         self.controller.update(self.req, self.D.id, body=body)
 1071 
 1072         quota_limit['volumes'] = 1
 1073         self.controller.update(self.req, self.C.id, body=body)
 1074 
 1075         self.req.params['fix_allocated_quotas'] = True
 1076         self.controller.validate_setup_for_nested_quota_use(self.req)
 1077 
 1078         # Validate that the allocated values look right for each project
 1079         self.req.params = {'usage': 'True'}
 1080 
 1081         res = 'volumes'
 1082         # A has given 4 vols to B and 1 vol to C (from limits)
 1083         self._assert_quota_show(self.A.id, res, allocated=5, in_use=1, limit=7)
 1084         self._assert_quota_show(self.B.id, res, allocated=2, in_use=1, limit=4)
 1085         self._assert_quota_show(self.D.id, res, in_use=2, limit=-1)
 1086         self._assert_quota_show(self.C.id, res, limit=1)
 1087 
 1088         # Update B to -1 limit, and make sure that A's allocated gets updated
 1089         # with B + D's in_use values (one less than current limit
 1090         quota_limit['volumes'] = -1
 1091         self.controller.update(self.req, self.B.id, body=body)
 1092         self._assert_quota_show(self.A.id, res, allocated=4, in_use=1, limit=7)
 1093 
 1094         quota_limit['volumes'] = 6
 1095         self.assertRaises(
 1096             webob.exc.HTTPBadRequest,
 1097             self.controller.update, self.req, self.B.id, body=body)
 1098 
 1099         quota_limit['volumes'] = 5
 1100         self.controller.update(self.req, self.B.id, body=body)
 1101         self._assert_quota_show(self.A.id, res, allocated=6, in_use=1, limit=7)