From 3eda66271314c327668272f902e0cafe9e4f1428 Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Fri, 2 Jan 2015 19:09:41 +0200 Subject: [PATCH] Gracefully fail to delete nonempty S3 Bucket The AWS::S3::Bucket resource is susceptible to bug #1406263 as it is backed by the same Swift container - on deleting non empty container it produces incomprehensible truncated message. This patch uses the same logic as it is now in OS::Swift::Container resource, making the reason of failure clear. The error message is made to resemble the error produced by this resource on real AWS in the same usage scenario. Change-Id: Ib67c55f010613c8edb913bb264d3cc4a7315d847 Related-Bug: #1406263 --- heat/engine/resources/s3.py | 8 +++++++ heat/tests/test_s3.py | 45 ++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/heat/engine/resources/s3.py b/heat/engine/resources/s3.py index 871af7046..ce6a38ca1 100644 --- a/heat/engine/resources/s3.py +++ b/heat/engine/resources/s3.py @@ -14,6 +14,7 @@ import six from six.moves.urllib import parse as urlparse +from heat.common import exception from heat.common.i18n import _ from heat.engine import attributes from heat.engine import constraints @@ -153,6 +154,13 @@ class S3Bucket(resource.Resource): try: self.swift().delete_container(self.resource_id) except Exception as ex: + if self.client_plugin().is_conflict(ex): + container, objects = self.swift().get_container( + self.resource_id) + if objects: + msg = _("The bucket you tried to delete is not empty (%s)." + ) % self.resource_id + raise exception.ResourceActionNotSupported(action=msg) self.client_plugin().ignore_not_found(ex) def FnGetRefId(self): diff --git a/heat/tests/test_s3.py b/heat/tests/test_s3.py index 044d5c165..dd5338781 100644 --- a/heat/tests/test_s3.py +++ b/heat/tests/test_s3.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. - +import six import swiftclient.client as sc from heat.common import exception @@ -67,6 +67,7 @@ class s3Test(common.HeatTestCase): super(s3Test, self).setUp() self.m.CreateMock(sc.Connection) self.m.StubOutWithMock(sc.Connection, 'put_container') + self.m.StubOutWithMock(sc.Connection, 'get_container') self.m.StubOutWithMock(sc.Connection, 'delete_container') self.m.StubOutWithMock(sc.Connection, 'get_auth') self.stub_keystoneclient() @@ -233,6 +234,48 @@ class s3Test(common.HeatTestCase): self.m.VerifyAll() + def test_delete_conflict_not_empty(self): + container_name = utils.PhysName('test_stack', 'test_resource') + sc.Connection.put_container( + container_name, + {'X-Container-Write': 'test_tenant:test_username', + 'X-Container-Read': 'test_tenant:test_username'}).AndReturn(None) + sc.Connection.delete_container(container_name).AndRaise( + sc.ClientException('Not empty', http_status=409)) + sc.Connection.get_container(container_name).AndReturn( + ({'name': container_name}, [{'name': 'test_object'}])) + self.m.ReplayAll() + t = template_format.parse(swift_template) + stack = utils.parse_stack(t) + rsrc = self.create_resource(t, stack, 'S3Bucket') + deleter = scheduler.TaskRunner(rsrc.delete) + ex = self.assertRaises(exception.ResourceFailure, deleter) + self.assertIn("ResourceActionNotSupported: The bucket " + "you tried to delete is not empty", six.text_type(ex)) + + self.m.VerifyAll() + + def test_delete_conflict_empty(self): + container_name = utils.PhysName('test_stack', 'test_resource') + sc.Connection.put_container( + container_name, + {'X-Container-Write': 'test_tenant:test_username', + 'X-Container-Read': 'test_tenant:test_username'}).AndReturn(None) + sc.Connection.delete_container(container_name).AndRaise( + sc.ClientException('Conflict', http_status=409)) + sc.Connection.get_container(container_name).AndReturn( + ({'name': container_name}, [])) + + self.m.ReplayAll() + t = template_format.parse(swift_template) + stack = utils.parse_stack(t) + rsrc = self.create_resource(t, stack, 'S3Bucket') + deleter = scheduler.TaskRunner(rsrc.delete) + ex = self.assertRaises(exception.ResourceFailure, deleter) + self.assertIn("Conflict", six.text_type(ex)) + + self.m.VerifyAll() + def test_delete_retain(self): # first run, with retain policy sc.Connection.put_container(