summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-manage51
-rw-r--r--etc/nova/policy.json4
-rw-r--r--nova/api/openstack/compute/contrib/quotas.py78
-rw-r--r--nova/api/openstack/compute/limits.py15
-rw-r--r--nova/db/api.py105
-rw-r--r--nova/db/sqlalchemy/api.py250
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/115_make_user_quotas_key_and_value.py94
-rw-r--r--nova/db/sqlalchemy/models.py15
-rw-r--r--nova/exception.py5
-rw-r--r--nova/quota.py219
-rw-r--r--nova/tests/policy.json3
-rw-r--r--nova/tests/test_quota.py607
12 files changed, 1289 insertions, 157 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index 1bd0691f4..e581c8fd6 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -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()
@@ -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."""
@@ -1319,6 +1369,7 @@ CATEGORIES = [
('logs', GetLogCommands),
('network', NetworkCommands),
('project', ProjectCommands),
+ ('quota', QuotaCommands),
('service', ServiceCommands),
('shell', ShellCommands),
('sm', StorageManagerCommands),
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index dc8ed5b8f..b0a20ee94 100644
--- a/etc/nova/policy.json
+++ b/etc/nova/policy.json
@@ -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"]],
@@ -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": [],
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", [])
diff --git a/nova/db/api.py b/nova/db/api.py
index 5e390d9f9..e6ebecbdf 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -563,6 +563,12 @@ def instance_create(context, values):
return IMPL.instance_create(context, values)
+def instance_data_get_for_user(context, user_id, project_id, session=None):
+ """Get (instance_count, total_cores, total_ram) for user."""
+ return IMPL.instance_data_get_for_user(context, user_id, project_id,
+ session=session)
+
+
def instance_data_get_for_project(context, project_id, session=None):
"""Get (instance_count, total_cores, total_ram) for project."""
return IMPL.instance_data_get_for_project(context, project_id,
@@ -944,6 +950,42 @@ def quota_destroy(context, project_id, resource):
###################
+def quota_create_for_user(context, user_id, project_id, resource, limit):
+ """Create a quota for the given user and project."""
+ return IMPL.quota_create_for_user(context, user_id,
+ project_id, resource, limit)
+
+
+def quota_get_for_user(context, user_id, project_id, resource):
+ """Retrieve a quota or raise if it does not exist."""
+ return IMPL.quota_get_for_user(context, user_id,
+ project_id, resource)
+
+
+def quota_get_all_by_user(context, user_id, project_id):
+ """Retrieve all quotas associated with a given user and project."""
+ return IMPL.quota_get_all_by_user(context, user_id, project_id)
+
+
+def quota_get_remaining(context, project_id):
+ """Retrieve the remaining quotas associated with a given project."""
+ return IMPL.quota_get_remaining(context, project_id)
+
+
+def quota_update_for_user(context, user_id, project_id, resource, limit):
+ """Update a quota or raise if it does not exist."""
+ return IMPL.quota_update_for_user(context, user_id,
+ project_id, resource, limit)
+
+
+def quota_destroy_for_user(context, user_id, project_id, resource):
+ """Destroy the quota or raise if it does not exist."""
+ return IMPL.quota_destroy_for_user(context, user_id, project_id, resource)
+
+
+###################
+
+
def quota_class_create(context, class_name, resource, limit):
"""Create a quota class for the given name and resource."""
return IMPL.quota_class_create(context, class_name, resource, limit)
@@ -977,16 +1019,21 @@ def quota_class_destroy_all_by_name(context, class_name):
###################
-def quota_usage_create(context, project_id, resource, in_use, reserved,
- until_refresh):
- """Create a quota usage for the given project and resource."""
- return IMPL.quota_usage_create(context, project_id, resource,
+def quota_usage_create(context, user_id, project_id, resource, in_use,
+ reserved, until_refresh):
+ """Create a quota usage for the given user and resource."""
+ return IMPL.quota_usage_create(context, user_id, project_id, resource,
in_use, reserved, until_refresh)
-def quota_usage_get(context, project_id, resource):
+def quota_usage_get(context, user_id, project_id, resource):
"""Retrieve a quota usage or raise if it does not exist."""
- return IMPL.quota_usage_get(context, project_id, resource)
+ return IMPL.quota_usage_get(context, user_id, project_id, resource)
+
+
+def quota_usage_get_all_by_user(context, user_id, project_id):
+ """Retrieve all usage associated with a given user."""
+ return IMPL.quota_usage_get_all_by_user(context, user_id, project_id)
def quota_usage_get_all_by_project(context, project_id):
@@ -994,25 +1041,25 @@ def quota_usage_get_all_by_project(context, project_id):
return IMPL.quota_usage_get_all_by_project(context, project_id)
-def quota_usage_update(context, class_name, resource, in_use, reserved,
- until_refresh):
+def quota_usage_update(context, user_id, project_id, resource, in_use,
+ reserved, until_refresh):
"""Update a quota usage or raise if it does not exist."""
- return IMPL.quota_usage_update(context, project_id, resource,
+ return IMPL.quota_usage_update(context, user_id, project_id, resource,
in_use, reserved, until_refresh)
-def quota_usage_destroy(context, project_id, resource):
+def quota_usage_destroy(context, user_id, project_id, resource):
"""Destroy the quota usage or raise if it does not exist."""
- return IMPL.quota_usage_destroy(context, project_id, resource)
+ return IMPL.quota_usage_destroy(context, user_id, project_id, resource)
###################
-def reservation_create(context, uuid, usage, project_id, resource, delta,
- expire):
- """Create a reservation for the given project and resource."""
- return IMPL.reservation_create(context, uuid, usage, project_id,
+def reservation_create(context, uuid, usage, user_id, project_id, resource,
+ delta, expire):
+ """Create a reservation for the given user and resource."""
+ return IMPL.reservation_create(context, uuid, usage, user_id, project_id,
resource, delta, expire)
@@ -1021,9 +1068,9 @@ def reservation_get(context, uuid):
return IMPL.reservation_get(context, uuid)
-def reservation_get_all_by_project(context, project_id):
- """Retrieve all reservations associated with a given project."""
- return IMPL.reservation_get_all_by_project(context, project_id)
+def reservation_get_all_by_user(context, user_id, project_id):
+ """Retrieve all reservations associated with a given user."""
+ return IMPL.reservation_get_all_by_user(context, user_id, project_id)
def reservation_destroy(context, uuid):
@@ -1051,6 +1098,11 @@ def reservation_rollback(context, reservations):
return IMPL.reservation_rollback(context, reservations)
+def quota_destroy_all_by_user(context, user_id, project_id):
+ """Destroy all quotas associated with a given user."""
+ return IMPL.quota_destroy_all_by_user(context, user_id, project_id)
+
+
def quota_destroy_all_by_project(context, project_id):
"""Destroy all quotas associated with a given project."""
return IMPL.quota_destroy_all_by_project(context, project_id)
@@ -1079,6 +1131,12 @@ def volume_create(context, values):
return IMPL.volume_create(context, values)
+def volume_data_get_for_user(context, user_id, project_id, session=None):
+ """Get (volume_count, gigabytes) for user."""
+ return IMPL.volume_data_get_for_user(context, user_id, project_id,
+ session=session)
+
+
def volume_data_get_for_project(context, project_id, session=None):
"""Get (volume_count, gigabytes) for project."""
return IMPL.volume_data_get_for_project(context, project_id,
@@ -1260,6 +1318,11 @@ def security_group_get_by_name(context, project_id, group_name):
return IMPL.security_group_get_by_name(context, project_id, group_name)
+def security_group_get_by_user(context, user_id, project_id):
+ """Get all security groups belonging to a user."""
+ return IMPL.security_group_get_by_user(context, user_id, project_id)
+
+
def security_group_get_by_project(context, project_id):
"""Get all security groups belonging to a project."""
return IMPL.security_group_get_by_project(context, project_id)
@@ -1295,6 +1358,12 @@ def security_group_destroy(context, security_group_id):
return IMPL.security_group_destroy(context, security_group_id)
+def security_group_count_by_user(context, user_id, project_id, session=None):
+ """Count number of security groups for a user in specific project."""
+ return IMPL.security_group_count_by_user(context, user_id, project_id,
+ session=session)
+
+
def security_group_count_by_project(context, project_id, session=None):
"""Count number of security groups in a project."""
return IMPL.security_group_count_by_project(context, project_id,
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 1f5b07643..2993aaaf3 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1408,20 +1408,33 @@ def instance_create(context, values):
return instance_ref
-@require_admin_context
-def instance_data_get_for_project(context, project_id, session=None):
+def _get_instance_data(context, project_id, user_id=None, session=None):
result = model_query(context,
func.count(models.Instance.id),
func.sum(models.Instance.vcpus),
func.sum(models.Instance.memory_mb),
read_deleted="no",
session=session).\
- filter_by(project_id=project_id).\
- first()
+ filter_by(project_id=project_id)
+ if user_id:
+ result = result.filter_by(user_id=user_id).first()
+ else:
+ result = result.first()
# NOTE(vish): convert None to 0
return (result[0] or 0, result[1] or 0, result[2] or 0)
+@require_admin_context
+def instance_data_get_for_project(context, project_id, session=None):
+ return _get_instance_data(context, project_id, session=session)
+
+
+@require_admin_context
+def instance_data_get_for_user(context, user_id, project_id, session=None):
+ return _get_instance_data(context, project_id, user_id=user_id,
+ session=session)
+
+
@require_context
def instance_destroy(context, instance_uuid, constraint=None):
session = get_session()
@@ -2342,10 +2355,11 @@ def quota_get(context, project_id, resource, session=None):
@require_context
-def quota_get_all_by_project(context, project_id):
+def quota_get_all_by_project(context, project_id, session=None):
authorize_project_context(context, project_id)
- rows = model_query(context, models.Quota, read_deleted="no").\
+ rows = model_query(context, models.Quota, session=session,
+ read_deleted="no").\
filter_by(project_id=project_id).\
all()
@@ -2387,6 +2401,97 @@ def quota_destroy(context, project_id, resource):
@require_context
+def quota_get_for_user(context, user_id, project_id, resource, session=None):
+ authorize_project_context(context, project_id)
+ result = model_query(context, models.UserQuota, session=session,
+ read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ filter_by(resource=resource).\
+ first()
+
+ if not result:
+ raise exception.UserQuotaNotFound(project_id=project_id,
+ user_id=user_id)
+
+ return result
+
+
+@require_context
+def quota_get_all_by_user(context, user_id, project_id):
+ authorize_project_context(context, project_id)
+
+ rows = model_query(context, models.UserQuota, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+ result = {'user_id': user_id, 'project_id': project_id}
+ for row in rows:
+ result[row.resource] = row.hard_limit
+
+ return result
+
+
+@require_context
+def quota_get_remaining(context, project_id):
+ authorize_project_context(context, project_id)
+
+ session = get_session()
+ with session.begin():
+ rows = model_query(context, models.UserQuota, session=session,
+ read_deleted="no").\
+ filter_by(project_id=project_id).\
+ all()
+
+ result = quota_get_all_by_project(context, project_id, session=session)
+
+ for row in rows:
+ if row.resource in result:
+ result[row.resource] -= row.hard_limit
+
+ result['project_id'] = project_id
+
+ return result
+
+
+@require_context
+def quota_create_for_user(context, user_id, project_id, resource, limit):
+ authorize_project_context(context, project_id)
+ quota_ref = models.UserQuota()
+ quota_ref.user_id = user_id
+ quota_ref.project_id = project_id
+ quota_ref.resource = resource
+ quota_ref.hard_limit = limit
+ quota_ref.save()
+ return quota_ref
+
+
+@require_context
+def quota_update_for_user(context, user_id, project_id, resource, limit):
+ authorize_project_context(context, project_id)
+ session = get_session()
+ with session.begin():
+ quota_ref = quota_get_for_user(context, user_id, project_id, resource,
+ session=session)
+ quota_ref.hard_limit = limit
+ quota_ref.save(session=session)
+
+
+@require_context
+def quota_destroy_for_user(context, user_id, project_id, resource):
+ authorize_project_context(context, project_id)
+ session = get_session()
+ with session.begin():
+ quota_ref = quota_get_for_user(context, user_id, project_id, resource,
+ session=session)
+ quota_ref.delete(session=session)
+
+
+###################
+
+
+@require_context
def quota_class_get(context, class_name, resource, session=None):
result = model_query(context, models.QuotaClass, session=session,
read_deleted="no").\
@@ -2461,9 +2566,10 @@ def quota_class_destroy_all_by_name(context, class_name):
@require_context
-def quota_usage_get(context, project_id, resource, session=None):
+def quota_usage_get(context, user_id, project_id, resource, session=None):
result = model_query(context, models.QuotaUsage, session=session,
read_deleted="no").\
+ filter_by(user_id=user_id).\
filter_by(project_id=project_id).\
filter_by(resource=resource).\
first()
@@ -2475,6 +2581,22 @@ def quota_usage_get(context, project_id, resource, session=None):
@require_context
+def quota_usage_get_all_by_user(context, user_id, project_id):
+ authorize_project_context(context, project_id)
+
+ rows = model_query(context, models.QuotaUsage, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+ result = {'user_id': user_id, 'project_id': project_id}
+ for row in rows:
+ result[row.resource] = dict(in_use=row.in_use, reserved=row.reserved)
+
+ return result
+
+
+@require_context
def quota_usage_get_all_by_project(context, project_id):
authorize_project_context(context, project_id)
@@ -2483,16 +2605,20 @@ def quota_usage_get_all_by_project(context, project_id):
all()
result = {'project_id': project_id}
+ in_use = 0
+ reserved = 0
for row in rows:
- result[row.resource] = dict(in_use=row.in_use, reserved=row.reserved)
+ result[row.resource] = dict(in_use=in_use + row.in_use,
+ reserved=reserved + row.reserved)
return result
@require_admin_context
-def quota_usage_create(context, project_id, resource, in_use, reserved,
- until_refresh, session=None):
+def quota_usage_create(context, user_id, project_id, resource, in_use,
+ reserved, until_refresh, session=None):
quota_usage_ref = models.QuotaUsage()
+ quota_usage_ref.user_id = user_id
quota_usage_ref.project_id = project_id
quota_usage_ref.resource = resource
quota_usage_ref.in_use = in_use
@@ -2504,11 +2630,11 @@ def quota_usage_create(context, project_id, resource, in_use, reserved,
@require_admin_context
-def quota_usage_update(context, project_id, resource, in_use, reserved,
- until_refresh, session=None):
+def quota_usage_update(context, user_id, project_id, resource, in_use,
+ reserved, until_refresh, session=None):
def do_update(session):
- quota_usage_ref = quota_usage_get(context, project_id, resource,
- session=session)
+ quota_usage_ref = quota_usage_get(context, user_id, project_id,
+ resource, session=session)
quota_usage_ref.in_use = in_use
quota_usage_ref.reserved = reserved
quota_usage_ref.until_refresh = until_refresh
@@ -2524,11 +2650,11 @@ def quota_usage_update(context, project_id, resource, in_use, reserved,
@require_admin_context
-def quota_usage_destroy(context, project_id, resource):
+def quota_usage_destroy(context, user_id, project_id, resource):
session = get_session()
with session.begin():
- quota_usage_ref = quota_usage_get(context, project_id, resource,
- session=session)
+ quota_usage_ref = quota_usage_get(context, user_id, project_id,
+ resource, session=session)
quota_usage_ref.delete(session=session)
@@ -2549,14 +2675,15 @@ def reservation_get(context, uuid, session=None):
@require_context
-def reservation_get_all_by_project(context, project_id):
+def reservation_get_all_by_user(context, user_id, project_id):
authorize_project_context(context, project_id)
rows = model_query(context, models.QuotaUsage, read_deleted="no").\
+ filter_by(user_id=user_id).\
filter_by(project_id=project_id).\
all()
- result = {'project_id': project_id}
+ result = {'user_id': user_id, 'project_id': project_id}
for row in rows:
result.setdefault(row.resource, {})
result[row.resource][row.uuid] = row.delta
@@ -2565,11 +2692,12 @@ def reservation_get_all_by_project(context, project_id):
@require_admin_context
-def reservation_create(context, uuid, usage, project_id, resource, delta,
- expire, session=None):
+def reservation_create(context, uuid, usage, user_id, project_id, resource,
+ delta, expire, session=None):
reservation_ref = models.Reservation()
reservation_ref.uuid = uuid
reservation_ref.usage_id = usage['id']
+ reservation_ref.user_id = user_id
reservation_ref.project_id = project_id
reservation_ref.resource = resource
reservation_ref.delta = delta
@@ -2599,6 +2727,7 @@ def _get_quota_usages(context, session):
rows = model_query(context, models.QuotaUsage,
read_deleted="no",
session=session).\
+ filter_by(user_id=context.user_id).\
filter_by(project_id=context.project_id).\
with_lockmode('update').\
all()
@@ -2623,6 +2752,7 @@ def quota_reserve(context, resources, quotas, deltas, expire,
refresh = False
if resource not in usages:
usages[resource] = quota_usage_create(elevated,
+ context.user_id,
context.project_id,
resource,
0, 0,
@@ -2646,11 +2776,13 @@ def quota_reserve(context, resources, quotas, deltas, expire,
# Grab the sync routine
sync = resources[resource].sync
- updates = sync(elevated, context.project_id, session)
+ updates = sync(elevated, context.user_id,
+ context.project_id, session)
for res, in_use in updates.items():
# Make sure we have a destination for the usage!
if res not in usages:
usages[res] = quota_usage_create(elevated,
+ context.user_id,
context.project_id,
res,
0, 0,
@@ -2701,6 +2833,7 @@ def quota_reserve(context, resources, quotas, deltas, expire,
reservation = reservation_create(elevated,
str(utils.gen_uuid()),
usages[resource],
+ context.user_id,
context.project_id,
resource, delta, expire,
session=session)
@@ -2785,6 +2918,38 @@ def reservation_rollback(context, reservations):
@require_admin_context
+def quota_destroy_all_by_user(context, user_id, project_id):
+ session = get_session()
+ with session.begin():
+ quotas = model_query(context, models.UserQuota, session=session,
+ read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+ for quota_ref in quotas:
+ quota_ref.delete(session=session)
+
+ quota_usages = model_query(context, models.QuotaUsage,
+ session=session, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+ for quota_usage_ref in quota_usages:
+ quota_usage_ref.delete(session=session)
+
+ reservations = model_query(context, models.Reservation,
+ session=session, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+ for reservation_ref in reservations:
+ reservation_ref.delete(session=session)
+
+
+@require_admin_context
def quota_destroy_all_by_project(context, project_id):
session = get_session()
with session.begin():
@@ -2889,21 +3054,34 @@ def volume_create(context, values):
return volume_ref
-@require_admin_context
-def volume_data_get_for_project(context, project_id, session=None):
+def _get_volume_data(context, project_id, user_id=None, session=None):
result = model_query(context,
func.count(models.Volume.id),
func.sum(models.Volume.size),
read_deleted="no",
session=session).\
- filter_by(project_id=project_id).\
- first()
+ filter_by(project_id=project_id)
+
+ if user_id:
+ result = result.filter_by(user_id=user_id).first()
+ else:
+ result = result.first()
- # NOTE(vish): convert None to 0
return (result[0] or 0, result[1] or 0)
@require_admin_context
+def volume_data_get_for_user(context, user_id, project_id, session=None):
+ return _get_volume_data(context, project_id, user_id=user_id,
+ session=session)
+
+
+@require_admin_context
+def volume_data_get_for_project(context, project_id, session=None):
+ return _get_volume_data(context, project_id, session=session)
+
+
+@require_admin_context
def volume_destroy(context, volume_id):
session = get_session()
with session.begin():
@@ -3412,6 +3590,14 @@ def security_group_get_by_name(context, project_id, group_name,
@require_context
+def security_group_get_by_user(context, user_id, project_id):
+ return _security_group_get_query(context, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+
+@require_context
def security_group_get_by_project(context, project_id):
return _security_group_get_query(context, read_deleted="no").\
filter_by(project_id=project_id).\
@@ -3507,6 +3693,16 @@ def security_group_destroy(context, security_group_id):
@require_context
+def security_group_count_by_user(context, user_id, project_id, session=None):
+ authorize_project_context(context, project_id)
+ return model_query(context, models.SecurityGroup, read_deleted="no",
+ session=session).\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ count()
+
+
+@require_context
def security_group_count_by_project(context, project_id, session=None):
authorize_project_context(context, project_id)
return model_query(context, models.SecurityGroup, read_deleted="no",
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/115_make_user_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/115_make_user_quotas_key_and_value.py
new file mode 100644
index 000000000..447307952
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/115_make_user_quotas_key_and_value.py
@@ -0,0 +1,94 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova.openstack.common import log as logging
+from sqlalchemy import Boolean, Column, DateTime, Integer
+from sqlalchemy import MetaData, String, Table
+
+LOG = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ # Add 'user_id' column to quota_usages table.
+ quota_usages = Table('quota_usages', meta, autoload=True)
+ user_id = Column('user_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False))
+ quota_usages.create_column(user_id)
+
+ # Add 'user_id' column to reservations table.
+ reservations = Table('reservations', meta, autoload=True)
+ user_id = Column('user_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False))
+ reservations.create_column(user_id)
+
+ # New table.
+ user_quotas = Table('user_quotas', meta,
+ Column('id', Integer(), primary_key=True),
+ Column('created_at', DateTime(timezone=False)),
+ Column('updated_at', DateTime(timezone=False)),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(), default=False),
+ Column('user_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False)),
+ Column('project_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False)),
+ Column('resource',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False),
+ nullable=False),
+ Column('hard_limit', Integer(), nullable=True),
+ mysql_engine='InnoDB',
+ mysql_charset='utf8',
+ )
+
+ try:
+ user_quotas.create()
+ except Exception:
+ LOG.error(_("Table |%s| not created!"), repr(user_quotas))
+ raise
+
+
+def downgrade(migrate_engine):
+ # Operations to reverse the above upgrade go here.
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ quota_usages = Table('quota_usages', meta, autoload=True)
+ quota_usages.drop_column('user_id')
+
+ reservations = Table('reservations', meta, autoload=True)
+ reservations.drop_column('user_id')
+
+ user_quotas = Table('user_quotas', meta, autoload=True)
+ try:
+ user_quotas.drop()
+ except Exception:
+ LOG.error(_("user_quotas table not dropped"))
+ raise
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 7d4435a7a..fa3ebe8f0 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -428,6 +428,19 @@ class Quota(BASE, NovaBase):
hard_limit = Column(Integer, nullable=True)
+class UserQuota(BASE, NovaBase):
+ """Represents a single quota override for a user."""
+
+ __tablename__ = 'user_quotas'
+ id = Column(Integer, primary_key=True)
+
+ user_id = Column(String(255), index=True)
+ project_id = Column(String(255), index=True)
+
+ resource = Column(String(255))
+ hard_limit = Column(Integer, nullable=True)
+
+
class QuotaClass(BASE, NovaBase):
"""Represents a single quota override for a quota class.
@@ -451,6 +464,7 @@ class QuotaUsage(BASE, NovaBase):
__tablename__ = 'quota_usages'
id = Column(Integer, primary_key=True)
+ user_id = Column(String(255), index=True)
project_id = Column(String(255), index=True)
resource = Column(String(255))
@@ -473,6 +487,7 @@ class Reservation(BASE, NovaBase):
usage_id = Column(Integer, ForeignKey('quota_usages.id'), nullable=False)
+ user_id = Column(String(255), index=True)
project_id = Column(String(255), index=True)
resource = Column(String(255))
diff --git a/nova/exception.py b/nova/exception.py
index e4e738e85..9c579322b 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -721,6 +721,11 @@ class QuotaResourceUnknown(QuotaNotFound):
message = _("Unknown quota resources %(unknown)s.")
+class UserQuotaNotFound(QuotaNotFound):
+ message = _("Quota for user %(user_id)s in project %(project_id)s "
+ "could not be found.")
+
+
class ProjectQuotaNotFound(QuotaNotFound):
message = _("Quota for project %(project_id)s could not be found.")
diff --git a/nova/quota.py b/nova/quota.py
index d3ba0aa02..44e3c593d 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -101,6 +101,11 @@ class DbQuotaDriver(object):
return db.quota_get(context, project_id, resource)
+ def get_by_user(self, context, user_id, project_id, resource):
+ """Get a specific quota by user."""
+
+ return db.quota_get_for_user(context, user_id, project_id, resource)
+
def get_by_class(self, context, quota_class, resource):
"""Get a specific quota by quota class."""
@@ -143,16 +148,16 @@ class DbQuotaDriver(object):
return quotas
- def get_project_quotas(self, context, resources, project_id,
- quota_class=None, defaults=True,
- usages=True):
+ def _process_quotas(self, context, resources, project_id, quotas,
+ quota_class=None, defaults=True, usages=None):
"""
- Given a list of resources, retrieve the quotas for the given
- project.
+ Given a list of resources, process the quotas for the given
+ quotas and usages.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param project_id: The ID of the project to return quotas for.
+ :param quotas: The quotas dictionary need to be processed.
:param quota_class: If project_id != context.project_id, the
quota class cannot be determined. This
parameter allows it to be specified. It
@@ -162,16 +167,11 @@ class DbQuotaDriver(object):
default value, if there is no value from the
quota class) will be reported if there is no
specific value for the resource.
- :param usages: If True, the current in_use and reserved counts
+ :param usages: If not None, the current in_use and reserved counts
will also be returned.
"""
- quotas = {}
- project_quotas = db.quota_get_all_by_project(context, project_id)
- if usages:
- project_usages = db.quota_usage_get_all_by_project(context,
- project_id)
-
+ modified_quotas = {}
# Get the quotas for the appropriate class. If the project ID
# matches the one in the context, we use the quota_class from
# the context, otherwise, we use the provided quota_class (if
@@ -185,11 +185,11 @@ class DbQuotaDriver(object):
for resource in resources.values():
# Omit default/quota class values
- if not defaults and resource.name not in project_quotas:
+ if not defaults and resource.name not in quotas:
continue
- quotas[resource.name] = dict(
- limit=project_quotas.get(resource.name, class_quotas.get(
+ modified_quotas[resource.name] = dict(
+ limit=quotas.get(resource.name, class_quotas.get(
resource.name, resource.default)),
)
@@ -197,13 +197,96 @@ class DbQuotaDriver(object):
# internal consumer of this interface wants to access the
# usages directly from inside a transaction.
if usages:
- usage = project_usages.get(resource.name, {})
- quotas[resource.name].update(
+ usage = usages.get(resource.name, {})
+ modified_quotas[resource.name].update(
in_use=usage.get('in_use', 0),
reserved=usage.get('reserved', 0),
)
- return quotas
+ return modified_quotas
+
+ def get_project_quotas(self, context, resources, project_id,
+ quota_class=None, defaults=True,
+ usages=True):
+ """
+ Given a list of resources, retrieve the quotas for the given
+ project.
+
+ :param context: The request context, for access checks.
+ :param resources: A dictionary of the registered resources.
+ :param project_id: The ID of the project to return quotas for.
+ :param quota_class: If project_id != context.project_id, the
+ quota class cannot be determined. This
+ parameter allows it to be specified. It
+ will be ignored if project_id ==
+ context.project_id.
+ :param defaults: If True, the quota class value (or the
+ default value, if there is no value from the
+ quota class) will be reported if there is no
+ specific value for the resource.
+ :param usages: If True, the current in_use and reserved counts
+ will also be returned.
+ """
+
+ project_quotas = db.quota_get_all_by_project(context, project_id)
+
+ project_usages = None
+ if usages:
+ project_usages = db.quota_usage_get_all_by_project(context,
+ project_id)
+
+ return self._process_quotas(context, resources,
+ project_id, project_quotas,
+ quota_class=quota_class,
+ defaults=defaults,
+ usages=project_usages)
+
+ def get_user_quotas(self, context, resources, user_id, project_id,
+ quota_class=None, defaults=True,
+ usages=True):
+ """
+ Given a list of resources, retrieve the quotas for the given
+ user.
+
+ :param context: The request context, for access checks.
+ :param resources: A dictionary of the registered resources.
+ :param project_id: The ID of the project to return quotas for.
+ :param user_id: The ID of the user to return quotas for.
+ :param quota_class: If project_id != context.project_id, the
+ quota class cannot be determined. This
+ parameter allows it to be specified. It
+ will be ignored if project_id ==
+ context.project_id.
+ :param defaults: If True, the quota class value (or the
+ default value, if there is no value from the
+ quota class) will be reported if there is no
+ specific value for the resource.
+ :param usages: If True, the current in_use and reserved counts
+ will also be returned.
+ """
+
+ user_quotas = db.quota_get_all_by_user(context, user_id, project_id)
+
+ user_usages = None
+ if usages:
+ user_usages = db.quota_usage_get_all_by_user(context,
+ user_id,
+ project_id)
+
+ return self._process_quotas(context, resources,
+ project_id, user_quotas,
+ quota_class=quota_class,
+ defaults=defaults,
+ usages=user_usages)
+
+ def get_remaining_quotas(self, context, project_id, resources):
+ """Get the remaining quotas for the given project."""
+ defaults = self.get_defaults(context, resources)
+ quotas = db.quota_get_remaining(context, project_id)
+ for key in defaults.keys():
+ if key in quotas:
+ defaults[key] = quotas[key]
+ return defaults
def _get_quotas(self, context, resources, keys, has_sync):
"""
@@ -235,9 +318,10 @@ class DbQuotaDriver(object):
raise exception.QuotaResourceUnknown(unknown=sorted(unknown))
# Grab and return the quotas (without usages)
- quotas = self.get_project_quotas(context, sub_resources,
- context.project_id,
- context.quota_class, usages=False)
+ quotas = self.get_user_quotas(context, sub_resources,
+ context.user_id,
+ context.project_id,
+ context.quota_class, usages=False)
return dict((k, v['limit']) for k, v in quotas.items())
@@ -368,6 +452,18 @@ class DbQuotaDriver(object):
db.quota_destroy_all_by_project(context, project_id)
+ def destroy_all_by_user(self, context, user_id, project_id):
+ """
+ Destroy all quotas, usages, and reservations associated with a
+ user.
+
+ :param context: The request context, for access checks.
+ :param user_id: The ID of the user being deleted.
+ :param project_id: The ID of the project being deleted.
+ """
+
+ db.quota_destroy_all_by_user(context, user_id, project_id)
+
def expire(self, context):
"""Expire reservations.
@@ -566,6 +662,11 @@ class QuotaEngine(object):
return self._driver.get_by_project(context, project_id, resource)
+ def get_by_user(self, context, user_id, project_id, resource):
+ """Get a specific quota by user."""
+
+ return self._driver.get_by_user(context, user_id, project_id, resource)
+
def get_by_class(self, context, quota_class, resource):
"""Get a specific quota by quota class."""
@@ -611,10 +712,46 @@ class QuotaEngine(object):
"""
return self._driver.get_project_quotas(context, self._resources,
- project_id,
- quota_class=quota_class,
- defaults=defaults,
- usages=usages)
+ project_id,
+ quota_class=quota_class,
+ defaults=defaults,
+ usages=usages)
+
+ def get_user_quotas(self, context, user_id, project_id,
+ quota_class=None, defaults=True,
+ usages=True):
+ """Retrieve the quotas for the given user.
+
+ :param context: The request context, for access checks.
+ :param user_id: The ID of the user to return quotas for.
+ :param project_id: The ID of the project to return quotas for.
+ :param quota_class: If project_id != context.project_id, the
+ quota class cannot be determined. This
+ parameter allows it to be specified.
+ :param defaults: If True, the quota class value (or the
+ default value, if there is no value from the
+ quota class) will be reported if there is no
+ specific value for the resource.
+ :param usages: If True, the current in_use and reserved counts
+ will also be returned.
+ """
+
+ return self._driver.get_user_quotas(context, self._resources,
+ user_id,
+ project_id,
+ quota_class=quota_class,
+ defaults=defaults,
+ usages=usages)
+
+ def get_remaining_quotas(self, context, project_id):
+ """Retrieve the remaining quotas for the given project.
+
+ :param context: The request context, for access checks.
+ :param project_id: The ID of the project to return quotas for.
+ """
+
+ return self._driver.get_remaining_quotas(context, project_id,
+ self._resources)
def count(self, context, resource, *args, **kwargs):
"""Count a resource.
@@ -745,6 +882,18 @@ class QuotaEngine(object):
self._driver.destroy_all_by_project(context, project_id)
+ def destroy_all_by_user(self, context, user_id, project_id):
+ """
+ Destroy all quotas, usages, and reservations associated with a
+ user.
+
+ :param context: The request context, for access checks.
+ :param user_id: The ID of the user being deleted.
+ :param project_id: The ID of the project being deleted.
+ """
+
+ self._driver.destroy_all_by_user(context, user_id, project_id)
+
def expire(self, context):
"""Expire reservations.
@@ -761,26 +910,26 @@ class QuotaEngine(object):
return sorted(self._resources.keys())
-def _sync_instances(context, project_id, session):
+def _sync_instances(context, user_id, project_id, session):
return dict(zip(('instances', 'cores', 'ram'),
- db.instance_data_get_for_project(
- context, project_id, session=session)))
+ db.instance_data_get_for_user(
+ context, user_id, project_id, session=session)))
-def _sync_volumes(context, project_id, session):
+def _sync_volumes(context, user_id, project_id, session):
return dict(zip(('volumes', 'gigabytes'),
- db.volume_data_get_for_project(
- context, project_id, session=session)))
+ db.volume_data_get_for_user(
+ context, user_id, project_id, session=session)))
-def _sync_floating_ips(context, project_id, session):
+def _sync_floating_ips(context, user_id, project_id, session):
return dict(floating_ips=db.floating_ip_count_by_project(
context, project_id, session=session))
-def _sync_security_groups(context, project_id, session):
- return dict(security_groups=db.security_group_count_by_project(
- context, project_id, session=session))
+def _sync_security_groups(context, user_id, project_id, session):
+ return dict(security_groups=db.security_group_count_by_user(
+ context, user_id, project_id, session=session))
QUOTAS = QuotaEngine()
diff --git a/nova/tests/policy.json b/nova/tests/policy.json
index 3e4d166a3..4836a33b4 100644
--- a/nova/tests/policy.json
+++ b/nova/tests/policy.json
@@ -105,7 +105,8 @@
"compute_extension:networks": [],
"compute_extension:networks:view": [],
"compute_extension:quotas:show": [],
- "compute_extension:quotas:update": [],
+ "compute_extension:quotas:update_for_project": [],
+ "compute_extension:quotas:update_for_user": [],
"compute_extension:quota_classes": [],
"compute_extension:rescue": [],
"compute_extension:security_groups": [],
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index 794c578d6..1fcff0e4a 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -243,8 +243,10 @@ class FakeContext(object):
class FakeDriver(object):
- def __init__(self, by_project=None, by_class=None, reservations=None):
+ def __init__(self, by_user=None, by_project=None, by_class=None,
+ reservations=None):
self.called = []
+ self.by_user = by_user or {}
self.by_project = by_project or {}
self.by_class = by_class or {}
self.reservations = reservations or []
@@ -256,6 +258,15 @@ class FakeDriver(object):
except KeyError:
raise exception.ProjectQuotaNotFound(project_id=project_id)
+ def get_by_user(self, context, user_id, project_id, resource):
+ self.called.append(('get_by_user', context, user_id, project_id,
+ resource))
+ try:
+ return self.by_user[user_id][resource]
+ except KeyError:
+ raise exception.UserQuotaNotFound(project_id=project_id,
+ user_id=user_id)
+
def get_by_class(self, context, quota_class, resource):
self.called.append(('get_by_class', context, quota_class, resource))
try:
@@ -279,6 +290,13 @@ class FakeDriver(object):
project_id, quota_class, defaults, usages))
return resources
+ def get_user_quotas(self, context, resources, user_id, project_id,
+ quota_class=None, defaults=True, usages=True):
+ self.called.append(('get_user_quotas', context, resources,
+ user_id, project_id, quota_class, defaults,
+ usages))
+ return resources
+
def limit_check(self, context, resources, values):
self.called.append(('limit_check', context, resources, values))
@@ -295,6 +313,10 @@ class FakeDriver(object):
def destroy_all_by_project(self, context, project_id):
self.called.append(('destroy_all_by_project', context, project_id))
+ def destroy_all_by_user(self, context, user_id, project_id,):
+ self.called.append(('destroy_all_by_user', context, user_id,
+ project_id))
+
def expire(self, context):
self.called.append(('expire', context))
@@ -481,6 +503,19 @@ class QuotaEngineTestCase(test.TestCase):
])
self.assertEqual(result, 42)
+ def test_get_by_user(self):
+ context = FakeContext('test_project', 'test_class')
+ driver = FakeDriver(by_user=dict(
+ fake_user=dict(test_resource=42)))
+ quota_obj = quota.QuotaEngine(quota_driver_class=driver)
+ result = quota_obj.get_by_user(context, 'fake_user',
+ 'test_project', 'test_resource')
+
+ self.assertEqual(driver.called, [
+ ('get_by_user', context, 'fake_user', 'test_project',
+ 'test_resource'), ])
+ self.assertEqual(result, 42)
+
def test_get_by_class(self):
context = FakeContext('test_project', 'test_class')
driver = FakeDriver(by_class=dict(
@@ -551,6 +586,27 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(result1, quota_obj._resources)
self.assertEqual(result2, quota_obj._resources)
+ def test_get_user_quotas(self):
+ context = FakeContext(None, None)
+ driver = FakeDriver()
+ quota_obj = self._make_quota_obj(driver)
+ result1 = quota_obj.get_user_quotas(context, 'fake_user',
+ 'test_project')
+ result2 = quota_obj.get_user_quotas(context, 'fake_user',
+ 'test_project',
+ quota_class='test_class',
+ defaults=False,
+ usages=False)
+
+ self.assertEqual(driver.called, [
+ ('get_user_quotas', context, quota_obj._resources,
+ 'fake_user', 'test_project', None, True, True),
+ ('get_user_quotas', context, quota_obj._resources,
+ 'fake_user', 'test_project', 'test_class', False, False),
+ ])
+ self.assertEqual(result1, quota_obj._resources)
+ self.assertEqual(result2, quota_obj._resources)
+
def test_count_no_resource(self):
context = FakeContext(None, None)
driver = FakeDriver()
@@ -662,6 +718,16 @@ class QuotaEngineTestCase(test.TestCase):
('destroy_all_by_project', context, 'test_project'),
])
+ def test_destroy_all_by_user(self):
+ context = FakeContext(None, None)
+ driver = FakeDriver()
+ quota_obj = self._make_quota_obj(driver)
+ quota_obj.destroy_all_by_user(context, 'fake_user', 'test_project')
+
+ self.assertEqual(driver.called, [
+ ('destroy_all_by_user', context, 'fake_user', 'test_project'),
+ ])
+
def test_expire(self):
context = FakeContext(None, None)
driver = FakeDriver()
@@ -1146,8 +1212,378 @@ class DbQuotaDriverTestCase(test.TestCase):
self.stubs.Set(self.driver, 'get_project_quotas',
fake_get_project_quotas)
+ def _stub_get_by_user(self):
+ def fake_qgabp(context, user_id, project_id):
+ self.calls.append('quota_get_all_by_user')
+ self.assertEqual(project_id, 'test_project')
+ self.assertEqual(user_id, 'fake_user')
+ return dict(
+ cores=10,
+ gigabytes=50,
+ injected_files=2,
+ injected_file_path_bytes=127,
+ )
+
+ def fake_qugabp(context, user_id, project_id):
+ self.calls.append('quota_usage_get_all_by_user')
+ self.assertEqual(project_id, 'test_project')
+ self.assertEqual(user_id, 'fake_user')
+ return dict(
+ instances=dict(in_use=2, reserved=2),
+ cores=dict(in_use=4, reserved=4),
+ ram=dict(in_use=10 * 1024, reserved=0),
+ volumes=dict(in_use=2, reserved=0),
+ gigabytes=dict(in_use=10, reserved=0),
+ floating_ips=dict(in_use=2, reserved=0),
+ metadata_items=dict(in_use=0, reserved=0),
+ injected_files=dict(in_use=0, reserved=0),
+ injected_file_content_bytes=dict(in_use=0, reserved=0),
+ injected_file_path_bytes=dict(in_use=0, reserved=0),
+ )
+
+ self.stubs.Set(db, 'quota_get_all_by_user', fake_qgabp)
+ self.stubs.Set(db, 'quota_usage_get_all_by_user', fake_qugabp)
+
+ self._stub_quota_class_get_all_by_name()
+
+ def test_get_user_quotas(self):
+ self._stub_get_by_user()
+ result = self.driver.get_user_quotas(
+ FakeContext('test_project', 'test_class'),
+ quota.QUOTAS._resources, 'fake_user', 'test_project')
+
+ self.assertEqual(self.calls, [
+ 'quota_get_all_by_user',
+ 'quota_usage_get_all_by_user',
+ 'quota_class_get_all_by_name',
+ ])
+ self.assertEqual(result, dict(
+ instances=dict(
+ limit=5,
+ in_use=2,
+ reserved=2,
+ ),
+ cores=dict(
+ limit=10,
+ in_use=4,
+ reserved=4,
+ ),
+ ram=dict(
+ limit=25 * 1024,
+ in_use=10 * 1024,
+ reserved=0,
+ ),
+ volumes=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ gigabytes=dict(
+ limit=50,
+ in_use=10,
+ reserved=0,
+ ),
+ floating_ips=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ metadata_items=dict(
+ limit=64,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_files=dict(
+ limit=2,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_content_bytes=dict(
+ limit=5 * 1024,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_path_bytes=dict(
+ limit=127,
+ in_use=0,
+ reserved=0,
+ ),
+ security_groups=dict(
+ limit=10,
+ in_use=0,
+ reserved=0,
+ ),
+ security_group_rules=dict(
+ limit=20,
+ in_use=0,
+ reserved=0,
+ ),
+ key_pairs=dict(
+ limit=100,
+ in_use=0,
+ reserved=0,
+ ),
+ ))
+
+ def test_get_user_quotas_alt_context_no_class(self):
+ self._stub_get_by_user()
+ result = self.driver.get_user_quotas(
+ FakeContext('other_project', 'other_class'),
+ quota.QUOTAS._resources, 'fake_user', 'test_project')
+
+ self.assertEqual(self.calls, [
+ 'quota_get_all_by_user',
+ 'quota_usage_get_all_by_user',
+ ])
+ self.assertEqual(result, dict(
+ instances=dict(
+ limit=10,
+ in_use=2,
+ reserved=2,
+ ),
+ cores=dict(
+ limit=10,
+ in_use=4,
+ reserved=4,
+ ),
+ ram=dict(
+ limit=50 * 1024,
+ in_use=10 * 1024,
+ reserved=0,
+ ),
+ volumes=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ gigabytes=dict(
+ limit=50,
+ in_use=10,
+ reserved=0,
+ ),
+ floating_ips=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ metadata_items=dict(
+ limit=128,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_files=dict(
+ limit=2,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_content_bytes=dict(
+ limit=10 * 1024,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_path_bytes=dict(
+ limit=127,
+ in_use=0,
+ reserved=0,
+ ),
+ security_groups=dict(
+ limit=10,
+ in_use=0,
+ reserved=0,
+ ),
+ security_group_rules=dict(
+ limit=20,
+ in_use=0,
+ reserved=0,
+ ),
+ key_pairs=dict(
+ limit=100,
+ in_use=0,
+ reserved=0,
+ ),
+ ))
+
+ def test_get_user_quotas_alt_context_with_class(self):
+ self._stub_get_by_user()
+ result = self.driver.get_user_quotas(
+ FakeContext('other_project', 'other_class'),
+ quota.QUOTAS._resources, 'fake_user', 'test_project',
+ quota_class='test_class')
+
+ self.assertEqual(self.calls, [
+ 'quota_get_all_by_user',
+ 'quota_usage_get_all_by_user',
+ 'quota_class_get_all_by_name',
+ ])
+ self.assertEqual(result, dict(
+ instances=dict(
+ limit=5,
+ in_use=2,
+ reserved=2,
+ ),
+ cores=dict(
+ limit=10,
+ in_use=4,
+ reserved=4,
+ ),
+ ram=dict(
+ limit=25 * 1024,
+ in_use=10 * 1024,
+ reserved=0,
+ ),
+ volumes=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ gigabytes=dict(
+ limit=50,
+ in_use=10,
+ reserved=0,
+ ),
+ floating_ips=dict(
+ limit=10,
+ in_use=2,
+ reserved=0,
+ ),
+ metadata_items=dict(
+ limit=64,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_files=dict(
+ limit=2,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_content_bytes=dict(
+ limit=5 * 1024,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_path_bytes=dict(
+ limit=127,
+ in_use=0,
+ reserved=0,
+ ),
+ security_groups=dict(
+ limit=10,
+ in_use=0,
+ reserved=0,
+ ),
+ security_group_rules=dict(
+ limit=20,
+ in_use=0,
+ reserved=0,
+ ),
+ key_pairs=dict(
+ limit=100,
+ in_use=0,
+ reserved=0,
+ ),
+ ))
+
+ def test_get_user_quotas_no_defaults(self):
+ self._stub_get_by_user()
+ result = self.driver.get_user_quotas(
+ FakeContext('test_project', 'test_class'),
+ quota.QUOTAS._resources, 'fake_user', 'test_project',
+ defaults=False)
+
+ self.assertEqual(self.calls, [
+ 'quota_get_all_by_user',
+ 'quota_usage_get_all_by_user',
+ 'quota_class_get_all_by_name',
+ ])
+ self.assertEqual(result, dict(
+ cores=dict(
+ limit=10,
+ in_use=4,
+ reserved=4,
+ ),
+ gigabytes=dict(
+ limit=50,
+ in_use=10,
+ reserved=0,
+ ),
+ injected_files=dict(
+ limit=2,
+ in_use=0,
+ reserved=0,
+ ),
+ injected_file_path_bytes=dict(
+ limit=127,
+ in_use=0,
+ reserved=0,
+ ),
+ ))
+
+ def test_get_user_quotas_no_usages(self):
+ self._stub_get_by_user()
+ result = self.driver.get_user_quotas(
+ FakeContext('test_project', 'test_class'),
+ quota.QUOTAS._resources, 'fake_user', 'test_project',
+ usages=False)
+
+ self.assertEqual(self.calls, [
+ 'quota_get_all_by_user',
+ 'quota_class_get_all_by_name',
+ ])
+ self.assertEqual(result, dict(
+ instances=dict(
+ limit=5,
+ ),
+ cores=dict(
+ limit=10,
+ ),
+ ram=dict(
+ limit=25 * 1024,
+ ),
+ volumes=dict(
+ limit=10,
+ ),
+ gigabytes=dict(
+ limit=50,
+ ),
+ floating_ips=dict(
+ limit=10,
+ ),
+ metadata_items=dict(
+ limit=64,
+ ),
+ injected_files=dict(
+ limit=2,
+ ),
+ injected_file_content_bytes=dict(
+ limit=5 * 1024,
+ ),
+ injected_file_path_bytes=dict(
+ limit=127,
+ ),
+ security_groups=dict(
+ limit=10,
+ ),
+ security_group_rules=dict(
+ limit=20,
+ ),
+ key_pairs=dict(
+ limit=100,
+ ),
+ ))
+
+ def _stub_get_user_quotas(self):
+ def fake_get_user_quotas(context, resources, user_id, project_id,
+ quota_class=None, defaults=True,
+ usages=True):
+ self.calls.append('get_user_quotas')
+ return dict((k, dict(limit=v.default))
+ for k, v in resources.items())
+
+ self.stubs.Set(self.driver, 'get_user_quotas',
+ fake_get_user_quotas)
+
def test_get_quotas_has_sync_unknown(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas,
None, quota.QUOTAS._resources,
@@ -1155,7 +1591,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertEqual(self.calls, [])
def test_get_quotas_no_sync_unknown(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas,
None, quota.QUOTAS._resources,
@@ -1163,7 +1599,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertEqual(self.calls, [])
def test_get_quotas_has_sync_no_sync_resource(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas,
None, quota.QUOTAS._resources,
@@ -1171,7 +1607,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertEqual(self.calls, [])
def test_get_quotas_no_sync_has_sync_resource(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas,
None, quota.QUOTAS._resources,
@@ -1179,7 +1615,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertEqual(self.calls, [])
def test_get_quotas_has_sync(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
result = self.driver._get_quotas(FakeContext('test_project',
'test_class'),
quota.QUOTAS._resources,
@@ -1188,7 +1624,7 @@ class DbQuotaDriverTestCase(test.TestCase):
'floating_ips', 'security_groups'],
True)
- self.assertEqual(self.calls, ['get_project_quotas'])
+ self.assertEqual(self.calls, ['get_user_quotas'])
self.assertEqual(result, dict(
instances=10,
cores=20,
@@ -1200,7 +1636,7 @@ class DbQuotaDriverTestCase(test.TestCase):
))
def test_get_quotas_no_sync(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
result = self.driver._get_quotas(FakeContext('test_project',
'test_class'),
quota.QUOTAS._resources,
@@ -1209,7 +1645,7 @@ class DbQuotaDriverTestCase(test.TestCase):
'injected_file_path_bytes',
'security_group_rules'], False)
- self.assertEqual(self.calls, ['get_project_quotas'])
+ self.assertEqual(self.calls, ['get_user_quotas'])
self.assertEqual(result, dict(
metadata_items=128,
injected_files=5,
@@ -1219,7 +1655,7 @@ class DbQuotaDriverTestCase(test.TestCase):
))
def test_limit_check_under(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.InvalidQuotaValue,
self.driver.limit_check,
FakeContext('test_project', 'test_class'),
@@ -1227,7 +1663,7 @@ class DbQuotaDriverTestCase(test.TestCase):
dict(metadata_items=-1))
def test_limit_check_over(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.assertRaises(exception.OverQuota,
self.driver.limit_check,
FakeContext('test_project', 'test_class'),
@@ -1236,13 +1672,13 @@ class DbQuotaDriverTestCase(test.TestCase):
def test_limit_check_unlimited(self):
self.flags(quota_metadata_items=-1)
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.driver.limit_check(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources,
dict(metadata_items=32767))
def test_limit_check(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self.driver.limit_check(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources,
dict(metadata_items=128))
@@ -1256,7 +1692,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.stubs.Set(db, 'quota_reserve', fake_quota_reserve)
def test_reserve_bad_expire(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
self.assertRaises(exception.InvalidReservationExpiration,
self.driver.reserve,
@@ -1266,7 +1702,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertEqual(self.calls, [])
def test_reserve_default_expire(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources,
@@ -1274,13 +1710,13 @@ class DbQuotaDriverTestCase(test.TestCase):
expire = timeutils.utcnow() + datetime.timedelta(seconds=86400)
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 0, 0),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
def test_reserve_int_expire(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources,
@@ -1288,13 +1724,13 @@ class DbQuotaDriverTestCase(test.TestCase):
expire = timeutils.utcnow() + datetime.timedelta(seconds=3600)
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 0, 0),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
def test_reserve_timedelta_expire(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
expire_delta = datetime.timedelta(seconds=60)
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
@@ -1303,13 +1739,13 @@ class DbQuotaDriverTestCase(test.TestCase):
expire = timeutils.utcnow() + expire_delta
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 0, 0),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
def test_reserve_datetime_expire(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
@@ -1317,13 +1753,13 @@ class DbQuotaDriverTestCase(test.TestCase):
dict(instances=2), expire=expire)
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 0, 0),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
def test_reserve_until_refresh(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
self.flags(until_refresh=500)
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
@@ -1332,13 +1768,13 @@ class DbQuotaDriverTestCase(test.TestCase):
dict(instances=2), expire=expire)
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 500, 0),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
def test_reserve_max_age(self):
- self._stub_get_project_quotas()
+ self._stub_get_user_quotas()
self._stub_quota_reserve()
self.flags(max_age=86400)
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
@@ -1347,7 +1783,7 @@ class DbQuotaDriverTestCase(test.TestCase):
dict(instances=2), expire=expire)
self.assertEqual(self.calls, [
- 'get_project_quotas',
+ 'get_user_quotas',
('quota_reserve', expire, 0, 86400),
])
self.assertEqual(result, ['resv-1', 'resv-2', 'resv-3'])
@@ -1380,7 +1816,7 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.sync_called = set()
def make_sync(res_name):
- def sync(context, project_id, session):
+ def sync(context, user_id, project_id, session):
self.sync_called.add(res_name)
if res_name in self.usages:
if self.usages[res_name].in_use < 0:
@@ -1407,21 +1843,22 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
def fake_get_quota_usages(context, session):
return self.usages.copy()
- def fake_quota_usage_create(context, project_id, resource, in_use,
- reserved, until_refresh, session=None,
- save=True):
+ def fake_quota_usage_create(context, user_id, project_id, resource,
+ in_use, reserved, until_refresh,
+ session=None, save=True):
quota_usage_ref = self._make_quota_usage(
- project_id, resource, in_use, reserved, until_refresh,
- timeutils.utcnow(), timeutils.utcnow())
+ user_id, project_id, resource, in_use, reserved,
+ until_refresh, timeutils.utcnow(), timeutils.utcnow())
self.usages_created[resource] = quota_usage_ref
return quota_usage_ref
- def fake_reservation_create(context, uuid, usage_id, project_id,
- resource, delta, expire, session=None):
+ def fake_reservation_create(context, uuid, usage_id, user_id,
+ project_id, resource, delta, expire,
+ session=None):
reservation_ref = self._make_reservation(
- uuid, usage_id, project_id, resource, delta, expire,
+ uuid, usage_id, user_id, project_id, resource, delta, expire,
timeutils.utcnow(), timeutils.utcnow())
self.reservations_created[resource] = reservation_ref
@@ -1435,10 +1872,11 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
timeutils.set_time_override()
- def _make_quota_usage(self, project_id, resource, in_use, reserved,
- until_refresh, created_at, updated_at):
+ def _make_quota_usage(self, user_id, project_id, resource, in_use,
+ reserved, until_refresh, created_at, updated_at):
quota_usage_ref = FakeUsage()
quota_usage_ref.id = len(self.usages) + len(self.usages_created)
+ quota_usage_ref.user_id = user_id
quota_usage_ref.project_id = project_id
quota_usage_ref.resource = resource
quota_usage_ref.in_use = in_use
@@ -1451,14 +1889,15 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
return quota_usage_ref
- def init_usage(self, project_id, resource, in_use, reserved,
+ def init_usage(self, user_id, project_id, resource, in_use, reserved,
until_refresh=None, created_at=None, updated_at=None):
if created_at is None:
created_at = timeutils.utcnow()
if updated_at is None:
updated_at = timeutils.utcnow()
- quota_usage_ref = self._make_quota_usage(project_id, resource, in_use,
+ quota_usage_ref = self._make_quota_usage(user_id, project_id,
+ resource, in_use,
reserved, until_refresh,
created_at, updated_at)
@@ -1473,12 +1912,13 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
"%s != %s on usage for resource %s" %
(actual, value, resource))
- def _make_reservation(self, uuid, usage_id, project_id, resource,
+ def _make_reservation(self, uuid, usage_id, user_id, project_id, resource,
delta, expire, created_at, updated_at):
reservation_ref = sqa_models.Reservation()
reservation_ref.id = len(self.reservations_created)
reservation_ref.uuid = uuid
reservation_ref.usage_id = usage_id
+ reservation_ref.user_id = user_id
reservation_ref.project_id = project_id
reservation_ref.resource = resource
reservation_ref.delta = delta
@@ -1525,16 +1965,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram']))
self.compare_usage(self.usages_created, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=0,
reserved=2,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=0,
reserved=4,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=0,
reserved=2 * 1024,
@@ -1543,10 +1986,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages_created['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=2),
dict(resource='cores',
usage_id=self.usages_created['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=4),
dict(resource='ram',
@@ -1555,9 +2000,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
])
def test_quota_reserve_negative_in_use(self):
- self.init_usage('test_project', 'instances', -1, 0, until_refresh=1)
- self.init_usage('test_project', 'cores', -1, 0, until_refresh=1)
- self.init_usage('test_project', 'ram', -1, 0, until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'instances', -1, 0,
+ until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'cores', -1, 0,
+ until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'ram', -1, 0,
+ until_refresh=1)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1575,16 +2023,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram']))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2,
until_refresh=5),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=4,
until_refresh=5),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2 * 1024,
@@ -1594,10 +2045,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=4),
dict(resource='ram',
@@ -1606,9 +2059,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
])
def test_quota_reserve_until_refresh(self):
- self.init_usage('test_project', 'instances', 3, 0, until_refresh=1)
- self.init_usage('test_project', 'cores', 3, 0, until_refresh=1)
- self.init_usage('test_project', 'ram', 3, 0, until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'instances', 3, 0,
+ until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'cores', 3, 0,
+ until_refresh=1)
+ self.init_usage('fake_user', 'test_project', 'ram', 3, 0,
+ until_refresh=1)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1626,16 +2082,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram']))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2,
until_refresh=5),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=4,
until_refresh=5),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2 * 1024,
@@ -1645,10 +2104,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=4),
dict(resource='ram',
@@ -1660,11 +2121,11 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
max_age = 3600
record_created = (timeutils.utcnow() -
datetime.timedelta(seconds=max_age))
- self.init_usage('test_project', 'instances', 3, 0,
+ self.init_usage('fake_user', 'test_project', 'instances', 3, 0,
created_at=record_created, updated_at=record_created)
- self.init_usage('test_project', 'cores', 3, 0,
+ self.init_usage('fake_user', 'test_project', 'cores', 3, 0,
created_at=record_created, updated_at=record_created)
- self.init_usage('test_project', 'ram', 3, 0,
+ self.init_usage('fake_user', 'test_project', 'ram', 3, 0,
created_at=record_created, updated_at=record_created)
context = FakeContext('test_project', 'test_class')
quotas = dict(
@@ -1683,16 +2144,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set(['instances', 'cores', 'ram']))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=4,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=2,
reserved=2 * 1024,
@@ -1702,10 +2166,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=4),
dict(resource='ram',
@@ -1714,9 +2180,9 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
])
def test_quota_reserve_no_refresh(self):
- self.init_usage('test_project', 'instances', 3, 0)
- self.init_usage('test_project', 'cores', 3, 0)
- self.init_usage('test_project', 'ram', 3, 0)
+ self.init_usage('fake_user', 'test_project', 'instances', 3, 0)
+ self.init_usage('fake_user', 'test_project', 'cores', 3, 0)
+ self.init_usage('fake_user', 'test_project', 'ram', 3, 0)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1734,16 +2200,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set([]))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=3,
reserved=2,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=3,
reserved=4,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=3,
reserved=2 * 1024,
@@ -1753,10 +2222,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=4),
dict(resource='ram',
@@ -1765,9 +2236,9 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
])
def test_quota_reserve_unders(self):
- self.init_usage('test_project', 'instances', 1, 0)
- self.init_usage('test_project', 'cores', 3, 0)
- self.init_usage('test_project', 'ram', 1 * 1024, 0)
+ self.init_usage('fake_user', 'test_project', 'instances', 1, 0)
+ self.init_usage('fake_user', 'test_project', 'cores', 3, 0)
+ self.init_usage('fake_user', 'test_project', 'ram', 1 * 1024, 0)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1785,16 +2256,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set([]))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=1,
reserved=0,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=3,
reserved=0,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=1 * 1024,
reserved=0,
@@ -1804,10 +2278,12 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=-2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=-4),
dict(resource='ram',
@@ -1816,9 +2292,9 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
])
def test_quota_reserve_overs(self):
- self.init_usage('test_project', 'instances', 4, 0)
- self.init_usage('test_project', 'cores', 8, 0)
- self.init_usage('test_project', 'ram', 10 * 1024, 0)
+ self.init_usage('fake_user', 'test_project', 'instances', 4, 0)
+ self.init_usage('fake_user', 'test_project', 'cores', 8, 0)
+ self.init_usage('fake_user', 'test_project', 'ram', 10 * 1024, 0)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1838,16 +2314,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set([]))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=4,
reserved=0,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=8,
reserved=0,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=10 * 1024,
reserved=0,
@@ -1857,9 +2336,9 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.reservations_created, {})
def test_quota_reserve_reduction(self):
- self.init_usage('test_project', 'instances', 10, 0)
- self.init_usage('test_project', 'cores', 20, 0)
- self.init_usage('test_project', 'ram', 20 * 1024, 0)
+ self.init_usage('fake_user', 'test_project', 'instances', 10, 0)
+ self.init_usage('fake_user', 'test_project', 'cores', 20, 0)
+ self.init_usage('fake_user', 'test_project', 'ram', 20 * 1024, 0)
context = FakeContext('test_project', 'test_class')
quotas = dict(
instances=5,
@@ -1877,16 +2356,19 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.assertEqual(self.sync_called, set([]))
self.compare_usage(self.usages, [
dict(resource='instances',
+ user_id='fake_user',
project_id='test_project',
in_use=10,
reserved=0,
until_refresh=None),
dict(resource='cores',
+ user_id='fake_user',
project_id='test_project',
in_use=20,
reserved=0,
until_refresh=None),
dict(resource='ram',
+ user_id='fake_user',
project_id='test_project',
in_use=20 * 1024,
reserved=0,
@@ -1896,14 +2378,17 @@ class QuotaReserveSqlAlchemyTestCase(test.TestCase):
self.compare_reservation(result, [
dict(resource='instances',
usage_id=self.usages['instances'],
+ user_id='fake_user',
project_id='test_project',
delta=-2),
dict(resource='cores',
usage_id=self.usages['cores'],
+ user_id='fake_user',
project_id='test_project',
delta=-4),
dict(resource='ram',
usage_id=self.usages['ram'],
+ user_id='fake_user',
project_id='test_project',
delta=-2 * 1024),
])