Make DeletionPolicy a resource attribute instead of a property.

This improves validation of the different values, and make the Retain
policy works for all resources.

Fixes: bug #1160779
Change-Id: Idb0d275c44661db341f693d9d80629661d66c7ac
This commit is contained in:
Thomas Herve 2013-05-01 16:54:57 +02:00
parent 7c708e7607
commit 147e1e6afe
9 changed files with 111 additions and 21 deletions

View File

@ -400,8 +400,22 @@ class Resource(object):
def validate(self):
logger.info('Validating %s' % str(self))
self.validate_deletion_policy(self.t)
return self.properties.validate()
@staticmethod
def validate_deletion_policy(template):
deletion_policy = template.get('DeletionPolicy', 'Delete')
if deletion_policy not in ('Delete', 'Retain', 'Snapshot'):
msg = 'Invalid DeletionPolicy %s' % deletion_policy
raise exception.StackValidationFailed(message=msg)
elif deletion_policy == 'Snapshot':
# Some resources will support it in the future, in which case we
# should check for the presence of a handle_snapshot method for
# example.
msg = 'Snapshot DeletionPolicy not supported'
raise exception.StackValidationFailed(message=msg)
def delete(self):
'''
Delete the resource. Subclasses should provide a handle_delete() method
@ -420,8 +434,9 @@ class Resource(object):
try:
self.state_set(self.DELETE_IN_PROGRESS)
if callable(getattr(self, 'handle_delete', None)):
self.handle_delete()
if self.t.get('DeletionPolicy', 'Delete') == 'Delete':
if callable(getattr(self, 'handle_delete', None)):
self.handle_delete()
except Exception as ex:
logger.exception('Delete %s', str(self))
failure = exception.ResourceFailure(ex)

View File

@ -35,9 +35,6 @@ class S3Bucket(resource.Resource):
'AuthenticatedRead',
'BucketOwnerRead',
'BucketOwnerFullControl']},
'DeletionPolicy': {
'Type': 'String',
'AllowedValues': ['Delete', 'Retain']},
'WebsiteConfiguration': {'Type': 'Map',
'Schema': website_schema}}
@ -93,8 +90,6 @@ class S3Bucket(resource.Resource):
def handle_delete(self):
"""Perform specified delete policy"""
if self.properties['DeletionPolicy'] == 'Retain':
return
logger.debug('S3Bucket delete container %s' % self.resource_id)
if self.resource_id is not None:
try:

View File

@ -29,10 +29,7 @@ class SwiftContainer(resource.Resource):
'name': {'Type': 'String'},
'X-Container-Read': {'Type': 'String'},
'X-Container-Write': {'Type': 'String'},
'X-Container-Meta': {'Type': 'Map', 'Default': {}},
'DeletionPolicy': {
'Type': 'String',
'AllowedValues': ['Delete', 'Retain']}}
'X-Container-Meta': {'Type': 'Map', 'Default': {}}}
def __init__(self, name, json_snippet, stack):
super(SwiftContainer, self).__init__(name, json_snippet, stack)
@ -82,8 +79,6 @@ class SwiftContainer(resource.Resource):
def handle_delete(self):
"""Perform specified delete policy"""
if self.properties['DeletionPolicy'] == 'Retain':
return
logger.debug('SwiftContainer delete container %s' % self.resource_id)
if self.resource_id is not None:
try:

View File

@ -290,6 +290,7 @@ class EngineService(service.Service):
props = properties.Properties(ResourceClass.properties_schema,
res.get('Properties', {}))
try:
ResourceClass.validate_deletion_policy(res)
props.validate(with_value=False)
except Exception as ex:
return {'Error': str(ex)}

View File

@ -225,12 +225,13 @@ class s3Test(HeatTestCase):
self.m.ReplayAll()
t = self.load_template()
properties = t['Resources']['S3Bucket']['Properties']
properties['DeletionPolicy'] = 'Retain'
bucket = t['Resources']['S3Bucket']
bucket['DeletionPolicy'] = 'Retain'
stack = self.parse_stack(t)
resource = self.create_resource(t, stack, 'S3Bucket')
# if delete_container is called, mox verify will succeed
resource.delete()
self.assertEqual(resource.DELETE_COMPLETE, resource.state)
try:
self.m.VerifyAll()

View File

@ -246,12 +246,13 @@ class swiftTest(HeatTestCase):
self.m.ReplayAll()
t = self.load_template()
properties = t['Resources']['SwiftContainer']['Properties']
properties['DeletionPolicy'] = 'Retain'
container = t['Resources']['SwiftContainer']
container['DeletionPolicy'] = 'Retain'
stack = self.parse_stack(t)
resource = self.create_resource(t, stack, 'SwiftContainer')
# if delete_container is called, mox verify will succeed
resource.delete()
self.assertEqual(resource.DELETE_COMPLETE, resource.state)
try:
self.m.VerifyAll()

View File

@ -17,6 +17,7 @@ from heat.tests.v1_1 import fakes
from heat.common import exception
from heat.common import template_format
from heat.engine.resources import instance as instances
from heat.engine import resources
from heat.engine import service
import heat.db.api as db_api
from heat.engine import parser
@ -30,6 +31,7 @@ test_template_volumeattach = '''
"Resources" : {
"WikiDatabase": {
"Type": "AWS::EC2::Instance",
"DeletionPolicy": "Delete",
"Properties": {
"ImageId": "image_name",
"InstanceType": "m1.large",
@ -262,11 +264,70 @@ test_template_unimplemented_property = '''
}
'''
test_template_invalid_deletion_policy = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "test.",
"Parameters" : {
"KeyName" : {
''' + \
'"Description" : "Name of an existing EC2' + \
'KeyPair to enable SSH access to the instances",' + \
'''
"Type" : "String"
}
},
"Resources" : {
"WikiDatabase": {
"Type": "AWS::EC2::Instance",
"DeletionPolicy": "Destroy",
"Properties": {
"ImageId": "image_name",
"InstanceType": "m1.large",
"KeyName": { "Ref" : "KeyName" }
}
}
}
}
'''
test_template_snapshot_deletion_policy = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "test.",
"Parameters" : {
"KeyName" : {
''' + \
'"Description" : "Name of an existing EC2' + \
'KeyPair to enable SSH access to the instances",' + \
'''
"Type" : "String"
}
},
"Resources" : {
"WikiDatabase": {
"Type": "AWS::EC2::Instance",
"DeletionPolicy": "Snapshot",
"Properties": {
"ImageId": "image_name",
"InstanceType": "m1.large",
"KeyName": { "Ref" : "KeyName" }
}
}
}
}
'''
class validateTest(HeatTestCase):
def setUp(self):
super(validateTest, self).setUp()
self.fc = fakes.FakeClient()
resources.initialise()
setup_dummy_db()
def test_validate_volumeattach_valid(self):
@ -373,3 +434,24 @@ class validateTest(HeatTestCase):
self.assertEqual(
res,
{'Error': 'Property SourceDestCheck not implemented yet'})
def test_invalid_deletion_policy(self):
t = template_format.parse(test_template_invalid_deletion_policy)
self.m.StubOutWithMock(instances.Instance, 'nova')
instances.Instance.nova().AndReturn(self.fc)
self.m.ReplayAll()
engine = service.EngineService('a', 't')
res = dict(engine.validate_template(None, t))
self.assertEqual(res, {'Error': 'Invalid DeletionPolicy Destroy'})
def test_snapshot_deletion_policy(self):
t = template_format.parse(test_template_snapshot_deletion_policy)
self.m.StubOutWithMock(instances.Instance, 'nova')
instances.Instance.nova().AndReturn(self.fc)
self.m.ReplayAll()
engine = service.EngineService('a', 't')
res = dict(engine.validate_template(None, t))
self.assertEqual(
res, {'Error': 'Snapshot DeletionPolicy not supported'})

View File

@ -6,13 +6,13 @@
"Resources" : {
"S3BucketWebsite" : {
"Type" : "AWS::S3::Bucket",
"DeletionPolicy" : "Delete",
"Properties" : {
"AccessControl" : "PublicRead",
"WebsiteConfiguration" : {
"IndexDocument" : "index.html",
"ErrorDocument" : "error.html"
},
"DeletionPolicy" : "Delete"
}
}
},
"S3Bucket" : {

View File

@ -6,13 +6,13 @@
"Resources" : {
"SwiftContainerWebsite" : {
"Type" : "OS::Swift::Container",
"DeletionPolicy" : "Delete",
"Properties" : {
"X-Container-Read" : ".r:*",
"X-Container-Meta" : {
"Web-Index" : "index.html",
"Web-Error" : "error.html"
},
"DeletionPolicy" : "Delete"
}
}
},
"SwiftContainer" : {