Add active wait function
[yardstick.git] / yardstick / common / utils.py
index 3f2d546..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)
@@ -93,6 +97,21 @@ def import_modules_from_package(package, raise_exception=False):
                 logger.exception('Unable to import module %s', module_name)
 
 
+NON_NONE_DEFAULT = object()
+
+
+def get_key_with_default(data, key, default=NON_NONE_DEFAULT):
+    value = data.get(key, default)
+    if value is NON_NONE_DEFAULT:
+        raise KeyError(key)
+    return value
+
+
+def make_dict_from_map(data, key_map):
+    return {dest_key: get_key_with_default(data, src_key, default)
+            for dest_key, (src_key, default) in key_map.items()}
+
+
 def makedirs(d):
     try:
         os.makedirs(d)
@@ -121,6 +140,11 @@ def source_env(env_file):
     p = subprocess.Popen(". %s; env" % env_file, stdout=subprocess.PIPE,
                          shell=True)
     output = p.communicate()[0]
+
+    # sometimes output type would be binary_type, and it don't have splitlines
+    # method, so we need to decode
+    if isinstance(output, six.binary_type):
+        output = encodeutils.safe_decode(output)
     env = dict(line.split('=', 1) for line in output.splitlines() if '=' in line)
     os.environ.update(env)
     return env
@@ -282,6 +306,19 @@ def get_ip_version(ip_addr):
         return address.version
 
 
+def make_ip_addr(ip, mask):
+    """
+    :param ip[str]: ip adddress
+    :param mask[str]: /24 prefix of 255.255.255.0 netmask
+    :return: IPv4Interface object
+    """
+    try:
+        return ipaddress.ip_interface(six.text_type('/'.join([ip, mask])))
+    except (TypeError, ValueError):
+        # None so we can skip later
+        return None
+
+
 def ip_to_hex(ip_addr, separator=''):
     try:
         address = ipaddress.ip_address(six.text_type(ip_addr))
@@ -385,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):
@@ -440,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