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)
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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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',

View File

@@ -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'],

View File

@@ -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"))