Merge "Make Quotas object favor the API database"

This commit is contained in:
Jenkins 2017-07-24 21:31:40 +00:00 committed by Gerrit Code Review
commit 616d78e3ea
9 changed files with 750 additions and 54 deletions

View File

@ -21,8 +21,8 @@ from nova.api.openstack.compute.schemas import quota_classes
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api import validation
from nova import db
from nova import exception
from nova import objects
from nova.policies import quota_class_sets as qcs_policies
from nova import quota
from nova import utils
@ -94,9 +94,9 @@ class QuotaClassSetsController(wsgi.Controller):
for key, value in body['quota_class_set'].items():
try:
db.quota_class_update(context, quota_class, key, value)
objects.Quotas.update_class(context, quota_class, key, value)
except exception.QuotaClassNotFound:
db.quota_class_create(context, quota_class, key, value)
objects.Quotas.create_class(context, quota_class, key, value)
values = QUOTAS.get_class_quotas(context, quota_class)
return self._format_quota_set(None, values, req)

View File

@ -276,11 +276,11 @@ class ProjectCommands(object):
print(_('Quota limit must be less than %s.') % maximum)
return 2
try:
db.quota_create(ctxt, project_id, key, value,
user_id=user_id)
objects.Quotas.create_limit(ctxt, project_id, key, value,
user_id=user_id)
except exception.QuotaExists:
db.quota_update(ctxt, project_id, key, value,
user_id=user_id)
objects.Quotas.update_limit(ctxt, project_id, key, value,
user_id=user_id)
else:
print(_('%(key)s is not a valid quota key. Valid options are: '
'%(options)s.') % {'key': key,

View File

@ -1035,6 +1035,10 @@ class QuotaClassNotFound(QuotaNotFound):
msg_fmt = _("Quota class %(class_name)s could not be found.")
class QuotaClassExists(NovaException):
msg_fmt = _("Quota class %(class_name)s exists for resource %(resource)s")
class QuotaUsageNotFound(QuotaNotFound):
msg_fmt = _("Quota usage for project %(project_id)s could not be found.")

View File

@ -14,7 +14,11 @@
import collections
from oslo_db import exception as db_exc
from nova import db
from nova.db.sqlalchemy import api as db_api
from nova.db.sqlalchemy import api_models
from nova import exception
from nova.objects import base
from nova.objects import fields
@ -71,6 +75,179 @@ class Quotas(base.NovaObject):
self.user_id = None
self.obj_reset_changes()
@staticmethod
@db_api.api_context_manager.reader
def _get_from_db(context, project_id, resource, user_id=None):
model = api_models.ProjectUserQuota if user_id else api_models.Quota
query = context.session.query(model).\
filter_by(project_id=project_id).\
filter_by(resource=resource)
if user_id:
query = query.filter_by(user_id=user_id)
result = query.first()
if not result:
if user_id:
raise exception.ProjectUserQuotaNotFound(project_id=project_id,
user_id=user_id)
else:
raise exception.ProjectQuotaNotFound(project_id=project_id)
return result
@staticmethod
@db_api.api_context_manager.reader
def _get_all_from_db(context, project_id):
return context.session.query(api_models.ProjectUserQuota).\
filter_by(project_id=project_id).\
all()
@staticmethod
@db_api.api_context_manager.reader
def _get_all_from_db_by_project(context, project_id):
# by_project refers to the returned dict that has a 'project_id' key
rows = context.session.query(api_models.Quota).\
filter_by(project_id=project_id).\
all()
result = {'project_id': project_id}
for row in rows:
result[row.resource] = row.hard_limit
return result
@staticmethod
@db_api.api_context_manager.reader
def _get_all_from_db_by_project_and_user(context, project_id, user_id):
# by_project_and_user refers to the returned dict that has
# 'project_id' and 'user_id' keys
columns = (api_models.ProjectUserQuota.resource,
api_models.ProjectUserQuota.hard_limit)
user_quotas = context.session.query(*columns).\
filter_by(project_id=project_id).\
filter_by(user_id=user_id).\
all()
result = {'project_id': project_id, 'user_id': user_id}
for user_quota in user_quotas:
result[user_quota.resource] = user_quota.hard_limit
return result
@staticmethod
@db_api.api_context_manager.writer
def _destroy_all_in_db_by_project(context, project_id):
per_project = context.session.query(api_models.Quota).\
filter_by(project_id=project_id).\
delete(synchronize_session=False)
per_user = context.session.query(api_models.ProjectUserQuota).\
filter_by(project_id=project_id).\
delete(synchronize_session=False)
if not per_project and not per_user:
raise exception.ProjectQuotaNotFound(project_id=project_id)
@staticmethod
@db_api.api_context_manager.writer
def _destroy_all_in_db_by_project_and_user(context, project_id, user_id):
result = context.session.query(api_models.ProjectUserQuota).\
filter_by(project_id=project_id).\
filter_by(user_id=user_id).\
delete(synchronize_session=False)
if not result:
raise exception.ProjectUserQuotaNotFound(project_id=project_id,
user_id=user_id)
@staticmethod
@db_api.api_context_manager.reader
def _get_class_from_db(context, class_name, resource):
result = context.session.query(api_models.QuotaClass).\
filter_by(class_name=class_name).\
filter_by(resource=resource).\
first()
if not result:
raise exception.QuotaClassNotFound(class_name=class_name)
return result
@staticmethod
@db_api.api_context_manager.reader
def _get_all_class_from_db_by_name(context, class_name):
# by_name refers to the returned dict that has a 'class_name' key
rows = context.session.query(api_models.QuotaClass).\
filter_by(class_name=class_name).\
all()
result = {'class_name': class_name}
for row in rows:
result[row.resource] = row.hard_limit
return result
@staticmethod
@db_api.api_context_manager.writer
def _create_limit_in_db(context, project_id, resource, limit,
user_id=None):
# TODO(melwitt): We won't have per project resources after nova-network
# is removed.
per_user = (user_id and
resource not in db_api.quota_get_per_project_resources())
quota_ref = (api_models.ProjectUserQuota() if per_user
else api_models.Quota())
if per_user:
quota_ref.user_id = user_id
quota_ref.project_id = project_id
quota_ref.resource = resource
quota_ref.hard_limit = limit
try:
quota_ref.save(context.session)
except db_exc.DBDuplicateEntry:
raise exception.QuotaExists(project_id=project_id,
resource=resource)
return quota_ref
@staticmethod
@db_api.api_context_manager.writer
def _update_limit_in_db(context, project_id, resource, limit,
user_id=None):
# TODO(melwitt): We won't have per project resources after nova-network
# is removed.
per_user = (user_id and
resource not in db_api.quota_get_per_project_resources())
model = api_models.ProjectUserQuota if per_user else api_models.Quota
query = context.session.query(model).\
filter_by(project_id=project_id).\
filter_by(resource=resource)
if per_user:
query = query.filter_by(user_id=user_id)
result = query.update({'hard_limit': limit})
if not result:
if per_user:
raise exception.ProjectUserQuotaNotFound(project_id=project_id,
user_id=user_id)
else:
raise exception.ProjectQuotaNotFound(project_id=project_id)
@staticmethod
@db_api.api_context_manager.writer
def _create_class_in_db(context, class_name, resource, limit):
# NOTE(melwitt): There's no unique constraint on the QuotaClass model,
# so check for duplicate manually.
try:
Quotas._get_class_from_db(context, class_name, resource)
except exception.QuotaClassNotFound:
pass
else:
raise exception.QuotaClassExists(class_name=class_name,
resource=resource)
quota_class_ref = api_models.QuotaClass()
quota_class_ref.class_name = class_name
quota_class_ref.resource = resource
quota_class_ref.hard_limit = limit
quota_class_ref.save(context.session)
return quota_class_ref
@staticmethod
@db_api.api_context_manager.writer
def _update_class_in_db(context, class_name, resource, limit):
result = context.session.query(api_models.QuotaClass).\
filter_by(class_name=class_name).\
filter_by(resource=resource).\
update({'hard_limit': limit})
if not result:
raise exception.QuotaClassNotFound(class_name=class_name)
@classmethod
def from_reservations(cls, context, reservations, instance=None):
"""Transitional for compatibility."""
@ -207,17 +384,121 @@ class Quotas(base.NovaObject):
@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
# doesn't map very well to objects. Since there is quite a bit of
# logic in the db api layer for this, just pass this through for now.
db.quota_create(context, project_id, resource, limit, user_id=user_id)
try:
db.quota_get(context, project_id, resource, user_id=user_id)
except exception.QuotaNotFound:
cls._create_limit_in_db(context, project_id, resource, limit,
user_id=user_id)
else:
raise exception.QuotaExists(project_id=project_id,
resource=resource)
@base.remotable_classmethod
def update_limit(cls, context, project_id, resource, limit, user_id=None):
# NOTE(danms,comstud): Quotas likely needs an overhaul and currently
# doesn't map very well to objects. Since there is quite a bit of
# logic in the db api layer for this, just pass this through for now.
db.quota_update(context, project_id, resource, limit, user_id=user_id)
try:
cls._update_limit_in_db(context, project_id, resource, limit,
user_id=user_id)
except exception.QuotaNotFound:
db.quota_update(context, project_id, resource, limit,
user_id=user_id)
@classmethod
def create_class(cls, context, class_name, resource, limit):
try:
db.quota_class_get(context, class_name, resource)
except exception.QuotaClassNotFound:
cls._create_class_in_db(context, class_name, resource, limit)
else:
raise exception.QuotaClassExists(class_name=class_name,
resource=resource)
@classmethod
def update_class(cls, context, class_name, resource, limit):
try:
cls._update_class_in_db(context, class_name, resource, limit)
except exception.QuotaClassNotFound:
db.quota_class_update(context, class_name, resource, limit)
# NOTE(melwitt): The following methods are not remotable and return
# dict-like database model objects. We are using classmethods to provide
# a common interface for accessing the api/main databases.
@classmethod
def get(cls, context, project_id, resource, user_id=None):
try:
quota = cls._get_from_db(context, project_id, resource,
user_id=user_id)
except exception.QuotaNotFound:
quota = db.quota_get(context, project_id, resource,
user_id=user_id)
return quota
@classmethod
def get_all(cls, context, project_id):
api_db_quotas = cls._get_all_from_db(context, project_id)
main_db_quotas = db.quota_get_all(context, project_id)
return api_db_quotas + main_db_quotas
@classmethod
def get_all_by_project(cls, context, project_id):
api_db_quotas_dict = cls._get_all_from_db_by_project(context,
project_id)
main_db_quotas_dict = db.quota_get_all_by_project(context, project_id)
for k, v in api_db_quotas_dict.items():
main_db_quotas_dict[k] = v
return main_db_quotas_dict
@classmethod
def get_all_by_project_and_user(cls, context, project_id, user_id):
api_db_quotas_dict = cls._get_all_from_db_by_project_and_user(
context, project_id, user_id)
main_db_quotas_dict = db.quota_get_all_by_project_and_user(
context, project_id, user_id)
for k, v in api_db_quotas_dict.items():
main_db_quotas_dict[k] = v
return main_db_quotas_dict
@classmethod
def destroy_all_by_project(cls, context, project_id):
try:
cls._destroy_all_in_db_by_project(context, project_id)
except exception.ProjectQuotaNotFound:
db.quota_destroy_all_by_project(context, project_id)
@classmethod
def destroy_all_by_project_and_user(cls, context, project_id, user_id):
try:
cls._destroy_all_in_db_by_project_and_user(context, project_id,
user_id)
except exception.ProjectUserQuotaNotFound:
db.quota_destroy_all_by_project_and_user(context, project_id,
user_id)
@classmethod
def get_class(cls, context, class_name, resource):
try:
qclass = cls._get_class_from_db(context, class_name, resource)
except exception.QuotaClassNotFound:
qclass = db.quota_class_get(context, class_name, resource)
return qclass
@classmethod
def get_default_class(cls, context):
try:
qclass = cls._get_all_class_from_db_by_name(
context, db_api._DEFAULT_QUOTA_NAME)
except exception.QuotaClassNotFound:
qclass = db.quota_class_get_default(context)
return qclass
@classmethod
def get_all_class_by_name(cls, context, class_name):
api_db_quotas_dict = cls._get_all_class_from_db_by_name(context,
class_name)
main_db_quotas_dict = db.quota_class_get_all_by_name(context,
class_name)
for k, v in api_db_quotas_dict.items():
main_db_quotas_dict[k] = v
return main_db_quotas_dict
@base.NovaObjectRegistry.register

View File

@ -48,17 +48,18 @@ class DbQuotaDriver(object):
def get_by_project_and_user(self, context, project_id, user_id, resource):
"""Get a specific quota by project and user."""
return db.quota_get(context, project_id, resource, user_id=user_id)
return objects.Quotas.get(context, project_id, resource,
user_id=user_id)
def get_by_project(self, context, project_id, resource):
"""Get a specific quota by project."""
return db.quota_get(context, project_id, resource)
return objects.Quotas.get(context, project_id, resource)
def get_by_class(self, context, quota_class, resource):
"""Get a specific quota by quota class."""
return db.quota_class_get(context, quota_class, resource)
return objects.Quotas.get_class(context, quota_class, resource)
def get_defaults(self, context, resources):
"""Given a list of resources, retrieve the default quotas.
@ -70,7 +71,7 @@ class DbQuotaDriver(object):
"""
quotas = {}
default_quotas = db.quota_class_get_default(context)
default_quotas = objects.Quotas.get_default_class(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
@ -95,7 +96,8 @@ class DbQuotaDriver(object):
"""
quotas = {}
class_quotas = db.quota_class_get_all_by_name(context, quota_class)
class_quotas = objects.Quotas.get_all_class_by_name(context,
quota_class)
for resource in resources.values():
if defaults or resource.name in class_quotas:
quotas[resource.name] = class_quotas.get(resource.name,
@ -114,7 +116,8 @@ class DbQuotaDriver(object):
if project_id == context.project_id:
quota_class = context.quota_class
if quota_class:
class_quotas = db.quota_class_get_all_by_name(context, quota_class)
class_quotas = objects.Quotas.get_all_class_by_name(context,
quota_class)
else:
class_quotas = {}
@ -148,7 +151,7 @@ class DbQuotaDriver(object):
# 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)
all_quotas = objects.Quotas.get_all(context, project_id)
for quota in all_quotas:
if quota.resource in modified_quotas:
modified_quotas[quota.resource]['remains'] -= \
@ -246,11 +249,10 @@ class DbQuotaDriver(object):
if user_quotas:
user_quotas = user_quotas.copy()
else:
user_quotas = db.quota_get_all_by_project_and_user(context,
project_id,
user_id)
user_quotas = objects.Quotas.get_all_by_project_and_user(
context, project_id, user_id)
# Use the project quota for default user quota.
proj_quotas = project_quotas or db.quota_get_all_by_project(
proj_quotas = project_quotas or objects.Quotas.get_all_by_project(
context, project_id)
for key, value in proj_quotas.items():
if key not in user_quotas.keys():
@ -286,7 +288,7 @@ class DbQuotaDriver(object):
will be returned.
:param project_quotas: Quotas dictionary for the specified project.
"""
project_quotas = project_quotas or db.quota_get_all_by_project(
project_quotas = project_quotas or objects.Quotas.get_all_by_project(
context, project_id)
project_usages = {}
if usages:
@ -332,14 +334,13 @@ class DbQuotaDriver(object):
"""
settable_quotas = {}
db_proj_quotas = db.quota_get_all_by_project(context, project_id)
db_proj_quotas = objects.Quotas.get_all_by_project(context, project_id)
project_quotas = self.get_project_quotas(context, resources,
project_id, remains=True,
project_quotas=db_proj_quotas)
if user_id:
setted_quotas = db.quota_get_all_by_project_and_user(context,
project_id,
user_id)
setted_quotas = objects.Quotas.get_all_by_project_and_user(
context, project_id, user_id)
user_quotas = self.get_user_quotas(context, resources,
project_id, user_id,
project_quotas=db_proj_quotas,
@ -491,7 +492,7 @@ class DbQuotaDriver(object):
user_id = context.user_id
# Get the applicable quotas
project_quotas = db.quota_get_all_by_project(context, project_id)
project_quotas = objects.Quotas.get_all_by_project(context, project_id)
quotas = self._get_quotas(context, resources, values.keys(),
project_id=project_id,
project_quotas=project_quotas)
@ -598,7 +599,7 @@ class DbQuotaDriver(object):
# 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)
project_quotas = objects.Quotas.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,
@ -731,7 +732,7 @@ class DbQuotaDriver(object):
# NOTE(Vek): We're not worried about races at this point.
# Yes, the admin may be in the process of reducing
# quotas, but that's a pretty rare thing.
project_quotas = db.quota_get_all_by_project(context, project_id)
project_quotas = objects.Quotas.get_all_by_project(context, project_id)
LOG.debug('Quota limits for project %(project_id)s: '
'%(project_quotas)s', {'project_id': project_id,
'project_quotas': project_quotas})
@ -883,25 +884,24 @@ class DbQuotaDriver(object):
project_id=project_id, user_id=user_id)
def destroy_all_by_project_and_user(self, context, project_id, user_id):
"""Destroy all quotas, usages, and reservations associated with a
project and user.
"""Destroy all quotas associated with a project and user.
:param context: The request context, for access checks.
:param project_id: The ID of the project being deleted.
:param user_id: The ID of the user being deleted.
"""
db.quota_destroy_all_by_project_and_user(context, project_id, user_id)
objects.Quotas.destroy_all_by_project_and_user(context, project_id,
user_id)
def destroy_all_by_project(self, context, project_id):
"""Destroy all quotas, usages, and reservations associated with a
project.
"""Destroy all quotas associated with a project.
:param context: The request context, for access checks.
:param project_id: The ID of the project being deleted.
"""
db.quota_destroy_all_by_project(context, project_id)
objects.Quotas.destroy_all_by_project(context, project_id)
def expire(self, context):
"""Expire reservations.
@ -1215,8 +1215,7 @@ class NoopQuotaDriver(object):
pass
def destroy_all_by_project_and_user(self, context, project_id, user_id):
"""Destroy all quotas, usages, and reservations associated with a
project and user.
"""Destroy all quotas associated with a project and user.
:param context: The request context, for access checks.
:param project_id: The ID of the project being deleted.
@ -1225,8 +1224,7 @@ class NoopQuotaDriver(object):
pass
def destroy_all_by_project(self, context, project_id):
"""Destroy all quotas, usages, and reservations associated with a
project.
"""Destroy all quotas associated with a project.
:param context: The request context, for access checks.
:param project_id: The ID of the project being deleted.
@ -1356,7 +1354,7 @@ class ReservableResource(BaseResource):
class AbsoluteResource(BaseResource):
"""Describe a non-reservable resource."""
"""Describe a resource that does not correspond to database objects."""
valid_method = 'check'

View File

@ -0,0 +1,208 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import context
from nova import exception
from nova.objects import quotas
from nova import test
from nova.tests.unit.db import test_db_api
class QuotasObjectTestCase(test.TestCase,
test_db_api.ModelsObjectComparatorMixin):
def setUp(self):
super(QuotasObjectTestCase, self).setUp()
self.context = context.RequestContext('fake-user', 'fake-project')
def test_create_class(self):
created = quotas.Quotas._create_class_in_db(self.context, 'foo',
'cores', 10)
db_class = quotas.Quotas._get_class_from_db(self.context, 'foo',
'cores')
self._assertEqualObjects(created, db_class)
def test_create_class_exists(self):
quotas.Quotas._create_class_in_db(self.context, 'foo', 'cores', 10)
self.assertRaises(exception.QuotaClassExists,
quotas.Quotas._create_class_in_db, self.context,
'foo', 'cores', 10)
def test_update_class(self):
created = quotas.Quotas._create_class_in_db(self.context, 'foo',
'cores', 10)
quotas.Quotas._update_class_in_db(self.context, 'foo', 'cores', 20)
db_class = quotas.Quotas._get_class_from_db(self.context, 'foo',
'cores')
# Should have a limit of 20 now
created['hard_limit'] = 20
self._assertEqualObjects(created, db_class, ignored_keys='updated_at')
def test_update_class_not_found(self):
self.assertRaises(exception.QuotaClassNotFound,
quotas.Quotas._update_class_in_db, self.context,
'foo', 'cores', 20)
def test_create_per_project_limit(self):
created = quotas.Quotas._create_limit_in_db(self.context,
'fake-project',
'fixed_ips', 10)
db_limit = quotas.Quotas._get_from_db(self.context, 'fake-project',
'fixed_ips')
self._assertEqualObjects(created, db_limit)
def test_create_per_user_limit(self):
created = quotas.Quotas._create_limit_in_db(self.context,
'fake-project', 'cores',
10, user_id='fake-user')
db_limit = quotas.Quotas._get_from_db(self.context, 'fake-project',
'cores', user_id='fake-user')
self._assertEqualObjects(created, db_limit)
def test_create_limit_duplicate(self):
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'cores', 10)
self.assertRaises(exception.QuotaExists,
quotas.Quotas._create_limit_in_db, self.context,
'fake-project', 'cores', 20)
def test_update_per_project_limit(self):
created = quotas.Quotas._create_limit_in_db(self.context,
'fake-project',
'fixed_ips', 10)
quotas.Quotas._update_limit_in_db(self.context, 'fake-project',
'fixed_ips', 20)
db_limit = quotas.Quotas._get_from_db(self.context, 'fake-project',
'fixed_ips')
# Should have a limit of 20 now
created['hard_limit'] = 20
self._assertEqualObjects(created, db_limit, ignored_keys='updated_at')
def test_update_per_project_limit_not_found(self):
self.assertRaises(exception.ProjectQuotaNotFound,
quotas.Quotas._update_limit_in_db, self.context,
'fake-project', 'fixed_ips', 20)
def test_update_per_user_limit(self):
created = quotas.Quotas._create_limit_in_db(self.context,
'fake-project', 'cores',
10, user_id='fake-user')
quotas.Quotas._update_limit_in_db(self.context, 'fake-project',
'cores', 20, user_id='fake-user')
db_limit = quotas.Quotas._get_from_db(self.context, 'fake-project',
'cores', user_id='fake-user')
# Should have a limit of 20 now
created['hard_limit'] = 20
self._assertEqualObjects(created, db_limit, ignored_keys='updated_at')
def test_update_per_user_limit_not_found(self):
self.assertRaises(exception.ProjectUserQuotaNotFound,
quotas.Quotas._update_limit_in_db, self.context,
'fake-project', 'cores', 20, user_id='fake-user')
def test_get_per_project_limit_not_found(self):
self.assertRaises(exception.ProjectQuotaNotFound,
quotas.Quotas._get_from_db, self.context,
'fake-project', 'fixed_ips')
def test_get_per_user_limit_not_found(self):
self.assertRaises(exception.ProjectUserQuotaNotFound,
quotas.Quotas._get_from_db, self.context,
'fake-project', 'cores', user_id='fake-user')
def test_get_all_per_user_limits(self):
created = []
created.append(quotas.Quotas._create_limit_in_db(self.context,
'fake-project',
'cores', 10,
user_id='fake-user'))
created.append(quotas.Quotas._create_limit_in_db(self.context,
'fake-project', 'ram',
8192,
user_id='fake-user'))
db_limits = quotas.Quotas._get_all_from_db(self.context,
'fake-project')
for i, db_limit in enumerate(db_limits):
self._assertEqualObjects(created[i], db_limit)
def test_get_all_per_project_limits_by_project(self):
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'fixed_ips', 20)
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'floating_ips', 10)
limits_dict = quotas.Quotas._get_all_from_db_by_project(self.context,
'fake-project')
self.assertEqual('fake-project', limits_dict['project_id'])
self.assertEqual(20, limits_dict['fixed_ips'])
self.assertEqual(10, limits_dict['floating_ips'])
def test_get_all_per_user_limits_by_project_and_user(self):
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'instances', 5, user_id='fake-user')
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'cores', 10, user_id='fake-user')
limits_dict = quotas.Quotas._get_all_from_db_by_project_and_user(
self.context, 'fake-project', 'fake-user')
self.assertEqual('fake-project', limits_dict['project_id'])
self.assertEqual('fake-user', limits_dict['user_id'])
self.assertEqual(5, limits_dict['instances'])
self.assertEqual(10, limits_dict['cores'])
def test_destroy_per_project_and_per_user_limits(self):
# per user limit
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'instances', 5, user_id='fake-user')
# per project limit
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'fixed_ips', 10)
quotas.Quotas._destroy_all_in_db_by_project(self.context,
'fake-project')
self.assertRaises(exception.ProjectUserQuotaNotFound,
quotas.Quotas._get_from_db, self.context,
'fake-project', 'instances', user_id='fake-user')
self.assertRaises(exception.ProjectQuotaNotFound,
quotas.Quotas._get_from_db, self.context,
'fake-project', 'fixed_ips')
def test_destroy_per_project_and_per_user_limits_not_found(self):
self.assertRaises(exception.ProjectQuotaNotFound,
quotas.Quotas._destroy_all_in_db_by_project,
self.context, 'fake-project')
def test_destroy_per_user_limits(self):
quotas.Quotas._create_limit_in_db(self.context, 'fake-project',
'instances', 5, user_id='fake-user')
quotas.Quotas._destroy_all_in_db_by_project_and_user(self.context,
'fake-project',
'fake-user')
self.assertRaises(exception.ProjectUserQuotaNotFound,
quotas.Quotas._get_from_db, self.context,
'fake-project', 'instances', user_id='fake-user')
def test_destroy_per_user_limits_not_found(self):
self.assertRaises(
exception.ProjectUserQuotaNotFound,
quotas.Quotas._destroy_all_in_db_by_project_and_user,
self.context, 'fake-project', 'fake-user')
def test_get_class_not_found(self):
self.assertRaises(exception.QuotaClassNotFound,
quotas.Quotas._get_class_from_db, self.context,
'foo', 'cores')
def test_get_all_class_by_name(self):
quotas.Quotas._create_class_in_db(self.context, 'foo', 'instances', 5)
quotas.Quotas._create_class_in_db(self.context, 'foo', 'cores', 10)
limits_dict = quotas.Quotas._get_all_class_from_db_by_name(
self.context, 'foo')
self.assertEqual('foo', limits_dict['class_name'])
self.assertEqual(5, limits_dict['instances'])
self.assertEqual(10, limits_dict['cores'])

