Add check_deltas() and limit_check_project_and_user() to Quotas

This adds two new methods to the Quotas object in preparation for the
switch to counting resources for checking quota.

The existing limit_check() method only supports checking quota against
per-user limits because currently, the only CountableResources that
exist are not ever counted across a project.

To implement resource counting for all resources, we will change each
resource to a countable resource and some of the resources will be
counted across a project and across a user and checked against
per-project and per-user limits, respectively. To do this, we add a new
method: count_as_dict that returns a count as a dictionary to support
multiple counts (over a project and over a user) and a new method:
limit_check_project_and_user() that supports checking quota against
both per-project and per-user limits.

The check_deltas() method is a wrapper around the count_as_dict() and
limit_check_project_and_user() calls where only the deltas are passed
in and the counting and limit check are done inside the wrapper. This
cleans up the call sites and enables us to potentially make the
count + check atomic in the future behind the check_deltas() interface.

A change has also been made to the _validate_method_call_check_resource
methods to let them take the Resources dict as an argument instead of
assuming and hard-coding the global QuotaEngine's resources. This was
mainly needed for unit testing because in order to cover both the
per-project and per-user limit checking, a CountableResource counted
across both a project and a user is needed, and currently none exist
in the global QuotaEngine. Resources will be changed over in
subsequent patches.

Co-Authored-By: melanie witt <melwittt@gmail.com>

Part of blueprint cells-count-resources-to-check-quota-in-api

Change-Id: Ib52a21836c4638cb9f7a5a55a5befbf0963fe6bf
This commit is contained in:
Dan Smith 2017-03-15 17:02:16 -07:00 committed by melanie witt
parent d9d300d5c8
commit 832cc4709f
9 changed files with 949 additions and 134 deletions

View File

