From 391f345dfff174108e330de1ed4c7ed18f8ff923 Mon Sep 17 00:00:00 2001 From: Kylin CG Date: Mon, 4 Jun 2012 22:21:41 +0800 Subject: Adds per-user-quotas support for more detailed quotas management Implements blueprint per-user-quotas. Based on the original quotas structure. NOTE: quota_instances, quota_cores, quota_ram, quota_volumes, quota_gigabytes, quota_key_pairs and quota_security_groups are supported per user. Allow 'projectadmin' role to access the user quota setting methods. Add commands 'nova-manage quota project/user' for quotas management. Change-Id: I07a39499432571fedd819c53ae414240cefc3354 --- nova/api/openstack/compute/contrib/quotas.py | 78 +++++++++++++++++++++++----- nova/api/openstack/compute/limits.py | 15 +++++- 2 files changed, 79 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/compute/contrib/quotas.py b/nova/api/openstack/compute/contrib/quotas.py index 33584badc..56583ff79 100644 --- a/nova/api/openstack/compute/contrib/quotas.py +++ b/nova/api/openstack/compute/contrib/quotas.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import urlparse import webob from nova.api.openstack import extensions @@ -24,13 +25,15 @@ from nova import db from nova.db.sqlalchemy import api as sqlalchemy_api from nova import exception from nova import quota +from nova import utils QUOTAS = quota.QUOTAS -authorize_update = extensions.extension_authorizer('compute', 'quotas:update') -authorize_show = extensions.extension_authorizer('compute', 'quotas:show') +def authorize_action(context, action_name): + action = 'quotas:%s' % action_name + extensions.extension_authorizer('compute', action)(context) class QuotaTemplate(xmlutil.TemplateBuilder): @@ -57,51 +60,102 @@ class QuotaSetsController(object): return dict(quota_set=result) - def _validate_quota_limit(self, limit): + def _validate_quota_limit(self, limit, remain, quota): # NOTE: -1 is a flag value for unlimited if limit < -1: msg = _("Quota limit must be -1 or greater.") raise webob.exc.HTTPBadRequest(explanation=msg) - def _get_quotas(self, context, id, usages=False): - values = QUOTAS.get_project_quotas(context, id, usages=usages) + # Quota limit must be less than the remains of the project. + if remain != -1 and remain < limit - quota: + msg = _("Quota limit exceed the remains of the project.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + def _get_quotas(self, context, id, user_id=None, remaining=False, + usages=False): + # Get the remaining quotas for a project. + if remaining: + values = QUOTAS.get_remaining_quotas(context, id) + return values + + if user_id: + # If user_id, return quotas for the given user. + values = QUOTAS.get_user_quotas(context, user_id, id, + usages=usages) + else: + values = QUOTAS.get_project_quotas(context, id, usages=usages) if usages: return values else: return dict((k, v['limit']) for k, v in values.items()) + def _request_params(self, req): + qs = req.environ.get('QUERY_STRING', '') + return urlparse.parse_qs(qs) + @wsgi.serializers(xml=QuotaTemplate) def show(self, req, id): context = req.environ['nova.context'] - authorize_show(context) + authorize_action(context, 'show') + params = self._request_params(req) + remaining = False + if 'remaining' in params: + remaining = utils.bool_from_str(params["remaining"][0]) + user_id = None + if 'user_id' in params: + user_id = params["user_id"][0] try: sqlalchemy_api.authorize_project_context(context, id) - return self._format_quota_set(id, self._get_quotas(context, id)) + return self._format_quota_set(id, + self._get_quotas(context, id, user_id, remaining)) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() @wsgi.serializers(xml=QuotaTemplate) def update(self, req, id, body): context = req.environ['nova.context'] - authorize_update(context) + params = self._request_params(req) project_id = id + user_id = None + remains = {} + quotas = {} + if 'user_id' in params: + # Project admins are able to modify per-user quotas. + authorize_action(context, 'update_for_user') + user_id = params["user_id"][0] + remains = self._get_quotas(context, project_id, remaining=True) + quotas = db.quota_get_all_by_user(context, user_id, project_id) + else: + # Only admins are able to modify per-project quotas. + authorize_action(context, 'update_for_project') + 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) + if user_id: + self._validate_quota_limit(value, remains.get(key, 0), + quotas.get(key, 0)) + db.quota_update_for_user(context, user_id, + project_id, key, value) + else: + self._validate_quota_limit(value, remains.get(key, -1), + quotas.get(key, 0)) + db.quota_update(context, project_id, key, value) except exception.ProjectQuotaNotFound: db.quota_create(context, project_id, key, value) + except exception.UserQuotaNotFound: + db.quota_create_for_user(context, user_id, + project_id, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() - return {'quota_set': self._get_quotas(context, id)} + return {'quota_set': self._get_quotas(context, id, user_id)} @wsgi.serializers(xml=QuotaTemplate) def defaults(self, req, id): context = req.environ['nova.context'] - authorize_show(context) + authorize_action(context, 'show') return self._format_quota_set(id, QUOTAS.get_defaults(context)) diff --git a/nova/api/openstack/compute/limits.py b/nova/api/openstack/compute/limits.py index c0ef65670..990c08a10 100644 --- a/nova/api/openstack/compute/limits.py +++ b/nova/api/openstack/compute/limits.py @@ -23,6 +23,7 @@ import httplib import math import re import time +import urlparse import webob.dec import webob.exc @@ -85,8 +86,18 @@ class LimitsController(object): Return all global and rate limit information. """ context = req.environ['nova.context'] - quotas = QUOTAS.get_project_quotas(context, context.project_id, - usages=False) + qs = req.environ.get('QUERY_STRING', '') + params = urlparse.parse_qs(qs) + if 'user_id' in params: + user_id = params["user_id"][0] + quotas = QUOTAS.get_user_quotas(context, user_id, + context.project_id, + usages=False) + else: + quotas = QUOTAS.get_project_quotas(context, + context.project_id, + usages=False) + abs_limits = dict((k, v['limit']) for k, v in quotas.items()) rate_limits = req.environ.get("nova.limits", []) -- cgit