Example #1
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)
Example #2
0
    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)}
Example #3
0
    def delete(self, req, id):
        """Delete 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 a delete.

        :param req: request
        :param id: target project id that needs to be updated
        """
        context = req.environ['cinder.context']
        authorize_delete(context)

        # 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, id)
        parent_id = target_project.parent_id

        try:
            project_quotas = QUOTAS.get_project_quotas(
                context,
                target_project.id,
                usages=True,
                parent_project_id=parent_id,
                defaults=False)
        except exception.NotAuthorized:
            raise webob.exc.HTTPForbidden()

        # 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 = 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, parent_project_id=parent_id)

            # Delete child quota first and later update parent's quota.
            try:
                db.quota_destroy_by_project(context, target_project.id)
            except exception.AdminRequired:
                raise webob.exc.HTTPForbidden()

            # Update the allocated of the parent
            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(context, parent_id, key,
                                          parent_allocated)
        else:
            try:
                db.quota_destroy_by_project(context, target_project.id)
            except exception.AdminRequired:
                raise webob.exc.HTTPForbidden()
Example #4
0
    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)
        }
Example #5
0
    def delete(self, req, id):
        """Delete 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 a delete.

        :param req: request
        :param id: target project id that needs to be updated
        """
        context = req.environ['cinder.context']
        authorize_delete(context)

        # 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, id)
        parent_id = target_project.parent_id

        try:
            project_quotas = QUOTAS.get_project_quotas(
                context, target_project.id, usages=True,
                parent_project_id=parent_id)
        except exception.NotAuthorized:
            raise webob.exc.HTTPForbidden()

        # 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 = 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, parent_project_id=parent_id)

            # Delete child quota first and later update parent's quota.
            try:
                db.quota_destroy_by_project(context, target_project.id)
            except exception.AdminRequired:
                raise webob.exc.HTTPForbidden()

            # Update the allocated of the parent
            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(context, parent_id, key,
                                          parent_allocated)
        else:
            try:
                db.quota_destroy_by_project(context, target_project.id)
            except exception.AdminRequired:
                raise webob.exc.HTTPForbidden()
Example #6
0
File: quota.py Project: dims/cinder
    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)