]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat engine : Implement Count property for WaitCondition
authorSteven Hardy <shardy@redhat.com>
Mon, 14 Jan 2013 16:59:03 +0000 (16:59 +0000)
committerSteven Hardy <shardy@redhat.com>
Mon, 14 Jan 2013 18:58:49 +0000 (18:58 +0000)
The Count property of WaitCondition is currently ignored, so
regardless of the number specified the WaitCondition resource will
always go to CREATE_COMPLETE on the first WaitConditionHandle signal.
This patch fixes this so the Count property is handled correctly.

fixes bug 1097813

Change-Id: I71348c553702c0282a8826488c953ac3c2c76e38
Signed-off-by: Steven Hardy <shardy@redhat.com>
heat/engine/resources/wait_condition.py
heat/tests/test_waitcondition.py
templates/WordPress_Single_Instance_With_HA.template

index 49557b78a2cc71d17a736d902f199c7f6ae881fc..fb18e371bad823bf70f4897d655e2c242a35c453 100644 (file)
@@ -118,32 +118,60 @@ class WaitConditionHandle(resource.Resource):
         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',
 )
@@ -179,15 +207,13 @@ class WaitCondition(resource.Resource):
         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.
@@ -196,15 +222,24 @@ class WaitCondition(resource.Resource):
                 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:
index c11e1864d3f9e1ac292ff54742479dd8b5b29d5a..d134d1770cd4fb9d5095245299fa767c74a72d64 100644 (file)
@@ -50,14 +50,35 @@ test_template_waitcondition = '''
 }
 '''
 
+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')
@@ -68,91 +89,137 @@ class WaitConditionTest(unittest.TestCase):
         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')
@@ -163,8 +230,11 @@ class WaitConditionHandleTest(unittest.TestCase):
                              '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={}):
@@ -180,9 +250,8 @@ class WaitConditionHandleTest(unittest.TestCase):
         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))
 
@@ -195,20 +264,19 @@ class WaitConditionHandleTest(unittest.TestCase):
         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([
@@ -227,50 +295,96 @@ class WaitConditionHandleTest(unittest.TestCase):
 
         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')
index a3c864ceb813278f091201876832956cd5128c57..bb048fad7601bcc1f7a6793a6397000a0969afd6 100644 (file)
       "DependsOn" : "WikiDatabase",
       "Properties" : {
         "Handle" : {"Ref" : "WaitHandle"},
+        "Count" : "1",
         "Timeout" : "600"
       }
     }