Merge "Quotas by Volume Type"

This commit is contained in:
Jenkins
2013-07-04 17:13:21 +00:00
committed by Gerrit Code Review
6 changed files with 396 additions and 162 deletions

View File

@@ -217,10 +217,12 @@ def volume_data_get_for_host(context, host, session=None):
session) session)
def volume_data_get_for_project(context, project_id, session=None): def volume_data_get_for_project(context, project_id, volume_type_id=None,
session=None):
"""Get (volume_count, gigabytes) for project.""" """Get (volume_count, gigabytes) for project."""
return IMPL.volume_data_get_for_project(context, return IMPL.volume_data_get_for_project(context,
project_id, project_id,
volume_type_id,
session) session)
@@ -317,10 +319,12 @@ def snapshot_update(context, snapshot_id, values):
return IMPL.snapshot_update(context, snapshot_id, values) return IMPL.snapshot_update(context, snapshot_id, values)
def snapshot_data_get_for_project(context, project_id, session=None): def snapshot_data_get_for_project(context, project_id, volume_type_id=None,
session=None):
"""Get count and gigabytes used for snapshots for specified project.""" """Get count and gigabytes used for snapshots for specified project."""
return IMPL.snapshot_data_get_for_project(context, return IMPL.snapshot_data_get_for_project(context,
project_id, project_id,
volume_type_id,
session) session)

View File

@@ -988,14 +988,19 @@ def volume_data_get_for_host(context, host, session=None):
@require_admin_context @require_admin_context
def volume_data_get_for_project(context, project_id, session=None): def volume_data_get_for_project(context, project_id, volume_type_id=None,
result = model_query(context, session=None):
query = model_query(context,
func.count(models.Volume.id), func.count(models.Volume.id),
func.sum(models.Volume.size), func.sum(models.Volume.size),
read_deleted="no", read_deleted="no",
session=session).\ session=session).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id)
first()
if volume_type_id:
query = query.filter_by(volume_type_id=volume_type_id)
result = query.first()
# NOTE(vish): convert None to 0 # NOTE(vish): convert None to 0
return (result[0] or 0, result[1] or 0) return (result[0] or 0, result[1] or 0)
@@ -1284,15 +1289,20 @@ def snapshot_get_all_by_project(context, project_id):
@require_context @require_context
def snapshot_data_get_for_project(context, project_id, session=None): def snapshot_data_get_for_project(context, project_id, volume_type_id=None,
session=None):
authorize_project_context(context, project_id) authorize_project_context(context, project_id)
result = model_query(context, query = model_query(context,
func.count(models.Snapshot.id), func.count(models.Snapshot.id),
func.sum(models.Snapshot.volume_size), func.sum(models.Snapshot.volume_size),
read_deleted="no", read_deleted="no",
session=session).\ session=session).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id)
first()
if volume_type_id:
query = query.join('volume').filter_by(volume_type_id=volume_type_id)
result = query.first()
# NOTE(vish): convert None to 0 # NOTE(vish): convert None to 0
return (result[0] or 0, result[1] or 0) return (result[0] or 0, result[1] or 0)

View File

