From e9e9934c7476df9c3b742c1bc58e03f30c5825ba Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Thu, 1 Dec 2016 10:50:54 +0800 Subject: [PATCH] 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 --- cinder/consistencygroup/api.py | 29 ++++++ .../api/contrib/test_consistencygroups.py | 97 ++++++++++++++++++- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/cinder/consistencygroup/api.py b/cinder/consistencygroup/api.py index 96a0db7a77d..b4005251ddb 100644 --- a/cinder/consistencygroup/api.py +++ b/cinder/consistencygroup/api.py @@ -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 " diff --git a/cinder/tests/unit/api/contrib/test_consistencygroups.py b/cinder/tests/unit/api/contrib/test_consistencygroups.py index 6ae77038b78..a825ce6af8f 100644 --- a/cinder/tests/unit/api/contrib/test_consistencygroups.py +++ b/cinder/tests/unit/api/contrib/test_consistencygroups.py @@ -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()