Make the TestResource a bit smarter

This is a better representation of how actual resources work.

1. implement check_*_complete
2. measure the time, not just do one sleep
3. add client_name and entity_name so we can actually talk to a service
   (even if it fails).
4. add some example usage in test_create_update.py
5. don't use json_snippet._properties

The timeout works in a similar way to the waitcondition.

Change-Id: I7d3fd0340fefe4a06b8d4f0952c162be604aa58c
diff --git a/common/test_resources/test_resource.py b/common/test_resources/test_resource.py
index 55255a6..92b09f1 100644
--- a/common/test_resources/test_resource.py
+++ b/common/test_resources/test_resource.py
@@ -11,7 +11,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import datetime
 import eventlet
+from oslo_utils import timeutils
+import six
 
 from heat.common.i18n import _
 from heat.engine import attributes
@@ -34,10 +37,19 @@
 
     support_status = support.SupportStatus(version='2014.1')
 
-    PROPERTIES = (
-        VALUE, UPDATE_REPLACE, FAIL, WAIT_SECS
+    ACTION_TIMES = (
+        CREATE_WAIT_SECS, UPDATE_WAIT_SECS, DELETE_WAIT_SECS
     ) = (
-        'value', 'update_replace', 'fail', 'wait_secs'
+        'create', 'update', 'delete')
+
+    PROPERTIES = (
+        VALUE, UPDATE_REPLACE, FAIL,
+        CLIENT_NAME, ENTITY_NAME,
+        WAIT_SECS, ACTION_WAIT_SECS
+    ) = (
+        'value', 'update_replace', 'fail',
+        'client_name', 'entity_name',
+        'wait_secs', 'action_wait_secs'
     )
 
     ATTRIBUTES = (
@@ -69,11 +81,47 @@
         ),
         WAIT_SECS: properties.Schema(
             properties.Schema.NUMBER,
-            _('Value which can be set for resource to wait after an action '
-              'is performed.'),
+            _('Seconds to wait after an action (-1 is infinite)'),
             update_allowed=True,
             default=0,
         ),
+        ACTION_WAIT_SECS: properties.Schema(
+            properties.Schema.MAP,
+            _('Options for simulating waiting.'),
+            update_allowed=True,
+            schema={
+                CREATE_WAIT_SECS: properties.Schema(
+                    properties.Schema.NUMBER,
+                    _('Seconds to wait after a create. '
+                      'Defaults to the global wait_secs'),
+                    update_allowed=True,
+                ),
+                UPDATE_WAIT_SECS: properties.Schema(
+                    properties.Schema.NUMBER,
+                    _('Seconds to wait after an update. '
+                      'Defaults to the global wait_secs'),
+                    update_allowed=True,
+                ),
+                DELETE_WAIT_SECS: properties.Schema(
+                    properties.Schema.NUMBER,
+                    _('Seconds to wait after a delete. '
+                      'Defaults to the global wait_secs'),
+                    update_allowed=True,
+                ),
+            }
+        ),
+        CLIENT_NAME: properties.Schema(
+            properties.Schema.STRING,
+            _('Client to poll.'),
+            default='',
+            update_allowed=True
+        ),
+        ENTITY_NAME: properties.Schema(
+            properties.Schema.STRING,
+            _('Client entity to poll.'),
+            default='',
+            update_allowed=True
+        ),
     }
 
     attributes_schema = {
@@ -84,44 +132,88 @@
         ),
     }
 
+    def _wait_secs(self):
+        secs = None
+        if self.properties[self.ACTION_WAIT_SECS]:
+            secs = self.properties[self.ACTION_WAIT_SECS][self.action.lower()]
+        if secs is None:
+            secs = self.properties[self.WAIT_SECS]
+        LOG.info('%s wait_secs:%s, action:%s' % (self.name, secs,
+                                                 self.action.lower()))
+        return secs
+
     def handle_create(self):
-        value = self.properties.get(self.VALUE)
         fail_prop = self.properties.get(self.FAIL)
-        sleep_secs = self.properties.get(self.WAIT_SECS)
+        if not fail_prop:
+            value = self.properties.get(self.VALUE)
+            self.data_set('value', value, redact=False)
+            self.resource_id_set(self.physical_resource_name())
 
