Add quota limit check and enhance roll back for cg create

when a consistency group is created from a source consistency group
and the number of volumes associated with the source CG exceeds
the volumes quota, the creation of the CG will fail and will leave
volumes in "creating".

This patch add the quota limit check and also let the new created
volumes can be deleted if error occured.

Change-Id: I4f2ce1999c0eee798e2f4650a940d7335c548d40
Closes-bug: #1609889
This commit is contained in:
wangxiyuan 2016-12-01 10:50:54 +08:00 committed by Walter A. Boring IV (hemna)
parent b877bca044
commit e9e9934c74
2 changed files with 122 additions and 4 deletions

View File

@ -45,6 +45,7 @@ CONF = cfg.CONF
LOG = logging.getLogger(__name__)
CGQUOTAS = quota.CGQUOTAS
QUOTAS = quota.QUOTAS
VALID_REMOVE_VOL_FROM_CG_STATUS = (
'available',
'in-use',
@ -232,6 +233,16 @@ class API(base.Base):
"will be created.")
raise exception.InvalidConsistencyGroup(reason=msg)
try:
values = {'volumes': len(snapshots)}
QUOTAS.limit_check(context, project_id=context.project_id,
**values)
except exception.OverQuota as e:
group.destroy()
quotas = e.kwargs['quotas']
raise exception.VolumeLimitExceeded(
allowed=e.kwargs['overs'], limit=quotas['volumes'])
for snapshot in snapshots:
kwargs = {}
kwargs['availability_zone'] = group.availability_zone
@ -265,6 +276,10 @@ class API(base.Base):
except Exception:
with excutils.save_and_reraise_exception():
try:
new_vols = self.db.volume_get_all_by_group(context,
group.id)
for vol in new_vols:
self.volume_api.delete(context, vol, force=True)
group.destroy()
finally:
LOG.error(_LE("Error occurred when creating consistency "
@ -295,6 +310,16 @@ class API(base.Base):
"will be created.")
raise exception.InvalidConsistencyGroup(reason=msg)
try:
values = {'volumes': len(source_vols)}
QUOTAS.limit_check(context, project_id=context.project_id,
**values)
except exception.OverQuota as e:
group.destroy()
quotas = e.kwargs['quotas']
raise exception.VolumeLimitExceeded(
allowed=e.kwargs['overs'], limit=quotas['volumes'])
for source_vol in source_vols:
kwargs = {}
kwargs['availability_zone'] = group.availability_zone
@ -328,6 +353,10 @@ class API(base.Base):
except Exception:
with excutils.save_and_reraise_exception():
try:
new_vols = self.db.volume_get_all_by_group(context,
group.id)
for vol in new_vols:
self.volume_api.delete(context, vol, force=True)
group.destroy()
finally:
LOG.error(_LE("Error occurred when creating consistency "

View File

@ -1133,9 +1133,10 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
consistencygroup.destroy()
@mock.patch('cinder.quota.QuotaEngine.limit_check')
@mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
def test_create_consistencygroup_from_src(self, mock_validate):
def test_create_consistencygroup_from_src(self, mock_validate, mock_quota):
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
consistencygroup = utils.create_consistencygroup(self.ctxt)
@ -1178,7 +1179,8 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
consistencygroup.destroy()
cgsnapshot.destroy()
def test_create_consistencygroup_from_src_cg(self):
@mock.patch('cinder.quota.QuotaEngine.limit_check')
def test_create_consistencygroup_from_src_cg(self, mock_quota):
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
source_cg = utils.create_consistencygroup(self.ctxt)
@ -1434,11 +1436,12 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertIsNotNone(res_dict['itemNotFound']['message'])
@mock.patch('cinder.quota.QuotaEngine.limit_check')
@mock.patch.object(volume_api.API, 'create',
side_effect=exception.CinderException(
'Create volume failed.'))
def test_create_consistencygroup_from_src_cgsnapshot_create_volume_failed(
self, mock_create):
self, mock_create, mock_quota):
consistencygroup = utils.create_consistencygroup(self.ctxt)
volume_id = utils.create_volume(
self.ctxt,
@ -1475,11 +1478,12 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
consistencygroup.destroy()
cgsnapshot.destroy()
@mock.patch('cinder.quota.QuotaEngine.limit_check')
@mock.patch.object(volume_api.API, 'create',
side_effect=exception.CinderException(
'Create volume failed.'))
def test_create_consistencygroup_from_src_cg_create_volume_failed(
self, mock_create):
self, mock_create, mock_quota):
source_cg = utils.create_consistencygroup(self.ctxt)
volume_id = utils.create_volume(
self.ctxt,
@ -1505,3 +1509,88 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
db.volume_destroy(self.ctxt.elevated(), volume_id)
source_cg.destroy()
@mock.patch('cinder.quota.QuotaEngine.limit_check')
def test_create_consistencygroup_from_src_cg_over_quota(self, mock_quota):
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
source_cg = utils.create_consistencygroup(self.ctxt)
volume_id = utils.create_volume(
self.ctxt,
consistencygroup_id=source_cg.id)['id']
mock_quota.side_effect = exception.OverQuota(
overs=10, quotas='volumes', usages={})
test_cg_name = 'test cg'
body = {"consistencygroup-from-src": {"name": test_cg_name,
"description":
"Consistency Group 1",
"source_cgid": source_cg.id}}
req = webob.Request.blank('/v2/%s/consistencygroups/create_from_src' %
fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
res_dict = jsonutils.loads(res.body)
self.assertEqual(400, res.status_int)
self.assertIn('message', res_dict['badRequest'])
cg = objects.ConsistencyGroupList.get_all(self.ctxt)
# The new cg has been deleted already.
self.assertEqual(1, len(cg))
db.volume_destroy(self.ctxt.elevated(), volume_id)
source_cg.destroy()
@mock.patch('cinder.quota.QuotaEngine.limit_check')
@mock.patch(
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
def test_create_consistencygroup_from_src_cgsnapshot_over_quota(
self, mock_validate, mock_quota):
self.mock_object(volume_api.API, "create", v2_fakes.fake_volume_create)
consistencygroup = utils.create_consistencygroup(self.ctxt)
volume_id = utils.create_volume(
self.ctxt,
consistencygroup_id=consistencygroup.id)['id']
cgsnapshot = utils.create_cgsnapshot(
self.ctxt, consistencygroup_id=consistencygroup.id)
snapshot = utils.create_snapshot(
self.ctxt,
volume_id,
cgsnapshot_id=cgsnapshot.id,
status=fields.SnapshotStatus.AVAILABLE)
mock_quota.side_effect = exception.OverQuota(
overs=10, quotas='volumes', usages={})
test_cg_name = 'test cg'
body = {"consistencygroup-from-src": {"name": test_cg_name,
"description":
"Consistency Group 1",
"cgsnapshot_id": cgsnapshot.id}}
req = webob.Request.blank('/v2/%s/consistencygroups/create_from_src' %
fake.PROJECT_ID)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes(body)
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
res_dict = jsonutils.loads(res.body)
self.assertEqual(400, res.status_int)
self.assertIn('message', res_dict['badRequest'])
self.assertTrue(mock_validate.called)
cg = objects.ConsistencyGroupList.get_all(self.ctxt)
# The new cg has been deleted already.
self.assertEqual(1, len(cg))
snapshot.destroy()
db.volume_destroy(self.ctxt.elevated(), volume_id)
consistencygroup.destroy()
cgsnapshot.destroy()