View File

@ -3232,8 +3232,8 @@ class ServersControllerCreateTest(test.TestCase):
self.assertEqual(encodeutils.safe_decode(robj['Location']), selfhref)
@mock.patch('nova.db.quota_get_all_by_project')
@mock.patch('nova.db.quota_get_all_by_project_and_user')
@mock.patch('nova.objects.Quotas.get_all_by_project')
@mock.patch('nova.objects.Quotas.get_all_by_project_and_user')
@mock.patch('nova.objects.Quotas.count_as_dict')
def _do_test_create_instance_above_quota(self, resource, allowed,
quota, expected_msg, mock_count, mock_get_all_pu,

View File

@ -15,6 +15,7 @@
import mock
from nova import context
from nova.db.sqlalchemy import api as db_api
from nova import exception
from nova.objects import quotas as quotas_obj
from nova import quota
@ -140,14 +141,22 @@ class _TestQuotasObject(object):
quotas.rollback()
self.assertFalse(rollback_mock.called)
@mock.patch('nova.db.quota_create')
def test_create_limit(self, mock_create):
@mock.patch('nova.db.quota_get', side_effect=exception.QuotaNotFound)
@mock.patch('nova.objects.Quotas._create_limit_in_db')
def test_create_limit(self, mock_create, mock_get):
quotas_obj.Quotas.create_limit(self.context, 'fake-project',
'foo', 10, user_id='user')
mock_create.assert_called_once_with(self.context, 'fake-project',
'foo', 10, user_id='user')
@mock.patch('nova.db.quota_update')
@mock.patch('nova.db.quota_get')
@mock.patch('nova.objects.Quotas._create_limit_in_db')
def test_create_limit_exists_in_main(self, mock_create, mock_get):
self.assertRaises(exception.QuotaExists,
quotas_obj.Quotas.create_limit, self.context,
'fake-project', 'foo', 10, user_id='user')
@mock.patch('nova.objects.Quotas._update_limit_in_db')
def test_update_limit(self, mock_update):
quotas_obj.Quotas.update_limit(self.context, 'fake-project',
'foo', 10, user_id='user')
@ -279,6 +288,202 @@ class _TestQuotasObject(object):
user_values={'foo': 2},
user_id='a-user')
@mock.patch('nova.objects.Quotas._update_limit_in_db',
side_effect=exception.QuotaNotFound)
@mock.patch('nova.db.quota_update')
def test_update_limit_main(self, mock_update_main, mock_update):
quotas_obj.Quotas.update_limit(self.context, 'fake-project',
'foo', 10, user_id='user')
mock_update.assert_called_once_with(self.context, 'fake-project',
'foo', 10, user_id='user')
mock_update_main.assert_called_once_with(self.context, 'fake-project',
'foo', 10,
user_id='user')
@mock.patch('nova.objects.Quotas._get_from_db')
def test_get(self, mock_get):
qclass = quotas_obj.Quotas.get(self.context, 'fake-project', 'foo',
user_id='user')
mock_get.assert_called_once_with(self.context, 'fake-project', 'foo',
user_id='user')
self.assertEqual(mock_get.return_value, qclass)
@mock.patch('nova.objects.Quotas._get_from_db',
side_effect=exception.QuotaNotFound)
@mock.patch('nova.db.quota_get')
def test_get_main(self, mock_get_main, mock_get):
quotas_obj.Quotas.get(self.context, 'fake-project', 'foo',
user_id='user')
mock_get.assert_called_once_with(self.context, 'fake-project', 'foo',
user_id='user')
mock_get_main.assert_called_once_with(self.context, 'fake-project',
'foo', user_id='user')
@mock.patch('nova.objects.Quotas._get_all_from_db')
@mock.patch('nova.db.quota_get_all')
def test_get_all(self, mock_get_all_main, mock_get_all):
mock_get_all.return_value = ['api1']
mock_get_all_main.return_value = ['main1', 'main2']
quotas = quotas_obj.Quotas.get_all(self.context, 'fake-project')
mock_get_all.assert_called_once_with(self.context, 'fake-project')
mock_get_all_main.assert_called_once_with(self.context, 'fake-project')
self.assertEqual(['api1', 'main1', 'main2'], quotas)
@mock.patch('nova.objects.Quotas._get_all_from_db_by_project')
@mock.patch('nova.db.quota_get_all_by_project')
def test_get_all_by_project(self, mock_get_all_main, mock_get_all):
mock_get_all.return_value = {'project_id': 'fake-project',
'fixed_ips': 20, 'floating_ips': 5}
mock_get_all_main.return_value = {'project_id': 'fake-project',
'fixed_ips': 10, 'networks': 5}
quotas_dict = quotas_obj.Quotas.get_all_by_project(self.context,
'fake-project')
mock_get_all.assert_called_once_with(self.context, 'fake-project')
mock_get_all_main.assert_called_once_with(self.context, 'fake-project')
expected = {'project_id': 'fake-project', 'fixed_ips': 20,
'floating_ips': 5, 'networks': 5}
self.assertEqual(expected, quotas_dict)
@mock.patch('nova.objects.Quotas._get_all_from_db_by_project_and_user')
@mock.patch('nova.db.quota_get_all_by_project_and_user')
def test_get_all_by_project_and_user(self, mock_get_all_main,
mock_get_all):
mock_get_all.return_value = {'project_id': 'fake-project',
'user_id': 'user', 'instances': 5,
'cores': 10}
mock_get_all_main.return_value = {'project_id': 'fake-project',
'user_id': 'user', 'instances': 10,
'ram': 8192}
quotas_dict = quotas_obj.Quotas.get_all_by_project_and_user(
self.context, 'fake-project', 'user')
mock_get_all.assert_called_once_with(self.context, 'fake-project',
'user')
mock_get_all_main.assert_called_once_with(self.context, 'fake-project',
'user')
expected = {'project_id': 'fake-project', 'user_id': 'user',
'instances': 5, 'cores': 10, 'ram': 8192}
self.assertEqual(expected, quotas_dict)
@mock.patch('nova.objects.Quotas._destroy_all_in_db_by_project')
def test_destroy_all_by_project(self, mock_destroy_all):
quotas_obj.Quotas.destroy_all_by_project(self.context, 'fake-project')
mock_destroy_all.assert_called_once_with(self.context, 'fake-project')
@mock.patch('nova.objects.Quotas._destroy_all_in_db_by_project',
side_effect=exception.ProjectQuotaNotFound(
project_id='fake-project'))
@mock.patch('nova.db.quota_destroy_all_by_project')
def test_destroy_all_by_project_main(self, mock_destroy_all_main,
mock_destroy_all):
quotas_obj.Quotas.destroy_all_by_project(self.context, 'fake-project')
mock_destroy_all.assert_called_once_with(self.context, 'fake-project')
mock_destroy_all_main.assert_called_once_with(self.context,
'fake-project')
@mock.patch('nova.objects.Quotas._destroy_all_in_db_by_project_and_user')
def test_destroy_all_by_project_and_user(self, mock_destroy_all):
quotas_obj.Quotas.destroy_all_by_project_and_user(self.context,
'fake-project',
'user')
mock_destroy_all.assert_called_once_with(self.context, 'fake-project',
'user')
@mock.patch('nova.objects.Quotas._destroy_all_in_db_by_project_and_user',
side_effect=exception.ProjectUserQuotaNotFound(
user_id='user', project_id='fake-project'))
@mock.patch('nova.db.quota_destroy_all_by_project_and_user')
def test_destroy_all_by_project_and_user_main(self, mock_destroy_all_main,
mock_destroy_all):
quotas_obj.Quotas.destroy_all_by_project_and_user(self.context,
'fake-project',
'user')
mock_destroy_all.assert_called_once_with(self.context, 'fake-project',
'user')
mock_destroy_all_main.assert_called_once_with(self.context,
'fake-project', 'user')
@mock.patch('nova.objects.Quotas._get_class_from_db')
def test_get_class(self, mock_get):
qclass = quotas_obj.Quotas.get_class(self.context, 'class', 'resource')
mock_get.assert_called_once_with(self.context, 'class', 'resource')
self.assertEqual(mock_get.return_value, qclass)
@mock.patch('nova.objects.Quotas._get_class_from_db',
side_effect=exception.QuotaClassNotFound(class_name='class'))
@mock.patch('nova.db.quota_class_get')
def test_get_class_main(self, mock_get_main, mock_get):
qclass = quotas_obj.Quotas.get_class(self.context, 'class', 'resource')
mock_get.assert_called_once_with(self.context, 'class', 'resource')
mock_get_main.assert_called_once_with(self.context, 'class',
'resource')
self.assertEqual(mock_get_main.return_value, qclass)
@mock.patch('nova.objects.Quotas._get_all_class_from_db_by_name')
def test_get_default_class(self, mock_get_all):
qclass = quotas_obj.Quotas.get_default_class(self.context)
mock_get_all.assert_called_once_with(self.context,
db_api._DEFAULT_QUOTA_NAME)
self.assertEqual(mock_get_all.return_value, qclass)
@mock.patch('nova.objects.Quotas._get_all_class_from_db_by_name',
side_effect=exception.QuotaClassNotFound(class_name='class'))
@mock.patch('nova.db.quota_class_get_default')
def test_get_default_class_main(self, mock_get_default_main, mock_get_all):
qclass = quotas_obj.Quotas.get_default_class(self.context)
mock_get_all.assert_called_once_with(self.context,
db_api._DEFAULT_QUOTA_NAME)
mock_get_default_main.assert_called_once_with(self.context)
self.assertEqual(mock_get_default_main.return_value, qclass)
@mock.patch('nova.objects.Quotas._get_all_class_from_db_by_name')
@mock.patch('nova.db.quota_class_get_all_by_name')
def test_get_class_by_name(self, mock_get_all_main, mock_get_all):
mock_get_all.return_value = {'class_name': 'foo', 'cores': 10,
'instances': 5}
mock_get_all_main.return_value = {'class_name': 'foo', 'cores': 20,
'fixed_ips': 10}
quotas_dict = quotas_obj.Quotas.get_all_class_by_name(self.context,
'foo')
mock_get_all.assert_called_once_with(self.context, 'foo')
mock_get_all_main.assert_called_once_with(self.context, 'foo')
expected = {'class_name': 'foo', 'cores': 10, 'instances': 5,
'fixed_ips': 10}
self.assertEqual(expected, quotas_dict)
@mock.patch('nova.db.quota_class_get',
side_effect=exception.QuotaClassNotFound(class_name='class'))
@mock.patch('nova.objects.Quotas._create_class_in_db')
def test_create_class(self, mock_create, mock_get):
quotas_obj.Quotas.create_class(self.context, 'class', 'resource',
'limit')
mock_create.assert_called_once_with(self.context, 'class', 'resource',
'limit')
@mock.patch('nova.db.quota_class_get')
@mock.patch('nova.objects.Quotas._create_class_in_db')
def test_create_class_exists_in_main(self, mock_create, mock_get):
self.assertRaises(exception.QuotaClassExists,
quotas_obj.Quotas.create_class, self.context,
'class', 'resource', 'limit')
@mock.patch('nova.objects.Quotas._update_class_in_db')
def test_update_class(self, mock_update):
quotas_obj.Quotas.update_class(self.context, 'class', 'resource',
'limit')
mock_update.assert_called_once_with(self.context, 'class', 'resource',
'limit')
@mock.patch('nova.objects.Quotas._update_class_in_db',
side_effect=exception.QuotaClassNotFound(class_name='class'))
@mock.patch('nova.db.quota_class_update')
def test_update_class_main(self, mock_update_main, mock_update):
quotas_obj.Quotas.update_class(self.context, 'class', 'resource',
'limit')
mock_update.assert_called_once_with(self.context, 'class', 'resource',
'limit')
mock_update_main.assert_called_once_with(self.context, 'class',
'resource', 'limit')
class TestQuotasObject(_TestQuotasObject, test_objects._LocalTest):
pass

