def _delete_nested_quota(self, ctxt, proj_id): # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical # namespace. try: project_quotas = QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) project_group_quotas = GROUP_QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) project_quotas.update(project_group_quotas) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() target_project = quota_utils.get_project_hierarchy( ctxt, proj_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( ctxt, ctxt.project_id, subtree_as_ids=True) self._authorize_update_or_delete(context_project, target_project.id, parent_id) defaults = QUOTAS.get_defaults(ctxt, proj_id) defaults.update(GROUP_QUOTAS.get_defaults(ctxt, proj_id)) # If the project which is being deleted has allocated part of its # quota to its subprojects, then subprojects' quotas should be # deleted first. for res, value in project_quotas.items(): if 'allocated' in project_quotas[res].keys(): if project_quotas[res]['allocated'] > 0: msg = _("About to delete child projects having " "non-zero quota. This should not be performed") raise webob.exc.HTTPBadRequest(explanation=msg) # Ensure quota usage wouldn't exceed limit on a delete self._validate_existing_resource( res, defaults[res], project_quotas) try: db.quota_destroy_by_project(ctxt, target_project.id) except exception.AdminRequired: raise webob.exc.HTTPForbidden() for res, limit in project_quotas.items(): # Update child limit to 0 so the parent hierarchy gets it's # allocated values updated properly self._update_nested_quota_allocated( ctxt, target_project, project_quotas, res, 0)
def _delete_nested_quota(self, ctxt, proj_id): # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical # namespace. try: project_quotas = QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() target_project = quota_utils.get_project_hierarchy( ctxt, proj_id) parent_id = target_project.parent_id # If the project which is being deleted has allocated part of its # quota to its subprojects, then subprojects' quotas should be # deleted first. for key, value in project_quotas.items(): if 'allocated' in project_quotas[key].keys(): if project_quotas[key]['allocated'] != 0: msg = _("About to delete child projects having " "non-zero quota. This should not be performed") raise webob.exc.HTTPBadRequest(explanation=msg) 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( ctxt, ctxt.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( ctxt, parent_id) # Delete child quota first and later update parent's quota. try: db.quota_destroy_by_project(ctxt, target_project.id) except exception.AdminRequired: raise webob.exc.HTTPForbidden() # The parent "gives" quota to its child using the "allocated" value # and since the child project is getting deleted, we should restore # the child projects quota to the parent quota, but lowering it's # allocated value for key, value in project_quotas.items(): project_hard_limit = project_quotas[key]['limit'] parent_allocated = parent_project_quotas[key]['allocated'] parent_allocated -= project_hard_limit db.quota_allocated_update(ctxt, parent_id, key, parent_allocated)
def _delete_nested_quota(self, ctxt, proj_id): # Get the parent_id of the target project to verify whether we are # dealing with hierarchical namespace or non-hierarchical # namespace. try: project_quotas = QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) project_group_quotas = GROUP_QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) project_quotas.update(project_group_quotas) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() target_project = quota_utils.get_project_hierarchy( ctxt, proj_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( ctxt, ctxt.project_id, subtree_as_ids=True) self._authorize_update_or_delete(context_project, target_project.id, parent_id) defaults = QUOTAS.get_defaults(ctxt, proj_id) defaults.update(GROUP_QUOTAS.get_defaults(ctxt, proj_id)) # If the project which is being deleted has allocated part of its # quota to its subprojects, then subprojects' quotas should be # deleted first. for res, value in project_quotas.items(): if 'allocated' in project_quotas[res].keys(): if project_quotas[res]['allocated'] > 0: msg = _("About to delete child projects having " "non-zero quota. This should not be performed") raise webob.exc.HTTPBadRequest(explanation=msg) # Ensure quota usage wouldn't exceed limit on a delete self._validate_existing_resource( res, defaults[res], project_quotas) db.quota_destroy_by_project(ctxt, target_project.id) for res, limit in project_quotas.items(): # Update child limit to 0 so the parent hierarchy gets it's # allocated values updated properly self._update_nested_quota_allocated( ctxt, target_project, project_quotas, res, 0)
def test_get_project_keystoneclient_v2(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v2.0' expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v2.0') project = quota_utils.get_project_hierarchy( self.context, self.context.project_id) self.assertEqual(expected_project.__dict__, project.__dict__)
def test_get_project_keystoneclient_v2(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v2.0' expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v2.0') project = quota_utils.get_project_hierarchy(self.context, self.context.project_id) self.assertEqual(expected_project.__dict__, project.__dict__)
def test__filter_domain_id_from_parents_domain_as_parent( self, mock_client): # Test with a top level project (domain is direct parent) self._setup_mock_ksclient(mock_client, parents={'default': None}) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, parents_as_ids=True) self.assertIsNone(project.parent_id) self.assertIsNone(project.parents)
def test__filter_domain_id_from_parents_domain_as_grandparent( self, mock_client): # Test with a child project (domain is more than a parent) self._setup_mock_ksclient(mock_client, parents={'bar': {'default': None}}) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, parents_as_ids=True) self.assertEqual('bar', project.parent_id) self.assertEqual({'bar': None}, project.parents)
def test__filter_domain_id_from_parents_no_parents( self, mock_client): # Test that if top no parents are present (to simulate an older # keystone version) things don't blow up self._setup_mock_ksclient(mock_client) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, parents_as_ids=True) self.assertIsNone(project.parent_id) self.assertIsNone(project.parents)
def test__filter_domain_id_from_parents_no_parents(self, mock_client): # Test that if top no parents are present (to simulate an older # keystone version) things don't blow up self._setup_mock_ksclient(mock_client) project = quota_utils.get_project_hierarchy(self.context, self.context.project_id, parents_as_ids=True) self.assertIsNone(project.parent_id) self.assertIsNone(project.parents)
def show(self, req, id): """Show quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects admin of current project, immediate parent of the project or the CLOUD admin are able to perform a show. :param req: request :param id: target project id that needs to be shown """ context = req.environ['cinder.context'] authorize_show(context) params = req.params target_project_id = id if not hasattr(params, '__call__') and 'usage' in params: usage = strutils.bool_from_string(params['usage']) else: usage = False if QUOTAS.using_nested_quotas(): # With hierarchical projects, only the admin of the current project # or the root project has privilege to perform quota show # operations. target_project = quota_utils.get_project_hierarchy( context, target_project_id) context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_show(context_project, target_project) try: sqlalchemy_api.authorize_project_context(context, target_project_id) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() quotas = self._get_quotas(context, target_project_id, usage) return self._format_quota_set(target_project_id, quotas)
def test__filter_domain_id_from_parents_no_domain_in_parents( self, mock_client): # Test that if top most parent is not a domain (to simulate an older # keystone version) nothing gets removed from the tree parents = {'bar': {'foo': None}} self._setup_mock_ksclient(mock_client, parents=parents) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, parents_as_ids=True) self.assertEqual('bar', project.parent_id) self.assertEqual(parents, project.parents)
def test_get_project_keystoneclient_v3(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v3' returned_project = self.FakeProject(self.context.project_id, 'bar') del returned_project.subtree keystoneclient.projects.get.return_value = returned_project expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v3', 'bar') project = quota_utils.get_project_hierarchy(self.context, self.context.project_id) self.assertEqual(expected_project.__dict__, project.__dict__)
def test_get_project_keystoneclient_v3(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v3' returned_project = self.FakeProject(self.context.project_id, 'bar') del returned_project.subtree keystoneclient.projects.get.return_value = returned_project expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v3', 'bar') project = quota_utils.get_project_hierarchy( self.context, self.context.project_id) self.assertEqual(expected_project.__dict__, project.__dict__)
def show(self, req, id): """Show quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects admin of current project, immediate parent of the project or the CLOUD admin are able to perform a show. :param req: request :param id: target project id that needs to be shown """ context = req.environ['cinder.context'] authorize_show(context) params = req.params target_project_id = id if not hasattr(params, '__call__') and 'usage' in params: usage = utils.get_bool_param('usage', params) else: usage = False if QUOTAS.using_nested_quotas(): # With hierarchical projects, only the admin of the current project # or the root project has privilege to perform quota show # operations. target_project = quota_utils.get_project_hierarchy( context, target_project_id) context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_show(context_project, target_project) try: sqlalchemy_api.authorize_project_context(context, target_project_id) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() quotas = self._get_quotas(context, target_project_id, usage) return self._format_quota_set(target_project_id, quotas)
def show(self, req, id): """Show quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects admin of current project, immediate parent of the project or the CLOUD admin are able to perform a show. :param req: request :param id: target project id that needs to be shown """ context = req.environ['cinder.context'] params = req.params target_project_id = id context.authorize(policy.SHOW_POLICY, target={'project_id': target_project_id}) if not hasattr(params, '__call__') and 'usage' in params: usage = utils.get_bool_param('usage', params) else: usage = False if QUOTAS.using_nested_quotas(): # With hierarchical projects, only the admin of the current project # or the root project has privilege to perform quota show # operations. target_project = quota_utils.get_project_hierarchy( context, target_project_id) context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_show(context_project, target_project) quotas = self._get_quotas(context, target_project_id, usage) return self._format_quota_set(target_project_id, quotas)
def test_get_project_keystoneclient_v3_with_subtree(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v3' returned_project = self.FakeProject(self.context.project_id, 'bar') subtree_dict = {'baz': {'quux': None}} returned_project.subtree = subtree_dict keystoneclient.projects.get.return_value = returned_project expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v3', 'bar', subtree_dict) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, subtree_as_ids=True) keystoneclient.projects.get.assert_called_once_with( self.context.project_id, parents_as_ids=False, subtree_as_ids=True) self.assertEqual(expected_project.__dict__, project.__dict__)
def test_get_project_keystoneclient_v3_with_subtree(self, ksclient_class): keystoneclient = ksclient_class.return_value keystoneclient.version = 'v3' returned_project = self.FakeProject(self.context.project_id, 'bar') subtree_dict = {'baz': {'quux': None}} returned_project.subtree = subtree_dict keystoneclient.projects.get.return_value = returned_project expected_project = quota_utils.GenericProjectInfo( self.context.project_id, 'v3', 'bar', subtree_dict) project = quota_utils.get_project_hierarchy( self.context, self.context.project_id, subtree_as_ids=True) keystoneclient.projects.get.assert_called_once_with( self.context.project_id, subtree_as_ids=True) self.assertEqual(expected_project.__dict__, project.__dict__)
def _validate_project_and_authorize(self, context, project_id, policy_check): try: target_project = quota_utils.get_project_hierarchy( context, project_id) target_project = { 'project_id': target_project.id, 'domain_id': target_project.domain_id } context.authorize(policy_check, target=target_project) except ks_exc.http.NotFound: explanation = _("Project with id %s not found." % project_id) raise exc.HTTPNotFound(explanation=explanation) except exception.NotAuthorized: explanation = _("You are not authorized to perform this " "operation.") raise exc.HTTPForbidden(explanation=explanation)
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)}
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 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) # 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)}