def test_quota_update(self): db.quota_create(self.ctxt, "project1", "resource1", 41) db.quota_update(self.ctxt, "project1", "resource1", 42) quota = db.quota_get(self.ctxt, "project1", "resource1") self.assertEqual(quota.hard_limit, 42) self.assertEqual(quota.resource, "resource1") self.assertEqual(quota.project_id, "project1")
def test_quota_get_all_by_project(self): for i in range(3): for j in range(3): db.quota_create(self.ctxt, "proj%d" % i, "res%d" % j, j) for i in range(3): quotas_db = db.quota_get_all_by_project(self.ctxt, "proj%d" % i) self.assertEqual(quotas_db, {"project_id": "proj%d" % i, "res0": 0, "res1": 1, "res2": 2})
def test_quota_update(self): db.quota_create(self.ctxt, 'project1', 'resource1', 41) db.quota_update(self.ctxt, 'project1', 'resource1', 42) quota = db.quota_get(self.ctxt, 'project1', 'resource1') self.assertEqual(quota.hard_limit, 42) self.assertEqual(quota.resource, 'resource1') self.assertEqual(quota.project_id, 'project1')
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) project_id = id if not self.is_valid_body(body, 'quota_set'): msg = (_("Missing required element quota_set in request body.")) raise webob.exc.HTTPBadRequest(explanation=msg) bad_keys = [] for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue value = self._validate_quota_limit(body['quota_set'][key]) try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) project_id = id self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, project_id) valid_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue valid_quotas[key] = self.validate_integer( body['quota_set'][key], key, min_value=-1, max_value=db.MAX_INT) if not skip_flag: self._validate_existing_resource(key, value, quota_values) # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def test_unlimited_volumes(self): self.flags(quota_volumes=10, quota_gigabytes=-1) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 10) db.quota_create(self.context, self.project_id, 'volumes', -1) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 100) volumes = quota.allowed_volumes(self.context, 101, 1) self.assertEqual(volumes, 101)
def test_quota_get_all_by_project(self): for i in range(3): for j in range(3): db.quota_create(self.ctxt, 'proj%d' % i, 'res%d' % j, j) for i in range(3): quotas_db = db.quota_get_all_by_project(self.ctxt, 'proj%d' % i) self.assertEqual(quotas_db, {'project_id': 'proj%d' % i, 'res0': 0, 'res1': 1, 'res2': 2})
def update(self, req, id, body): """Update Quota for a particular tenant :param req: request :param id: target project id that needs to be updated :param body: key, value pair that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] target_project_id = id context.authorize(policy.UPDATE_POLICY, target={'project_id': target_project_id}) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) group_quota_values = GROUP_QUOTAS.get_project_quotas(context, target_project_id, defaults=False) quota_values.update(group_quota_values) valid_quotas = {} reservations = [] for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue self._validate_existing_resource(key, body['quota_set'][key], quota_values) valid_quotas[key] = body['quota_set'][key] # NOTE(ankit): Pass #2 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() if reservations: db.reservation_commit(context, reservations) return {'quota_set': self._get_quotas(context, target_project_id)}
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) project_id = id self.assert_valid_body(body, 'quota_set') bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. valid_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue valid_quotas[key] = self.validate_integer(body['quota_set'][key], key, min_value=-1, max_value=db.MAX_INT) # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def update(self, req, id, body): context = req.environ["cinder.context"] authorize_update(context) project_id = id for key in body["quota_set"].keys(): if key in QUOTAS: value = int(body["quota_set"][key]) self._validate_quota_limit(value) try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {"quota_set": self._get_quotas(context, id)}
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) project_id = id for key in body['quota_set'].keys(): if key in QUOTAS: self._validate_quota_limit(body['quota_set'][key]) value = int(body['quota_set'][key]) try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def _quota_reserve(context, project_id): """Create sample Quota, QuotaUsage and Reservation objects. There is no method db.quota_usage_create(), so we have to use db.quota_reserve() for creating QuotaUsage objects. Returns reservations uuids. """ def get_sync(resource, usage): def sync(elevated, project_id, session): return {resource: usage} return sync quotas = {} resources = {} deltas = {} for i, resource in enumerate(('volumes', 'gigabytes')): quotas[resource] = db.quota_create(context, project_id, resource, i + 1) resources[resource] = ReservableResource(resource, '_sync_%s' % resource) deltas[resource] = i + 1 return db.quota_reserve(context, resources, quotas, deltas, datetime.datetime.utcnow(), datetime.datetime.utcnow(), datetime.timedelta(days=1), project_id)
def _quota_reserve(context, project_id): """Create sample Quota, QuotaUsage and Reservation objects. There is no method db.quota_usage_create(), so we have to use db.quota_reserve() for creating QuotaUsage objects. Returns reservations uuids. """ def get_sync(resource, usage): def sync(elevated, project_id, session): return {resource: usage} return sync quotas = {} resources = {} deltas = {} for i in range(3): resource = 'res%d' % i quotas[resource] = db.quota_create(context, project_id, resource, i) resources[resource] = ReservableResource(resource, get_sync(resource, i), 'quota_res_%d' % i) deltas[resource] = i return db.quota_reserve(context, resources, quotas, deltas, datetime.utcnow(), datetime.utcnow(), timedelta(days=1), project_id)
def _quota_reserve(context, project_id): """Create sample Quota, QuotaUsage and Reservation objects. There is no method db.quota_usage_create(), so we have to use db.quota_reserve() for creating QuotaUsage objects. Returns reservations uuids. """ def get_sync(resource, usage): def sync(elevated, project_id, session): return {resource: usage} return sync quotas = {} resources = {} deltas = {} for i, resource in enumerate(('volumes', 'gigabytes')): quotas[resource] = db.quota_create(context, project_id, resource, i + 1) resources[resource] = ReservableResource(resource, '_sync_%s' % resource) deltas[resource] = i + 1 return db.quota_reserve( context, resources, quotas, deltas, datetime.datetime.utcnow(), datetime.datetime.utcnow(), datetime.timedelta(days=1), project_id )
def _quota_reserve(context, project_id): """Create sample Quota, QuotaUsage and Reservation objects. There is no method db.quota_usage_create(), so we have to use db.quota_reserve() for creating QuotaUsage objects. Returns reservations uuids. """ def get_sync(resource, usage): def sync(elevated, project_id, session): return {resource: usage} return sync quotas = {} resources = {} deltas = {} for i in range(3): resource = 'res%d' % i quotas[resource] = db.quota_create(context, project_id, resource, i) resources[resource] = ReservableResource( resource, get_sync(resource, i), 'quota_res_%d' % i) deltas[resource] = i return db.quota_reserve( context, resources, quotas, deltas, datetime.datetime.utcnow(), datetime.datetime.utcnow(), datetime.timedelta(days=1), project_id )
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) project_id = id if not self.is_valid_body(body, 'quota_set'): msg = (_("Missing required element quota_set in request body.")) raise webob.exc.HTTPBadRequest(explanation=msg) bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. valid_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue value = self._validate_quota_limit(body['quota_set'][key]) valid_quotas[key] = value # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def update(self, req, id, body): context = req.environ["cinder.context"] authorize_update(context) self.validate_string_length(id, "quota_set_name", min_length=1, max_length=255) project_id = id self.assert_valid_body(body, "quota_set") bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body["quota_set"].items(): if key not in QUOTAS and key not in NON_QUOTA_KEYS: bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. valid_quotas = {} for key in body["quota_set"].keys(): if key in NON_QUOTA_KEYS: continue valid_quotas[key] = self.validate_integer(body["quota_set"][key], key, min_value=-1, max_value=db.MAX_INT) # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {"quota_set": self._get_quotas(context, id)}
def test_retype_setup_fail_volume_is_available(self, mock_notify): """Verify volume is still available if retype prepare failed.""" elevated = context.get_admin_context() project_id = self.context.project_id db.volume_type_create(elevated, {'name': 'old', 'extra_specs': {}}) old_vol_type = db.volume_type_get_by_name(elevated, 'old') db.volume_type_create(elevated, {'name': 'new', 'extra_specs': {}}) new_vol_type = db.volume_type_get_by_name(elevated, 'new') db.quota_create(elevated, project_id, 'volumes_new', 0) volume = tests_utils.create_volume(self.context, size=1, host=CONF.host, status='available', volume_type_id=old_vol_type['id']) api = cinder.volume.api.API() self.assertRaises(exception.VolumeLimitExceeded, api.retype, self.context, volume, new_vol_type['id']) volume = db.volume_get(elevated, volume.id) mock_notify.assert_not_called() self.assertEqual('available', volume['status'])
def update(self, req, id, body): """Update Quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects only immediate parent admin or the CLOUD admin are able to perform an update. :param req: request :param id: target project id that needs to be updated :param body: key, value pair that that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) target_project_id = id bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # Saving off this value since we need to use it multiple times use_nested_quotas = QUOTAS.using_nested_quotas() if use_nested_quotas: # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical namespace target_project = quota_utils.get_project_hierarchy( context, target_project_id, parents_as_ids=True) parent_id = target_project.parent_id if parent_id: # Get the children of the project which the token is scoped to # in order to know if the target_project is in its hierarchy. context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True) self._authorize_update_or_delete(context_project, target_project.id, parent_id) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) valid_quotas = {} reservations = [] for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue value = utils.validate_integer( body['quota_set'][key], key, min_value=-1, max_value=db.MAX_INT) # Can't skip the validation of nested quotas since it could mess up # hierarchy if parent limit is less than childrens' current usage if not skip_flag or use_nested_quotas: self._validate_existing_resource(key, value, quota_values) if use_nested_quotas: try: reservations += self._update_nested_quota_allocated( context, target_project, quota_values, key, value) except exception.OverQuota as e: if reservations: db.reservation_rollback(context, reservations) raise webob.exc.HTTPBadRequest(explanation=e.msg) valid_quotas[key] = value # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() if reservations: db.reservation_commit(context, reservations) return {'quota_set': self._get_quotas(context, target_project_id)}
def test_quota_destroy(self): db.quota_create(self.ctxt, 'project1', 'resource1', 41) self.assertIsNone(db.quota_destroy(self.ctxt, 'project1', 'resource1')) self.assertRaises(exception.ProjectQuotaNotFound, db.quota_get, self.ctxt, 'project1', 'resource1')
def _retype_volume_exec(self, driver, mock_notify, snap=False, policy='on-demand', migrate_exc=False, exc=None, diff_equal=False, replica=False, reserve_vol_type_only=False, encryption_changed=False, replica_new=None): elevated = context.get_admin_context() project_id = self.context.project_id if replica: rep_status = 'enabled' extra_specs = {'replication_enabled': '<is> True'} else: rep_status = 'disabled' extra_specs = {} if replica_new is None: replica_new = replica new_specs = {'replication_enabled': '<is> True'} if replica_new else {} db.volume_type_create(elevated, {'name': 'old', 'extra_specs': extra_specs}) old_vol_type = db.volume_type_get_by_name(elevated, 'old') db.volume_type_create(elevated, {'name': 'new', 'extra_specs': new_specs}) vol_type = db.volume_type_get_by_name(elevated, 'new') db.quota_create(elevated, project_id, 'volumes_new', 10) volume = tests_utils.create_volume(self.context, size=1, host=CONF.host, status='retyping', volume_type_id=old_vol_type['id'], replication_status=rep_status) volume.previous_status = 'available' volume.save() if snap: create_snapshot(volume.id, size=volume.size, user_id=self.user_context.user_id, project_id=self.user_context.project_id, ctxt=self.user_context) if driver or diff_equal: host_obj = {'host': CONF.host, 'capabilities': {}} else: host_obj = {'host': 'newhost', 'capabilities': {}} reserve_opts = {'volumes': 1, 'gigabytes': volume.size} QUOTAS.add_volume_type_opts(self.context, reserve_opts, vol_type['id']) if reserve_vol_type_only: reserve_opts.pop('volumes') reserve_opts.pop('gigabytes') try: usage = db.quota_usage_get(elevated, project_id, 'volumes') total_volumes_in_use = usage.in_use usage = db.quota_usage_get(elevated, project_id, 'gigabytes') total_gigabytes_in_use = usage.in_use except exception.QuotaUsageNotFound: total_volumes_in_use = 0 total_gigabytes_in_use = 0 reservations = QUOTAS.reserve(self.context, project_id=project_id, **reserve_opts) old_reserve_opts = {'volumes': -1, 'gigabytes': -volume.size} QUOTAS.add_volume_type_opts(self.context, old_reserve_opts, old_vol_type['id']) old_reservations = QUOTAS.reserve(self.context, project_id=project_id, **old_reserve_opts) with mock.patch.object(self.volume.driver, 'retype') as _retype,\ mock.patch.object(volume_types, 'volume_types_diff') as _diff,\ mock.patch.object(self.volume, 'migrate_volume') as _mig,\ mock.patch.object(db.sqlalchemy.api, 'volume_get') as _vget,\ mock.patch.object(context.RequestContext, 'elevated') as _ctx: _vget.return_value = volume _retype.return_value = driver _ctx.return_value = self.context returned_diff = { 'encryption': {}, 'qos_specs': {}, 'extra_specs': {}, } if replica != replica_new: returned_diff['extra_specs']['replication_enabled'] = ( extra_specs.get('replication_enabled'), new_specs.get('replication_enabled')) expected_replica_status = 'enabled' if replica_new else 'disabled' if encryption_changed: returned_diff['encryption'] = 'fake' _diff.return_value = (returned_diff, diff_equal) if migrate_exc: _mig.side_effect = KeyError else: _mig.return_value = True if not exc: self.volume.retype(self.context, volume, vol_type['id'], host_obj, migration_policy=policy, reservations=reservations, old_reservations=old_reservations) else: self.assertRaises(exc, self.volume.retype, self.context, volume, vol_type['id'], host_obj, migration_policy=policy, reservations=reservations, old_reservations=old_reservations) if host_obj['host'] != CONF.host: _retype.assert_not_called() # get volume/quota properties volume = objects.Volume.get_by_id(elevated, volume.id) try: usage = db.quota_usage_get(elevated, project_id, 'volumes_new') volumes_in_use = usage.in_use except exception.QuotaUsageNotFound: volumes_in_use = 0 # Get new in_use after retype, it should not be changed. if reserve_vol_type_only: try: usage = db.quota_usage_get(elevated, project_id, 'volumes') new_total_volumes_in_use = usage.in_use usage = db.quota_usage_get(elevated, project_id, 'gigabytes') new_total_gigabytes_in_use = usage.in_use except exception.QuotaUsageNotFound: new_total_volumes_in_use = 0 new_total_gigabytes_in_use = 0 self.assertEqual(total_volumes_in_use, new_total_volumes_in_use) self.assertEqual(total_gigabytes_in_use, new_total_gigabytes_in_use) # check properties if driver or diff_equal: self.assertEqual(vol_type['id'], volume.volume_type_id) self.assertEqual('available', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(1, volumes_in_use) self.assert_notify_called(mock_notify, (['INFO', 'volume.retype'],)) elif not exc: self.assertEqual(old_vol_type['id'], volume.volume_type_id) self.assertEqual('retyping', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(1, volumes_in_use) self.assert_notify_called(mock_notify, (['INFO', 'volume.retype'],)) else: self.assertEqual(old_vol_type['id'], volume.volume_type_id) self.assertEqual('available', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(0, volumes_in_use) mock_notify.assert_not_called() if encryption_changed: self.assertTrue(_mig.called) self.assertEqual(expected_replica_status, volume.replication_status)
def test_quota_get(self): quota = db.quota_create(self.ctxt, 'project1', 'resource', 99) quota_db = db.quota_get(self.ctxt, 'project1', 'resource') self._assertEqualObjects(quota, quota_db)
def update(self, req, id, body): context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) project_id = id self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, project_id) valid_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue valid_quotas[key] = self.validate_integer(body['quota_set'][key], key, min_value=-1, max_value=db.MAX_INT) if not skip_flag: self._validate_existing_resource(key, value, quota_values) # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() return {'quota_set': self._get_quotas(context, id)}
def validate_nested_setup(self, ctxt, resources, project_tree, fix_allocated_quotas=False): """Ensures project_tree has quotas that make sense as nested quotas. Validates the following: * No child projects have a limit of -1 * No parent project has child_projects who have more combined quota than the parent's quota limit * No child quota has a larger in-use value than it's current limit (could happen before because child default values weren't enforced) * All parent projects' "allocated" quotas match the sum of the limits of its children projects """ project_queue = deque(project_tree.items()) borked_allocated_quotas = {} while project_queue: # Tuple of (current root node, subtree) cur_project_id, project_subtree = project_queue.popleft() # If we're on a leaf node, no need to do validation on it, and in # order to avoid complication trying to get its children, skip it. if not project_subtree: continue cur_project_quotas = self.get_project_quotas( ctxt, resources, cur_project_id) child_project_ids = project_subtree.keys() child_project_quotas = {child_id: self.get_project_quotas( ctxt, resources, child_id) for child_id in child_project_ids} # Validate each resource when compared to it's child quotas for resource in cur_project_quotas.keys(): child_limit_sum = 0 for child_id, child_quota in child_project_quotas.items(): child_limit = child_quota[resource]['limit'] # Don't want to continue validation if -1 limit for child # TODO(mc_nair) - remove when allowing -1 for subprojects if child_limit < 0: msg = _("Quota limit is -1 for child project " "'%(proj)s' for resource '%(res)s'") % { 'proj': child_id, 'res': resource } raise exception.InvalidNestedQuotaSetup(reason=msg) # Handle the case that child default quotas weren't being # properly enforced before elif child_quota[resource].get('in_use', 0) > child_limit: msg = _("Quota limit invalid for project '%(proj)s' " "for resource '%(res)s': limit of %(limit)d " "is less than in-use value of %(used)d") % { 'proj': child_id, 'res': resource, 'limit': child_limit, 'used': child_quota[resource]['in_use'] } raise exception.InvalidNestedQuotaSetup(reason=msg) child_limit_sum += child_quota[resource]['limit'] parent_quota = cur_project_quotas[resource] parent_limit = parent_quota['limit'] parent_usage = parent_quota['in_use'] parent_allocated = parent_quota.get('allocated', 0) if parent_limit > 0: parent_free_quota = parent_limit - parent_usage if parent_free_quota < child_limit_sum: msg = _("Sum of child limits '%(sum)s' is greater " "than free quota of '%(free)s' for project " "'%(proj)s' for resource '%(res)s'. Please " "lower the limit for one or more of the " "following projects: '%(child_ids)s'") % { 'sum': child_limit_sum, 'free': parent_free_quota, 'proj': cur_project_id, 'res': resource, 'child_ids': ', '.join(child_project_ids) } raise exception.InvalidNestedQuotaSetup(reason=msg) # Deal with the fact that using -1 limits in the past may # have messed some allocated values in DB if parent_allocated != child_limit_sum: # Decide whether to fix the allocated val or just # keep track of what's messed up if fix_allocated_quotas: try: db.quota_allocated_update(ctxt, cur_project_id, resource, child_limit_sum) except exception.ProjectQuotaNotFound: # Handles the case that the project is using # default quota value so nothing present to update db.quota_create( ctxt, cur_project_id, resource, parent_limit, allocated=child_limit_sum) else: if cur_project_id not in borked_allocated_quotas: borked_allocated_quotas[cur_project_id] = {} borked_allocated_quotas[cur_project_id][resource] = { 'db_allocated_quota': parent_allocated, 'expected_allocated_quota': child_limit_sum} project_queue.extend(project_subtree.items()) if borked_allocated_quotas: msg = _("Invalid allocated quotas defined for the following " "project quotas: %s") % borked_allocated_quotas raise exception.InvalidNestedQuotaSetup(message=msg)
def _retype_volume_exec(self, driver, mock_notify, snap=False, policy='on-demand', migrate_exc=False, exc=None, diff_equal=False, replica=False, reserve_vol_type_only=False, encryption_changed=False, replica_new=None): elevated = context.get_admin_context() project_id = self.context.project_id if replica: rep_status = 'enabled' extra_specs = {'replication_enabled': '<is> True'} else: rep_status = 'disabled' extra_specs = {} if replica_new is None: replica_new = replica new_specs = {'replication_enabled': '<is> True'} if replica_new else {} db.volume_type_create(elevated, { 'name': 'old', 'extra_specs': extra_specs }) old_vol_type = db.volume_type_get_by_name(elevated, 'old') db.volume_type_create(elevated, { 'name': 'new', 'extra_specs': new_specs }) vol_type = db.volume_type_get_by_name(elevated, 'new') db.quota_create(elevated, project_id, 'volumes_new', 10) volume = tests_utils.create_volume(self.context, size=1, host=CONF.host, status='retyping', volume_type_id=old_vol_type['id'], replication_status=rep_status) volume.previous_status = 'available' volume.save() if snap: create_snapshot(volume.id, size=volume.size) if driver or diff_equal: host_obj = {'host': CONF.host, 'capabilities': {}} else: host_obj = {'host': 'newhost', 'capabilities': {}} reserve_opts = {'volumes': 1, 'gigabytes': volume.size} QUOTAS.add_volume_type_opts(self.context, reserve_opts, vol_type['id']) if reserve_vol_type_only: reserve_opts.pop('volumes') reserve_opts.pop('gigabytes') try: usage = db.quota_usage_get(elevated, project_id, 'volumes') total_volumes_in_use = usage.in_use usage = db.quota_usage_get(elevated, project_id, 'gigabytes') total_gigabytes_in_use = usage.in_use except exception.QuotaUsageNotFound: total_volumes_in_use = 0 total_gigabytes_in_use = 0 reservations = QUOTAS.reserve(self.context, project_id=project_id, **reserve_opts) old_reserve_opts = {'volumes': -1, 'gigabytes': -volume.size} QUOTAS.add_volume_type_opts(self.context, old_reserve_opts, old_vol_type['id']) old_reservations = QUOTAS.reserve(self.context, project_id=project_id, **old_reserve_opts) with mock.patch.object(self.volume.driver, 'retype') as _retype,\ mock.patch.object(volume_types, 'volume_types_diff') as _diff,\ mock.patch.object(self.volume, 'migrate_volume') as _mig,\ mock.patch.object(db.sqlalchemy.api, 'volume_get') as mock_get: mock_get.return_value = volume _retype.return_value = driver returned_diff = { 'encryption': {}, 'qos_specs': {}, 'extra_specs': {}, } if replica != replica_new: returned_diff['extra_specs']['replication_enabled'] = ( extra_specs.get('replication_enabled'), new_specs.get('replication_enabled')) expected_replica_status = 'enabled' if replica_new else 'disabled' if encryption_changed: returned_diff['encryption'] = 'fake' _diff.return_value = (returned_diff, diff_equal) if migrate_exc: _mig.side_effect = KeyError else: _mig.return_value = True if not exc: self.volume.retype(self.context, volume, vol_type['id'], host_obj, migration_policy=policy, reservations=reservations, old_reservations=old_reservations) else: self.assertRaises(exc, self.volume.retype, self.context, volume, vol_type['id'], host_obj, migration_policy=policy, reservations=reservations, old_reservations=old_reservations) if host_obj['host'] != CONF.host: _retype.assert_not_called() # get volume/quota properties volume = objects.Volume.get_by_id(elevated, volume.id) try: usage = db.quota_usage_get(elevated, project_id, 'volumes_new') volumes_in_use = usage.in_use except exception.QuotaUsageNotFound: volumes_in_use = 0 # Get new in_use after retype, it should not be changed. if reserve_vol_type_only: try: usage = db.quota_usage_get(elevated, project_id, 'volumes') new_total_volumes_in_use = usage.in_use usage = db.quota_usage_get(elevated, project_id, 'gigabytes') new_total_gigabytes_in_use = usage.in_use except exception.QuotaUsageNotFound: new_total_volumes_in_use = 0 new_total_gigabytes_in_use = 0 self.assertEqual(total_volumes_in_use, new_total_volumes_in_use) self.assertEqual(total_gigabytes_in_use, new_total_gigabytes_in_use) # check properties if driver or diff_equal: self.assertEqual(vol_type['id'], volume.volume_type_id) self.assertEqual('available', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(1, volumes_in_use) self.assert_notify_called(mock_notify, (['INFO', 'volume.retype'], )) elif not exc: self.assertEqual(old_vol_type['id'], volume.volume_type_id) self.assertEqual('retyping', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(1, volumes_in_use) self.assert_notify_called(mock_notify, (['INFO', 'volume.retype'], )) else: self.assertEqual(old_vol_type['id'], volume.volume_type_id) self.assertEqual('available', volume.status) self.assertEqual(CONF.host, volume.host) self.assertEqual(0, volumes_in_use) mock_notify.assert_not_called() if encryption_changed: self.assertTrue(_mig.called) self.assertEqual(expected_replica_status, volume.replication_status)
def test_quota_create(self): quota = db.quota_create(self.ctxt, "project1", "resource", 99) self.assertEqual(quota.resource, "resource") self.assertEqual(quota.hard_limit, 99) self.assertEqual(quota.project_id, "project1")
def test_quota_create(self): quota = db.quota_create(self.ctxt, 'project1', 'resource', 99) self.assertEqual(quota.resource, 'resource') self.assertEqual(quota.hard_limit, 99) self.assertEqual(quota.project_id, 'project1')
def test_quota_get(self): quota = db.quota_create(self.ctxt, "project1", "resource", 99) quota_db = db.quota_get(self.ctxt, "project1", "resource") self._assertEqualObjects(quota, quota_db)
def update(self, req, id, body): """Update Quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects only immediate parent admin or the CLOUD admin are able to perform an update. :param req: request :param id: target project id that needs to be updated :param body: key, value pair that that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) target_project_id = id bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # Saving off this value since we need to use it multiple times use_nested_quotas = QUOTAS.using_nested_quotas() if use_nested_quotas: # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical namespace target_project = quota_utils.get_project_hierarchy( context, target_project_id, parents_as_ids=True) parent_id = target_project.parent_id if parent_id: # Get the children of the project which the token is scoped to # in order to know if the target_project is in its hierarchy. context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_update_or_delete(context_project, target_project.id, parent_id) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) valid_quotas = {} reservations = [] for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue value = utils.validate_integer(body['quota_set'][key], key, min_value=-1, max_value=db.MAX_INT) # Can't skip the validation of nested quotas since it could mess up # hierarchy if parent limit is less than childrens' current usage if not skip_flag or use_nested_quotas: self._validate_existing_resource(key, value, quota_values) if use_nested_quotas: try: reservations += self._update_nested_quota_allocated( context, target_project, quota_values, key, value) except exception.OverQuota as e: if reservations: db.reservation_rollback(context, reservations) raise webob.exc.HTTPBadRequest(explanation=e.message) valid_quotas[key] = value # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() if reservations: db.reservation_commit(context, reservations) return {'quota_set': self._get_quotas(context, target_project_id)}
def update(self, req, id, body): """Update Quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects only immediate parent admin or the CLOUD admin are able to perform an update. :param req: request :param id: target project id that needs to be updated :param body: key, value pair that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] target_project_id = id context.authorize(policy.UPDATE_POLICY, target={'project_id': target_project_id}) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) # Saving off this value since we need to use it multiple times use_nested_quotas = QUOTAS.using_nested_quotas() if use_nested_quotas: # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical namespace target_project = quota_utils.get_project_hierarchy( context, target_project_id, parents_as_ids=True) parent_id = target_project.parent_id if parent_id: # Get the children of the project which the token is scoped to # in order to know if the target_project is in its hierarchy. context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_update_or_delete(context_project, target_project.id, parent_id) # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) group_quota_values = GROUP_QUOTAS.get_project_quotas(context, target_project_id, defaults=False) quota_values.update(group_quota_values) valid_quotas = {} reservations = [] for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue self._validate_existing_resource(key, body['quota_set'][key], quota_values) if use_nested_quotas: try: reservations += self._update_nested_quota_allocated( context, target_project, quota_values, key, body['quota_set'][key]) except exception.OverQuota as e: if reservations: db.reservation_rollback(context, reservations) raise webob.exc.HTTPBadRequest(explanation=e.msg) valid_quotas[key] = body['quota_set'][key] # NOTE(ankit): Pass #2 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() if reservations: db.reservation_commit(context, reservations) return {'quota_set': self._get_quotas(context, target_project_id)}
def update(self, req, id, body): """Update Quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects only immediate parent admin or the CLOUD admin are able to perform an update. :param req: request :param id: target project id that needs to be updated :param body: key, value pair that that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) target_project_id = id bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical namespace. target_project = self._get_project(context, target_project_id) parent_id = target_project.parent_id if parent_id: # Get the children of the project which the token is scoped to # in order to know if the target_project is in its hierarchy. context_project = self._get_project(context, context.project_id, subtree_as_ids=True) self._authorize_update_or_delete(context_project, target_project.id, parent_id) parent_project_quotas = QUOTAS.get_project_quotas( context, parent_id) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) valid_quotas = {} allocated_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue if not skip_flag: self._validate_existing_resource(key, value, quota_values) if parent_id: value = self._validate_quota_limit(body['quota_set'], key, quota_values, parent_project_quotas) original_quota = 0 if quota_values.get(key): original_quota = quota_values[key]['limit'] allocated_quotas[key] = ( parent_project_quotas[key].get('allocated', 0) + value - original_quota) else: value = self._validate_quota_limit(body['quota_set'], key) valid_quotas[key] = value # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() # If hierarchical projects, update child's quota first # and then parents quota. In future this needs to be an # atomic operation. if parent_id: if key in allocated_quotas.keys(): try: db.quota_allocated_update(context, parent_id, key, allocated_quotas[key]) except exception.ProjectQuotaNotFound: parent_limit = parent_project_quotas[key]['limit'] db.quota_create(context, parent_id, key, parent_limit, allocated=allocated_quotas[key]) return { 'quota_set': self._get_quotas(context, target_project_id, parent_project_id=parent_id) }
def update(self, req, id, body): """Update Quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects only immediate parent admin or the CLOUD admin are able to perform an update. :param req: request :param id: target project id that needs to be updated :param body: key, value pair that that will be applied to the resources if the update succeeds """ context = req.environ['cinder.context'] authorize_update(context) self.validate_string_length(id, 'quota_set_name', min_length=1, max_length=255) self.assert_valid_body(body, 'quota_set') # Get the optional argument 'skip_validation' from body, # if skip_validation is False, then validate existing resource. skip_flag = body.get('skip_validation', True) if not utils.is_valid_boolstr(skip_flag): msg = _("Invalid value '%s' for skip_validation.") % skip_flag raise exception.InvalidParameterValue(err=msg) skip_flag = strutils.bool_from_string(skip_flag) target_project_id = id bad_keys = [] # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): if (key not in QUOTAS and key not in NON_QUOTA_KEYS): bad_keys.append(key) continue if len(bad_keys) > 0: msg = _("Bad key(s) in quota set: %s") % ",".join(bad_keys) raise webob.exc.HTTPBadRequest(explanation=msg) # Saving off this value since we need to use it multiple times use_nested_quotas = QUOTAS.using_nested_quotas() if use_nested_quotas: # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical namespace target_project = quota_utils.get_project_hierarchy( context, target_project_id) parent_id = target_project.parent_id if parent_id: # Get the children of the project which the token is scoped to # in order to know if the target_project is in its hierarchy. context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True) self._authorize_update_or_delete(context_project, target_project.id, parent_id) parent_project_quotas = QUOTAS.get_project_quotas( context, parent_id) # NOTE(ankit): Pass #2 - In this loop for body['quota_set'].keys(), # we validate the quota limits to ensure that we can bail out if # any of the items in the set is bad. Meanwhile we validate value # to ensure that the value can't be lower than number of existing # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) valid_quotas = {} allocated_quotas = {} for key in body['quota_set'].keys(): if key in NON_QUOTA_KEYS: continue if not skip_flag: self._validate_existing_resource(key, value, quota_values) if use_nested_quotas and parent_id: value = self._validate_quota_limit(body['quota_set'], key, quota_values, parent_project_quotas) if value < 0: # TODO(mc_nair): extend to handle -1 limits and recurse up # the hierarchy msg = _("Quota can't be set to -1 for child projects.") raise webob.exc.HTTPBadRequest(explanation=msg) original_quota = 0 if quota_values.get(key): original_quota = quota_values[key]['limit'] allocated_quotas[key] = ( parent_project_quotas[key].get('allocated', 0) + value - original_quota) else: value = self._validate_quota_limit(body['quota_set'], key) valid_quotas[key] = value # NOTE(ankit): Pass #3 - At this point we know that all the keys and # values are valid and we can iterate and update them all in one shot # without having to worry about rolling back etc as we have done # the validation up front in the 2 loops above. for key, value in valid_quotas.items(): try: db.quota_update(context, target_project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, target_project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() # If hierarchical projects, update child's quota first # and then parents quota. In future this needs to be an # atomic operation. if use_nested_quotas and parent_id: if key in allocated_quotas.keys(): try: db.quota_allocated_update(context, parent_id, key, allocated_quotas[key]) except exception.ProjectQuotaNotFound: parent_limit = parent_project_quotas[key]['limit'] db.quota_create(context, parent_id, key, parent_limit, allocated=allocated_quotas[key]) return {'quota_set': self._get_quotas(context, target_project_id)}