Add active wait function 03/58803/4
authorRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Thu, 19 Apr 2018 13:58:38 +0000 (14:58 +0100)
committerEmma Foley <emma.l.foley@intel.com>
Fri, 22 Jun 2018 17:38:15 +0000 (17:38 +0000)
Added function "wait_until_true". This function will make an active
wait until the predicate passed as an argument returns True.

If the timeout expires, the function will raise a generic exception
or a user defined one passed as an argument.

This function will be used in YARDSTICK-1127.

JIRA: YARDSTICK-1128

Change-Id: I9854e465ac6b586bf4be39ab4b266d5625b39e30
Signed-off-by: Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
(cherry picked from 099108aab37d1fae1b27f4e2e20136c234df1a52)

yardstick/common/exceptions.py
yardstick/common/utils.py
yardstick/tests/unit/common/test_utils.py

index c78fecc..ad246e0 100644 (file)
@@ -166,6 +166,14 @@ class TaskRenderError(YardstickException):
     message = 'Failed to render template:\n%(input_task)s'
 
 
+class TimerTimeout(YardstickException):
+    message = 'Timer timeout expired, %(timeout)s seconds'
+
+
+class WaitTimeout(YardstickException):
+    message = 'Wait timeout while waiting for condition'
+
+
 class ScenarioCreateNetworkError(YardstickException):
     message = 'Create Neutron Network Scenario failed'
 
index 864e625..869db46 100644 (file)
@@ -23,9 +23,11 @@ import logging
 import os
 import random
 import re
+import signal
 import socket
 import subprocess
 import sys
+import time
 
 import six
 from flask import jsonify
@@ -34,6 +36,8 @@ from oslo_serialization import jsonutils
 from oslo_utils import encodeutils
 
 import yardstick
+from yardstick.common import exceptions
+
 
 logger = logging.getLogger(__name__)
 logger.setLevel(logging.DEBUG)
@@ -418,15 +422,24 @@ class ErrorClass(object):
 
 
 class Timer(object):
-    def __init__(self):
+    def __init__(self, timeout=None):
         super(Timer, self).__init__()
         self.start = self.delta = None
+        self._timeout = int(timeout) if timeout else None
+
+    def _timeout_handler(self, *args):
+        raise exceptions.TimerTimeout(timeout=self._timeout)
 
     def __enter__(self):
         self.start = datetime.datetime.now()
+        if self._timeout:
+            signal.signal(signal.SIGALRM, self._timeout_handler)
+            signal.alarm(self._timeout)
         return self
 
     def __exit__(self, *_):
+        if self._timeout:
+            signal.alarm(0)
         self.delta = datetime.datetime.now() - self.start
 
     def __getattr__(self, item):
@@ -473,3 +486,22 @@ def open_relative_file(path, task_path):
         if e.errno == errno.ENOENT:
             return open(os.path.join(task_path, path))
         raise
+
+
+def wait_until_true(predicate, timeout=60, sleep=1, exception=None):
+    """Wait until callable predicate is evaluated as True
+
+    :param predicate: (func) callable deciding whether waiting should continue
+    :param timeout: (int) timeout in seconds how long should function wait
+    :param sleep: (int) polling interval for results in seconds
+    :param exception: exception instance to raise on timeout. If None is passed
+                      (default) then WaitTimeout exception is raised.
+    """
+    try:
+        with Timer(timeout=timeout):
+            while not predicate():
+                time.sleep(sleep)
+    except exceptions.TimerTimeout:
+        if exception and issubclass(exception, Exception):
+            raise exception  # pylint: disable=raising-bad-type
+        raise exceptions.WaitTimeout
index c61a95f..87995ac 100644 (file)
@@ -16,12 +16,14 @@ import mock
 import os
 import six
 from six.moves import configparser
+import time
 import unittest
 
 import yardstick
 from yardstick import ssh
 from yardstick.common import constants
 from yardstick.common import utils
+from yardstick.common import exceptions
 
 
 class IterSubclassesTestCase(unittest.TestCase):
@@ -1149,3 +1151,43 @@ class ReadMeminfoTestCase(unittest.TestCase):
             output = utils.read_meminfo(ssh_client)
             mock_get_client.assert_called_once_with('/proc/meminfo', mock.ANY)
         self.assertEqual(self.MEMINFO_DICT, output)
+
+
+class TimerTestCase(unittest.TestCase):
+
+    def test__getattr(self):
+        with utils.Timer() as timer:
+            time.sleep(1)
+        self.assertEqual(1, round(timer.total_seconds(), 0))
+        self.assertEqual(1, timer.delta.seconds)
+
+    def test__enter_with_timeout(self):
+        with utils.Timer(timeout=10) as timer:
+            time.sleep(1)
+        self.assertEqual(1, round(timer.total_seconds(), 0))
+
+    def test__enter_with_timeout_exception(self):
+        with self.assertRaises(exceptions.TimerTimeout):
+            with utils.Timer(timeout=1):
+                time.sleep(2)
+
+
+class WaitUntilTrueTestCase(unittest.TestCase):
+
+    def test_no_timeout(self):
+        self.assertIsNone(utils.wait_until_true(lambda: True,
+                                                timeout=1, sleep=1))
+
+    def test_timeout_generic_exception(self):
+        with self.assertRaises(exceptions.WaitTimeout):
+            self.assertIsNone(utils.wait_until_true(lambda: False,
+                                                    timeout=1, sleep=1))
+
+    def test_timeout_given_exception(self):
+        class MyTimeoutException(exceptions.YardstickException):
+            message = 'My timeout exception'
+
+        with self.assertRaises(MyTimeoutException):
+            self.assertIsNone(
+                utils.wait_until_true(lambda: False, timeout=1, sleep=1,
+                                      exception=MyTimeoutException))