@@ -23,6 +23,7 @@ import datetime
from oslo.config import cfg from oslo.config import cfg
from cinder import context
from cinder import db from cinder import db
from cinder import exception from cinder import exception
from cinder.openstack.common import importutils from cinder.openstack.common import importutils
@@ -70,15 +71,21 @@ class DbQuotaDriver(object):
database. database.
""" """
def get_by_project(self, context, project_id, resource): def get_by_project(self, context, project_id, resource_name):
"""Get a specific quota by project.""" """Get a specific quota by project."""
return db.quota_get(context, project_id, resource) return db.quota_get(context, project_id, resource_name)
def get_by_class(self, context, quota_class, resource): def get_by_class(self, context, quota_class, resource_name):
"""Get a specific quota by quota class.""" """Get a specific quota by quota class."""
return db.quota_class_get(context, quota_class, resource) return db.quota_class_get(context, quota_class, resource_name)
def get_default(self, context, resource):
"""Get a specific default quota for a resource."""
default_quotas = db.quota_class_get_default(context)
return default_quotas.get(resource.name, resource.default)
def get_defaults(self, context, resources): def get_defaults(self, context, resources):
"""Given a list of resources, retrieve the default quotas. """Given a list of resources, retrieve the default quotas.
@@ -121,10 +128,17 @@ class DbQuotaDriver(object):
""" """
quotas = {} quotas = {}
default_quotas = {}
class_quotas = db.quota_class_get_all_by_name(context, quota_class) class_quotas = db.quota_class_get_all_by_name(context, quota_class)
if defaults:
default_quotas = db.quota_class_get_default(context)
for resource in resources.values(): for resource in resources.values():
if defaults or resource.name in class_quotas: if resource.name in class_quotas:
quotas[resource.name] = class_quotas.get(resource.name, quotas[resource.name] = class_quotas[resource.name]
continue
if defaults:
quotas[resource.name] = default_quotas.get(resource.name,
resource.default) resource.default)
return quotas return quotas
@@ -460,7 +474,7 @@ class BaseResource(object):
pass pass
# OK, return the default # OK, return the default
return self.default return driver.get_default(context, self)
@property @property
def default(self): def default(self):
@@ -551,6 +565,64 @@ class CountableResource(AbsoluteResource):
self.count = count self.count = count
class VolumeTypeResource(ReservableResource):
"""ReservableResource for a specific volume type."""
def __init__(self, part_name, volume_type):
"""
Initializes a VolumeTypeResource.
:param part_name: The kind of resource, i.e., "volumes".
:param volume_type: The volume type for this resource.
"""
try:
method = getattr(self, '_sync_%s' % part_name)
except AttributeError:
raise ValueError('Invalid resource: %s' % part_name)
self.volume_type_name = volume_type['name']
self.volume_type_id = volume_type['id']
name = "%s_%s" % (part_name, self.volume_type_name)
super(VolumeTypeResource, self).__init__(name, method)
def _sync_snapshots(self, context, project_id, session):
"""Sync snapshots for this specific volume type."""
(snapshots, gigs) = db.snapshot_data_get_for_project(
context,
project_id,
volume_type_id=self.volume_type_id,
session=session)
return {'snapshots_%s' % self.volume_type_name: snapshots}
def _sync_volumes(self, context, project_id, session):
"""Sync volumes for this specific volume type."""
(volumes, gigs) = db.volume_data_get_for_project(
context,
project_id,
volume_type_id=self.volume_type_id,
session=session)
return {'volumes_%s' % self.volume_type_name: volumes}
def _sync_gigabytes(self, context, project_id, session):
"""Sync gigabytes for this specific volume type."""
key = 'gigabytes_%s' % self.volume_type_name
(_junk, vol_gigs) = db.volume_data_get_for_project(
context,
project_id,
volume_type_id=self.volume_type_id,
session=session)
if CONF.no_snapshot_gb_quota:
return {key: vol_gigs}
(_junk, snap_gigs) = db.snapshot_data_get_for_project(
context,
project_id,
volume_type_id=self.volume_type_id,
session=session)
return {key: vol_gigs + snap_gigs}
class QuotaEngine(object): class QuotaEngine(object):
"""Represent the set of recognized quotas.""" """Represent the set of recognized quotas."""
@@ -567,7 +639,7 @@ class QuotaEngine(object):
self._driver = quota_driver_class self._driver = quota_driver_class
def __contains__(self, resource): def __contains__(self, resource):
return resource in self._resources return resource in self.resources
def register_resource(self, resource): def register_resource(self, resource):
"""Register a resource.""" """Register a resource."""
@@ -580,15 +652,20 @@ class QuotaEngine(object):
for resource in resources: for resource in resources:
self.register_resource(resource) self.register_resource(resource)
def get_by_project(self, context, project_id, resource): def get_by_project(self, context, project_id, resource_name):
"""Get a specific quota by project.""" """Get a specific quota by project."""
return self._driver.get_by_project(context, project_id, resource) return self._driver.get_by_project(context, project_id, resource_name)
def get_by_class(self, context, quota_class, resource): def get_by_class(self, context, quota_class, resource_name):
"""Get a specific quota by quota class.""" """Get a specific quota by quota class."""
return self._driver.get_by_class(context, quota_class, resource) return self._driver.get_by_class(context, quota_class, resource_name)
def get_default(self, context, resource):
"""Get a specific default quota for a resource."""
return self._driver.get_default(context, resource)
def get_defaults(self, context): def get_defaults(self, context):
"""Retrieve the default quotas. """Retrieve the default quotas.
@@ -596,7 +673,7 @@ class QuotaEngine(object):
:param context: The request context, for access checks. :param context: The request context, for access checks.
""" """
return self._driver.get_defaults(context, self._resources) return self._driver.get_defaults(context, self.resources)
def get_class_quotas(self, context, quota_class, defaults=True): def get_class_quotas(self, context, quota_class, defaults=True):
"""Retrieve the quotas for the given quota class. """Retrieve the quotas for the given quota class.
@@ -609,7 +686,7 @@ class QuotaEngine(object):
resource. resource.
""" """
return self._driver.get_class_quotas(context, self._resources, return self._driver.get_class_quotas(context, self.resources,
quota_class, defaults=defaults) quota_class, defaults=defaults)
def get_project_quotas(self, context, project_id, quota_class=None, def get_project_quotas(self, context, project_id, quota_class=None,
@@ -629,7 +706,7 @@ class QuotaEngine(object):
will also be returned. will also be returned.
""" """
return self._driver.get_project_quotas(context, self._resources, return self._driver.get_project_quotas(context, self.resources,
project_id, project_id,
quota_class=quota_class, quota_class=quota_class,
defaults=defaults, defaults=defaults,
@@ -648,7 +725,7 @@ class QuotaEngine(object):
""" """
# Get the resource # Get the resource
res = self._resources.get(resource) res = self.resources.get(resource)
if not res or not hasattr(res, 'count'): if not res or not hasattr(res, 'count'):
raise exception.QuotaResourceUnknown(unknown=[resource]) raise exception.QuotaResourceUnknown(unknown=[resource])
@@ -679,7 +756,7 @@ class QuotaEngine(object):
common user's tenant. common user's tenant.
""" """
return self._driver.limit_check(context, self._resources, values, return self._driver.limit_check(context, self.resources, values,
project_id=project_id) project_id=project_id)
def reserve(self, context, expire=None, project_id=None, **deltas): def reserve(self, context, expire=None, project_id=None, **deltas):
@@ -717,7 +794,7 @@ class QuotaEngine(object):
common user's tenant. common user's tenant.
""" """
reservations = self._driver.reserve(context, self._resources, deltas, reservations = self._driver.reserve(context, self.resources, deltas,
expire=expire, expire=expire,
project_id=project_id) project_id=project_id)
@@ -788,9 +865,64 @@ class QuotaEngine(object):
self._driver.expire(context) self._driver.expire(context)
def add_volume_type_opts(self, context, opts, volume_type_id):
"""Add volume type resource options.
Adds elements to the opts hash for volume type quotas.
If a resource is being reserved ('gigabytes', etc) and the volume
type is set up for its own quotas, these reservations are copied
into keys for 'gigabytes_<volume type name>', etc.
:param context: The request context, for access checks.
:param opts: The reservations options hash.
:param volume_type_id: The volume type id for this reservation.
"""
if not volume_type_id:
return
volume_type = db.volume_type_get(context, volume_type_id)
for quota in ('volumes', 'gigabytes', 'snapshots'):
if quota in opts:
vtype_quota = "%s_%s" % (quota, volume_type['name'])
opts[vtype_quota] = opts[quota]
@property
def resource_names(self):
return sorted(self.resources.keys())
@property @property
def resources(self): def resources(self):
return sorted(self._resources.keys()) return self._resources
class VolumeTypeQuotaEngine(QuotaEngine):
"""Represent the set of all quotas."""
@property
def resources(self):
"""Fetches all possible quota resources."""
result = {}
# Global quotas.
argses = [('volumes', _sync_volumes, 'quota_volumes'),
('snapshots', _sync_snapshots, 'quota_snapshots'),
('gigabytes', _sync_gigabytes, 'quota_gigabytes'), ]
for args in argses:
resource = ReservableResource(*args)
result[resource.name] = resource
# Volume type quotas.
volume_types = db.volume_type_get_all(context.get_admin_context())
for volume_type in volume_types.values():
for part_name in ('volumes', 'gigabytes', 'snapshots'):
resource = VolumeTypeResource(part_name, volume_type)
result[resource.name] = resource
return result
def register_resource(self, resource):
raise NotImplementedError(_("Cannot register resource"))
def register_resources(self, resources):
raise NotImplementedError(_("Cannot register resources"))
def _sync_volumes(context, project_id, session): def _sync_volumes(context, project_id, session):
@@ -820,13 +952,4 @@ def _sync_gigabytes(context, project_id, session):
return {'gigabytes': vol_gigs + snap_gigs} return {'gigabytes': vol_gigs + snap_gigs}
QUOTAS = QuotaEngine() QUOTAS = VolumeTypeQuotaEngine()
resources = [
ReservableResource('volumes', _sync_volumes, 'quota_volumes'),
ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'),
ReservableResource('gigabytes', _sync_gigabytes, 'quota_gigabytes'), ]
QUOTAS.register_resources(resources)

View File

@@ -41,13 +41,15 @@ class QuotaIntegrationTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(QuotaIntegrationTestCase, self).setUp() super(QuotaIntegrationTestCase, self).setUp()
self.volume_type_name = CONF.default_volume_type
self.volume_type = db.volume_type_create(
context.get_admin_context(),
dict(name=self.volume_type_name))
self.flags(quota_volumes=2, self.flags(quota_volumes=2,
quota_snapshots=2, quota_snapshots=2,
quota_gigabytes=20) quota_gigabytes=20)
# Apparently needed by the RPC tests...
#self.network = self.start_service('network')
self.user_id = 'admin' self.user_id = 'admin'
self.project_id = 'admin' self.project_id = 'admin'
self.context = context.RequestContext(self.user_id, self.context = context.RequestContext(self.user_id,
@@ -61,16 +63,19 @@ class QuotaIntegrationTestCase(test.TestCase):
self.stubs.Set(rpc, 'call', rpc_call_wrapper) self.stubs.Set(rpc, 'call', rpc_call_wrapper)
def tearDown(self): def tearDown(self):
db.volume_type_destroy(context.get_admin_context(),
self.volume_type['id'])
super(QuotaIntegrationTestCase, self).tearDown() super(QuotaIntegrationTestCase, self).tearDown()
cinder.tests.image.fake.FakeImageService_reset() cinder.tests.image.fake.FakeImageService_reset()
def _create_volume(self, size=10): def _create_volume(self, size=1):
"""Create a test volume.""" """Create a test volume."""
vol = {} vol = {}
vol['user_id'] = self.user_id vol['user_id'] = self.user_id
vol['project_id'] = self.project_id vol['project_id'] = self.project_id
vol['size'] = size vol['size'] = size
vol['status'] = 'available' vol['status'] = 'available'
vol['volume_type_id'] = self.volume_type['id']
return db.volume_create(self.context, vol) return db.volume_create(self.context, vol)
def _create_snapshot(self, volume): def _create_snapshot(self, volume):
@@ -87,19 +92,52 @@ class QuotaIntegrationTestCase(test.TestCase):
for i in range(CONF.quota_volumes): for i in range(CONF.quota_volumes):
vol_ref = self._create_volume() vol_ref = self._create_volume()
volume_ids.append(vol_ref['id']) volume_ids.append(vol_ref['id'])
self.assertRaises(exception.QuotaError, self.assertRaises(exception.VolumeLimitExceeded,
volume.API().create, volume.API().create,
self.context, 10, '', '', None) self.context, 1, '', '',
volume_type=self.volume_type)
for volume_id in volume_ids: for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id) db.volume_destroy(self.context, volume_id)
def test_too_many_volumes_of_type(self):
resource = 'volumes_%s' % self.volume_type_name
db.quota_class_create(self.context, 'default', resource, 1)
flag_args = {
'quota_volumes': 2000,
'quota_gigabytes': 2000
}
self.flags(**flag_args)
vol_ref = self._create_volume()
self.assertRaises(exception.VolumeLimitExceeded,
volume.API().create,
self.context, 1, '', '',
volume_type=self.volume_type)
db.volume_destroy(self.context, vol_ref['id'])
def test_too_many_snapshots_of_type(self):
resource = 'snapshots_%s' % self.volume_type_name
db.quota_class_create(self.context, 'default', resource, 1)
flag_args = {
'quota_volumes': 2000,
'quota_gigabytes': 2000,
}
self.flags(**flag_args)
vol_ref = self._create_volume()
snap_ref = self._create_snapshot(vol_ref)
self.assertRaises(exception.SnapshotLimitExceeded,
volume.API().create_snapshot,
self.context, vol_ref, '', '')
db.snapshot_destroy(self.context, snap_ref['id'])
db.volume_destroy(self.context, vol_ref['id'])
def test_too_many_gigabytes(self): def test_too_many_gigabytes(self):
volume_ids = [] volume_ids = []
vol_ref = self._create_volume(size=20) vol_ref = self._create_volume(size=20)
volume_ids.append(vol_ref['id']) volume_ids.append(vol_ref['id'])
self.assertRaises(exception.QuotaError, self.assertRaises(exception.VolumeSizeExceedsAvailableQuota,
volume.API().create, volume.API().create,
self.context, 10, '', '', None) self.context, 1, '', '',
volume_type=self.volume_type)
for volume_id in volume_ids: for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id) db.volume_destroy(self.context, volume_id)
@@ -131,8 +169,6 @@ class QuotaIntegrationTestCase(test.TestCase):
self.assertEqual(reservations.get('gigabytes'), None) self.assertEqual(reservations.get('gigabytes'), None)
# Make sure the snapshot volume_size isn't included in usage. # Make sure the snapshot volume_size isn't included in usage.
vol_type = db.volume_type_create(self.context,
dict(name=CONF.default_volume_type))
vol_ref2 = volume.API().create(self.context, 10, '', '') vol_ref2 = volume.API().create(self.context, 10, '', '')
usages = db.quota_usage_get_all_by_project(self.context, usages = db.quota_usage_get_all_by_project(self.context,
self.project_id) self.project_id)
@@ -142,7 +178,21 @@ class QuotaIntegrationTestCase(test.TestCase):
db.snapshot_destroy(self.context, snap_ref2['id']) db.snapshot_destroy(self.context, snap_ref2['id'])
db.volume_destroy(self.context, vol_ref['id']) db.volume_destroy(self.context, vol_ref['id'])
db.volume_destroy(self.context, vol_ref2['id']) db.volume_destroy(self.context, vol_ref2['id'])
db.volume_type_destroy(self.context, vol_type['id'])
def test_too_many_gigabytes_of_type(self):
resource = 'gigabytes_%s' % self.volume_type_name
db.quota_class_create(self.context, 'default', resource, 10)
flag_args = {
'quota_volumes': 2000,
'quota_gigabytes': 2000,
}
self.flags(**flag_args)
vol_ref = self._create_volume(size=10)
self.assertRaises(exception.VolumeSizeExceedsAvailableQuota,
volume.API().create,
self.context, 1, '', '',
volume_type=self.volume_type)
db.volume_destroy(self.context, vol_ref['id'])
class FakeContext(object): class FakeContext(object):
@@ -179,6 +229,10 @@ class FakeDriver(object):
except KeyError: except KeyError:
raise exception.QuotaClassNotFound(class_name=quota_class) raise exception.QuotaClassNotFound(class_name=quota_class)
def get_default(self, context, resource):
self.called.append(('get_default', context, resource))
return resource.default
def get_defaults(self, context, resources): def get_defaults(self, context, resources):
self.called.append(('get_defaults', context, resources)) self.called.append(('get_defaults', context, resources))
return resources return resources
@@ -310,24 +364,35 @@ class BaseResourceTestCase(test.TestCase):
self.assertEqual(quota_value, 20) self.assertEqual(quota_value, 20)
class VolumeTypeResourceTestCase(test.TestCase):
def test_name_and_flag(self):
volume_type_name = 'foo'
volume = {'name': volume_type_name, 'id': 'myid'}
resource = quota.VolumeTypeResource('volumes', volume)
self.assertEqual(resource.name, 'volumes_%s' % volume_type_name)
self.assertEqual(resource.flag, None)
self.assertEqual(resource.default, -1)
class QuotaEngineTestCase(test.TestCase): class QuotaEngineTestCase(test.TestCase):
def test_init(self): def test_init(self):
quota_obj = quota.QuotaEngine() quota_obj = quota.QuotaEngine()
self.assertEqual(quota_obj._resources, {}) self.assertEqual(quota_obj.resources, {})
self.assertTrue(isinstance(quota_obj._driver, quota.DbQuotaDriver)) self.assertTrue(isinstance(quota_obj._driver, quota.DbQuotaDriver))
def test_init_override_string(self): def test_init_override_string(self):
quota_obj = quota.QuotaEngine( quota_obj = quota.QuotaEngine(
quota_driver_class='cinder.tests.test_quota.FakeDriver') quota_driver_class='cinder.tests.test_quota.FakeDriver')
self.assertEqual(quota_obj._resources, {}) self.assertEqual(quota_obj.resources, {})
self.assertTrue(isinstance(quota_obj._driver, FakeDriver)) self.assertTrue(isinstance(quota_obj._driver, FakeDriver))
def test_init_override_obj(self): def test_init_override_obj(self):
quota_obj = quota.QuotaEngine(quota_driver_class=FakeDriver) quota_obj = quota.QuotaEngine(quota_driver_class=FakeDriver)
self.assertEqual(quota_obj._resources, {}) self.assertEqual(quota_obj.resources, {})
self.assertEqual(quota_obj._driver, FakeDriver) self.assertEqual(quota_obj._driver, FakeDriver)
def test_register_resource(self): def test_register_resource(self):
@@ -335,7 +400,7 @@ class QuotaEngineTestCase(test.TestCase):
resource = quota.AbsoluteResource('test_resource') resource = quota.AbsoluteResource('test_resource')
quota_obj.register_resource(resource) quota_obj.register_resource(resource)
self.assertEqual(quota_obj._resources, dict(test_resource=resource)) self.assertEqual(quota_obj.resources, dict(test_resource=resource))
def test_register_resources(self): def test_register_resources(self):
quota_obj = quota.QuotaEngine() quota_obj = quota.QuotaEngine()
@@ -345,7 +410,7 @@ class QuotaEngineTestCase(test.TestCase):
quota.AbsoluteResource('test_resource3'), ] quota.AbsoluteResource('test_resource3'), ]
quota_obj.register_resources(resources) quota_obj.register_resources(resources)
self.assertEqual(quota_obj._resources, self.assertEqual(quota_obj.resources,
dict(test_resource1=resources[0], dict(test_resource1=resources[0],
test_resource2=resources[1], test_resource2=resources[1],
test_resource3=resources[2], )) test_resource3=resources[2], ))
@@ -428,8 +493,8 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [('get_defaults', self.assertEqual(driver.called, [('get_defaults',
context, context,
quota_obj._resources), ]) quota_obj.resources), ])
self.assertEqual(result, quota_obj._resources) self.assertEqual(result, quota_obj.resources)
def test_get_class_quotas(self): def test_get_class_quotas(self):
context = FakeContext(None, None) context = FakeContext(None, None)
@@ -441,13 +506,13 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [ self.assertEqual(driver.called, [
('get_class_quotas', ('get_class_quotas',
context, context,
quota_obj._resources, quota_obj.resources,
'test_class', True), 'test_class', True),
('get_class_quotas', ('get_class_quotas',
context, quota_obj._resources, context, quota_obj.resources,
'test_class', False), ]) 'test_class', False), ])
self.assertEqual(result1, quota_obj._resources) self.assertEqual(result1, quota_obj.resources)
self.assertEqual(result2, quota_obj._resources) self.assertEqual(result2, quota_obj.resources)
def test_get_project_quotas(self): def test_get_project_quotas(self):
context = FakeContext(None, None) context = FakeContext(None, None)
@@ -462,20 +527,20 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [ self.assertEqual(driver.called, [
('get_project_quotas', ('get_project_quotas',
context, context,
quota_obj._resources, quota_obj.resources,
'test_project', 'test_project',
None, None,
True, True,
True), True),
('get_project_quotas', ('get_project_quotas',
context, context,
quota_obj._resources, quota_obj.resources,
'test_project', 'test_project',
'test_class', 'test_class',
False, False,
False), ]) False), ])
self.assertEqual(result1, quota_obj._resources) self.assertEqual(result1, quota_obj.resources)
self.assertEqual(result2, quota_obj._resources) self.assertEqual(result2, quota_obj.resources)
def test_count_no_resource(self): def test_count_no_resource(self):
context = FakeContext(None, None) context = FakeContext(None, None)
@@ -518,7 +583,7 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [ self.assertEqual(driver.called, [
('limit_check', ('limit_check',
context, context,
quota_obj._resources, quota_obj.resources,
dict( dict(
test_resource1=4, test_resource1=4,
test_resource2=3, test_resource2=3,
@@ -546,7 +611,7 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [ self.assertEqual(driver.called, [
('reserve', ('reserve',
context, context,
quota_obj._resources, quota_obj.resources,
dict( dict(
test_resource1=4, test_resource1=4,
test_resource2=3, test_resource2=3,
@@ -556,7 +621,7 @@ class QuotaEngineTestCase(test.TestCase):
None), None),
('reserve', ('reserve',
context, context,
quota_obj._resources, quota_obj.resources,
dict( dict(
test_resource1=1, test_resource1=1,
test_resource2=2, test_resource2=2,
@@ -566,7 +631,7 @@ class QuotaEngineTestCase(test.TestCase):
None), None),
('reserve', ('reserve',
context, context,
quota_obj._resources, quota_obj.resources,
dict( dict(
test_resource1=1, test_resource1=1,
test_resource2=2, test_resource2=2,
@@ -634,14 +699,33 @@ class QuotaEngineTestCase(test.TestCase):
self.assertEqual(driver.called, [('expire', context), ]) self.assertEqual(driver.called, [('expire', context), ])
def test_resources(self): def test_resource_names(self):
quota_obj = self._make_quota_obj(None) quota_obj = self._make_quota_obj(None)
self.assertEqual(quota_obj.resources, self.assertEqual(quota_obj.resource_names,
['test_resource1', 'test_resource2', ['test_resource1', 'test_resource2',
'test_resource3', 'test_resource4']) 'test_resource3', 'test_resource4'])
class VolumeTypeQuotaEngineTestCase(test.TestCase):
def test_default_resources(self):
engine = quota.VolumeTypeQuotaEngine()
self.assertEqual(engine.resource_names,
['gigabytes', 'snapshots', 'volumes'])
def test_volume_type_resources(self):
ctx = context.RequestContext('admin', 'admin', is_admin=True)
vtype = db.volume_type_create(ctx, {'name': 'type1'})
vtype2 = db.volume_type_create(ctx, {'name': 'type_2'})
engine = quota.VolumeTypeQuotaEngine()
self.assertEqual(engine.resource_names,
['gigabytes', 'gigabytes_type1', 'gigabytes_type_2',
'snapshots', 'snapshots_type1', 'snapshots_type_2',
'volumes', 'volumes_type1', 'volumes_type_2'])
db.volume_type_destroy(ctx, vtype['id'])
db.volume_type_destroy(ctx, vtype2['id'])
class DbQuotaDriverTestCase(test.TestCase): class DbQuotaDriverTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(DbQuotaDriverTestCase, self).setUp() super(DbQuotaDriverTestCase, self).setUp()
@@ -667,7 +751,7 @@ class DbQuotaDriverTestCase(test.TestCase):
def test_get_defaults(self): def test_get_defaults(self):
# Use our pre-defined resources # Use our pre-defined resources
self._stub_quota_class_get_default() self._stub_quota_class_get_default()
result = self.driver.get_defaults(None, quota.QUOTAS._resources) result = self.driver.get_defaults(None, quota.QUOTAS.resources)
self.assertEqual( self.assertEqual(
result, result,
@@ -695,7 +779,7 @@ class DbQuotaDriverTestCase(test.TestCase):
def test_get_class_quotas(self): def test_get_class_quotas(self):
self._stub_quota_class_get_all_by_name() self._stub_quota_class_get_all_by_name()
result = self.driver.get_class_quotas(None, quota.QUOTAS._resources, result = self.driver.get_class_quotas(None, quota.QUOTAS.resources,
'test_class') 'test_class')
self.assertEqual(self.calls, ['quota_class_get_all_by_name']) self.assertEqual(self.calls, ['quota_class_get_all_by_name'])
@@ -705,7 +789,7 @@ class DbQuotaDriverTestCase(test.TestCase):
def test_get_class_quotas_no_defaults(self): def test_get_class_quotas_no_defaults(self):
self._stub_quota_class_get_all_by_name() self._stub_quota_class_get_all_by_name()
result = self.driver.get_class_quotas(None, quota.QUOTAS._resources, result = self.driver.get_class_quotas(None, quota.QUOTAS.resources,
'test_class', False) 'test_class', False)
self.assertEqual(self.calls, ['quota_class_get_all_by_name']) self.assertEqual(self.calls, ['quota_class_get_all_by_name'])
@@ -736,7 +820,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_by_project() self._stub_get_by_project()
result = self.driver.get_project_quotas( result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'), FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project') quota.QUOTAS.resources, 'test_project')
self.assertEqual(self.calls, ['quota_get_all_by_project', self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_usage_get_all_by_project', 'quota_usage_get_all_by_project',
@@ -756,7 +840,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_by_project() self._stub_get_by_project()
result = self.driver.get_project_quotas( result = self.driver.get_project_quotas(
FakeContext('other_project', 'other_class'), FakeContext('other_project', 'other_class'),
quota.QUOTAS._resources, 'test_project') quota.QUOTAS.resources, 'test_project')
self.assertEqual(self.calls, ['quota_get_all_by_project', self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_usage_get_all_by_project', 'quota_usage_get_all_by_project',
@@ -775,7 +859,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_by_project() self._stub_get_by_project()
result = self.driver.get_project_quotas( result = self.driver.get_project_quotas(
FakeContext('other_project', 'other_class'), FakeContext('other_project', 'other_class'),
quota.QUOTAS._resources, 'test_project', quota_class='test_class') quota.QUOTAS.resources, 'test_project', quota_class='test_class')
self.assertEqual(self.calls, ['quota_get_all_by_project', self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_usage_get_all_by_project', 'quota_usage_get_all_by_project',
@@ -795,7 +879,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_by_project() self._stub_get_by_project()
result = self.driver.get_project_quotas( result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'), FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', defaults=False) quota.QUOTAS.resources, 'test_project', defaults=False)
self.assertEqual(self.calls, ['quota_get_all_by_project', self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_usage_get_all_by_project', 'quota_usage_get_all_by_project',
@@ -816,7 +900,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_by_project() self._stub_get_by_project()
result = self.driver.get_project_quotas( result = self.driver.get_project_quotas(
FakeContext('test_project', 'test_class'), FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, 'test_project', usages=False) quota.QUOTAS.resources, 'test_project', usages=False)
self.assertEqual(self.calls, ['quota_get_all_by_project', self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_class_get_all_by_name', 'quota_class_get_all_by_name',
@@ -840,7 +924,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self.assertRaises(exception.QuotaResourceUnknown, self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas, self.driver._get_quotas,
None, quota.QUOTAS._resources, None, quota.QUOTAS.resources,
['unknown'], True) ['unknown'], True)
self.assertEqual(self.calls, []) self.assertEqual(self.calls, [])
@@ -848,7 +932,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self.assertRaises(exception.QuotaResourceUnknown, self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas, self.driver._get_quotas,
None, quota.QUOTAS._resources, None, quota.QUOTAS.resources,
['unknown'], False) ['unknown'], False)
self.assertEqual(self.calls, []) self.assertEqual(self.calls, [])
@@ -856,7 +940,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self.assertRaises(exception.QuotaResourceUnknown, self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas, self.driver._get_quotas,
None, quota.QUOTAS._resources, None, quota.QUOTAS.resources,
['metadata_items'], True) ['metadata_items'], True)
self.assertEqual(self.calls, []) self.assertEqual(self.calls, [])
@@ -864,7 +948,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self.assertRaises(exception.QuotaResourceUnknown, self.assertRaises(exception.QuotaResourceUnknown,
self.driver._get_quotas, self.driver._get_quotas,
None, quota.QUOTAS._resources, None, quota.QUOTAS.resources,
['volumes'], False) ['volumes'], False)
self.assertEqual(self.calls, []) self.assertEqual(self.calls, [])
@@ -872,7 +956,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
result = self.driver._get_quotas(FakeContext('test_project', result = self.driver._get_quotas(FakeContext('test_project',
'test_class'), 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
['volumes', 'gigabytes'], ['volumes', 'gigabytes'],
True) True)
@@ -893,7 +977,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.assertRaises(exception.InvalidReservationExpiration, self.assertRaises(exception.InvalidReservationExpiration,
self.driver.reserve, self.driver.reserve,
FakeContext('test_project', 'test_class'), FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire='invalid') dict(volumes=2), expire='invalid')
self.assertEqual(self.calls, []) self.assertEqual(self.calls, [])
@@ -901,7 +985,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self._stub_quota_reserve() self._stub_quota_reserve()
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2)) dict(volumes=2))
expire = timeutils.utcnow() + datetime.timedelta(seconds=86400) expire = timeutils.utcnow() + datetime.timedelta(seconds=86400)
@@ -913,7 +997,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_get_project_quotas() self._stub_get_project_quotas()
self._stub_quota_reserve() self._stub_quota_reserve()
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire=3600) dict(volumes=2), expire=3600)
expire = timeutils.utcnow() + datetime.timedelta(seconds=3600) expire = timeutils.utcnow() + datetime.timedelta(seconds=3600)
@@ -926,7 +1010,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_quota_reserve() self._stub_quota_reserve()
expire_delta = datetime.timedelta(seconds=60) expire_delta = datetime.timedelta(seconds=60)
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire=expire_delta) dict(volumes=2), expire=expire_delta)
expire = timeutils.utcnow() + expire_delta expire = timeutils.utcnow() + expire_delta
@@ -939,7 +1023,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self._stub_quota_reserve() self._stub_quota_reserve()
expire = timeutils.utcnow() + datetime.timedelta(seconds=120) expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire=expire) dict(volumes=2), expire=expire)
self.assertEqual(self.calls, ['get_project_quotas', self.assertEqual(self.calls, ['get_project_quotas',
@@ -952,7 +1036,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.flags(until_refresh=500) self.flags(until_refresh=500)
expire = timeutils.utcnow() + datetime.timedelta(seconds=120) expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire=expire) dict(volumes=2), expire=expire)
self.assertEqual(self.calls, ['get_project_quotas', self.assertEqual(self.calls, ['get_project_quotas',
@@ -965,7 +1049,7 @@ class DbQuotaDriverTestCase(test.TestCase):
self.flags(max_age=86400) self.flags(max_age=86400)
expire = timeutils.utcnow() + datetime.timedelta(seconds=120) expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
result = self.driver.reserve(FakeContext('test_project', 'test_class'), result = self.driver.reserve(FakeContext('test_project', 'test_class'),
quota.QUOTAS._resources, quota.QUOTAS.resources,
dict(volumes=2), expire=expire) dict(volumes=2), expire=expire)
self.assertEqual(self.calls, ['get_project_quotas', self.assertEqual(self.calls, ['get_project_quotas',

View File

@@ -159,8 +159,18 @@ class API(base.Base):
msg = _('Image minDisk size is larger than the volume size.') msg = _('Image minDisk size is larger than the volume size.')
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
if not volume_type and not source_volume:
volume_type = volume_types.get_default_volume_type()
if not volume_type and source_volume:
volume_type_id = source_volume['volume_type_id']
else:
volume_type_id = volume_type.get('id')
try: try:
reservations = QUOTAS.reserve(context, volumes=1, gigabytes=size) reserve_opts = {'volumes': 1, 'gigabytes': size}
QUOTAS.add_volume_type_opts(context, reserve_opts, volume_type_id)
reservations = QUOTAS.reserve(context, **reserve_opts)
except exception.OverQuota as e: except exception.OverQuota as e:
overs = e.kwargs['overs'] overs = e.kwargs['overs']
usages = e.kwargs['usages'] usages = e.kwargs['usages']
@@ -169,36 +179,29 @@ class API(base.Base):
def _consumed(name): def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use']) return (usages[name]['reserved'] + usages[name]['in_use'])
if 'gigabytes' in overs: for over in overs:
if 'gigabytes' in over:
msg = _("Quota exceeded for %(s_pid)s, tried to create " msg = _("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG volume (%(d_consumed)dG of %(d_quota)dG " "%(s_size)sG volume (%(d_consumed)dG of "
"already consumed)") "%(d_quota)dG already consumed)")
LOG.warn(msg % {'s_pid': context.project_id, LOG.warn(msg % {'s_pid': context.project_id,
's_size': size, 's_size': size,
'd_consumed': _consumed('gigabytes'), 'd_consumed': _consumed(over),
'd_quota': quotas['gigabytes']}) 'd_quota': quotas[over]})
raise exception.VolumeSizeExceedsAvailableQuota() raise exception.VolumeSizeExceedsAvailableQuota()
elif 'volumes' in overs: elif 'volumes' in over:
msg = _("Quota exceeded for %(s_pid)s, tried to create " msg = _("Quota exceeded for %(s_pid)s, tried to create "
"volume (%(d_consumed)d volumes" "volume (%(d_consumed)d volumes"
"already consumed)") "already consumed)")
LOG.warn(msg % {'s_pid': context.project_id, LOG.warn(msg % {'s_pid': context.project_id,
'd_consumed': _consumed('volumes')}) 'd_consumed': _consumed(over)})
raise exception.VolumeLimitExceeded(allowed=quotas['volumes']) raise exception.VolumeLimitExceeded(allowed=quotas[over])
if availability_zone is None: if availability_zone is None:
availability_zone = CONF.storage_availability_zone availability_zone = CONF.storage_availability_zone
else: else:
self._check_availabilty_zone(availability_zone) self._check_availabilty_zone(availability_zone)
if not volume_type and not source_volume:
volume_type = volume_types.get_default_volume_type()
if not volume_type and source_volume:
volume_type_id = source_volume['volume_type_id']
else:
volume_type_id = volume_type.get('id')
self._check_metadata_properties(context, metadata) self._check_metadata_properties(context, metadata)
options = {'size': size, options = {'size': size,
'user_id': context.user_id, 'user_id': context.user_id,
@@ -343,10 +346,13 @@ class API(base.Base):
# NOTE(vish): scheduling failed, so delete it # NOTE(vish): scheduling failed, so delete it
# Note(zhiteng): update volume quota reservation # Note(zhiteng): update volume quota reservation
try: try:
reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']}
QUOTAS.add_volume_type_opts(context,
reserve_opts,
volume['volume_type_id'])
reservations = QUOTAS.reserve(context, reservations = QUOTAS.reserve(context,
project_id=project_id, project_id=project_id,
volumes=-1, **reserve_opts)
gigabytes=-volume['size'])
except Exception: except Exception:
reservations = None reservations = None
LOG.exception(_("Failed to update quota for deleting volume")) LOG.exception(_("Failed to update quota for deleting volume"))
@@ -567,10 +573,13 @@ class API(base.Base):
try: try:
if CONF.no_snapshot_gb_quota: if CONF.no_snapshot_gb_quota:
reservations = QUOTAS.reserve(context, snapshots=1) reserve_opts = {'snapshots': 1}
else: else:
reservations = QUOTAS.reserve(context, snapshots=1, reserve_opts = {'snapshots': 1, 'gigabytes': volume['size']}
gigabytes=volume['size']) QUOTAS.add_volume_type_opts(context,
reserve_opts,
volume.get('volume_type_id'))
reservations = QUOTAS.reserve(context, **reserve_opts)
except exception.OverQuota as e: except exception.OverQuota as e:
overs = e.kwargs['overs'] overs = e.kwargs['overs']
usages = e.kwargs['usages'] usages = e.kwargs['usages']
@@ -579,24 +588,25 @@ class API(base.Base):
def _consumed(name): def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use']) return (usages[name]['reserved'] + usages[name]['in_use'])
if 'gigabytes' in overs: for over in overs:
if 'gigabytes' in over:
msg = _("Quota exceeded for %(s_pid)s, tried to create " msg = _("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG snapshot (%(d_consumed)dG of " "%(s_size)sG snapshot (%(d_consumed)dG of "
"%(d_quota)dG already consumed)") "%(d_quota)dG already consumed)")
LOG.warn(msg % {'s_pid': context.project_id, LOG.warn(msg % {'s_pid': context.project_id,
's_size': volume['size'], 's_size': volume['size'],
'd_consumed': _consumed('gigabytes'), 'd_consumed': _consumed(over),
'd_quota': quotas['gigabytes']}) 'd_quota': quotas[over]})
raise exception.VolumeSizeExceedsAvailableQuota() raise exception.VolumeSizeExceedsAvailableQuota()
elif 'snapshots' in overs: elif 'snapshots' in over:
msg = _("Quota exceeded for %(s_pid)s, tried to create " msg = _("Quota exceeded for %(s_pid)s, tried to create "
"snapshot (%(d_consumed)d snapshots " "snapshot (%(d_consumed)d snapshots "
"already consumed)") "already consumed)")
LOG.warn(msg % {'s_pid': context.project_id, LOG.warn(msg % {'s_pid': context.project_id,
'd_consumed': _consumed('snapshots')}) 'd_consumed': _consumed(over)})
raise exception.SnapshotLimitExceeded( raise exception.SnapshotLimitExceeded(
allowed=quotas['snapshots']) allowed=quotas[over])
self._check_metadata_properties(context, metadata) self._check_metadata_properties(context, metadata)
options = {'volume_id': volume['id'], options = {'volume_id': volume['id'],

View File

@@ -465,10 +465,13 @@ class VolumeManager(manager.SchedulerDependentManager):
# Get reservations # Get reservations
try: try:
reserve_opts = {'volumes': -1, 'gigabytes': -volume_ref['size']}
QUOTAS.add_volume_type_opts(context,
reserve_opts,
volume_ref.get('volume_type_id'))
reservations = QUOTAS.reserve(context, reservations = QUOTAS.reserve(context,
project_id=project_id, project_id=project_id,
volumes=-1, **reserve_opts)
gigabytes=-volume_ref['size'])
except Exception: except Exception:
reservations = None reservations = None
LOG.exception(_("Failed to update usages deleting volume")) LOG.exception(_("Failed to update usages deleting volume"))
@@ -522,15 +525,11 @@ class VolumeManager(manager.SchedulerDependentManager):
"""Deletes and unexports snapshot.""" """Deletes and unexports snapshot."""
context = context.elevated() context = context.elevated()
snapshot_ref = self.db.snapshot_get(context, snapshot_id) snapshot_ref = self.db.snapshot_get(context, snapshot_id)
project_id = snapshot_ref['project_id']
LOG.info(_("snapshot %s: deleting"), snapshot_ref['name']) LOG.info(_("snapshot %s: deleting"), snapshot_ref['name'])
self._notify_about_snapshot_usage( self._notify_about_snapshot_usage(
context, snapshot_ref, "delete.start") context, snapshot_ref, "delete.start")
if context.project_id != snapshot_ref['project_id']:
project_id = snapshot_ref['project_id']
else:
project_id = context.project_id
try: try:
LOG.debug(_("snapshot %s: deleting"), snapshot_ref['name']) LOG.debug(_("snapshot %s: deleting"), snapshot_ref['name'])
self.driver.delete_snapshot(snapshot_ref) self.driver.delete_snapshot(snapshot_ref)
@@ -550,15 +549,19 @@ class VolumeManager(manager.SchedulerDependentManager):
# Get reservations # Get reservations
try: try:
if CONF.no_snapshot_gb_quota: if CONF.no_snapshot_gb_quota:
reserve_opts = {'snapshots': -1}
else:
reserve_opts = {
'snapshots': -1,
'gigabytes': -snapshot_ref['volume_size'],
}
volume_ref = self.db.volume_get(context, snapshot_ref['volume_id'])
QUOTAS.add_volume_type_opts(context,
reserve_opts,
volume_ref.get('volume_type_id'))
reservations = QUOTAS.reserve(context, reservations = QUOTAS.reserve(context,
project_id=project_id, project_id=project_id,
snapshots=-1) **reserve_opts)
else:
reservations = QUOTAS.reserve(
context,
project_id=project_id,
snapshots=-1,
gigabytes=-snapshot_ref['volume_size'])
except Exception: except Exception:
reservations = None reservations = None
LOG.exception(_("Failed to update usages deleting snapshot")) LOG.exception(_("Failed to update usages deleting snapshot"))