Skip to content

Commit

Permalink
Adds per-user-quotas support for more detailed quotas management
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Kylin-CG committed Aug 6, 2012
1 parent 51002f0 commit 391f345
Show file tree
Hide file tree
Showing 12 changed files with 1,289 additions and 157 deletions.
51 changes: 51 additions & 0 deletions bin/nova-manage
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ def _db_error(caught_exception):
class ProjectCommands(object):
"""Class for managing projects."""

@args('--project', dest="project_id", metavar='<Project name>',
help='Project name')
@args('--key', dest="key", metavar='<key>', help='Key')
@args('--value', dest="value", metavar='<value>', help='Value')
def quota(self, project_id, key=None, value=None):
"""Set or display quotas for project"""
ctxt = context.get_admin_context()
Expand Down Expand Up @@ -256,6 +260,52 @@ class ProjectCommands(object):
AccountCommands = ProjectCommands


class QuotaCommands(object):
"""Class for managing quotas."""

@args('--project', dest="project_id", metavar='<Project name>',
help='Project name')
@args('--key', dest="key", metavar='<key>', help='Key')
@args('--value', dest="value", metavar='<value>', help='Value')
def project(self, project_id, key=None, value=None):
"""Set or display quotas for project"""
ctxt = context.get_admin_context()
if key:
if value.lower() == 'unlimited':
value = None
try:
db.quota_update(ctxt, project_id, key, value)
except exception.ProjectQuotaNotFound:
db.quota_create(ctxt, project_id, key, value)
project_quota = QUOTAS.get_project_quotas(ctxt, project_id)
for key, value in project_quota.iteritems():
if value['limit'] < 0 or value['limit'] is None:
value['limit'] = 'unlimited'
print '%s: %s' % (key, value['limit'])

@args('--user', dest="user_id", metavar='<User name>',
help='User name')
@args('--project', dest="project_id", metavar='<Project name>',
help='Project name')
@args('--key', dest="key", metavar='<key>', help='Key')
@args('--value', dest="value", metavar='<value>', help='Value')
def user(self, user_id, project_id, key=None, value=None):
"""Set or display quotas for user"""
ctxt = context.get_admin_context()
if key:
if value.lower() == 'unlimited':
value = None
try:
db.quota_update_for_user(ctxt, user_id, project_id, key, value)
except exception.UserQuotaNotFound:
db.quota_create_for_user(ctxt, user_id, project_id, key, value)
user_quota = QUOTAS.get_user_quotas(ctxt, user_id, project_id)
for key, value in user_quota.iteritems():
if value['limit'] < 0 or value['limit'] is None:
value['limit'] = 'unlimited'
print '%s: %s' % (key, value['limit'])


class FixedIpCommands(object):
"""Class for managing fixed ip."""

Expand Down Expand Up @@ -1319,6 +1369,7 @@ CATEGORIES = [
('logs', GetLogCommands),
('network', NetworkCommands),
('project', ProjectCommands),
('quota', QuotaCommands),
('service', ServiceCommands),
('shell', ShellCommands),
('sm', StorageManagerCommands),
Expand Down
4 changes: 3 additions & 1 deletion etc/nova/policy.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"admin_or_owner": [["role:admin"], ["project_id:%(project_id)s"]],
"admin_or_projectadmin": [["role:projectadmin"], ["role:admin"]],
"default": [["rule:admin_or_owner"]],


Expand Down Expand Up @@ -48,7 +49,8 @@
"compute_extension:networks": [["rule:admin_api"]],
"compute_extension:networks:view": [],
"compute_extension:quotas:show": [],
"compute_extension:quotas:update": [["rule:admin_api"]],
"compute_extension:quotas:update_for_project": [["rule:admin_api"]],
"compute_extension:quotas:update_for_user": [["rule:admin_or_projectadmin"]],
"compute_extension:quota_classes": [],
"compute_extension:rescue": [],
"compute_extension:security_groups": [],
Expand Down
78 changes: 66 additions & 12 deletions nova/api/openstack/compute/contrib/quotas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,13 +25,15 @@
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):
Expand All @@ -57,51 +60,102 @@ def _format_quota_set(self, project_id, quota_set):

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))


Expand Down
15 changes: 13 additions & 2 deletions nova/api/openstack/compute/limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import math
import re
import time
import urlparse

import webob.dec
import webob.exc
Expand Down Expand Up @@ -85,8 +86,18 @@ def index(self, req):
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", [])

Expand Down
Loading

0 comments on commit 391f345

Please sign in to comment.