Merge "Handle quota exceed exception"

This commit is contained in:
Jenkins 2016-06-16 18:01:29 +00:00 committed by Gerrit Code Review
commit cb41b2713e
11 changed files with 233 additions and 235 deletions

View File

@ -33,11 +33,12 @@ from cinder.backup import rpcapi as backup_rpcapi
from cinder import context
from cinder.db import base
from cinder import exception
from cinder.i18n import _, _LI, _LW
from cinder.i18n import _, _LI
from cinder import objects
from cinder.objects import fields
import cinder.policy
from cinder import quota
from cinder import quota_utils
from cinder import utils
import cinder.volume
from cinder.volume import utils as volume_utils
@ -279,37 +280,10 @@ class API(base.Base):
'backup_gigabytes': volume['size']}
reservations = QUOTAS.reserve(context, **reserve_opts)
except exception.OverQuota as e:
overs = e.kwargs['overs']
usages = e.kwargs['usages']
quotas = e.kwargs['quotas']
def _consumed(resource_name):
return (usages[resource_name]['reserved'] +
usages[resource_name]['in_use'])
for over in overs:
if 'gigabytes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG backup (%(d_consumed)dG of "
"%(d_quota)dG already consumed)")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': volume['size'],
'd_consumed': _consumed(over),
'd_quota': quotas[over]})
raise exception.VolumeBackupSizeExceedsAvailableQuota(
requested=volume['size'],
consumed=_consumed('backup_gigabytes'),
quota=quotas['backup_gigabytes'])
elif 'backups' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"backups (%(d_consumed)d backups "
"already consumed)")
LOG.warning(msg, {'s_pid': context.project_id,
'd_consumed': _consumed(over)})
raise exception.BackupLimitExceeded(
allowed=quotas[over])
quota_utils.process_reserve_over_quota(
context, e,
resource='backups',
size=volume.size)
# Find the latest backup and use it as the parent backup to do an
# incremental backup.
latest_backup = None

View File

@ -540,6 +540,10 @@ class SnapshotLimitExceeded(QuotaError):
message = _("Maximum number of snapshots allowed (%(allowed)d) exceeded")
class UnexpectedOverQuota(QuotaError):
message = _("Unexpected over quota on %(name)s.")
class BackupLimitExceeded(QuotaError):
message = _("Maximum number of backups allowed (%(allowed)d) exceeded")

View File