View File

@ -908,7 +908,7 @@ class DbQuotaDriverTestCase(test.TestCase):
def _stub_quota_class_get_default(self):
# Stub out quota_class_get_default
def fake_qcgd(context):
def fake_qcgd(cls, context):
self.calls.append('quota_class_get_default')
return dict(
instances=5,
@ -916,11 +916,11 @@ class DbQuotaDriverTestCase(test.TestCase):
metadata_items=64,
injected_file_content_bytes=5 * 1024,
)
self.stub_out('nova.db.quota_class_get_default', fake_qcgd)
self.stub_out('nova.objects.Quotas.get_default_class', fake_qcgd)
def _stub_quota_class_get_all_by_name(self):
# Stub out quota_class_get_all_by_name
def fake_qcgabn(context, quota_class):
def fake_qcgabn(cls, context, quota_class):
self.calls.append('quota_class_get_all_by_name')
self.assertEqual(quota_class, 'test_class')
return dict(
@ -929,7 +929,7 @@ class DbQuotaDriverTestCase(test.TestCase):
metadata_items=64,
injected_file_content_bytes=5 * 1024,
)
self.stub_out('nova.db.quota_class_get_all_by_name', fake_qcgabn)
self.stub_out('nova.objects.Quotas.get_all_class_by_name', fake_qcgabn)
def test_get_class_quotas(self):
self._stub_quota_class_get_all_by_name()