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
This commit is contained in:
Pavlo Shchelokovskyy 2015-01-02 19:09:41 +02:00
parent e930bc2887
commit 3eda662713
2 changed files with 52 additions and 1 deletions

View File

@ -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):

View File

@ -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(