@ -70,37 +70,9 @@ def get_volume_type_reservation(ctxt, volume, type_id,
project_id=project_id,
**reserve_opts)
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'])
for over in overs:
if 'gigabytes' in over:
s_size = volume['size']
d_quota = quotas[over]
d_consumed = _consumed(over)
LOG.warning(
_LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG volume - (%(d_consumed)dG of "
"%(d_quota)dG already consumed)"),
{'s_pid': ctxt.project_id,
's_size': s_size,
'd_consumed': d_consumed,
'd_quota': d_quota})
raise exception.VolumeSizeExceedsAvailableQuota(
requested=s_size, quota=d_quota, consumed=d_consumed)
elif 'volumes' in over:
LOG.warning(
_LW("Quota exceeded for %(s_pid)s, tried to create "
"volume (%(d_consumed)d volumes "
"already consumed)"),
{'s_pid': ctxt.project_id,
'd_consumed': _consumed(over)})
raise exception.VolumeLimitExceeded(
allowed=quotas[over])
process_reserve_over_quota(ctxt, e,
resource='volumes',
size=volume.size)
return reservations
@ -261,3 +233,65 @@ def _keystone_client(context, version=(3, 0)):
(CONF.keystone_authtoken.cafile or True))
return client.Client(auth_url=CONF.keystone_authtoken.auth_uri,
session=client_session, version=version)
OVER_QUOTA_RESOURCE_EXCEPTIONS = {'snapshots': exception.SnapshotLimitExceeded,
'backups': exception.BackupLimitExceeded,
'volumes': exception.VolumeLimitExceeded, }
def process_reserve_over_quota(context, over_quota_exception,
resource, size=None):
"""Handle OverQuota exception.
Analyze OverQuota exception, and raise new exception related to
resource type. If there are unexpected items in overs,
UnexpectedOverQuota is raised.
:param context: security context
:param over_quota_exception: OverQuota exception
:param resource: can be backups, snapshots, and volumes
:param size: requested size in reservation
"""
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
overs = over_quota_exception.kwargs['overs']
usages = over_quota_exception.kwargs['usages']
quotas = over_quota_exception.kwargs['quotas']
invalid_overs = []
for over in overs:
if 'gigabytes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)dG %(s_resource)s (%(d_consumed)dG of "
"%(d_quota)dG already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': size,
's_resource': resource[:-1],
'd_consumed': _consumed(over),
'd_quota': quotas[over]})
if resource == 'backups':
exc = exception.VolumeBackupSizeExceedsAvailableQuota
else:
exc = exception.VolumeSizeExceedsAvailableQuota
raise exc(
name=over,
requested=size,
consumed=_consumed(over),
quota=quotas[over])
if (resource in OVER_QUOTA_RESOURCE_EXCEPTIONS.keys() and
resource in over):
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_resource)s (%(d_consumed)d %(s_resource)ss "
"already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
'd_consumed': _consumed(over),
's_resource': resource[:-1]})
raise OVER_QUOTA_RESOURCE_EXCEPTIONS[resource](
allowed=quotas[over],
name=over)
invalid_overs.append(over)
if invalid_overs:
raise exception.UnexpectedOverQuota(name=', '.join(invalid_overs))

View File

@ -165,3 +165,80 @@ class QuotaUtilsTest(test.TestCase):
self.assertRaises(exception.CinderException,
quota_utils.validate_setup_for_nested_quota_use,
self.context, [], None)
def _process_reserve_over_quota(self, overs, usages, quotas,
expected_ex,
resource='volumes'):
ctxt = context.get_admin_context()
ctxt.project_id = 'fake'
size = 1
kwargs = {'overs': overs,
'usages': usages,
'quotas': quotas}
exc = exception.OverQuota(**kwargs)
self.assertRaises(expected_ex,
quota_utils.process_reserve_over_quota,
ctxt, exc,
resource=resource,
size=size)
def test_volume_size_exceed_quota(self):
overs = ['gigabytes']
usages = {'gigabytes': {'reserved': 1, 'in_use': 9}}
quotas = {'gigabytes': 10, 'snapshots': 10}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.VolumeSizeExceedsAvailableQuota)
def test_snapshot_limit_exceed_quota(self):
overs = ['snapshots']
usages = {'snapshots': {'reserved': 1, 'in_use': 9}}
quotas = {'gigabytes': 10, 'snapshots': 10}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.SnapshotLimitExceeded,
resource='snapshots')
def test_backup_gigabytes_exceed_quota(self):
overs = ['backup_gigabytes']
usages = {'backup_gigabytes': {'reserved': 1, 'in_use': 9}}
quotas = {'backup_gigabytes': 10}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.VolumeBackupSizeExceedsAvailableQuota,
resource='backups')
def test_backup_limit_quota(self):
overs = ['backups']
usages = {'backups': {'reserved': 1, 'in_use': 9}}
quotas = {'backups': 9}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.BackupLimitExceeded,
resource='backups')
def test_volumes_limit_quota(self):
overs = ['volumes']
usages = {'volumes': {'reserved': 1, 'in_use': 9}}
quotas = {'volumes': 9}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.VolumeLimitExceeded)
def test_unknown_quota(self):
overs = ['unknown']
usages = {'volumes': {'reserved': 1, 'in_use': 9}}
quotas = {'volumes': 9}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.UnexpectedOverQuota)
def test_unknown_quota2(self):
overs = ['volumes']
usages = {'volumes': {'reserved': 1, 'in_use': 9}}
quotas = {'volumes': 9}
self._process_reserve_over_quota(
overs, usages, quotas,
exception.UnexpectedOverQuota,
resource='snapshots')

View File

@ -88,6 +88,12 @@ fake_opt = [
]
OVER_SNAPSHOT_QUOTA_EXCEPTION = exception.OverQuota(
overs=['snapshots'],
usages = {'snapshots': {'reserved': 1, 'in_use': 9}},
quotas = {'gigabytes': 10, 'snapshots': 10})
def create_snapshot(volume_id, size=1, metadata=None, ctxt=None,
**kwargs):
"""Create a snapshot object."""
@ -3099,10 +3105,8 @@ class VolumeTestCase(BaseVolumeTestCase):
"""Test exception handling when create snapshot in db failed."""
test_volume = tests_utils.create_volume(
self.context,
**self.volume_params)
self.volume.create_volume(self.context, test_volume.id,
volume=test_volume)
test_volume['status'] = 'available'
status='available',
host=CONF.host)
volume_api = cinder.volume.api.API()
self.assertRaises(exception.InvalidSnapshot,
volume_api.create_snapshot,
@ -3115,10 +3119,8 @@ class VolumeTestCase(BaseVolumeTestCase):
"""Test exception handling when create snapshot in maintenance."""
test_volume = tests_utils.create_volume(
self.context,
**self.volume_params)
self.volume.create_volume(self.context, test_volume.id,
volume=test_volume)
test_volume['status'] = 'maintenance'
status='maintenance',
host=CONF.host)
volume_api = cinder.volume.api.API()
self.assertRaises(exception.InvalidVolume,
volume_api.create_snapshot,
@ -3134,10 +3136,8 @@ class VolumeTestCase(BaseVolumeTestCase):
"""Test exception handling when snapshot quota commit failed."""
test_volume = tests_utils.create_volume(
self.context,
**self.volume_params)
self.volume.create_volume(self.context, test_volume.id,
request_spec={}, volume=test_volume)
test_volume['status'] = 'available'
status='available',
host=CONF.host)
volume_api = cinder.volume.api.API()
self.assertRaises(exception.QuotaError,
volume_api.create_snapshot,
@ -3146,6 +3146,40 @@ class VolumeTestCase(BaseVolumeTestCase):
'fake_name',
'fake_description')
@mock.patch.object(QUOTAS, 'reserve',
side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION)
def test_create_snapshot_failed_quota_reserve(self, mock_reserve):
"""Test exception handling when snapshot quota reserve failed."""
test_volume = tests_utils.create_volume(
self.context,
status='available',
host=CONF.host)
volume_api = cinder.volume.api.API()
self.assertRaises(exception.SnapshotLimitExceeded,
volume_api.create_snapshot,
self.context,
test_volume,
'fake_name',
'fake_description')
@mock.patch.object(QUOTAS, 'reserve',
side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION)
def test_create_snapshots_in_db_failed_quota_reserve(self, mock_reserve):
"""Test exception handling when snapshot quota reserve failed."""
test_volume = tests_utils.create_volume(
self.context,
status='available',
host=CONF.host)
volume_api = cinder.volume.api.API()
self.assertRaises(exception.SnapshotLimitExceeded,
volume_api.create_snapshots_in_db,
self.context,
[test_volume],
'fake_name',
'fake_description',
False,
fake.CONSISTENCY_GROUP_ID)
def test_cannot_delete_volume_in_use(self):
"""Test volume can't be deleted in in-use status."""
self._test_cannot_delete_volume('in-use')
@ -4519,6 +4553,20 @@ class VolumeTestCase(BaseVolumeTestCase):
'is_snapshot': False}
self.assertEqual(expected_result, result)
@mock.patch.object(QUOTAS, 'reserve',
side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION)
def test_existing_snapshot_failed_quota_reserve(self, mock_reserve):
vol = tests_utils.create_volume(self.context)
snap = tests_utils.create_snapshot(self.context, vol.id)
with mock.patch.object(
self.volume.driver,
'manage_existing_snapshot_get_size') as mock_get_size:
mock_get_size.return_value = 1
self.assertRaises(exception.SnapshotLimitExceeded,
self.volume.manage_existing_snapshot,
self.context,
snap)
@ddt.ddt
class VolumeMigrationTestCase(BaseVolumeTestCase):

View File

@ -816,20 +816,3 @@ class VolumeUtilsTestCase(test.TestCase):
self.assertEqual(
expected_dict,
volume_utils.convert_config_string_to_dict(test_string))
def test_process_reserve_over_quota(self):
ctxt = context.get_admin_context()
ctxt.project_id = 'fake'
overs_one = ['gigabytes']
over_two = ['snapshots']
usages = {'gigabytes': {'reserved': 1, 'in_use': 9},
'snapshots': {'reserved': 1, 'in_use': 9}}
quotas = {'gigabytes': 10, 'snapshots': 10}
size = 1
self.assertRaises(exception.VolumeSizeExceedsAvailableQuota,
volume_utils.process_reserve_over_quota,
ctxt, overs_one, usages, quotas, size)
self.assertRaises(exception.SnapshotLimitExceeded,
volume_utils.process_reserve_over_quota,
ctxt, over_two, usages, quotas, size)

View File

@ -29,8 +29,9 @@ import six
from cinder.db import base
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder.i18n import _, _LE, _LI
from cinder import quota
from cinder import quota_utils
from cinder.volume import api as volume_api
from cinder.volume import utils as volume_utils
@ -178,35 +179,9 @@ class API(base.Base):
vol_ref.volume_type_id)
reservations = QUOTAS.reserve(context, **reserve_opts)
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'])
for over in overs:
if 'gigabytes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG volume (%(d_consumed)dG of "
"%(d_quota)dG already consumed)")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': vol_ref['size'],
'd_consumed': _consumed(over),
'd_quota': quotas[over]})
raise exception.VolumeSizeExceedsAvailableQuota(
requested=vol_ref['size'],
consumed=_consumed(over),
quota=quotas[over])
elif 'volumes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"volume (%(d_consumed)d volumes "
"already consumed)")
LOG.warning(msg, {'s_pid': context.project_id,
'd_consumed': _consumed(over)})
raise exception.VolumeLimitExceeded(allowed=quotas[over],
name=over)
quota_utils.process_reserve_over_quota(context, e,
resource='volumes',
size=vol_ref.size)
try:
donor_id = vol_ref['project_id']
reserve_opts = {'volumes': -1, 'gigabytes': -vol_ref.size}

View File

@ -750,36 +750,10 @@ class API(base.Base):
volume.get('volume_type_id'))
reservations = QUOTAS.reserve(context, **reserve_opts)
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'])
for over in overs:
if 'gigabytes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to "
"create %(s_size)sG snapshot (%(d_consumed)d"
"G of %(d_quota)dG already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': volume['size'],
'd_consumed': _consumed(over),
'd_quota': quotas[over]})
raise exception.VolumeSizeExceedsAvailableQuota(
requested=volume['size'],
consumed=_consumed('gigabytes'),
quota=quotas['gigabytes'])
elif 'snapshots' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to "
"create snapshot (%(d_consumed)d snapshots "
"already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
'd_consumed': _consumed(over)})
raise exception.SnapshotLimitExceeded(
allowed=quotas[over])
quota_utils.process_reserve_over_quota(
context, e,
resource='snapshots',
size=volume.size)
self._check_metadata_properties(metadata)
snapshot = None
@ -893,11 +867,9 @@ class API(base.Base):
total_reserve_opts[key] + value
reservations = QUOTAS.reserve(context, **total_reserve_opts)
except exception.OverQuota as e:
overs = e.kwargs['overs']
usages = e.kwargs['usages']
quotas = e.kwargs['quotas']
volume_utils.process_reserve_over_quota(context, overs, usages,
quotas, volume['size'])
quota_utils.process_reserve_over_quota(context, e,
resource='snapshots',
size=volume.size)
return reservations