@ -1057,13 +1057,14 @@ class API(base.Base):
if instance_group:
if check_server_group_quota:
count = objects.Quotas.count(context,
count = objects.Quotas.count_as_dict(context,
'server_group_members',
instance_group,
context.user_id)
count_value = count['user']['server_group_members']
try:
objects.Quotas.limit_check(context,
server_group_members=count + 1)
objects.Quotas.limit_check(
context, server_group_members=count_value + 1)
except exception.OverQuota:
msg = _("Quota exceeded, too many servers in "
"group")
@ -4861,10 +4862,11 @@ class KeypairAPI(base.Base):
reason=_('Keypair name must be string and between '
'1 and 255 characters long'))
count = objects.Quotas.count(context, 'key_pairs', user_id)
count = objects.Quotas.count_as_dict(context, 'key_pairs', user_id)
count_value = count['user']['key_pairs']
try:
objects.Quotas.limit_check(context, key_pairs=count + 1)
objects.Quotas.limit_check(context, key_pairs=count_value + 1)
except exception.OverQuota:
raise exception.KeypairLimitExceeded()
@ -5189,9 +5191,11 @@ class SecurityGroupAPI(base.Base, security_group_base.SecurityGroupBase):
this function is written to support both.
"""
count = objects.Quotas.count(context, 'security_group_rules', id)
count = objects.Quotas.count_as_dict(context,
'security_group_rules', id)
count_value = count['user']['security_group_rules']
try:
projected = count + len(vals)
projected = count_value + len(vals)
objects.Quotas.limit_check(context, security_group_rules=projected)
except exception.OverQuota:
msg = _("Quota exceeded, too many security group rules.")

View File

@ -12,8 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
from nova import db
from nova import exception
from nova.objects import base
from nova.objects import fields
from nova import quota
@ -51,7 +53,9 @@ class Quotas(base.NovaObject):
# Version 1.0: initial version
# Version 1.1: Added create_limit() and update_limit()
# Version 1.2: Added limit_check() and count()
VERSION = '1.2'
# Version 1.3: Added check_deltas(), limit_check_project_and_user(),
# and count_as_dict()
VERSION = '1.3'
fields = {
'reservations': fields.ListOfStringsField(nullable=True),
@ -122,12 +126,85 @@ class Quotas(base.NovaObject):
return quota.QUOTAS.limit_check(
context, project_id=project_id, user_id=user_id, **values)
@base.remotable_classmethod
def limit_check_project_and_user(cls, context, project_values=None,
user_values=None, project_id=None,
user_id=None):
"""Check values against quota limits."""
return quota.QUOTAS.limit_check_project_and_user(context,
project_values=project_values, user_values=user_values,
project_id=project_id, user_id=user_id)
# NOTE(melwitt): This can be removed once no old code can call count().
@base.remotable_classmethod
def count(cls, context, resource, *args, **kwargs):
"""Count a resource."""
return quota.QUOTAS.count(
count = quota.QUOTAS.count_as_dict(context, resource, *args, **kwargs)
key = 'user' if 'user' in count else 'project'
return count[key][resource]
@base.remotable_classmethod
def count_as_dict(cls, context, resource, *args, **kwargs):
"""Count a resource and return a dict."""
return quota.QUOTAS.count_as_dict(
context, resource, *args, **kwargs)
@base.remotable_classmethod
def check_deltas(cls, context, deltas, *count_args, **count_kwargs):
"""Check usage delta against quota limits.
This does a Quotas.count_as_dict() followed by a
Quotas.limit_check_project_and_user() using the provided deltas.
:param context: The request context, for access checks
:param deltas: A dict of {resource_name: delta, ...} to check against
the quota limits
:param count_args: Optional positional arguments to pass to
count_as_dict()
:param count_kwargs: Optional keyword arguments to pass to
count_as_dict()
:param check_project_id: Optional project_id for scoping the limit
check to a different project than in the
context
:param check_user_id: Optional user_id for scoping the limit check to a
different user than in the context
:raises: exception.OverQuota if the limit check exceeds the quota
limits
"""
# We can't do f(*args, kw=None, **kwargs) in python 2.x
check_project_id = count_kwargs.pop('check_project_id', None)
check_user_id = count_kwargs.pop('check_user_id', None)
check_kwargs = collections.defaultdict(dict)
for resource in deltas:
# If we already counted a resource in a batch count, avoid
# unnecessary re-counting and avoid creating empty dicts in
# the defaultdict.
if (resource in check_kwargs.get('project_values', {}) or
resource in check_kwargs.get('user_values', {})):
continue
count = cls.count_as_dict(context, resource, *count_args,
**count_kwargs)
for res in count.get('project', {}):
if res in deltas:
total = count['project'][res] + deltas[res]
check_kwargs['project_values'][res] = total
for res in count.get('user', {}):
if res in deltas:
total = count['user'][res] + deltas[res]
check_kwargs['user_values'][res] = total
if check_project_id is not None:
check_kwargs['project_id'] = check_project_id
if check_user_id is not None:
check_kwargs['user_id'] = check_user_id
try:
cls.limit_check_project_and_user(context, **check_kwargs)
except exception.OverQuota as exc:
# Report usage in the exception when going over quota
key = 'user' if 'user' in count else 'project'
exc.kwargs['usages'] = count[key]
raise exc
@base.remotable_classmethod
def create_limit(cls, context, project_id, resource, limit, user_id=None):
# NOTE(danms,comstud): Quotas likely needs an overhaul and currently

View File

@ -16,6 +16,7 @@
"""Quotas for resources per project."""
import copy
import datetime
from oslo_log import log as logging
@ -69,6 +70,9 @@ class DbQuotaDriver(object):
quotas = {}
default_quotas = db.quota_class_get_default(context)
for resource in resources.values():
# resource.default returns the config options. So if there's not
# an entry for the resource in the default class, it uses the
# config option.
quotas[resource.name] = default_quotas.get(resource.name,
resource.default)
@ -132,11 +136,16 @@ class DbQuotaDriver(object):
in_use=usage.get('in_use', 0),
reserved=usage.get('reserved', 0),
)
# Initialize remains quotas.
# Initialize remains quotas with the default limits.
if remains:
modified_quotas[resource.name].update(remains=limit)
if remains:
# Get all user quotas for a project and subtract their limits
# from the class limits to get the remains. For example, if the
# class/default is 20 and there are two users each with quota of 5,
# then there is quota of 10 left to give out.
all_quotas = db.quota_get_all(context, project_id)
for quota in all_quotas:
if quota.resource in modified_quotas:
@ -145,6 +154,68 @@ class DbQuotaDriver(object):
return modified_quotas
def _get_usages(self, context, resources, project_id, user_id=None):
"""Get usages of specified resources.
This function is called to get resource usages for validating quota
limit creates or updates in the os-quota-sets API and for displaying
resource usages in the os-used-limits API. This function is not used
for checking resource usage against quota limits.
:param context: The request context for access checks
:param resources: The dict of Resources for which to get usages
:param project_id: The project_id for scoping the usage count
:param user_id: Optional user_id for scoping the usage count
:returns: A dict containing resources and their usage information,
for example:
{'project_id': 'project-uuid',
'user_id': 'user-uuid',
'instances': {'in_use': 5},
'fixed_ips': {'in_use': 5}}
"""
usages = {}
for resource in resources.values():
# NOTE(melwitt): This is to keep things working while we're in the
# middle of converting ReservableResources to CountableResources.
# We should skip resources that are not countable and eventually
# when there are no more ReservableResources, we won't need this.
if not isinstance(resource, CountableResource):
continue
if resource.name in usages:
# This is needed because for any of the resources:
# ('instances', 'cores', 'ram'), they are counted at the same
# time for efficiency (query the instances table once instead
# of multiple times). So, a count of any one of them contains
# counts for the others and we can avoid re-counting things.
continue
if resource.name in ('key_pairs', 'server_group_members',
'security_group_rules'):
# These per user resources are special cases whose usages
# are not considered when validating limit create/update or
# displaying used limits. They are always zero.
usages[resource.name] = {'in_use': 0}
else:
if resource.name in db.quota_get_per_project_resources():
count = resource.count_as_dict(context, project_id)
key = 'project'
else:
# NOTE(melwitt): This assumes a specific signature for
# count_as_dict(). Usages used to be records in the
# database but now we are counting resources. The
# count_as_dict() function signature needs to match this
# call, else it should get a conditional in this function.
count = resource.count_as_dict(context, project_id,
user_id=user_id)
key = 'user' if user_id else 'project'
# Example count_as_dict() return value:
# {'project': {'instances': 5},
# 'user': {'instances': 2}}
counted_resources = count[key].keys()
for res in counted_resources:
count_value = count[key][res]
usages[res] = {'in_use': count_value}
return usages
def get_user_quotas(self, context, resources, project_id, user_id,
quota_class=None, defaults=True,
usages=True, project_quotas=None,
@ -183,11 +254,21 @@ class DbQuotaDriver(object):
for key, value in proj_quotas.items():
if key not in user_quotas.keys():
user_quotas[key] = value
user_usages = None
user_usages = {}
if usages:
user_usages = db.quota_usage_get_all_by_project_and_user(context,
project_id,
user_id)
user_usages = self._get_usages(context, resources, project_id,
user_id=user_id)
# TODO(melwitt): This is for compat with ReservableResources and
# should be removed when all of the ReservableResources have been
# removed. ReservableResource usage comes from the quota_usages
# table in the database, so we need to query usages from there as
# long as we still have ReservableResources.
from_usages_table = db.quota_usage_get_all_by_project_and_user(
context, project_id, user_id)
for k, v in from_usages_table.items():
if k in user_usages:
continue
user_usages[k] = v
return self._process_quotas(context, resources, project_id,
user_quotas, quota_class,
defaults=defaults, usages=user_usages)
@ -218,11 +299,20 @@ class DbQuotaDriver(object):
"""
project_quotas = project_quotas or db.quota_get_all_by_project(
context, project_id)
project_usages = None
project_usages = {}
if usages:
LOG.debug('Getting all quota usages for project: %s', project_id)
project_usages = db.quota_usage_get_all_by_project(context,
project_id)
project_usages = self._get_usages(context, resources, project_id)
# TODO(melwitt): This is for compat with ReservableResources and
# should be removed when all of the ReservableResources have been
# removed. ReservableResource usage comes from the quota_usages
# table in the database, so we need to query usages from there as
# long as we still have ReservableResources.
from_usages_table = db.quota_usage_get_all_by_project(context,
project_id)
for k, v in from_usages_table.items():
if k in project_usages:
continue
project_usages[k] = v
return self._process_quotas(context, resources, project_id,
project_quotas, quota_class,
defaults=defaults, usages=project_usages,
@ -277,9 +367,22 @@ class DbQuotaDriver(object):
project_quotas=db_proj_quotas,
user_quotas=setted_quotas)
for key, value in user_quotas.items():
# Maximum is the remaining quota for a project (class/default
# minus the sum of all user quotas in the project), plus the
# given user's quota. So if the class/default is 20 and there
# are two users each with quota of 5, then there is quota of
# 10 remaining. The given user currently has quota of 5, so
# the maximum you could update their quota to would be 15.
# Class/default 20 - currently used in project 10 + current
# user 5 = 15.
maximum = \
self._sum_quota_values(project_quotas[key]['remains'],
setted_quotas.get(key, 0))
# This function is called for the quota_sets api and the
# corresponding nova-manage command. The idea is when someone
# attempts to update a quota, the value chosen must be at least
# as much as the current usage and less than or equal to the
# project limit less the sum of existing per user limits.
minimum = value['in_use'] + value['reserved']
settable_quotas[key] = {'minimum': minimum, 'maximum': maximum}
else:
@ -404,7 +507,7 @@ class DbQuotaDriver(object):
is admin and admin wants to impact on
common user.
"""
_valid_method_call_check_resources(values, 'check')
_valid_method_call_check_resources(values, 'check', resources)
# Ensure no value is less than zero
unders = [key for key, val in values.items() if val < 0]
@ -443,6 +546,151 @@ class DbQuotaDriver(object):
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
usages={}, headroom=headroom)
def limit_check_project_and_user(self, context, resources,
project_values=None, user_values=None,
project_id=None, user_id=None):
"""Check values (usage + desired delta) against quota limits.
For limits--this method checks that a set of
proposed values are permitted by the limit restriction.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it is not a simple limit
resource.
If any of the proposed values is over the defined quota, an
OverQuota exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns
nothing.
:param context: The request context, for access checks
:param resources: A dictionary of the registered resources
:param project_values: Optional dict containing the resource values to
check against project quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param user_values: Optional dict containing the resource values to
check against user quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param project_id: Optional project_id for scoping the limit check to a
different project than in the context
:param user_id: Optional user_id for scoping the limit check to a
different user than in the context
"""
if project_values is None:
project_values = {}
if user_values is None:
user_values = {}
_valid_method_call_check_resources(project_values, 'check', resources)
_valid_method_call_check_resources(user_values, 'check', resources)
if not any([project_values, user_values]):
raise exception.Invalid(
'Must specify at least one of project_values or user_values '
'for the limit check.')
# Ensure no value is less than zero
for vals in (project_values, user_values):
unders = [key for key, val in vals.items() if val < 0]
if unders:
raise exception.InvalidQuotaValue(unders=sorted(unders))
# Get a set of all keys for calling _get_quotas() so we get all of the
# resource limits we need.
all_keys = set(project_values).union(user_values)
# Keys that are in both project_values and user_values need to be
# checked against project quota and user quota, respectively.
# Keys that are not in both only need to be checked against project
# quota or user quota, if it is defined. Separate the keys that don't
# need to be checked against both quotas, merge them into one dict,
# and remove them from project_values and user_values.
keys_to_merge = set(project_values).symmetric_difference(user_values)
merged_values = {}
for key in keys_to_merge:
# The key will be either in project_values or user_values based on
# the earlier symmetric_difference.
merged_values[key] = (project_values.get(key) or
user_values.get(key))
project_values.pop(key, None)
user_values.pop(key, None)
# If project_id is None, then we use the project_id in context
if project_id is None:
project_id = context.project_id
# If user id is None, then we use the user_id in context
if user_id is None:
user_id = context.user_id
# Get the applicable quotas. They will be merged together (taking the
# min limit) if project_values and user_values were not specified
# together.
# per project quota limits (quotas that have no concept of
# user-scoping: fixed_ips, networks, floating_ips)
project_quotas = db.quota_get_all_by_project(context, project_id)
# per user quotas, project quota limits (for quotas that have
# user-scoping, limits for the project)
quotas = self._get_quotas(context, resources, all_keys,
has_sync=False, project_id=project_id,
project_quotas=project_quotas)
# per user quotas, user quota limits (for quotas that have
# user-scoping, the limits for the user)
user_quotas = self._get_quotas(context, resources, all_keys,
has_sync=False, project_id=project_id,
user_id=user_id,
project_quotas=project_quotas)
if merged_values:
# This is for resources that are not counted across a project and
# must pass both the quota for the project and the quota for the
# user.
# Combine per user project quotas and user_quotas for use in the
# checks, taking the minimum limit between the two.
merged_quotas = copy.deepcopy(quotas)
for k, v in user_quotas.items():
if k in merged_quotas:
merged_quotas[k] = min(merged_quotas[k], v)
else:
merged_quotas[k] = v
# Check the quotas and construct a list of the resources that
# would be put over limit by the desired values
overs = [key for key, val in merged_values.items()
if merged_quotas[key] >= 0 and merged_quotas[key] < val]
if overs:
headroom = {}
for key in overs:
headroom[key] = merged_quotas[key]
raise exception.OverQuota(overs=sorted(overs),
quotas=merged_quotas, usages={},
headroom=headroom)
# This is for resources that are counted across a project and
# across a user (instances, cores, ram, security_groups,
# server_groups). The project_values must pass the quota for the
# project and the user_values must pass the quota for the user.
over_user_quota = False
overs = []
for key in user_values.keys():
# project_values and user_values should contain the same keys or
# be empty after the keys in the symmetric_difference were removed
# from both dicts.
if quotas[key] >= 0 and quotas[key] < project_values[key]:
overs.append(key)
elif (user_quotas[key] >= 0 and
user_quotas[key] < user_values[key]):
overs.append(key)
over_user_quota = True
if overs:
quotas_exceeded = user_quotas if over_user_quota else quotas
headroom = {}
for key in overs:
headroom[key] = quotas_exceeded[key]
raise exception.OverQuota(overs=sorted(overs),
quotas=quotas_exceeded, usages={},
headroom=headroom)
def reserve(self, context, resources, deltas, expire=None,
project_id=None, user_id=None):
"""Check quotas and reserve resources.
@ -481,7 +729,7 @@ class DbQuotaDriver(object):
is admin and admin wants to impact on
common user.
"""
_valid_method_call_check_resources(deltas, 'reserve')
_valid_method_call_check_resources(deltas, 'reserve', resources)
# Set up the reservation expiration
if expire is None:
@ -855,6 +1103,38 @@ class NoopQuotaDriver(object):
"""
pass
def limit_check_project_and_user(self, context, resources,
project_values=None, user_values=None,
project_id=None, user_id=None):
"""Check values against quota limits.
For limits--this method checks that a set of
proposed values are permitted by the limit restriction.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it is not a simple limit
resource.
If any of the proposed values is over the defined quota, an
OverQuota exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns
nothing.
:param context: The request context, for access checks
:param resources: A dictionary of the registered resources
:param project_values: Optional dict containing the resource values to
check against project quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param user_values: Optional dict containing the resource values to
check against user quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param project_id: Optional project_id for scoping the limit check to a
different project than in the context
:param user_id: Optional user_id for scoping the limit check to a
different user than in the context
"""
pass
def reserve(self, context, resources, deltas, expire=None,
project_id=None, user_id=None):
"""Check quotas and reserve resources.
@ -1117,7 +1397,7 @@ class CountableResource(AbsoluteResource):
project ID.
"""
def __init__(self, name, count, flag=None):
def __init__(self, name, count_as_dict, flag=None):
"""Initializes a CountableResource.
Countable resources are those resources which directly
@ -1129,8 +1409,28 @@ class CountableResource(AbsoluteResource):
The counting function will be passed the context, along with
the extra positional and keyword arguments that are passed to
Quota.count(). It should return an integer specifying the
count.
Quota.count_as_dict(). It should return a dict specifying the
count scoped to a project and/or a user.
Example count of instances, cores, or ram returned as a rollup
of all the resources since we only want to query the instances
table once, not multiple times, for each resource.
Instances, cores, and ram are counted across a project and
across a user:
{'project': {'instances': 5, 'cores': 8, 'ram': 4096},
'user': {'instances': 1, 'cores': 2, 'ram': 512}}
Example count of server groups keeping a consistent format.
Server groups are counted across a project and across a user:
{'project': {'server_groups': 7},
'user': {'server_groups': 2}}
Example count of key pairs keeping a consistent format.
Key pairs are counted across a user only:
{'user': {'key_pairs': 5}}
Note that this counting is not performed in a transaction-safe
manner. This resource class is a temporary measure to provide
@ -1138,16 +1438,16 @@ class CountableResource(AbsoluteResource):
this problem can be evolved.
:param name: The name of the resource, i.e., "instances".
:param count: A callable which returns the count of the
resource. The arguments passed are as described
above.
:param count_as_dict: A callable which returns the count of the
resource as a dict. The arguments passed are as
described above.
:param flag: The name of the flag or configuration option
which specifies the default value of the quota
for this resource.
"""
super(CountableResource, self).__init__(name, flag=flag)
self.count = count
self.count_as_dict = count_as_dict
class QuotaEngine(object):
@ -1170,13 +1470,6 @@ class QuotaEngine(object):
self.__driver = self._driver_cls
return self.__driver
def __contains__(self, resource):
return resource in self._resources
def __getitem__(self, key):
if key in self._resources:
return self._resources[key]
def register_resource(self, resource):
"""Register a resource."""
@ -1289,25 +1582,33 @@ class QuotaEngine(object):
project_id,
user_id=user_id)
def count(self, context, resource, *args, **kwargs):
"""Count a resource.
def count_as_dict(self, context, resource, *args, **kwargs):
"""Count a resource and return a dict.
For countable resources, invokes the count() function and
For countable resources, invokes the count_as_dict() function and
returns its result. Arguments following the context and
resource are passed directly to the count function declared by
the resource.
:param context: The request context, for access checks.
:param resource: The name of the resource, as a string.
:returns: A dict containing the count(s) for the resource, for example:
{'project': {'instances': 2, 'cores': 4, 'ram': 1024},
'user': {'instances': 1, 'cores': 2, 'ram': 512}}
another example:
{'user': {'key_pairs': 5}}
"""
# Get the resource
res = self._resources.get(resource)
if not res or not hasattr(res, 'count'):
if not res or not hasattr(res, 'count_as_dict'):
raise exception.QuotaResourceUnknown(unknown=[resource])
return res.count(context, *args, **kwargs)
return res.count_as_dict(context, *args, **kwargs)
# TODO(melwitt): This can be removed once no old code can call
# limit_check(). It will be replaced with limit_check_project_and_user().
def limit_check(self, context, project_id=None, user_id=None, **values):
"""Check simple quota limits.
@ -1339,6 +1640,39 @@ class QuotaEngine(object):
return self._driver.limit_check(context, self._resources, values,
project_id=project_id, user_id=user_id)
def limit_check_project_and_user(self, context, project_values=None,
user_values=None, project_id=None,
user_id=None):
"""Check values against quota limits.
For limits--this method checks that a set of
proposed values are permitted by the limit restriction.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it is not a simple limit
resource.
If any of the proposed values is over the defined quota, an
OverQuota exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns
nothing.
:param context: The request context, for access checks
:param project_values: Optional dict containing the resource values to
check against project quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param user_values: Optional dict containing the resource values to
check against user quota,
e.g. {'instances': 1, 'cores': 2, 'memory_mb': 512}
:param project_id: Optional project_id for scoping the limit check to a
different project than in the context
:param user_id: Optional user_id for scoping the limit check to a
different user than in the context
"""
return self._driver.limit_check_project_and_user(
context, self._resources, project_values=project_values,
user_values=user_values, project_id=project_id, user_id=user_id)
def reserve(self, context, expire=None, project_id=None, user_id=None,
**deltas):
"""Check quotas and reserve resources.
@ -1512,14 +1846,23 @@ class QuotaEngine(object):
return sorted(self._resources.keys())
def _keypair_get_count_by_user(*args, **kwargs):
"""Helper method to avoid referencing objects.KeyPairList on import."""
return objects.KeyPairList.get_count_by_user(*args, **kwargs)
def _keypair_get_count_by_user(context, user_id):
count = objects.KeyPairList.get_count_by_user(context, user_id)
return {'user': {'key_pairs': count}}
def _server_group_count_members_by_user(context, group, user_id):
"""Helper method to avoid referencing objects.InstanceGroup on import."""
return group.count_members_by_user(user_id)
count = group.count_members_by_user(user_id)
return {'user': {'server_group_members': count}}
def _security_group_rule_count_by_group(context, security_group_id):
count = db.security_group_rule_count_by_group(context, security_group_id)
# NOTE(melwitt): Neither 'project' nor 'user' fit perfectly here as
# security group rules are counted per security group, not by user or
# project. But, the quota limits for security_group_rules can be scoped to
# a user, so we'll use 'user' here.
return {'user': {'security_group_rules': count}}
QUOTAS = QuotaEngine()
@ -1541,10 +1884,9 @@ resources = [
AbsoluteResource('injected_file_path_bytes',
'injected_file_path_length'),
CountableResource('security_group_rules',
db.security_group_rule_count_by_group,
_security_group_rule_count_by_group,
'security_group_rules'),
CountableResource('key_pairs', _keypair_get_count_by_user,
'key_pairs'),
CountableResource('key_pairs', _keypair_get_count_by_user, 'key_pairs'),
ReservableResource('server_groups', '_sync_server_groups',
'server_groups'),
CountableResource('server_group_members',
@ -1556,17 +1898,22 @@ resources = [
QUOTAS.register_resources(resources)
def _valid_method_call_check_resource(name, method):
if name not in QUOTAS:
def _valid_method_call_check_resource(name, method, resources):
if name not in resources:
raise exception.InvalidQuotaMethodUsage(method=method, res=name)
res = QUOTAS[name]
res = resources[name]
if res.valid_method != method:
raise exception.InvalidQuotaMethodUsage(method=method, res=name)
def _valid_method_call_check_resources(resource, method):
"""A method to check whether the resource can use the quota method."""
def _valid_method_call_check_resources(resource_values, method, resources):
"""A method to check whether the resource can use the quota method.
for name in resource.keys():
_valid_method_call_check_resource(name, method)
:param resource_values: Dict containing the resource names and values
:param method: The quota method to check
:param resources: Dict containing Resource objects to validate against
"""
for name in resource_values.keys():
_valid_method_call_check_resource(name, method, resources)

View File

@ -184,9 +184,9 @@ class KeypairsTestV21(test.TestCase):
def test_keypair_import_quota_limit(self):
def fake_quotas_count(self, context, resource, *args, **kwargs):
return 100
return {'user': {'key_pairs': 100}}
self.stubs.Set(QUOTAS, "count", fake_quotas_count)
self.stubs.Set(QUOTAS, "count_as_dict", fake_quotas_count)
body = {
'keypair': {
@ -210,9 +210,9 @@ class KeypairsTestV21(test.TestCase):
def test_keypair_create_quota_limit(self):
def fake_quotas_count(self, context, resource, *args, **kwargs):
return 100
return {'user': {'key_pairs': 100}}
self.stubs.Set(QUOTAS, "count", fake_quotas_count)
self.stubs.Set(QUOTAS, "count_as_dict", fake_quotas_count)
body = {
'keypair': {

View File

@ -3209,7 +3209,7 @@ class ServersControllerCreateTest(test.TestCase):
self.assertEqual(group.uuid, fake_group.uuid)
self.assertEqual(user_id,
self.req.environ['nova.context'].user_id)
return 10
return {'user': {'server_group_members': 10}}
def fake_limit_check(context, **kwargs):
if 'server_group_members' in kwargs:
@ -3218,7 +3218,7 @@ class ServersControllerCreateTest(test.TestCase):
def fake_instance_destroy(context, uuid, constraint):
return fakes.stub_instance(1)
self.stubs.Set(fakes.QUOTAS, 'count', fake_count)
self.stubs.Set(fakes.QUOTAS, 'count_as_dict', fake_count)
self.stubs.Set(fakes.QUOTAS, 'limit_check', fake_limit_check)
self.stub_out('nova.db.instance_destroy', fake_instance_destroy)
self.body['os:scheduler_hints'] = {'group': fake_group.uuid}

View File

@ -151,9 +151,9 @@ class CreateImportSharedTestMixIn(object):
def test_quota_limit(self):
def fake_quotas_count(self, context, resource, *args, **kwargs):
return CONF.quota.key_pairs
return {'user': {'key_pairs': CONF.quota.key_pairs}}
self.stubs.Set(QUOTAS, "count", fake_quotas_count)
self.stubs.Set(QUOTAS, "count_as_dict", fake_quotas_count)
msg = "Maximum number of key pairs exceeded"
self.assertKeypairRaises(exception.KeypairLimitExceeded, msg, 'foo')

View File

@ -1142,8 +1142,8 @@ object_data = {
'PciDevicePool': '1.1-3f5ddc3ff7bfa14da7f6c7e9904cc000',
'PciDevicePoolList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'PowerVMLiveMigrateData': '1.1-ac0fdd26da685f12d7038782cabd393a',
'Quotas': '1.2-1fe4cd50593aaf5d36a6dc5ab3f98fb3',
'QuotasNoOp': '1.2-e041ddeb7dc8188ca71706f78aad41c1',
'Quotas': '1.3-40fcefe522111dddd3e5e6155702cf4e',
'QuotasNoOp': '1.3-b19f8a5d187f75ccf372aa23c5f906a4',
'RequestSpec': '1.8-35033ecef47a880f9a5e46e2269e2b97',
'ResourceClass': '1.0-e6b367e2cf1733c5f3526f20a3286fe9',
'ResourceClassList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',

View File

@ -15,6 +15,7 @@
import mock
from nova import context
from nova import exception
from nova.objects import quotas as quotas_obj
from nova import quota
from nova import test
@ -153,6 +154,131 @@ class _TestQuotasObject(object):
mock_update.assert_called_once_with(self.context, 'fake-project',
'foo', 10, user_id='user')
@mock.patch.object(QUOTAS, 'count_as_dict')
def test_count(self, mock_count):
# key_pairs can't actually be counted across a project, this is just
# for testing.
mock_count.return_value = {'project': {'key_pairs': 5},
'user': {'key_pairs': 4}}
count = quotas_obj.Quotas.count(self.context, 'key_pairs', 'a-user')
self.assertEqual(4, count)
# key_pairs can't actually be counted across a project, this is just
# for testing.
mock_count.return_value = {'project': {'key_pairs': 5}}
count = quotas_obj.Quotas.count(self.context, 'key_pairs', 'a-user')
self.assertEqual(5, count)
mock_count.return_value = {'user': {'key_pairs': 3}}
count = quotas_obj.Quotas.count(self.context, 'key_pairs', 'a-user')
self.assertEqual(3, count)
@mock.patch('nova.objects.Quotas.count_as_dict')
def test_check_deltas(self, mock_count):
self.flags(key_pairs=3, group='quota')
self.flags(server_group_members=3, group='quota')
def fake_count(context, resource):
if resource in ('key_pairs', 'server_group_members'):
return {'project': {'key_pairs': 2, 'server_group_members': 2},
'user': {'key_pairs': 1, 'server_group_members': 2}}
else:
return {'user': {resource: 2}}
mock_count.side_effect = fake_count
deltas = {'key_pairs': 1,
'server_group_members': 1,
'security_group_rules': 1}
project_id = 'fake-other-project'
user_id = 'fake-other-user'
quotas_obj.Quotas.check_deltas(self.context, deltas,
check_project_id=project_id,
check_user_id=user_id)
# Should be called twice: once for key_pairs/server_group_members,
# once for security_group_rules.
self.assertEqual(2, mock_count.call_count)
call1 = mock.call(self.context, 'key_pairs')
call2 = mock.call(self.context, 'server_group_members')
call3 = mock.call(self.context, 'security_group_rules')
self.assertTrue(call1 in mock_count.mock_calls or
call2 in mock_count.mock_calls)
self.assertIn(call3, mock_count.mock_calls)
@mock.patch('nova.objects.Quotas.count_as_dict')
def test_check_deltas_zero(self, mock_count):
# This will test that we will raise OverQuota if given a zero delta if
# an object creation has put us over the allowed quota.
# This is for the scenario where we recheck quota and delete an object
# if we have gone over quota during a race.
self.flags(key_pairs=3, group='quota')
self.flags(server_group_members=3, group='quota')
def fake_count(context, resource):
return {'user': {resource: 4}}
mock_count.side_effect = fake_count
deltas = {'key_pairs': 0, 'server_group_members': 0}
project_id = 'fake-other-project'
user_id = 'fake-other-user'
self.assertRaises(exception.OverQuota, quotas_obj.Quotas.check_deltas,
self.context, deltas,
check_project_id=project_id,
check_user_id=user_id)
# Should be called twice, once for key_pairs, once for
# server_group_members
self.assertEqual(2, mock_count.call_count)
call1 = mock.call(self.context, 'key_pairs')
call2 = mock.call(self.context, 'server_group_members')
mock_count.assert_has_calls([call1, call2], any_order=True)
@mock.patch('nova.objects.Quotas.count_as_dict')
def test_check_deltas_negative(self, mock_count):
"""Test check_deltas with a negative delta.
Negative deltas probably won't be used going forward for countable
resources because there are no usage records to decrement and there
won't be quota operations done when deleting resources. When resources
are deleted, they will no longer be reflected in the count.
"""
self.flags(key_pairs=3, group='quota')
mock_count.return_value = {'user': {'key_pairs': 4}}
deltas = {'key_pairs': -1}
# Should pass because the delta makes 3 key_pairs
quotas_obj.Quotas.check_deltas(self.context, deltas, 'a-user',
something='something')
# args for the count function should get passed along
mock_count.assert_called_once_with(self.context, 'key_pairs', 'a-user',
something='something')
@mock.patch('nova.objects.Quotas.count_as_dict')
@mock.patch('nova.objects.Quotas.limit_check_project_and_user')
def test_check_deltas_limit_check_scoping(self, mock_check, mock_count):
# check_project_id and check_user_id kwargs should get passed along to
# limit_check_project_and_user()
mock_count.return_value = {'project': {'foo': 5}, 'user': {'foo': 1}}
deltas = {'foo': 1}
quotas_obj.Quotas.check_deltas(self.context, deltas, 'a-project')
mock_check.assert_called_once_with(self.context,
project_values={'foo': 6},
user_values={'foo': 2})
mock_check.reset_mock()
quotas_obj.Quotas.check_deltas(self.context, deltas, 'a-project',
check_project_id='a-project')
mock_check.assert_called_once_with(self.context,
project_values={'foo': 6},
user_values={'foo': 2},
project_id='a-project')
mock_check.reset_mock()
quotas_obj.Quotas.check_deltas(self.context, deltas, 'a-project',
check_user_id='a-user')
mock_check.assert_called_once_with(self.context,
project_values={'foo': 6},
user_values={'foo': 2},
user_id='a-user')
class TestQuotasObject(_TestQuotasObject, test_objects._LocalTest):
pass

View File

@ -36,6 +36,22 @@ import nova.tests.unit.image.fake
CONF = nova.conf.CONF
def _get_fake_get_usages(updates=None):
# These values are not realistic (they should all be 0) and are
# only for testing that countable usages get included in the
# results.
usages = {'security_group_rules': {'in_use': 1},
'key_pairs': {'in_use': 2},
'server_group_members': {'in_use': 3}}
if updates:
usages.update(updates)
def fake_get_usages(*a, **k):
return usages
return fake_get_usages
class QuotaIntegrationTestCase(test.TestCase):
REQUIRES_LOCKING = True
@ -258,8 +274,10 @@ class QuotaIntegrationTestCase(test.TestCase):
@enginefacade.transaction_context_provider
class FakeContext(object):
class FakeContext(context.RequestContext):
def __init__(self, project_id, quota_class):
super(FakeContext, self).__init__(project_id=project_id,
quota_class=quota_class)
self.is_admin = False
self.user_id = 'fake_user'
self.project_id = project_id
@ -334,6 +352,12 @@ class FakeDriver(object):
self.called.append(('limit_check', context, resources,
values, project_id, user_id))
def limit_check_project_and_user(self, context, resources,
project_values=None, user_values=None,
project_id=None, user_id=None):
self.called.append(('limit_check_project_and_user', context, resources,
project_values, user_values, project_id, user_id))
def reserve(self, context, resources, deltas, expire=None,
project_id=None, user_id=None):
self.called.append(('reserve', context, resources, deltas,
@ -463,41 +487,41 @@ class BaseResourceTestCase(test.TestCase):
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'limit')
resources, 'limit', quota.QUOTAS._resources)
def test_valid_method_call_check_invalid_method(self):
resources = {'key_pairs': 1}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'dummy')
resources, 'dummy', quota.QUOTAS._resources)
def test_valid_method_call_check_multiple(self):
resources = {'key_pairs': 1, 'dummy': 2}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'check')
resources, 'check', quota.QUOTAS._resources)
resources = {'key_pairs': 1, 'instances': 2, 'dummy': 3}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'check')
resources, 'check', quota.QUOTAS._resources)
def test_valid_method_call_check_wrong_method_reserve(self):
resources = {'key_pairs': 1}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'reserve')
resources, 'reserve', quota.QUOTAS._resources)
def test_valid_method_call_check_wrong_method_check(self):
resources = {'fixed_ips': 1}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'check')
resources, 'check', quota.QUOTAS._resources)
class QuotaEngineTestCase(test.TestCase):
@ -660,36 +684,37 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(result1, quota_obj._resources)
self.assertEqual(result2, quota_obj._resources)
def test_count_no_resource(self):
def test_count_as_dict_no_resource(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
self.assertRaises(exception.QuotaResourceUnknown,
quota_obj.count, context, 'test_resource5',
quota_obj.count_as_dict, context, 'test_resource5',
True, foo='bar')
def test_count_wrong_resource(self):
def test_count_as_dict_wrong_resource(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
self.assertRaises(exception.QuotaResourceUnknown,
quota_obj.count, context, 'test_resource1',
quota_obj.count_as_dict, context, 'test_resource1',
True, foo='bar')
def test_count(self):
def fake_count(context, *args, **kwargs):
def test_count_as_dict(self):
def fake_count_as_dict(context, *args, **kwargs):
self.assertEqual(args, (True,))
self.assertEqual(kwargs, dict(foo='bar'))
return 5
return {'project': {'test_resource5': 5}}
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
quota_obj.register_resource(quota.CountableResource('test_resource5',
fake_count))
result = quota_obj.count(context, 'test_resource5', True, foo='bar')
quota_obj.register_resource(
quota.CountableResource('test_resource5', fake_count_as_dict))
result = quota_obj.count_as_dict(context, 'test_resource5', True,
foo='bar')
self.assertEqual(result, 5)
self.assertEqual({'project': {'test_resource5': 5}}, result)
def test_limit_check(self):
context = FakeContext(None, None)
@ -707,6 +732,23 @@ class QuotaEngineTestCase(test.TestCase):
), None, None),
])
def test_limit_check_project_and_user(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
project_values = dict(test_resource1=4, test_resource2=3)
user_values = dict(test_resource3=2, test_resource4=1)
quota_obj.limit_check_project_and_user(context,
project_values=project_values,
user_values=user_values)
self.assertEqual([('limit_check_project_and_user', context,
quota_obj._resources,
dict(test_resource1=4, test_resource2=3),
dict(test_resource3=2, test_resource4=1),
None, None)],
driver.called)
def test_reserve(self):
context = FakeContext(None, None)
driver = FakeDriver(reservations=[
@ -980,12 +1022,100 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_quota_class_get_all_by_name()
def test_get_user_quotas(self):
def _get_fake_countable_resources(self):
# Create several countable resources with fake count functions
def fake_instances_cores_ram_count(*a, **k):
return {'project': {'instances': 2, 'cores': 4, 'ram': 1024},
'user': {'instances': 1, 'cores': 2, 'ram': 512}}
def fake_security_group_count(*a, **k):
return {'project': {'security_groups': 2},
'user': {'security_groups': 1}}
def fake_server_group_count(*a, **k):
return {'project': {'server_groups': 5},
'user': {'server_groups': 3}}
resources = {}
resources['key_pairs'] = quota.CountableResource(
'key_pairs', lambda *a, **k: {'user': {'key_pairs': 1}},
'key_pairs')
resources['instances'] = quota.CountableResource(
'instances', fake_instances_cores_ram_count, 'instances')
resources['cores'] = quota.CountableResource(
'cores', fake_instances_cores_ram_count, 'cores')
resources['ram'] = quota.CountableResource(
'ram', fake_instances_cores_ram_count, 'ram')
resources['security_groups'] = quota.CountableResource(
'security_groups', fake_security_group_count, 'security_groups')
resources['floating_ips'] = quota.CountableResource(
'floating_ips', lambda *a, **k: {'project': {'floating_ips': 4}},
'floating_ips')
resources['fixed_ips'] = quota.CountableResource(
'fixed_ips', lambda *a, **k: {'project': {'fixed_ips': 5}},
'fixed_ips')
resources['server_groups'] = quota.CountableResource(
'server_groups', fake_server_group_count, 'server_groups')
resources['server_group_members'] = quota.CountableResource(
'server_group_members',
lambda *a, **k: {'user': {'server_group_members': 7}},
'server_group_members')
resources['security_group_rules'] = quota.CountableResource(
'security_group_rules',
lambda *a, **k: {'project': {'security_group_rules': 8}},
'security_group_rules')
return resources
def test_get_usages_for_project(self):
resources = self._get_fake_countable_resources()
actual = self.driver._get_usages(
FakeContext('test_project', 'test_class'), resources,
'test_project')
# key_pairs, server_group_members, and security_group_rules are never
# counted as a usage. Their counts are only for quota limit checking.
expected = {'key_pairs': {'in_use': 0},
'instances': {'in_use': 2},
'cores': {'in_use': 4},
'ram': {'in_use': 1024},
'security_groups': {'in_use': 2},
'floating_ips': {'in_use': 4},
'fixed_ips': {'in_use': 5},
'server_groups': {'in_use': 5},
'server_group_members': {'in_use': 0},
'security_group_rules': {'in_use': 0}}
self.assertEqual(expected, actual)
def test_get_usages_for_user(self):
resources = self._get_fake_countable_resources()
actual = self.driver._get_usages(
FakeContext('test_project', 'test_class'), resources,
'test_project', user_id='fake_user')
# key_pairs, server_group_members, and security_group_rules are never
# counted as a usage. Their counts are only for quota limit checking.
expected = {'key_pairs': {'in_use': 0},
'instances': {'in_use': 1},
'cores': {'in_use': 2},
'ram': {'in_use': 512},
'security_groups': {'in_use': 1},
'floating_ips': {'in_use': 4},
'fixed_ips': {'in_use': 5},
'server_groups': {'in_use': 3},
'server_group_members': {'in_use': 0},
'security_group_rules': {'in_use': 0}}
self.assertEqual(expected, actual)
@mock.patch('nova.quota.DbQuotaDriver._get_usages')
def test_get_user_quotas(self, mock_get_usages):
# This will test that the counted usage will not be overwritten by
# the quota_usages records (in_use=2, reserved=2) from the database.
usages = {'instances': {'in_use': 5}}
mock_get_usages.side_effect = _get_fake_get_usages(updates=usages)
self.maxDiff = None
self._stub_get_by_project_and_user()
ctxt = FakeContext('test_project', 'test_class')
result = self.driver.get_user_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', 'fake_user')
ctxt, quota.QUOTAS._resources, 'test_project', 'fake_user')
self.assertEqual(self.calls, [
'quota_get_all_by_project_and_user',
@ -993,11 +1123,14 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_usage_get_all_by_project_and_user',
'quota_class_get_all_by_name',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project',
user_id='fake_user')
self.assertEqual(result, dict(
instances=dict(
limit=5,
in_use=2,
reserved=2,
in_use=5,
reserved=0,
),
cores=dict(
limit=10,
@ -1046,12 +1179,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1061,7 +1194,7 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
@ -1127,12 +1260,18 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_quota_class_get_all_by_name()
self._stub_quota_class_get_default()
def test_get_project_quotas(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages')
def test_get_project_quotas(self, mock_get_usages):
# This will test that the counted usage will not be overwritten by
# the quota_usages records (in_use=2, reserved=2) from the database.
usages = {'instances': {'in_use': 5}}
mock_get_usages.side_effect = _get_fake_get_usages(updates=usages)
self.maxDiff = None
self._stub_get_by_project()
ctxt = FakeContext('test_project', 'test_class')
result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project')
ctxt, quota.QUOTAS._resources, 'test_project')
self.assertEqual(self.calls, [
'quota_get_all_by_project',
@ -1140,11 +1279,13 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_class_get_all_by_name',
'quota_class_get_default',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project')
self.assertEqual(result, dict(
instances=dict(
limit=5,
in_use=2,
reserved=2,
in_use=5,
reserved=0,
),
cores=dict(
limit=10,
@ -1193,12 +1334,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1208,17 +1349,19 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
def test_get_project_quotas_with_remains(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_project_quotas_with_remains(self, mock_get_usages):
self.maxDiff = None
self._stub_get_by_project()
ctxt = FakeContext('test_project', 'test_class')
result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', remains=True)
ctxt, quota.QUOTAS._resources, 'test_project', remains=True)
self.assertEqual(self.calls, [
'quota_get_all_by_project',
@ -1227,6 +1370,8 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_class_get_default',
'quota_get_all',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project')
self.assertEqual(result, dict(
instances=dict(
limit=5,
@ -1290,13 +1435,13 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
remains=20,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
remains=100,
),
@ -1308,24 +1453,29 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
remains=10,
),
))
def test_get_user_quotas_alt_context_no_class(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_user_quotas_alt_context_no_class(self, mock_get_usages):
self.maxDiff = None
self._stub_get_by_project_and_user()
ctxt = FakeContext('other_project', None)
result = self.driver.get_user_quotas(
FakeContext('test_project', None),
quota.QUOTAS._resources, 'test_project', 'fake_user')
ctxt, quota.QUOTAS._resources, 'test_project', 'fake_user')
self.assertEqual(self.calls, [
'quota_get_all_by_project_and_user',
'quota_get_all_by_project',
'quota_usage_get_all_by_project_and_user',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project',
user_id='fake_user')
self.assertEqual(result, dict(
instances=dict(
limit=10,
@ -1379,12 +1529,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1394,23 +1544,27 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
def test_get_project_quotas_alt_context_no_class(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_project_quotas_alt_context_no_class(self, mock_get_usages):
self.maxDiff = None
self._stub_get_by_project()
ctxt = FakeContext('other_project', None)
result = self.driver.get_project_quotas(
FakeContext('other_project', 'other_class'),
quota.QUOTAS._resources, 'test_project')
ctxt, quota.QUOTAS._resources, 'test_project')
self.assertEqual(self.calls, [
'quota_get_all_by_project',
'quota_usage_get_all_by_project',
'quota_class_get_default',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project')
self.assertEqual(result, dict(
instances=dict(
limit=5,
@ -1464,12 +1618,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1479,17 +1633,19 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
def test_get_user_quotas_alt_context_with_class(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_user_quotas_alt_context_with_class(self, mock_get_usages):
self.maxDiff = None
self._stub_get_by_project_and_user()
ctxt = FakeContext('other_project', 'other_class')
result = self.driver.get_user_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', 'fake_user',
ctxt, quota.QUOTAS._resources, 'test_project', 'fake_user',
quota_class='test_class')
self.assertEqual(self.calls, [
@ -1498,6 +1654,9 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_usage_get_all_by_project_and_user',
'quota_class_get_all_by_name',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project',
user_id='fake_user')
self.assertEqual(result, dict(
instances=dict(
limit=5,
@ -1551,12 +1710,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1566,17 +1725,20 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
def test_get_project_quotas_alt_context_with_class(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_project_quotas_alt_context_with_class(self, mock_get_usages):
self.maxDiff = None
self._stub_get_by_project()
ctxt = FakeContext('other_project', 'other_class')
result = self.driver.get_project_quotas(
FakeContext('other_project', 'other_class'),
quota.QUOTAS._resources, 'test_project', quota_class='test_class')
ctxt, quota.QUOTAS._resources, 'test_project',
quota_class='test_class')
self.assertEqual(self.calls, [
'quota_get_all_by_project',
@ -1584,6 +1746,8 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_class_get_all_by_name',
'quota_class_get_default',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project')
self.assertEqual(result, dict(
instances=dict(
limit=5,
@ -1637,12 +1801,12 @@ class DbQuotaDriverTestCase(test.TestCase):
),
security_group_rules=dict(
limit=20,
in_use=0,
in_use=1,
reserved=0,
),
key_pairs=dict(
limit=100,
in_use=0,
in_use=2,
reserved=0,
),
server_groups=dict(
@ -1652,16 +1816,18 @@ class DbQuotaDriverTestCase(test.TestCase):
),
server_group_members=dict(
limit=10,
in_use=0,
in_use=3,
reserved=0,
),
))
def test_get_user_quotas_no_defaults(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_user_quotas_no_defaults(self, mock_get_usages):
self._stub_get_by_project_and_user()
ctxt = FakeContext('test_project', 'test_class')
result = self.driver.get_user_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', 'fake_user',
ctxt, quota.QUOTAS._resources, 'test_project', 'fake_user',
defaults=False)
self.assertEqual(self.calls, [
@ -1670,6 +1836,9 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_usage_get_all_by_project_and_user',
'quota_class_get_all_by_name',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project',
user_id='fake_user')
self.assertEqual(result, dict(
cores=dict(
limit=10,
@ -1688,11 +1857,13 @@ class DbQuotaDriverTestCase(test.TestCase):
),
))
def test_get_project_quotas_no_defaults(self):
@mock.patch('nova.quota.DbQuotaDriver._get_usages',
side_effect=_get_fake_get_usages())
def test_get_project_quotas_no_defaults(self, mock_get_usages):
self._stub_get_by_project()
ctxt = FakeContext('test_project', 'test_class')
result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', defaults=False)
ctxt, quota.QUOTAS._resources, 'test_project', defaults=False)
self.assertEqual(self.calls, [
'quota_get_all_by_project',
@ -1700,6 +1871,8 @@ class DbQuotaDriverTestCase(test.TestCase):
'quota_class_get_all_by_name',
'quota_class_get_default',
])
mock_get_usages.assert_called_once_with(ctxt, quota.QUOTAS._resources,
'test_project')
self.assertEqual(result, dict(
cores=dict(
limit=10,
@ -2231,6 +2404,94 @@ class DbQuotaDriverTestCase(test.TestCase):
quota.QUOTAS._resources,
dict(metadata_items=128))
def test_limit_check_project_and_user_no_values(self):
self.assertRaises(exception.Invalid,
self.driver.limit_check_project_and_user,
FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources)
def test_limit_check_project_and_user_under(self):
self._stub_get_project_quotas()
ctxt = FakeContext('test_project', 'test_class')
resources = self._get_fake_countable_resources()
# Check: only project_values, only user_values, and then both.
kwargs = [{'project_values': {'fixed_ips': -1}},
{'user_values': {'key_pairs': -1}},
{'project_values': {'instances': -1},
'user_values': {'instances': -1}}]
for kwarg in kwargs:
self.assertRaises(exception.InvalidQuotaValue,
self.driver.limit_check_project_and_user,
ctxt, resources, **kwarg)
def test_limit_check_project_and_user_over_project(self):
# Check the case where user_values pass user quota but project_values
# exceed project quota.
self.flags(instances=5, group='quota')
self._stub_get_project_quotas()
resources = self._get_fake_countable_resources()
self.assertRaises(exception.OverQuota,
self.driver.limit_check_project_and_user,
FakeContext('test_project', 'test_class'),
resources,
project_values=dict(instances=6),
user_values=dict(instances=5))
def test_limit_check_project_and_user_over_user(self):
self.flags(instances=5, group='quota')
self._stub_get_project_quotas()
resources = self._get_fake_countable_resources()
# It's not realistic for user_values to be higher than project_values,
# but this is just for testing the fictional case where project_values
# pass project quota but user_values exceed user quota.
self.assertRaises(exception.OverQuota,
self.driver.limit_check_project_and_user,
FakeContext('test_project', 'test_class'),
resources,
project_values=dict(instances=5),
user_values=dict(instances=6))
def test_limit_check_project_and_user_overs(self):
self._stub_get_project_quotas()
ctxt = FakeContext('test_project', 'test_class')
resources = self._get_fake_countable_resources()
# Check: only project_values, only user_values, and then both.
kwargs = [{'project_values': {'fixed_ips': 10241}},
{'user_values': {'key_pairs': 256}},
{'project_values': {'instances': 512},
'user_values': {'instances': 256}}]
for kwarg in kwargs:
self.assertRaises(exception.OverQuota,
self.driver.limit_check_project_and_user,
ctxt, resources, **kwarg)
def test_limit_check_project_and_user_unlimited(self):
self.flags(fixed_ips=-1, group='quota')
self.flags(key_pairs=-1, group='quota')
self.flags(instances=-1, group='quota')
self._stub_get_project_quotas()
ctxt = FakeContext('test_project', 'test_class')
resources = self._get_fake_countable_resources()
# Check: only project_values, only user_values, and then both.
kwargs = [{'project_values': {'fixed_ips': 32767}},
{'user_values': {'key_pairs': 32767}},
{'project_values': {'instances': 32767},
'user_values': {'instances': 32767}}]
for kwarg in kwargs:
self.driver.limit_check_project_and_user(ctxt, resources, **kwarg)
def test_limit_check_project_and_user(self):
self._stub_get_project_quotas()
ctxt = FakeContext('test_project', 'test_class')
resources = self._get_fake_countable_resources()
# Check: only project_values, only user_values, and then both.
kwargs = [{'project_values': {'fixed_ips': 5}},
{'user_values': {'key_pairs': 5}},
{'project_values': {'instances': 5},
'user_values': {'instances': 5}}]
for kwarg in kwargs:
self.driver.limit_check_project_and_user(ctxt, resources, **kwarg)
def _stub_quota_reserve(self):
def fake_quota_reserve(context, resources, quotas, user_quotas, deltas,
expire, until_refresh, max_age, project_id=None,