-        self.data_set('value', value, redact=False)
-        self.resource_id_set(self.physical_resource_name())
-
-        # sleep for specified time
-        if sleep_secs:
-            LOG.debug("Resource %s sleeping for %s seconds",
-                      self.name, sleep_secs)
-            eventlet.sleep(sleep_secs)
-
-        # emulate failure
-        if fail_prop:
-            raise ValueError("Test Resource failed %s" % self.name)
+        return timeutils.utcnow(), self._wait_secs()
 
     def handle_update(self, json_snippet=None, tmpl_diff=None, prop_diff=None):
+        self.properties = json_snippet.properties(self.properties_schema,
+                                                  self.context)
         value = prop_diff.get(self.VALUE)
-        new_prop = json_snippet._properties
         if value:
-            update_replace = new_prop.get(self.UPDATE_REPLACE, False)
+            update_replace = self.properties[self.UPDATE_REPLACE]
             if update_replace:
                 raise resource.UpdateReplace(self.name)
             else:
-                fail_prop = new_prop.get(self.FAIL, False)
-                sleep_secs = new_prop.get(self.WAIT_SECS, 0)
                 # emulate failure
-                if fail_prop:
-                    raise Exception("Test Resource failed %s", self.name)
-                # update in place
-                self.data_set('value', value, redact=False)
+                fail_prop = self.properties[self.FAIL]
+                if not fail_prop:
+                    # update in place
+                    self.data_set('value', value, redact=False)
+                return timeutils.utcnow(), self._wait_secs()
+        return timeutils.utcnow(), 0
 
-                if sleep_secs:
-                    LOG.debug("Update of Resource %s sleeping for %s seconds",
-                              self.name, sleep_secs)
-                    eventlet.sleep(sleep_secs)
+    def handle_delete(self):
+        return timeutils.utcnow(), self._wait_secs()
+
+    def check_create_complete(self, cookie):
+        return self._check_status_complete(*cookie)
+
+    def check_update_complete(self, cookie):
+        return self._check_status_complete(*cookie)
+
+    def check_delete_complete(self, cookie):
+        return self._check_status_complete(*cookie)
+
+    def _check_status_complete(self, started_at, wait_secs):
+        def simulated_effort():
+            client_name = self.properties[self.CLIENT_NAME]
+            self.entity = self.properties[self.ENTITY_NAME]
+            if client_name and self.entity:
+                # Allow the user to set the value to a real resource id.
+                entity_id = self.data().get('value') or self.resource_id
+                try:
+                    obj = getattr(self.client(name=client_name), self.entity)
+                    obj.get(entity_id)
+                except Exception as exc:
+                    LOG.debug('%s.%s(%s) %s' % (client_name, self.entity,
+                                                entity_id, six.text_type(exc)))
+            else:
+                # just sleep some more
+                eventlet.sleep(1)
+
+        if isinstance(started_at, six.string_types):
+            started_at = timeutils.parse_isotime(started_at)
+
+        started_at = timeutils.normalize_time(started_at)
+        waited = timeutils.utcnow() - started_at
+        LOG.info("Resource %s waited %s/%s seconds",
+                 self.name, waited, wait_secs)
+
+        # wait_secs < 0 is an infinite wait time.
+        if wait_secs >= 0 and waited > datetime.timedelta(seconds=wait_secs):
+            fail_prop = self.properties[self.FAIL]
+            if fail_prop and self.action != self.DELETE:
+                raise ValueError("Test Resource failed %s" % self.name)
+            return True
+
+        simulated_effort()
+        return False
 
     def _resolve_attribute(self, name):
         if name == self.OUTPUT:
diff --git a/functional/test_create_update.py b/functional/test_create_update.py
index 87d7a23..b2aad34 100644
--- a/functional/test_create_update.py
+++ b/functional/test_create_update.py
@@ -26,7 +26,10 @@
                 'value': 'Test1',
                 'fail': False,
                 'update_replace': False,
-                'wait_secs': 0
+                'wait_secs': 0,
+                'action_wait_secs': {'create': 1},
+                'client_name': 'nova',
+                'entity_name': 'servers',
             }
         }
     }
@@ -42,7 +45,8 @@
                 'value': 'Test1',
                 'fail': False,
                 'update_replace': False,
-                'wait_secs': 0
+                'wait_secs': 0,
+                'action_wait_secs': {'update': 1}
             }
         },
         'test2': {