class VolumeSizeExceedsAvailableQuota(QuotaError):
- message = _("Requested volume exceeds allowed volume size quota")
+ message = _("Requested volume or snapshot exceeds "
+ "allowed Gigabytes quota")
class VolumeSizeExceedsQuota(QuotaError):
- message = _("Maximum volume size exceeded")
+ message = _("Maximum volume/snapshot size exceeded")
class VolumeLimitExceeded(QuotaError):
message = _("Maximum number of volumes allowed (%(allowed)d) exceeded")
+class SnapshotLimitExceeded(QuotaError):
+ message = _("Maximum number of snapshots allowed (%(allowed)d) exceeded")
+
+
class DuplicateSfVolumeNames(Duplicate):
message = _("Detected more than one volume with name %(vol_name)s")
cfg.IntOpt('quota_volumes',
default=10,
help='number of volumes allowed per project'),
+ cfg.IntOpt('quota_snapshots',
+ default=10,
+ help='number of volume snapshots allowed per project'),
cfg.IntOpt('quota_gigabytes',
default=1000,
- help='number of volume gigabytes allowed per project'),
+ help='number of volume gigabytes (snapshots are also included) '
+ 'allowed per project'),
cfg.IntOpt('reservation_expire',
default=86400,
help='number of seconds until a reservation expires'),
session=session)))
+def _sync_snapshots(context, project_id, session):
+ return dict(zip(('snapshots', 'gigabytes'),
+ db.volume_data_get_for_project(context,
+ project_id,
+ session=session)))
+
+
QUOTAS = QuotaEngine()
resources = [
ReservableResource('volumes', _sync_volumes, 'quota_volumes'),
+ ReservableResource('snapshots', _sync_snapshots, 'quota_snapshots'),
ReservableResource('gigabytes', _sync_volumes, 'quota_gigabytes'), ]
self.assertEqual(res.status_int, 413)
self.assertEqual(res_dict['overLimit']['code'], 413)
self.assertEqual(res_dict['overLimit']['message'],
- 'Requested volume exceeds allowed volume size quota')
+ 'Requested volume or snapshot exceeds allowed '
+ 'Gigabytes quota')
def test_restore_backup_with_VolumeLimitExceeded(self):
def setUp(self):
super(QuotaIntegrationTestCase, self).setUp()
self.flags(quota_volumes=2,
+ quota_snapshots=2,
quota_gigabytes=20)
# Apparently needed by the RPC tests...
super(DbQuotaDriverTestCase, self).setUp()
self.flags(quota_volumes=10,
+ quota_snapshots=10,
quota_gigabytes=1000,
reservation_expire=86400,
until_refresh=0,
result,
dict(
volumes=10,
+ snapshots=10,
gigabytes=1000, ))
def _stub_quota_class_get_all_by_name(self):
def fake_qcgabn(context, quota_class):
self.calls.append('quota_class_get_all_by_name')
self.assertEqual(quota_class, 'test_class')
- return dict(gigabytes=500, volumes=10, )
+ return dict(gigabytes=500, volumes=10, snapshots=10, )
self.stubs.Set(db, 'quota_class_get_all_by_name', fake_qcgabn)
def test_get_class_quotas(self):
'test_class')
self.assertEqual(self.calls, ['quota_class_get_all_by_name'])
- self.assertEqual(result, dict(volumes=10, gigabytes=500, ))
+ self.assertEqual(result, dict(volumes=10,
+ gigabytes=500,
+ snapshots=10))
def test_get_class_quotas_no_defaults(self):
self._stub_quota_class_get_all_by_name()
'test_class', False)
self.assertEqual(self.calls, ['quota_class_get_all_by_name'])
- self.assertEqual(result, dict(volumes=10, gigabytes=500, ))
+ self.assertEqual(result, dict(volumes=10,
+ gigabytes=500,
+ snapshots=10))
def _stub_get_by_project(self):
def fake_qgabp(context, project_id):
self.calls.append('quota_get_all_by_project')
self.assertEqual(project_id, 'test_project')
- return dict(volumes=10, gigabytes=50, reserved=0)
+ return dict(volumes=10, gigabytes=50, reserved=0, snapshots=10)
def fake_qugabp(context, project_id):
self.calls.append('quota_usage_get_all_by_project')
self.assertEqual(project_id, 'test_project')
return dict(volumes=dict(in_use=2, reserved=0),
+ snapshots=dict(in_use=2, reserved=0),
gigabytes=dict(in_use=10, reserved=0), )
self.stubs.Set(db, 'quota_get_all_by_project', fake_qgabp)
self.assertEqual(result, dict(volumes=dict(limit=10,
in_use=2,
reserved=0, ),
+ snapshots=dict(limit=10,
+ in_use=2,
+ reserved=0, ),
gigabytes=dict(limit=50,
in_use=10,
reserved=0, ), ))
self.assertEqual(result, dict(volumes=dict(limit=10,
in_use=2,
reserved=0, ),
+ snapshots=dict(limit=10,
+ in_use=2,
+ reserved=0, ),
gigabytes=dict(limit=50,
in_use=10,
reserved=0, ), ))
self.assertEqual(result, dict(volumes=dict(limit=10,
in_use=2,
reserved=0, ),
+ snapshots=dict(limit=10,
+ in_use=2,
+ reserved=0, ),
gigabytes=dict(limit=50,
in_use=10,
reserved=0, ), ))
dict(gigabytes=dict(limit=50,
in_use=10,
reserved=0, ),
+ snapshots=dict(limit=10,
+ in_use=2,
+ reserved=0, ),
volumes=dict(limit=10,
in_use=2,
reserved=0, ), ))
self.assertEqual(self.calls, ['quota_get_all_by_project',
'quota_class_get_all_by_name', ])
self.assertEqual(result, dict(volumes=dict(limit=10, ),
+ snapshots=dict(limit=10, ),
gigabytes=dict(limit=50, ), ))
def _stub_get_project_quotas(self):
msg = _("must be available")
raise exception.InvalidVolume(reason=msg)
+ try:
+ reservations = QUOTAS.reserve(context, snapshots=1,
+ gigabytes=volume['size'])
+ except exception.OverQuota as e:
+ overs = e.kwargs['overs']
+ usages = e.kwargs['usages']
+ quotas = e.kwargs['quotas']
+
+ def _consumed(name):
+ return (usages[name]['reserved'] + usages[name]['in_use'])
+
+ pid = context.project_id
+ if 'gigabytes' in overs:
+ consumed = _consumed('gigabytes')
+ quota = quotas['gigabytes']
+ LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
+ "%(size)sG volume (%(consumed)dG of %(quota)dG "
+ "already consumed)") % locals())
+ raise exception.VolumeSizeExceedsAvailableQuota()
+ elif 'snapshots' in overs:
+ consumed = _consumed('snapshots')
+ LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
+ "snapshot (%(consumed)d snapshots "
+ "already consumed)") % locals())
+ raise exception.SnapshotLimitExceeded(
+ allowed=quotas['snapshots'])
+
self._check_metadata_properties(context, metadata)
options = {'volume_id': volume['id'],
'user_id': context.user_id,
'display_description': description,
'metadata': metadata}
- snapshot = self.db.snapshot_create(context, options)
+ try:
+ snapshot = self.db.snapshot_create(context, options)
+ QUOTAS.commit(context, reservations)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ try:
+ self.db.snapshot_destroy(context, volume['id'])
+ finally:
+ QUOTAS.rollback(context, reservations)
+
self.volume_rpcapi.create_snapshot(context, volume, snapshot)
return snapshot
# number of volumes allowed per project (integer value)
#quota_volumes=10
-# number of volume gigabytes allowed per project (integer
+# number of volume snapshots allowed per project (integer value)
+#quota_snapshots=10
+
+# number of volume and snapshot gigabytes allowed per project (integer
# value)
#quota_gigabytes=1000