Merge "Add check_deltas() and limit_check_project_and_user() to Quotas"
This commit is contained in:
commit
4d7a4979d8
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
435
nova/quota.py
435
nova/quota.py
|
@ -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_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)
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue