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)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
181
cinder/quota.py
181
cinder/quota.py
@@ -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)
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
Reference in New Issue
Block a user