Skip to content

Commit

Permalink
Distinguish over-quota for volume size and number.
Browse files Browse the repository at this point in the history
Fixes LP 1020634

Ensure that exceeding the allowed number of volumes is not
mis-represented in log messages and exception handling as
excessive space usage.

Change-Id: I71ec995c77bc447bfc9221084b057bd8d69a4513
  • Loading branch information
Eoghan Glynn committed Jul 6, 2012
1 parent b0feaff commit 19631f3
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 40 deletions.
35 changes: 24 additions & 11 deletions nova/quota.py
Expand Up @@ -129,23 +129,36 @@ def allowed_instances(context, requested_instances, instance_type):


def allowed_volumes(context, requested_volumes, size):
"""Check quota and return min(requested_volumes, allowed_volumes)."""
"""Check volume quotas and return breached if any."""
project_id = context.project_id
context = context.elevated()
size = int(size)
requested_gigabytes = requested_volumes * size

allowed = {}
overs = []

used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
project_id)
quota = get_project_quotas(context, project_id)
allowed_volumes = _get_request_allotment(requested_volumes, used_volumes,
quota['volumes'])
allowed_gigabytes = _get_request_allotment(requested_gigabytes,
used_gigabytes,
quota['gigabytes'])
usages = dict(volumes=used_volumes, gigabytes=used_gigabytes)

quotas = get_project_quotas(context, project_id)

def _check_allowed(resource, requested):
allow = _get_request_allotment(requested,
usages[resource],
quotas[resource])
if requested and allow < requested:
overs.append(resource)
allowed[resource] = allow

_check_allowed('volumes', requested_volumes)
_check_allowed('gigabytes', requested_volumes * size)

if size != 0:
allowed_volumes = min(allowed_volumes,
int(allowed_gigabytes // size))
return min(requested_volumes, allowed_volumes)
allowed['volumes'] = min(allowed['volumes'],
int(allowed['gigabytes'] // size))

return dict(overs=overs, usages=usages, quotas=quotas, allowed=allowed)


def allowed_floating_ips(context, requested_floating_ips):
Expand Down
55 changes: 30 additions & 25 deletions nova/tests/test_quota.py
Expand Up @@ -196,31 +196,36 @@ def test_unlimited_cores(self):
instance_type)
self.assertEqual(num_instances, 101)

def test_unlimited_volumes(self):
self.flags(quota_volumes=10, quota_gigabytes=-1)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 10)
db.quota_create(self.context, self.project_id, 'volumes', None)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 100)
db.quota_create(self.context, self.project_id, 'volumes', -1)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 100)
volumes = quota.allowed_volumes(self.context, 101, 1)
self.assertEqual(volumes, 101)

def test_unlimited_gigabytes(self):
self.flags(quota_volumes=-1, quota_gigabytes=10)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 10)
db.quota_create(self.context, self.project_id, 'gigabytes', None)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 100)
db.quota_create(self.context, self.project_id, 'gigabytes', -1)
volumes = quota.allowed_volumes(self.context, 100, 1)
self.assertEqual(volumes, 100)
volumes = quota.allowed_volumes(self.context, 101, 1)
self.assertEqual(volumes, 101)
def _do_test_volume_quota(self, resource):

def _validate(result, request, quota, allow):
overs = result['overs']
usages = result['usages']
quotas = result['quotas']
allowed = result['allowed']
self.assertEquals(request > allow, resource in overs)
self.assertEquals(usages[resource], 0)
self.assertEquals(quotas[resource], quota)
self.assertEquals(allowed[resource], allow)

quota_volumes = 10 if resource == 'volumes' else -1
quota_gigabytes = 10 if resource == 'gigabytes' else -1
self.flags(quota_volumes=quota_volumes,
quota_gigabytes=quota_gigabytes)
_validate(quota.allowed_volumes(self.context, 100, 1), 100, 10, 10)

db.quota_create(self.context, self.project_id, resource, None)
_validate(quota.allowed_volumes(self.context, 100, 1), 100, None, 100)

db.quota_create(self.context, self.project_id, resource, -1)
_validate(quota.allowed_volumes(self.context, 100, 1), 100, None, 100)
_validate(quota.allowed_volumes(self.context, 101, 1), 101, None, 101)

def test_volumes_quota(self):
self._do_test_volume_quota('volumes')

def test_gigabytes_quota(self):
self._do_test_volume_quota('gigabytes')

def test_unlimited_floating_ips(self):
self.flags(quota_floating_ips=10)
Expand Down
30 changes: 30 additions & 0 deletions nova/tests/test_volume.py
Expand Up @@ -81,6 +81,36 @@ def test_create_delete_volume(self):
self.context,
volume_id)

def _do_test_create_over_quota(self, resource, expected):
"""Test volume creation over quota."""

def fake_allowed_volumes(context, requested_volumes, size):
return dict(overs=[resource],
usages=dict(gigabytes=999, volumes=9),
quotas=dict(gigabytes=1000, volumes=10),
allowed=dict(gigabytes=0, volumes=0))

self.stubs.Set(nova.quota, 'allowed_volumes', fake_allowed_volumes)

volume_api = nova.volume.api.API()

try:
volume_api.create(self.context,
2,
'name',
'description')
self.fail('expected QuotaError')
except exception.QuotaError as qe:
self.assertTrue(str(qe).endswith('code=%s' % expected))

def test_create_volumes_over_quota(self):
self._do_test_create_over_quota('volumes',
'VolumeLimitExceeded')

def test_create_gigabytes_over_quota(self):
self._do_test_create_over_quota('gigabytes',
'VolumeSizeTooLarge')

def test_delete_busy_volume(self):
"""Test volume survives deletion if driver reports it as busy."""
volume = self._create_volume()
Expand Down
26 changes: 22 additions & 4 deletions nova/volume/api.py
Expand Up @@ -80,11 +80,29 @@ def create(self, context, size, name, description, snapshot=None,
else:
snapshot_id = None

if quota.allowed_volumes(context, 1, size) < 1:
result = quota.allowed_volumes(context, 1, size)

overs = result['overs']
usages = result['usages']
quotas = result['quotas']
allowed = result['allowed']

if allowed['volumes'] < 1:
pid = context.project_id
LOG.warn(_("Quota exceeded for %(pid)s, tried to create"
" %(size)sG volume") % locals())
raise exception.QuotaError(code="VolumeSizeTooLarge")
if 'gigabytes' in overs:
consumed = usages['gigabytes']
limit = quotas['gigabytes']
LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
"%(size)sG volume (%(consumed)dG of %(limit)dG "
"already consumed)") % locals())
code = "VolumeSizeTooLarge"
elif 'volumes' in overs:
consumed = usages['volumes']
LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
"volume (%(consumed)d volumes already consumed)")
% locals())
code = "VolumeLimitExceeded"
raise exception.QuotaError(code=code)

if availability_zone is None:
availability_zone = FLAGS.storage_availability_zone
Expand Down

0 comments on commit 19631f3

Please sign in to comment.