Merge "task_state must be consider before many action"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index cca3cdd..cd57354 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -122,6 +122,10 @@
 # Number of seconds to wait to authenticate to an instance
 ssh_timeout = 300
 
+# Additinal wait time for clean state, when there is
+# no OS-EXT-STS extension availiable
+ready_wait = 0
+
 # Number of seconds to wait for output from ssh channel
 ssh_channel_timeout = 60
 
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
new file mode 100644
index 0000000..15569cd
--- /dev/null
+++ b/tempest/common/waiters.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+import time
+
+from tempest import config
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+
+CONFIG = config.TempestConfig()
+LOG = logging.getLogger(__name__)
+
+
+# NOTE(afazekas): This function needs to know a token and a subject.
+def wait_for_server_status(client, server_id, status, ready_wait=True):
+    """Waits for a server to reach a given status."""
+
+    def _get_task_state(body):
+        task_state = body.get('OS-EXT-STS:task_state', None)
+        return task_state
+
+    # NOTE(afazekas): UNKNOWN status possible on ERROR
+    # or in a very early stage.
+    resp, body = client.get_server(server_id)
+    old_status = server_status = body['status']
+    old_task_state = task_state = _get_task_state(body)
+    start_time = int(time.time())
+    while True:
+        # NOTE(afazekas): Now the BUILD status only reached
+        # between the UNKOWN->ACTIVE transition.
+        # TODO(afazekas): enumerate and validate the stable status set
+        if status == 'BUILD' and server_status != 'UNKNOWN':
+            return
+        if server_status == status:
+            if ready_wait:
+                if status == 'BUILD':
+                    return
+                # NOTE(afazekas): The instance is in "ready for action state"
+                # when no task in progress
+                # NOTE(afazekas): Converted to string bacuse of the XML
+                # responses
+                if str(task_state) == "None":
+                    # without state api extension 3 sec usually enough
+                    time.sleep(CONFIG.compute.ready_wait)
+                    return
+            else:
+                return
+
+        time.sleep(client.build_interval)
+        resp, body = client.get_server(server_id)
+        server_status = body['status']
+        task_state = _get_task_state(body)
+        if (server_status != old_status) or (task_state != old_task_state):
+            LOG.info('State transition "%s" ==> "%s" after %d second wait',
+                     '/'.join((old_status, str(old_task_state))),
+                     '/'.join((server_status, str(task_state))),
+                     time.time() - start_time)
+        if server_status == 'ERROR':
+            raise exceptions.BuildErrorException(server_id=server_id)
+
+        timed_out = int(time.time()) - start_time >= client.build_timeout
+
+        if timed_out:
+            message = ('Server %s failed to reach %s status within the '
+                       'required time (%s s).' %
+                       (server_id, status, client.build_timeout))
+            message += ' Current status: %s.' % server_status
+            raise exceptions.TimeoutException(message)
+        old_status = server_status
+        old_task_state = task_state
diff --git a/tempest/config.py b/tempest/config.py
index 100c673..b386968 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -177,6 +177,10 @@
                default=300,
                help="Timeout in seconds to wait for authentication to "
                     "succeed."),
+    cfg.IntOpt('ready_wait',
+               default=0,
+               help="Additinal wait time for clean state, when there is"
+                    " no OS-EXT-STS extension availiable"),
     cfg.IntOpt('ssh_channel_timeout',
                default=60,
                help="Timeout in seconds to wait for output from ssh "
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index c5827f6..1f2daec 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -21,6 +21,7 @@
 import urllib
 
 from tempest.common.rest_client import RestClient
+from tempest.common import waiters
 from tempest import exceptions
 
 
@@ -152,28 +153,7 @@
 
     def wait_for_server_status(self, server_id, status):
         """Waits for a server to reach a given status."""
-        resp, body = self.get_server(server_id)
-        server_status = body['status']
-        start = int(time.time())
-
-        while(server_status != status):
-            if status == 'BUILD' and server_status != 'UNKNOWN':
-                return
-            time.sleep(self.build_interval)
-            resp, body = self.get_server(server_id)
-            server_status = body['status']
-
-            if server_status == 'ERROR':
-                raise exceptions.BuildErrorException(server_id=server_id)
-
-            timed_out = int(time.time()) - start >= self.build_timeout
-
-            if server_status != status and timed_out:
-                message = ('Server %s failed to reach %s status within the '
-                           'required time (%s s).' %
-                           (server_id, status, self.build_timeout))
-                message += ' Current status: %s.' % server_status
-                raise exceptions.TimeoutException(message)
+        return waiters.wait_for_server_status(self, server_id, status)
 
     def wait_for_server_termination(self, server_id, ignore_error=False):
         """Waits for server to reach termination."""
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 6f17611..bf72bdc 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -22,6 +22,7 @@
 from lxml import etree
 
 from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
 from tempest import exceptions
 from tempest.openstack.common import log as logging
 from tempest.services.compute.xml.common import Document
@@ -336,28 +337,7 @@
 
     def wait_for_server_status(self, server_id, status):
         """Waits for a server to reach a given status."""
-        resp, body = self.get_server(server_id)
-        server_status = body['status']
-        start = int(time.time())
-
-        while(server_status != status):
-            if status == 'BUILD' and server_status != 'UNKNOWN':
-                return
-            time.sleep(self.build_interval)
-            resp, body = self.get_server(server_id)
-            server_status = body['status']
-
-            if server_status == 'ERROR':
-                raise exceptions.BuildErrorException(server_id=server_id)
-
-            timed_out = int(time.time()) - start >= self.build_timeout
-
-            if server_status != status and timed_out:
-                message = ('Server %s failed to reach %s status within the '
-                           'required time (%s s).' %
-                           (server_id, status, self.build_timeout))
-                message += ' Current status: %s.' % server_status
-                raise exceptions.TimeoutException(message)
+        return waiters.wait_for_server_status(self, server_id, status)
 
     def wait_for_server_termination(self, server_id, ignore_error=False):
         """Waits for server to reach termination."""