Check the format of the provided metadata is as expected.
metadata must use the following format:
{
- "Status" : "Status (should be SUCCESS or FAILURE)"
- "UniqueId" : "Some ID, must be unique for Count>1",
+ "Status" : "Status (must be SUCCESS or FAILURE)"
+ "UniqueId" : "Some ID, should be unique for Count>1",
"Data" : "Arbitrary Data",
"Reason" : "Reason String"
}
"""
expected_keys = ['Data', 'Reason', 'Status', 'UniqueId']
- return sorted(metadata.keys()) == expected_keys
+ if sorted(metadata.keys()) == expected_keys:
+ return metadata['Status'] in (SUCCESS, FAILURE)
def metadata_update(self, metadata):
'''
Validate and update the resource metadata
'''
if self._metadata_format_ok(metadata):
- self.metadata = metadata
+ rsrc_metadata = self.metadata
+ if metadata['UniqueId'] in rsrc_metadata:
+ logger.warning("Overwriting Metadata item for UniqueId %s!" %
+ metadata['UniqueId'])
+ new_metadata = {}
+ for k in ('Data', 'Reason', 'Status'):
+ new_metadata[k] = metadata[k]
+ # Note we can't update self.metadata directly, as it
+ # is a Metadata descriptor object which only supports get/set
+ rsrc_metadata.update({metadata['UniqueId']: new_metadata})
+ self.metadata = rsrc_metadata
else:
logger.error("Metadata failed validation for %s" % self.name)
raise ValueError("Metadata format invalid")
+ def get_status(self):
+ '''
+ Return a list of the Status values for the handle signals
+ '''
+ return [self.metadata[s]['Status']
+ for s in self.metadata]
+
+ def get_status_reason(self, status):
+ '''
+ Return the reason associated with a particular status
+ If there is more than one handle signal matching the specified status
+ then return a semicolon delimited string containing all reasons
+ '''
+ return ';'.join([self.metadata[s]['Reason']
+ for s in self.metadata
+ if self.metadata[s]['Status'] == status])
+
WAIT_STATUSES = (
- WAITING,
+ FAILURE,
TIMEDOUT,
SUCCESS,
) = (
- 'WAITING',
+ 'FAILURE',
'TIMEDOUT',
'SUCCESS',
)
handle_id = identifier.ResourceIdentifier.from_arn_url(handle_url)
return handle_id.resource_name
- def _get_status_reason(self, handle):
- return (handle.metadata.get('Status', WAITING),
- handle.metadata.get('Reason', 'Reason not provided'))
-
def _create_timeout(self):
return eventlet.Timeout(self.timeout)
def handle_create(self):
tmo = None
+ status = FAILURE
+ reason = "Unknown reason"
try:
# keep polling our Metadata to see if the cfn-signal has written
# it yet. The execution here is limited by timeout.
handle = self.stack[handle_res_name]
self.resource_id_set(handle_res_name)
- (status, reason) = (WAITING, '')
-
- while status == WAITING:
- (status, reason) = self._get_status_reason(handle)
- if status == WAITING:
- logger.debug('Polling for WaitCondition completion,' +
- ' sleeping for %s seconds, timeout %s' %
- (self.sleep_time, self.timeout))
- eventlet.sleep(self.sleep_time)
+ # Poll for WaitConditionHandle signals indicating
+ # SUCCESS/FAILURE. We need self.count SUCCESS signals
+ # before we can declare the WaitCondition CREATE_COMPLETE
+ handle_status = handle.get_status()
+ while (FAILURE not in handle_status
+ and len(handle_status) < self.count):
+ logger.debug('Polling for WaitCondition completion,' +
+ ' sleeping for %s seconds, timeout %s' %
+ (self.sleep_time, self.timeout))
+ eventlet.sleep(self.sleep_time)
+ handle_status = handle.get_status()
+
+ if FAILURE in handle_status:
+ reason = handle.get_status_reason(FAILURE)
+ elif (len(handle_status) == self.count and
+ handle_status == [SUCCESS] * self.count):
+ logger.debug("WaitCondition %s SUCCESS" % self.name)
+ status = SUCCESS
except eventlet.Timeout as t:
if t is not tmo:
}
'''
+test_template_wc_count = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "Just a WaitCondition.",
+ "Parameters" : {},
+ "Resources" : {
+ "WaitHandle" : {
+ "Type" : "AWS::CloudFormation::WaitConditionHandle"
+ },
+ "WaitForTheHandle" : {
+ "Type" : "AWS::CloudFormation::WaitCondition",
+ "Properties" : {
+ "Handle" : {"Ref" : "WaitHandle"},
+ "Timeout" : "5",
+ "Count" : "3"
+ }
+ }
+ }
+}
+'''
+
@attr(tag=['unit', 'resource', 'WaitCondition'])
@attr(speed='slow')
class WaitConditionTest(unittest.TestCase):
def setUp(self):
self.m = mox.Mox()
- self.m.StubOutWithMock(wc.WaitCondition,
- '_get_status_reason')
+ self.m.StubOutWithMock(wc.WaitConditionHandle,
+ 'get_status')
self.m.StubOutWithMock(wc.WaitCondition,
'_create_timeout')
self.m.StubOutWithMock(eventlet, 'sleep')
self.fc = fakes.FakeKeystoneClient()
def tearDown(self):
+ self.stack.delete()
+ self.m.VerifyAll()
self.m.UnsetStubs()
- def create_stack(self, stack_name, temp, params):
+ def create_stack(self, stack_name='test_stack',
+ template=test_template_waitcondition, params={}):
+ temp = template_format.parse(template)
template = parser.Template(temp)
parameters = parser.Parameters(stack_name, template, params)
stack = parser.Stack(context.get_admin_context(), stack_name,
template, parameters)
stack.store()
+
+ self.m.StubOutWithMock(wc.WaitConditionHandle, 'keystone')
+ wc.WaitConditionHandle.keystone().MultipleTimes().AndReturn(self.fc)
+
+ id = identifier.ResourceIdentifier('test_tenant', stack.name,
+ stack.id, '', 'WaitHandle')
+ self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier')
+ wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id)
+
return stack
def test_post_success_to_handle(self):
+ self.stack = self.create_stack()
+ wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5))
+ wc.WaitConditionHandle.get_status().AndReturn([])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn([])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS'])
- t = template_format.parse(test_template_waitcondition)
- stack = self.create_stack('test_stack', t, {})
+ self.m.ReplayAll()
+
+ self.stack.create()
+
+ resource = self.stack.resources['WaitForTheHandle']
+ self.assertEqual(resource.state,
+ 'CREATE_COMPLETE')
+
+ r = db_api.resource_get_by_name_and_stack(None, 'WaitHandle',
+ self.stack.id)
+ self.assertEqual(r.name, 'WaitHandle')
+ def test_post_failure_to_handle(self):
+ self.stack = self.create_stack()
wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5))
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('WAITING', ''))
+ wc.WaitConditionHandle.get_status().AndReturn([])
eventlet.sleep(1).AndReturn(None)
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('WAITING', ''))
+ wc.WaitConditionHandle.get_status().AndReturn([])
eventlet.sleep(1).AndReturn(None)
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('SUCCESS', 'woot toot'))
+ wc.WaitConditionHandle.get_status().AndReturn(['FAILURE'])
- self.m.StubOutWithMock(wc.WaitConditionHandle, 'keystone')
- wc.WaitConditionHandle.keystone().MultipleTimes().AndReturn(self.fc)
+ self.m.ReplayAll()
- id = identifier.ResourceIdentifier('test_tenant', stack.name,
- stack.id, '', 'WaitHandle')
- self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier')
- wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id)
+ self.stack.create()
+
+ resource = self.stack.resources['WaitForTheHandle']
+ self.assertEqual(resource.state,
+ 'CREATE_FAILED')
+
+ r = db_api.resource_get_by_name_and_stack(None, 'WaitHandle',
+ self.stack.id)
+ self.assertEqual(r.name, 'WaitHandle')
+
+ def test_post_success_to_handle_count(self):
+ self.stack = self.create_stack(template=test_template_wc_count)
+ wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5))
+ wc.WaitConditionHandle.get_status().AndReturn([])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS'])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS', 'SUCCESS'])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS', 'SUCCESS',
+ 'SUCCESS'])
self.m.ReplayAll()
- stack.create()
+ self.stack.create()
- resource = stack.resources['WaitForTheHandle']
+ resource = self.stack.resources['WaitForTheHandle']
self.assertEqual(resource.state,
'CREATE_COMPLETE')
r = db_api.resource_get_by_name_and_stack(None, 'WaitHandle',
- stack.id)
+ self.stack.id)
self.assertEqual(r.name, 'WaitHandle')
- self.m.VerifyAll()
+ def test_post_failure_to_handle_count(self):
+ self.stack = self.create_stack(template=test_template_wc_count)
+ wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5))
+ wc.WaitConditionHandle.get_status().AndReturn([])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS'])
+ eventlet.sleep(1).AndReturn(None)
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS', 'FAILURE'])
- def test_timeout(self):
+ self.m.ReplayAll()
+
+ self.stack.create()
+
+ resource = self.stack.resources['WaitForTheHandle']
+ self.assertEqual(resource.state,
+ 'CREATE_FAILED')
- t = template_format.parse(test_template_waitcondition)
- stack = self.create_stack('test_stack', t, {})
+ r = db_api.resource_get_by_name_and_stack(None, 'WaitHandle',
+ self.stack.id)
+ self.assertEqual(r.name, 'WaitHandle')
+ def test_timeout(self):
+ self.stack = self.create_stack()
tmo = eventlet.Timeout(6)
wc.WaitCondition._create_timeout().AndReturn(tmo)
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('WAITING', ''))
+ wc.WaitConditionHandle.get_status().AndReturn([])
eventlet.sleep(1).AndReturn(None)
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('WAITING', ''))
+ wc.WaitConditionHandle.get_status().AndReturn([])
eventlet.sleep(1).AndRaise(tmo)
- self.m.StubOutWithMock(wc.WaitConditionHandle, 'keystone')
- wc.WaitConditionHandle.keystone().MultipleTimes().AndReturn(self.fc)
-
- id = identifier.ResourceIdentifier('test_tenant', stack.name,
- stack.id, '', 'WaitHandle')
- self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier')
- wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id)
-
self.m.ReplayAll()
- stack.create()
+ self.stack.create()
- resource = stack.resources['WaitForTheHandle']
+ resource = self.stack.resources['WaitForTheHandle']
self.assertEqual(resource.state,
'CREATE_FAILED')
self.assertEqual(wc.WaitCondition.UPDATE_REPLACE,
resource.handle_update())
- stack.delete()
-
- self.m.VerifyAll()
-
@attr(tag=['unit', 'resource', 'WaitConditionHandle'])
@attr(speed='fast')
'http://127.0.0.1:8000/v1/waitcondition')
self.fc = fakes.FakeKeystoneClient()
+ self.stack = self.create_stack()
def tearDown(self):
+ self.stack.delete()
+ self.m.VerifyAll()
self.m.UnsetStubs()
def create_stack(self, stack_name='test_stack2', params={}):
stack.store()
# Stub waitcondition status so all goes CREATE_COMPLETE
- self.m.StubOutWithMock(wc.WaitCondition, '_get_status_reason')
- wc.WaitCondition._get_status_reason(
- mox.IgnoreArg()).AndReturn(('SUCCESS', 'woot toot'))
+ self.m.StubOutWithMock(wc.WaitConditionHandle, 'get_status')
+ wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS'])
self.m.StubOutWithMock(wc.WaitCondition, '_create_timeout')
wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5))
self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier')
wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id)
+ self.m.ReplayAll()
+ stack.create()
+
return stack
def test_handle(self):
- stack = self.create_stack()
-
# Stub time to a fixed value so we can get an expected signature
t = time.gmtime(1354196977)
self.m.StubOutWithMock(time, 'gmtime')
time.gmtime().MultipleTimes().AndReturn(t)
-
self.m.ReplayAll()
- stack.create()
- resource = stack.resources['WaitHandle']
+ resource = self.stack.resources['WaitHandle']
self.assertEqual(resource.state, 'CREATE_COMPLETE')
expected_url = "".join([
self.assertEqual(resource.UPDATE_REPLACE, resource.handle_update())
- stack.delete()
-
- self.m.VerifyAll()
-
def test_metadata_update(self):
- stack = self.create_stack()
- self.m.ReplayAll()
- stack.create()
-
- resource = stack.resources['WaitHandle']
+ resource = self.stack.resources['WaitHandle']
self.assertEqual(resource.state, 'CREATE_COMPLETE')
test_metadata = {'Data': 'foo', 'Reason': 'bar',
'Status': 'SUCCESS', 'UniqueId': '123'}
resource.metadata_update(test_metadata)
- self.assertEqual(resource.metadata, test_metadata)
-
- stack.delete()
-
- self.m.VerifyAll()
+ handle_metadata = {u'123': {u'Data': u'foo',
+ u'Reason': u'bar',
+ u'Status': u'SUCCESS'}}
+ self.assertEqual(resource.metadata, handle_metadata)
def test_metadata_update_invalid(self):
- stack = self.create_stack()
- self.m.ReplayAll()
- stack.create()
-
- resource = stack.resources['WaitHandle']
+ resource = self.stack.resources['WaitHandle']
self.assertEqual(resource.state, 'CREATE_COMPLETE')
# metadata_update should raise a ValueError if the metadata
# is missing any of the expected keys
- err1_metadata = {'Data': 'foo', 'Status': 'SUCCESS', 'UniqueId': '123'}
- self.assertRaises(ValueError, resource.metadata_update, err1_metadata)
+ err_metadata = {'Data': 'foo', 'Status': 'SUCCESS', 'UniqueId': '123'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+
+ err_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+
+ err_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+
+ err_metadata = {'data': 'foo', 'reason': 'bar',
+ 'status': 'SUCCESS', 'uniqueid': '1234'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+
+ # Also any Status other than SUCCESS or FAILURE should be rejected
+ err_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'UCCESS', 'UniqueId': '123'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+ err_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'wibble', 'UniqueId': '123'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+ err_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'success', 'UniqueId': '123'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+ err_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'FAIL', 'UniqueId': '123'}
+ self.assertRaises(ValueError, resource.metadata_update, err_metadata)
+
+ def test_get_status(self):
+ resource = self.stack.resources['WaitHandle']
+ self.assertEqual(resource.state, 'CREATE_COMPLETE')
- err1_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'}
- self.assertRaises(ValueError, resource.metadata_update, err1_metadata)
+ # UnsetStubs before teardown, don't want get_status stubbed anymore..
+ self.m.UnsetStubs()
+
+ # ..and re-Stub the stuff we still need stubbed
+ self.m.StubOutWithMock(wc.WaitConditionHandle, 'keystone')
+ wc.WaitConditionHandle.keystone().MultipleTimes().AndReturn(self.fc)
- err1_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'}
- self.assertRaises(ValueError, resource.metadata_update, err1_metadata)
+ id = identifier.ResourceIdentifier('test_tenant', self.stack.name,
+ self.stack.id, '', 'WaitHandle')
+ self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier')
+ wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id)
- err1_metadata = {'data': 'foo', 'reason': 'bar',
- 'status': 'SUCCESS', 'uniqueid': '1234'}
- self.assertRaises(ValueError, resource.metadata_update, err1_metadata)
+ self.m.ReplayAll()
- stack.delete()
+ self.assertEqual(resource.get_status(), [])
- self.m.VerifyAll()
+ test_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'SUCCESS', 'UniqueId': '123'}
+ resource.metadata_update(test_metadata)
+ self.assertEqual(resource.get_status(), ['SUCCESS'])
+
+ test_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'SUCCESS', 'UniqueId': '456'}
+ resource.metadata_update(test_metadata)
+ self.assertEqual(resource.get_status(), ['SUCCESS', 'SUCCESS'])
+
+ def test_get_status_reason(self):
+ resource = self.stack.resources['WaitHandle']
+ self.assertEqual(resource.state, 'CREATE_COMPLETE')
+
+ test_metadata = {'Data': 'foo', 'Reason': 'bar',
+ 'Status': 'SUCCESS', 'UniqueId': '123'}
+ resource.metadata_update(test_metadata)
+ self.assertEqual(resource.get_status_reason('SUCCESS'), 'bar')
+
+ test_metadata = {'Data': 'dog', 'Reason': 'cat',
+ 'Status': 'SUCCESS', 'UniqueId': '456'}
+ resource.metadata_update(test_metadata)
+ self.assertEqual(resource.get_status_reason('SUCCESS'), 'bar;cat')
+
+ test_metadata = {'Data': 'boo', 'Reason': 'hoo',
+ 'Status': 'FAILURE', 'UniqueId': '789'}
+ resource.metadata_update(test_metadata)
+ self.assertEqual(resource.get_status_reason('FAILURE'), 'hoo')