View File

@ -26,6 +26,7 @@ from cinder import objects
from cinder.objects import fields
from cinder import policy
from cinder import quota
from cinder import quota_utils
from cinder import utils
from cinder.volume.flows import common
from cinder.volume import utils as vol_utils
@ -601,53 +602,9 @@ class QuotaReserveTask(flow_utils.CinderTask):
'reservations': reservations,
}
except exception.OverQuota as e:
overs = e.kwargs['overs']
quotas = e.kwargs['quotas']
usages = e.kwargs['usages']
def _consumed(name):
usage = usages[name]
return usage['reserved'] + usage['in_use'] + usage.get(
'allocated', 0)
def _get_over(name):
for over in overs:
if name in over:
return over
return None
over_name = _get_over('gigabytes')
exceeded_vol_limit_name = _get_over('volumes')
if over_name:
# TODO(mc_nair): improve error message for child -1 limit
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG volume (%(d_consumed)dG "
"of %(d_quota)dG already consumed)")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': size,
'd_consumed': _consumed(over_name),
'd_quota': quotas[over_name]})
raise exception.VolumeSizeExceedsAvailableQuota(
name=over_name,
requested=size,
consumed=_consumed(over_name),
quota=quotas[over_name])
elif exceeded_vol_limit_name:
msg = _LW("Quota %(s_name)s exceeded for %(s_pid)s, tried "
"to create volume (%(d_consumed)d volume(s) "
"already consumed).")
LOG.warning(msg,
{'s_name': exceeded_vol_limit_name,
's_pid': context.project_id,
'd_consumed':
_consumed(exceeded_vol_limit_name)})
# TODO(mc_nair): improve error message for child -1 limit
raise exception.VolumeLimitExceeded(
allowed=quotas[exceeded_vol_limit_name],
name=exceeded_vol_limit_name)
else:
# If nothing was reraised, ensure we reraise the initial error
raise
quota_utils.process_reserve_over_quota(context, e,
resource='volumes',
size=size)
def revert(self, context, result, optional_args, **kwargs):
# We never produced a result and therefore can't destroy anything.

View File

@ -23,6 +23,7 @@ from cinder import flow_utils
from cinder.i18n import _, _LE, _LI
from cinder import objects
from cinder import quota
from cinder import quota_utils
from cinder.volume.flows import common as flow_common
from cinder.volume import utils as volume_utils
@ -153,11 +154,10 @@ class QuotaReserveTask(flow_utils.CinderTask):
'reservations': reservations,
}
except exception.OverQuota as e:
overs = e.kwargs['overs']
quotas = e.kwargs['quotas']
usages = e.kwargs['usages']
volume_utils.process_reserve_over_quota(context, overs, usages,
quotas, size)
quota_utils.process_reserve_over_quota(
context, e,
resource='snapshots',
size=size)
def revert(self, context, result, optional_args, **kwargs):
# We never produced a result and therefore can't destroy anything.

View File

@ -710,29 +710,3 @@ def convert_config_string_to_dict(config_string):
{'config_string': config_string})
return resultant_dict
def process_reserve_over_quota(context, overs, usages, quotas, size):
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
for over in overs:
if 'gigabytes' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"%(s_size)sG snapshot (%(d_consumed)dG of "
"%(d_quota)dG already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
's_size': size,
'd_consumed': _consumed(over),
'd_quota': quotas[over]})
raise exception.VolumeSizeExceedsAvailableQuota(
requested=size,
consumed=_consumed('gigabytes'),
quota=quotas['gigabytes'])
elif 'snapshots' in over:
msg = _LW("Quota exceeded for %(s_pid)s, tried to create "
"snapshot (%(d_consumed)d snapshots "
"already consumed).")
LOG.warning(msg, {'s_pid': context.project_id,
'd_consumed': _consumed(over)})
raise exception.SnapshotLimitExceeded(allowed=quotas[over])