From ae0ccc4b51d3f6755e53c67f2e7895f3713b56dc Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Mon, 29 Dec 2014 15:37:41 +0000 Subject: [PATCH] Better error on deleting non-empty Swift container Swift client method delete_container() does not allow to delete non-empty containers, in which case user gets a confusing 409 error exessively truncated by Swift with no actual reason information. This patch catches 409 errors from Swift client on container delete, checks for objects in the container and raises a suitable error if container is not empty, so that the DELETE_FAILED reason is clearly seen. Change-Id: Ibca3851d11b509413b739a693b1ddd244479d37b Closes-Bug: #1406263 --- heat/engine/resources/swift.py | 8 ++++++ heat/tests/test_swift.py | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/heat/engine/resources/swift.py b/heat/engine/resources/swift.py index 84d546f7f8..09adc6e638 100644 --- a/heat/engine/resources/swift.py +++ b/heat/engine/resources/swift.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.common.i18n import _LW from heat.engine import attributes @@ -150,6 +151,13 @@ class SwiftContainer(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 = _("Deleting non-empty container (%s)" + ) % self.resource_id + raise exception.NotSupported(feature=msg) self.client_plugin().ignore_not_found(ex) def handle_check(self): diff --git a/heat/tests/test_swift.py b/heat/tests/test_swift.py index aa3f54240e..7cc68169d6 100644 --- a/heat/tests/test_swift.py +++ b/heat/tests/test_swift.py @@ -72,6 +72,7 @@ class swiftTest(common.HeatTestCase): self.m.CreateMock(sc.Connection) self.m.StubOutWithMock(sc.Connection, 'post_account') 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, 'head_container') self.m.StubOutWithMock(sc.Connection, 'get_auth') @@ -255,6 +256,50 @@ class swiftTest(common.HeatTestCase): self.m.VerifyAll() + def test_delete_conflict_non_empty(self): + container_name = utils.PhysName('test_stack', 'test_resource') + sc.Connection.put_container( + container_name, + {}).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}, + [{'name': 'test'}])) + + self.m.ReplayAll() + t = template_format.parse(swift_template) + stack = utils.parse_stack(t) + rsrc = self.create_resource(t, stack, 'SwiftContainer') + deleter = scheduler.TaskRunner(rsrc.delete) + ex = self.assertRaises(exception.ResourceFailure, deleter) + self.assertIn('NotSupported: Deleting non-empty container', + six.text_type(ex)) + + self.m.VerifyAll() + + def test_delete_conflict_other(self): + container_name = utils.PhysName('test_stack', 'test_resource') + sc.Connection.put_container( + container_name, + {}).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, 'SwiftContainer') + deleter = scheduler.TaskRunner(rsrc.delete) + ex = self.assertRaises(exception.ResourceFailure, deleter) + self.assertIn('Conflict', six.text_type(ex)) + + self.m.VerifyAll() + def _get_check_resource(self): t = template_format.parse(swift_template) stack = utils.parse_stack(t)