Merge "Quotas by Volume Type"
This commit is contained in:
@@ -217,10 +217,12 @@ def volume_data_get_for_host(context, host, session=None):
|
||||
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."""
|
||||
return IMPL.volume_data_get_for_project(context,
|
||||
project_id,
|
||||
volume_type_id,
|
||||
session)
|
||||
|
||||
|
||||
@@ -317,10 +319,12 @@ def 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."""
|
||||
return IMPL.snapshot_data_get_for_project(context,
|
||||
project_id,
|
||||
volume_type_id,
|
||||
session)
|
||||
|
||||
|
||||
|
||||
@@ -988,14 +988,19 @@ def volume_data_get_for_host(context, host, session=None):
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def volume_data_get_for_project(context, project_id, session=None):
|
||||
result = model_query(context,
|
||||
def volume_data_get_for_project(context, project_id, volume_type_id=None,
|
||||
session=None):
|
||||
query = model_query(context,
|
||||
func.count(models.Volume.id),
|
||||
func.sum(models.Volume.size),
|
||||
read_deleted="no",
|
||||
session=session).\
|
||||
filter_by(project_id=project_id).\
|
||||
first()
|
||||
filter_by(project_id=project_id)
|
||||
|
||||
if volume_type_id:
|
||||
query = query.filter_by(volume_type_id=volume_type_id)
|
||||
|
||||
result = query.first()
|
||||
|
||||
# NOTE(vish): convert None to 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
|
||||
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)
|
||||
result = model_query(context,
|
||||
query = model_query(context,
|
||||
func.count(models.Snapshot.id),
|
||||
func.sum(models.Snapshot.volume_size),
|
||||
read_deleted="no",
|
||||
session=session).\
|
||||
filter_by(project_id=project_id).\
|
||||
first()
|
||||
filter_by(project_id=project_id)
|
||||
|
||||
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
|
||||
return (result[0] or 0, result[1] or 0)
|
||||
|
||||
181
cinder/quota.py
181
cinder/quota.py
@@ -23,6 +23,7 @@ import datetime
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from cinder import context
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import importutils
|
||||
@@ -70,15 +71,21 @@ class DbQuotaDriver(object):
|
||||
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."""
|
||||
|
||||
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."""
|
||||
|
||||
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):
|
||||
"""Given a list of resources, retrieve the default quotas.
|
||||
@@ -121,10 +128,17 @@ class DbQuotaDriver(object):
|
||||
"""
|
||||
|
||||
quotas = {}
|
||||
default_quotas = {}
|
||||
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():
|
||||
if defaults or resource.name in class_quotas:
|
||||
quotas[resource.name] = class_quotas.get(resource.name,
|
||||
if resource.name in class_quotas:
|
||||
quotas[resource.name] = class_quotas[resource.name]
|
||||
continue
|
||||
|
||||
if defaults:
|
||||
quotas[resource.name] = default_quotas.get(resource.name,
|
||||
resource.default)
|
||||
|
||||
return quotas
|
||||
@@ -460,7 +474,7 @@ class BaseResource(object):
|
||||
pass
|
||||
|
||||
# OK, return the default
|
||||
return self.default
|
||||
return driver.get_default(context, self)
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
@@ -551,6 +565,64 @@ class CountableResource(AbsoluteResource):
|
||||
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):
|
||||
"""Represent the set of recognized quotas."""
|
||||
|
||||
@@ -567,7 +639,7 @@ class QuotaEngine(object):
|
||||
self._driver = quota_driver_class
|
||||
|
||||
def __contains__(self, resource):
|
||||
return resource in self._resources
|
||||
return resource in self.resources
|
||||
|
||||
def register_resource(self, resource):
|
||||
"""Register a resource."""
|
||||
@@ -580,15 +652,20 @@ class QuotaEngine(object):
|
||||
for resource in resources:
|
||||
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."""
|
||||
|
||||
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."""
|
||||
|
||||
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):
|
||||
"""Retrieve the default quotas.
|
||||
@@ -596,7 +673,7 @@ class QuotaEngine(object):
|
||||
: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):
|
||||
"""Retrieve the quotas for the given quota class.
|
||||
@@ -609,7 +686,7 @@ class QuotaEngine(object):
|
||||
resource.
|
||||
"""
|
||||
|
||||
return self._driver.get_class_quotas(context, self._resources,
|
||||
return self._driver.get_class_quotas(context, self.resources,
|
||||
quota_class, defaults=defaults)
|
||||
|
||||
def get_project_quotas(self, context, project_id, quota_class=None,
|
||||
@@ -629,7 +706,7 @@ class QuotaEngine(object):
|
||||
will also be returned.
|
||||
"""
|
||||
|
||||
return self._driver.get_project_quotas(context, self._resources,
|
||||
return self._driver.get_project_quotas(context, self.resources,
|
||||
project_id,
|
||||
quota_class=quota_class,
|
||||
defaults=defaults,
|
||||
@@ -648,7 +725,7 @@ class QuotaEngine(object):
|
||||
"""
|
||||
|
||||
# Get the resource
|
||||
res = self._resources.get(resource)
|
||||
res = self.resources.get(resource)
|
||||
if not res or not hasattr(res, 'count'):
|
||||
raise exception.QuotaResourceUnknown(unknown=[resource])
|
||||
|
||||
@@ -679,7 +756,7 @@ class QuotaEngine(object):
|
||||
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)
|
||||
|
||||
def reserve(self, context, expire=None, project_id=None, **deltas):
|
||||
@@ -717,7 +794,7 @@ class QuotaEngine(object):
|
||||
common user's tenant.
|
||||
"""
|
||||
|
||||
reservations = self._driver.reserve(context, self._resources, deltas,
|
||||
reservations = self._driver.reserve(context, self.resources, deltas,
|
||||
expire=expire,
|
||||
project_id=project_id)
|
||||
|
||||
@@ -788,9 +865,64 @@ class QuotaEngine(object):
|
||||
|
||||
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
|
||||
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):
|
||||
@@ -820,13 +952,4 @@ def _sync_gigabytes(context, project_id, session):
|
||||
return {'gigabytes': vol_gigs + snap_gigs}
|
||||
|
||||
|
||||
QUOTAS = QuotaEngine()
|
||||
|
||||
|
||||
resources = [
|
||||
ReservableResource('volumes', _sync_volumes, 'quota_volumes'),
|
||||
ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'),
|
||||
ReservableResource('gigabytes', _sync_gigabytes, 'quota_gigabytes'), ]
|
||||
|
||||
|
||||
QUOTAS.register_resources(resources)
|
||||
QUOTAS = VolumeTypeQuotaEngine()
|
||||
|
||||
@@ -41,13 +41,15 @@ class QuotaIntegrationTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
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,
|
||||
quota_snapshots=2,
|
||||
quota_gigabytes=20)
|
||||
|
||||
# Apparently needed by the RPC tests...
|
||||
#self.network = self.start_service('network')
|
||||
|
||||
self.user_id = 'admin'
|
||||
self.project_id = 'admin'
|
||||
self.context = context.RequestContext(self.user_id,
|
||||
@@ -61,16 +63,19 @@ class QuotaIntegrationTestCase(test.TestCase):
|
||||
self.stubs.Set(rpc, 'call', rpc_call_wrapper)
|
||||
|
||||
def tearDown(self):
|
||||
db.volume_type_destroy(context.get_admin_context(),
|
||||
self.volume_type['id'])
|
||||
super(QuotaIntegrationTestCase, self).tearDown()
|
||||
cinder.tests.image.fake.FakeImageService_reset()
|
||||
|
||||
def _create_volume(self, size=10):
|
||||
def _create_volume(self, size=1):
|
||||
"""Create a test volume."""
|
||||
vol = {}
|
||||
vol['user_id'] = self.user_id
|
||||
vol['project_id'] = self.project_id
|
||||
vol['size'] = size
|
||||
vol['status'] = 'available'
|
||||
vol['volume_type_id'] = self.volume_type['id']
|
||||
return db.volume_create(self.context, vol)
|
||||
|
||||
def _create_snapshot(self, volume):
|
||||
@@ -87,19 +92,52 @@ class QuotaIntegrationTestCase(test.TestCase):
|
||||
for i in range(CONF.quota_volumes):
|
||||
vol_ref = self._create_volume()
|
||||
volume_ids.append(vol_ref['id'])
|
||||
self.assertRaises(exception.QuotaError,
|
||||
self.assertRaises(exception.VolumeLimitExceeded,
|
||||
volume.API().create,
|
||||
self.context, 10, '', '', None)
|
||||
self.context, 1, '', '',
|
||||
volume_type=self.volume_type)
|
||||
for volume_id in volume_ids:
|
||||
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):
|
||||
volume_ids = []
|
||||
vol_ref = self._create_volume(size=20)
|
||||
volume_ids.append(vol_ref['id'])
|
||||
self.assertRaises(exception.QuotaError,
|
||||
self.assertRaises(exception.VolumeSizeExceedsAvailableQuota,
|
||||
volume.API().create,
|
||||
self.context, 10, '', '', None)
|
||||
self.context, 1, '', '',
|
||||
volume_type=self.volume_type)
|
||||
for volume_id in volume_ids:
|
||||
db.volume_destroy(self.context, volume_id)
|
||||
|
||||
@@ -131,8 +169,6 @@ class QuotaIntegrationTestCase(test.TestCase):
|
||||
self.assertEqual(reservations.get('gigabytes'), None)
|
||||
|
||||
# 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, '', '')
|
||||
usages = db.quota_usage_get_all_by_project(self.context,
|
||||
self.project_id)
|
||||
@@ -142,7 +178,21 @@ class QuotaIntegrationTestCase(test.TestCase):
|
||||
db.snapshot_destroy(self.context, snap_ref2['id'])
|
||||
db.volume_destroy(self.context, vol_ref['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):
|
||||
@@ -179,6 +229,10 @@ class FakeDriver(object):
|
||||
except KeyError:
|
||||
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):
|
||||
self.called.append(('get_defaults', context, resources))
|
||||
return resources
|
||||
@@ -310,24 +364,35 @@ class BaseResourceTestCase(test.TestCase):
|
||||
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):
|
||||
def test_init(self):
|
||||
quota_obj = quota.QuotaEngine()
|
||||
|
||||
self.assertEqual(quota_obj._resources, {})
|
||||
self.assertEqual(quota_obj.resources, {})
|
||||
self.assertTrue(isinstance(quota_obj._driver, quota.DbQuotaDriver))
|
||||
|
||||
def test_init_override_string(self):
|
||||
quota_obj = quota.QuotaEngine(
|
||||
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))
|
||||
|
||||
def test_init_override_obj(self):
|
||||
quota_obj = quota.QuotaEngine(quota_driver_class=FakeDriver)
|
||||
|
||||
self.assertEqual(quota_obj._resources, {})
|
||||
self.assertEqual(quota_obj.resources, {})
|
||||
self.assertEqual(quota_obj._driver, FakeDriver)
|
||||
|
||||
def test_register_resource(self):
|
||||
@@ -335,7 +400,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
resource = quota.AbsoluteResource('test_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):
|
||||
quota_obj = quota.QuotaEngine()
|
||||
@@ -345,7 +410,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
quota.AbsoluteResource('test_resource3'), ]
|
||||
quota_obj.register_resources(resources)
|
||||
|
||||
self.assertEqual(quota_obj._resources,
|
||||
self.assertEqual(quota_obj.resources,
|
||||
dict(test_resource1=resources[0],
|
||||
test_resource2=resources[1],
|
||||
test_resource3=resources[2], ))
|
||||
@@ -428,8 +493,8 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(driver.called, [('get_defaults',
|
||||
context,
|
||||
quota_obj._resources), ])
|
||||
self.assertEqual(result, quota_obj._resources)
|
||||
quota_obj.resources), ])
|
||||
self.assertEqual(result, quota_obj.resources)
|
||||
|
||||
def test_get_class_quotas(self):
|
||||
context = FakeContext(None, None)
|
||||
@@ -441,13 +506,13 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
self.assertEqual(driver.called, [
|
||||
('get_class_quotas',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
'test_class', True),
|
||||
('get_class_quotas',
|
||||
context, quota_obj._resources,
|
||||
context, quota_obj.resources,
|
||||
'test_class', False), ])
|
||||
self.assertEqual(result1, quota_obj._resources)
|
||||
self.assertEqual(result2, quota_obj._resources)
|
||||
self.assertEqual(result1, quota_obj.resources)
|
||||
self.assertEqual(result2, quota_obj.resources)
|
||||
|
||||
def test_get_project_quotas(self):
|
||||
context = FakeContext(None, None)
|
||||
@@ -462,20 +527,20 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
self.assertEqual(driver.called, [
|
||||
('get_project_quotas',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
'test_project',
|
||||
None,
|
||||
True,
|
||||
True),
|
||||
('get_project_quotas',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
'test_project',
|
||||
'test_class',
|
||||
False,
|
||||
False), ])
|
||||
self.assertEqual(result1, quota_obj._resources)
|
||||
self.assertEqual(result2, quota_obj._resources)
|
||||
self.assertEqual(result1, quota_obj.resources)
|
||||
self.assertEqual(result2, quota_obj.resources)
|
||||
|
||||
def test_count_no_resource(self):
|
||||
context = FakeContext(None, None)
|
||||
@@ -518,7 +583,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
self.assertEqual(driver.called, [
|
||||
('limit_check',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
dict(
|
||||
test_resource1=4,
|
||||
test_resource2=3,
|
||||
@@ -546,7 +611,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
self.assertEqual(driver.called, [
|
||||
('reserve',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
dict(
|
||||
test_resource1=4,
|
||||
test_resource2=3,
|
||||
@@ -556,7 +621,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
None),
|
||||
('reserve',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
dict(
|
||||
test_resource1=1,
|
||||
test_resource2=2,
|
||||
@@ -566,7 +631,7 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
None),
|
||||
('reserve',
|
||||
context,
|
||||
quota_obj._resources,
|
||||
quota_obj.resources,
|
||||
dict(
|
||||
test_resource1=1,
|
||||
test_resource2=2,
|
||||
@@ -634,14 +699,33 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(driver.called, [('expire', context), ])
|
||||
|
||||
def test_resources(self):
|
||||
def test_resource_names(self):
|
||||
quota_obj = self._make_quota_obj(None)
|
||||
|
||||
self.assertEqual(quota_obj.resources,
|
||||
self.assertEqual(quota_obj.resource_names,
|
||||
['test_resource1', 'test_resource2',
|
||||
'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):
|
||||
def setUp(self):
|
||||
super(DbQuotaDriverTestCase, self).setUp()
|
||||
@@ -667,7 +751,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
def test_get_defaults(self):
|
||||
# Use our pre-defined resources
|
||||
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(
|
||||
result,
|
||||
@@ -695,7 +779,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
|
||||
def test_get_class_quotas(self):
|
||||
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')
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
self.assertEqual(self.calls, ['quota_class_get_all_by_name'])
|
||||
@@ -736,7 +820,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_by_project()
|
||||
result = self.driver.get_project_quotas(
|
||||
FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources, 'test_project')
|
||||
quota.QUOTAS.resources, 'test_project')
|
||||
|
||||
self.assertEqual(self.calls, ['quota_get_all_by_project',
|
||||
'quota_usage_get_all_by_project',
|
||||
@@ -756,7 +840,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_by_project()
|
||||
result = self.driver.get_project_quotas(
|
||||
FakeContext('other_project', 'other_class'),
|
||||
quota.QUOTAS._resources, 'test_project')
|
||||
quota.QUOTAS.resources, 'test_project')
|
||||
|
||||
self.assertEqual(self.calls, ['quota_get_all_by_project',
|
||||
'quota_usage_get_all_by_project',
|
||||
@@ -775,7 +859,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_by_project()
|
||||
result = self.driver.get_project_quotas(
|
||||
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',
|
||||
'quota_usage_get_all_by_project',
|
||||
@@ -795,7 +879,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_by_project()
|
||||
result = self.driver.get_project_quotas(
|
||||
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',
|
||||
'quota_usage_get_all_by_project',
|
||||
@@ -816,7 +900,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_by_project()
|
||||
result = self.driver.get_project_quotas(
|
||||
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',
|
||||
'quota_class_get_all_by_name',
|
||||
@@ -840,7 +924,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self.assertRaises(exception.QuotaResourceUnknown,
|
||||
self.driver._get_quotas,
|
||||
None, quota.QUOTAS._resources,
|
||||
None, quota.QUOTAS.resources,
|
||||
['unknown'], True)
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
@@ -848,7 +932,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self.assertRaises(exception.QuotaResourceUnknown,
|
||||
self.driver._get_quotas,
|
||||
None, quota.QUOTAS._resources,
|
||||
None, quota.QUOTAS.resources,
|
||||
['unknown'], False)
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
@@ -856,7 +940,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self.assertRaises(exception.QuotaResourceUnknown,
|
||||
self.driver._get_quotas,
|
||||
None, quota.QUOTAS._resources,
|
||||
None, quota.QUOTAS.resources,
|
||||
['metadata_items'], True)
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
@@ -864,7 +948,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self.assertRaises(exception.QuotaResourceUnknown,
|
||||
self.driver._get_quotas,
|
||||
None, quota.QUOTAS._resources,
|
||||
None, quota.QUOTAS.resources,
|
||||
['volumes'], False)
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
@@ -872,7 +956,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
result = self.driver._get_quotas(FakeContext('test_project',
|
||||
'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
['volumes', 'gigabytes'],
|
||||
True)
|
||||
|
||||
@@ -893,7 +977,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self.assertRaises(exception.InvalidReservationExpiration,
|
||||
self.driver.reserve,
|
||||
FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire='invalid')
|
||||
self.assertEqual(self.calls, [])
|
||||
|
||||
@@ -901,7 +985,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self._stub_quota_reserve()
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2))
|
||||
|
||||
expire = timeutils.utcnow() + datetime.timedelta(seconds=86400)
|
||||
@@ -913,7 +997,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_get_project_quotas()
|
||||
self._stub_quota_reserve()
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire=3600)
|
||||
|
||||
expire = timeutils.utcnow() + datetime.timedelta(seconds=3600)
|
||||
@@ -926,7 +1010,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_quota_reserve()
|
||||
expire_delta = datetime.timedelta(seconds=60)
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire=expire_delta)
|
||||
|
||||
expire = timeutils.utcnow() + expire_delta
|
||||
@@ -939,7 +1023,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self._stub_quota_reserve()
|
||||
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire=expire)
|
||||
|
||||
self.assertEqual(self.calls, ['get_project_quotas',
|
||||
@@ -952,7 +1036,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self.flags(until_refresh=500)
|
||||
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire=expire)
|
||||
|
||||
self.assertEqual(self.calls, ['get_project_quotas',
|
||||
@@ -965,7 +1049,7 @@ class DbQuotaDriverTestCase(test.TestCase):
|
||||
self.flags(max_age=86400)
|
||||
expire = timeutils.utcnow() + datetime.timedelta(seconds=120)
|
||||
result = self.driver.reserve(FakeContext('test_project', 'test_class'),
|
||||
quota.QUOTAS._resources,
|
||||
quota.QUOTAS.resources,
|
||||
dict(volumes=2), expire=expire)
|
||||
|
||||
self.assertEqual(self.calls, ['get_project_quotas',
|
||||
|
||||
@@ -159,8 +159,18 @@ class API(base.Base):
|
||||
msg = _('Image minDisk size is larger than the volume size.')
|
||||
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:
|
||||
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:
|
||||
overs = e.kwargs['overs']
|
||||
usages = e.kwargs['usages']
|
||||
@@ -169,36 +179,29 @@ class API(base.Base):
|
||||
def _consumed(name):
|
||||
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 "
|
||||
"%(s_size)sG volume (%(d_consumed)dG of %(d_quota)dG "
|
||||
"already consumed)")
|
||||
"%(s_size)sG volume (%(d_consumed)dG of "
|
||||
"%(d_quota)dG already consumed)")
|
||||
LOG.warn(msg % {'s_pid': context.project_id,
|
||||
's_size': size,
|
||||
'd_consumed': _consumed('gigabytes'),
|
||||
'd_quota': quotas['gigabytes']})
|
||||
'd_consumed': _consumed(over),
|
||||
'd_quota': quotas[over]})
|
||||
raise exception.VolumeSizeExceedsAvailableQuota()
|
||||
elif 'volumes' in overs:
|
||||
elif 'volumes' in over:
|
||||
msg = _("Quota exceeded for %(s_pid)s, tried to create "
|
||||
"volume (%(d_consumed)d volumes"
|
||||
"already consumed)")
|
||||
LOG.warn(msg % {'s_pid': context.project_id,
|
||||
'd_consumed': _consumed('volumes')})
|
||||
raise exception.VolumeLimitExceeded(allowed=quotas['volumes'])
|
||||
'd_consumed': _consumed(over)})
|
||||
raise exception.VolumeLimitExceeded(allowed=quotas[over])
|
||||
|
||||
if availability_zone is None:
|
||||
availability_zone = CONF.storage_availability_zone
|
||||
else:
|
||||
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)
|
||||
options = {'size': size,
|
||||
'user_id': context.user_id,
|
||||
@@ -343,10 +346,13 @@ class API(base.Base):
|
||||
# NOTE(vish): scheduling failed, so delete it
|
||||
# Note(zhiteng): update volume quota reservation
|
||||
try:
|
||||
reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']}
|
||||
QUOTAS.add_volume_type_opts(context,
|
||||
reserve_opts,
|
||||
volume['volume_type_id'])
|
||||
reservations = QUOTAS.reserve(context,
|
||||
project_id=project_id,
|
||||
volumes=-1,
|
||||
gigabytes=-volume['size'])
|
||||
**reserve_opts)
|
||||
except Exception:
|
||||
reservations = None
|
||||
LOG.exception(_("Failed to update quota for deleting volume"))
|
||||
@@ -567,10 +573,13 @@ class API(base.Base):
|
||||
|
||||
try:
|
||||
if CONF.no_snapshot_gb_quota:
|
||||
reservations = QUOTAS.reserve(context, snapshots=1)
|
||||
reserve_opts = {'snapshots': 1}
|
||||
else:
|
||||
reservations = QUOTAS.reserve(context, snapshots=1,
|
||||
gigabytes=volume['size'])
|
||||
reserve_opts = {'snapshots': 1, '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:
|
||||
overs = e.kwargs['overs']
|
||||
usages = e.kwargs['usages']
|
||||
@@ -579,24 +588,25 @@ class API(base.Base):
|
||||
def _consumed(name):
|
||||
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 "
|
||||
"%(s_size)sG snapshot (%(d_consumed)dG of "
|
||||
"%(d_quota)dG already consumed)")
|
||||
LOG.warn(msg % {'s_pid': context.project_id,
|
||||
's_size': volume['size'],
|
||||
'd_consumed': _consumed('gigabytes'),
|
||||
'd_quota': quotas['gigabytes']})
|
||||
'd_consumed': _consumed(over),
|
||||
'd_quota': quotas[over]})
|
||||
raise exception.VolumeSizeExceedsAvailableQuota()
|
||||
elif 'snapshots' in overs:
|
||||
elif 'snapshots' in over:
|
||||
msg = _("Quota exceeded for %(s_pid)s, tried to create "
|
||||
"snapshot (%(d_consumed)d snapshots "
|
||||
"already consumed)")
|
||||
|
||||
LOG.warn(msg % {'s_pid': context.project_id,
|
||||
'd_consumed': _consumed('snapshots')})
|
||||
'd_consumed': _consumed(over)})
|
||||
raise exception.SnapshotLimitExceeded(
|
||||
allowed=quotas['snapshots'])
|
||||
allowed=quotas[over])
|
||||
|
||||
self._check_metadata_properties(context, metadata)
|
||||
options = {'volume_id': volume['id'],
|
||||
|
||||
@@ -465,10 +465,13 @@ class VolumeManager(manager.SchedulerDependentManager):
|
||||
|
||||
# Get reservations
|
||||
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,
|
||||
project_id=project_id,
|
||||
volumes=-1,
|
||||
gigabytes=-volume_ref['size'])
|
||||
**reserve_opts)
|
||||
except Exception:
|
||||
reservations = None
|
||||
LOG.exception(_("Failed to update usages deleting volume"))
|
||||
@@ -522,15 +525,11 @@ class VolumeManager(manager.SchedulerDependentManager):
|
||||
"""Deletes and unexports snapshot."""
|
||||
context = context.elevated()
|
||||
snapshot_ref = self.db.snapshot_get(context, snapshot_id)
|
||||
project_id = snapshot_ref['project_id']
|
||||
LOG.info(_("snapshot %s: deleting"), snapshot_ref['name'])
|
||||
self._notify_about_snapshot_usage(
|
||||
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:
|
||||
LOG.debug(_("snapshot %s: deleting"), snapshot_ref['name'])
|
||||
self.driver.delete_snapshot(snapshot_ref)
|
||||
@@ -550,15 +549,19 @@ class VolumeManager(manager.SchedulerDependentManager):
|
||||
# Get reservations
|
||||
try:
|
||||
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,
|
||||
project_id=project_id,
|
||||
snapshots=-1)
|
||||
else:
|
||||
reservations = QUOTAS.reserve(
|
||||
context,
|
||||
project_id=project_id,
|
||||
snapshots=-1,
|
||||
gigabytes=-snapshot_ref['volume_size'])
|
||||
**reserve_opts)
|
||||
except Exception:
|
||||
reservations = None
|
||||
LOG.exception(_("Failed to update usages deleting snapshot"))
|
||||
|
||||
Reference in New Issue
Block a user