Merge "Update quota when volume type renames"
This commit is contained in:
commit
265057ed2c
@ -766,6 +766,11 @@ def quota_update(context, project_id, resource, limit):
|
||||
return IMPL.quota_update(context, project_id, resource, limit)
|
||||
|
||||
|
||||
def quota_update_resource(context, old_res, new_res):
|
||||
"""Update resource of quotas."""
|
||||
return IMPL.quota_update_resource(context, old_res, new_res)
|
||||
|
||||
|
||||
def quota_destroy(context, project_id, resource):
|
||||
"""Destroy the quota or raise if it does not exist."""
|
||||
return IMPL.quota_destroy(context, project_id, resource)
|
||||
@ -799,6 +804,11 @@ def quota_class_update(context, class_name, resource, limit):
|
||||
return IMPL.quota_class_update(context, class_name, resource, limit)
|
||||
|
||||
|
||||
def quota_class_update_resource(context, resource, new_resource):
|
||||
"""Update resource name in quota_class."""
|
||||
return IMPL.quota_class_update_resource(context, resource, new_resource)
|
||||
|
||||
|
||||
def quota_class_destroy(context, class_name, resource):
|
||||
"""Destroy the quota class or raise if it does not exist."""
|
||||
return IMPL.quota_class_destroy(context, class_name, resource)
|
||||
@ -854,6 +864,11 @@ def reservation_expire(context):
|
||||
return IMPL.reservation_expire(context)
|
||||
|
||||
|
||||
def quota_usage_update_resource(context, old_res, new_res):
|
||||
"""Update resource field in quota_usages."""
|
||||
return IMPL.quota_usage_update_resource(context, old_res, new_res)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
|
@ -547,6 +547,15 @@ def quota_allocated_get_all_by_project(context, project_id):
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def _quota_get_by_resource(context, resource, session=None):
|
||||
rows = model_query(context, models.Quota,
|
||||
session=session,
|
||||
read_deleted='no').filter_by(
|
||||
resource=resource).all()
|
||||
return rows
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_create(context, project_id, resource, limit, allocated):
|
||||
quota_ref = models.Quota()
|
||||
@ -571,6 +580,15 @@ def quota_update(context, project_id, resource, limit):
|
||||
return quota_ref
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_update_resource(context, old_res, new_res):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
quotas = _quota_get_by_resource(context, old_res, session=session)
|
||||
for quota in quotas:
|
||||
quota.resource = new_res
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_allocated_update(context, project_id, resource, allocated):
|
||||
session = get_session()
|
||||
@ -637,6 +655,17 @@ def quota_class_get_all_by_name(context, class_name):
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def _quota_class_get_all_by_resource(context, resource, session):
|
||||
result = model_query(context, models.QuotaClass,
|
||||
session=session,
|
||||
read_deleted="no").\
|
||||
filter_by(resource=resource).\
|
||||
all()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_class_create(context, class_name, resource, limit):
|
||||
quota_class_ref = models.QuotaClass()
|
||||
@ -660,6 +689,16 @@ def quota_class_update(context, class_name, resource, limit):
|
||||
return quota_class_ref
|
||||
|
||||
|
||||
@require_context
|
||||
def quota_class_update_resource(context, old_res, new_res):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
quota_class_list = _quota_class_get_all_by_resource(
|
||||
context, old_res, session)
|
||||
for quota_class in quota_class_list:
|
||||
quota_class.resource = new_res
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def quota_class_destroy(context, class_name, resource):
|
||||
session = get_session()
|
||||
@ -763,6 +802,27 @@ def _get_quota_usages(context, session, project_id):
|
||||
return {row.resource: row for row in rows}
|
||||
|
||||
|
||||
def _get_quota_usages_by_resource(context, session, resource):
|
||||
rows = model_query(context, models.QuotaUsage,
|
||||
deleted="no",
|
||||
session=session).\
|
||||
filter_by(resource=resource).\
|
||||
with_lockmode('update').\
|
||||
all()
|
||||
return rows
|
||||
|
||||
|
||||
@require_context
|
||||
@_retry_on_deadlock
|
||||
def quota_usage_update_resource(context, old_res, new_res):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
usages = _get_quota_usages_by_resource(context, session, old_res)
|
||||
for usage in usages:
|
||||
usage.resource = new_res
|
||||
usage.until_refresh = 1
|
||||
|
||||
|
||||
@require_context
|
||||
@_retry_on_deadlock
|
||||
def quota_reserve(context, resources, quotas, deltas, expire,
|
||||
@ -915,15 +975,20 @@ def _quota_reservations(session, context, reservations):
|
||||
all()
|
||||
|
||||
|
||||
def _dict_with_usage_id(usages):
|
||||
return {row.id: row for row in usages.values()}
|
||||
|
||||
|
||||
@require_context
|
||||
@_retry_on_deadlock
|
||||
def reservation_commit(context, reservations, project_id=None):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
usages = _get_quota_usages(context, session, project_id)
|
||||
usages = _dict_with_usage_id(usages)
|
||||
|
||||
for reservation in _quota_reservations(session, context, reservations):
|
||||
usage = usages[reservation.resource]
|
||||
usage = usages[reservation.usage_id]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
usage.in_use += reservation.delta
|
||||
@ -937,9 +1002,9 @@ def reservation_rollback(context, reservations, project_id=None):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
usages = _get_quota_usages(context, session, project_id)
|
||||
|
||||
usages = _dict_with_usage_id(usages)
|
||||
for reservation in _quota_reservations(session, context, reservations):
|
||||
usage = usages[reservation.resource]
|
||||
usage = usages[reservation.usage_id]
|
||||
if reservation.delta >= 0:
|
||||
usage.reserved -= reservation.delta
|
||||
|
||||
|
@ -942,6 +942,30 @@ class VolumeTypeQuotaEngine(QuotaEngine):
|
||||
def register_resources(self, resources):
|
||||
raise NotImplementedError(_("Cannot register resources"))
|
||||
|
||||
def update_quota_resource(self, context, old_type_name, new_type_name):
|
||||
"""Update resource in quota.
|
||||
|
||||
This is to update resource in quotas, quota_classes, and
|
||||
quota_usages once the name of a volume type is changed.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param old_type_name: old name of volume type.
|
||||
:param new_type_name: new name of volume type.
|
||||
"""
|
||||
|
||||
for quota in ('volumes', 'gigabytes', 'snapshots'):
|
||||
old_res = "%s_%s" % (quota, old_type_name)
|
||||
new_res = "%s_%s" % (quota, new_type_name)
|
||||
db.quota_usage_update_resource(context,
|
||||
old_res,
|
||||
new_res)
|
||||
db.quota_class_update_resource(context,
|
||||
old_res,
|
||||
new_res)
|
||||
db.quota_update_resource(context,
|
||||
old_res,
|
||||
new_res)
|
||||
|
||||
|
||||
class CGQuotaEngine(QuotaEngine):
|
||||
"""Represent the consistencygroup quotas."""
|
||||
|
@ -18,6 +18,7 @@ import six
|
||||
import webob
|
||||
|
||||
from cinder.api.contrib import types_manage
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit.api import fakes
|
||||
@ -365,19 +366,26 @@ class VolumeTypesManageApiTest(test.TestCase):
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller._update, req, '1', body)
|
||||
|
||||
@mock.patch('cinder.volume.volume_types.update')
|
||||
@mock.patch('cinder.volume.volume_types.get_volume_type')
|
||||
def test_update_only_name(self, mock_get, mock_update):
|
||||
@mock.patch('cinder.db.volume_type_update')
|
||||
@mock.patch('cinder.quota.VolumeTypeQuotaEngine.'
|
||||
'update_quota_resource')
|
||||
def test_update_only_name(self, mock_update_quota,
|
||||
mock_update, mock_get):
|
||||
mock_get.return_value = return_volume_types_get_volume_type_updated(
|
||||
'999')
|
||||
|
||||
body = {"volume_type": {"name": "vol_type_999_999"}}
|
||||
ctxt = context.RequestContext('admin', 'fake', True)
|
||||
body = {"volume_type": {"name": "vol_type_999"}}
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/types/999')
|
||||
req.method = 'PUT'
|
||||
req.environ['cinder.context'] = ctxt
|
||||
|
||||
self.assertEqual(0, len(self.notifier.notifications))
|
||||
res_dict = self.controller._update(req, '999', body)
|
||||
self.assertEqual(1, len(self.notifier.notifications))
|
||||
mock_update_quota.assert_called_once_with(ctxt, 'vol_type_999_999',
|
||||
'vol_type_999')
|
||||
self._check_test_results(res_dict,
|
||||
{'expected_name': 'vol_type_999_999',
|
||||
'expected_desc': 'vol_type_desc_999'})
|
||||
|
@ -1719,6 +1719,15 @@ class DBAPIQuotaClassTestCase(BaseTest):
|
||||
updated = db.quota_class_get(self.ctxt, 'test_qc', 'test_resource')
|
||||
self.assertEqual(43, updated['hard_limit'])
|
||||
|
||||
def test_quota_class_update_resource(self):
|
||||
old = db.quota_class_get(self.ctxt, 'test_qc', 'test_resource')
|
||||
db.quota_class_update_resource(self.ctxt,
|
||||
'test_resource',
|
||||
'test_resource1')
|
||||
new = db.quota_class_get(self.ctxt, 'test_qc', 'test_resource1')
|
||||
self.assertEqual(old.id, new.id)
|
||||
self.assertEqual('test_resource1', new.resource)
|
||||
|
||||
def test_quota_class_destroy_all_by_name(self):
|
||||
db.quota_class_create(self.ctxt, 'test2', 'res1', 43)
|
||||
db.quota_class_create(self.ctxt, 'test2', 'res2', 44)
|
||||
@ -1761,6 +1770,13 @@ class DBAPIQuotaTestCase(BaseTest):
|
||||
self.assertEqual('resource1', quota.resource)
|
||||
self.assertEqual('project1', quota.project_id)
|
||||
|
||||
def test_quota_update_resource(self):
|
||||
old = db.quota_create(self.ctxt, 'project1', 'resource1', 41)
|
||||
db.quota_update_resource(self.ctxt, 'resource1', 'resource2')
|
||||
new = db.quota_get(self.ctxt, 'project1', 'resource2')
|
||||
self.assertEqual(old.id, new.id)
|
||||
self.assertEqual('resource2', new.resource)
|
||||
|
||||
def test_quota_update_nonexistent(self):
|
||||
self.assertRaises(exception.ProjectQuotaNotFound,
|
||||
db.quota_update,
|
||||
|
@ -888,6 +888,12 @@ class VolumeTypeQuotaEngineTestCase(test.TestCase):
|
||||
db.volume_type_destroy(ctx, vtype['id'])
|
||||
db.volume_type_destroy(ctx, vtype2['id'])
|
||||
|
||||
def test_update_quota_resource(self):
|
||||
ctx = context.RequestContext('admin', 'admin', is_admin=True)
|
||||
|
||||
engine = quota.VolumeTypeQuotaEngine()
|
||||
engine.update_quota_resource(ctx, 'type1', 'type2')
|
||||
|
||||
|
||||
class DbQuotaDriverTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
|
||||
import datetime
|
||||
import mock
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
@ -84,6 +85,23 @@ class VolumeTypeTestCase(test.TestCase):
|
||||
new_all_vtypes,
|
||||
'drive type was not deleted')
|
||||
|
||||
@mock.patch('cinder.quota.VolumeTypeQuotaEngine.'
|
||||
'update_quota_resource')
|
||||
def test_update_volume_type_name(self, mock_update_quota):
|
||||
type_ref = volume_types.create(self.ctxt,
|
||||
self.vol_type1_name,
|
||||
self.vol_type1_specs,
|
||||
description=self.vol_type1_description)
|
||||
new_type_name = self.vol_type1_name + '_updated'
|
||||
volume_types.update(self.ctxt,
|
||||
type_ref.id,
|
||||
new_type_name,
|
||||
None)
|
||||
mock_update_quota.assert_called_once_with(self.ctxt,
|
||||
self.vol_type1_name,
|
||||
new_type_name)
|
||||
volume_types.destroy(self.ctxt, type_ref.id)
|
||||
|
||||
def test_create_volume_type_with_invalid_params(self):
|
||||
"""Ensure exception will be returned."""
|
||||
vol_type_invalid_specs = "invalid_extra_specs"
|
||||
|
@ -28,10 +28,11 @@ from cinder import context
|
||||
from cinder import db
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE
|
||||
|
||||
from cinder import quota
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
|
||||
def create(context,
|
||||
@ -62,12 +63,20 @@ def update(context, id, name, description, is_public=None):
|
||||
if id is None:
|
||||
msg = _("id cannot be None")
|
||||
raise exception.InvalidVolumeType(reason=msg)
|
||||
old_volume_type = get_volume_type(context, id)
|
||||
try:
|
||||
type_updated = db.volume_type_update(context,
|
||||
id,
|
||||
dict(name=name,
|
||||
description=description,
|
||||
is_public=is_public))
|
||||
# Rename resource in quota if volume type name is changed.
|
||||
if name:
|
||||
old_type_name = old_volume_type.get('name')
|
||||
if old_type_name != name:
|
||||
QUOTAS.update_quota_resource(context,
|
||||
old_type_name,
|
||||
name)
|
||||
except db_exc.DBError:
|
||||
LOG.exception(_LE('DB error:'))
|
||||
raise exception.VolumeTypeUpdateFailed(id=id)
|
||||
|
Loading…
Reference in New Issue
Block a user