Refactor waiters in our tempest plugin

Deduplicates some of the code between common.waiters module and
BaremetalScenarioTest functions.

Change-Id: Ia87646cccdefba22caf121f4e347e8f2edf736e0
diff --git a/ironic_tempest_plugin/common/utils.py b/ironic_tempest_plugin/common/utils.py
new file mode 100644
index 0000000..67c4922
--- /dev/null
+++ b/ironic_tempest_plugin/common/utils.py
@@ -0,0 +1,33 @@
+#    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.
+
+
+def get_node(client, node_id=None, instance_uuid=None):
+    """Get a node by its identifier or instance UUID.
+
+    If both node_id and instance_uuid specified, node_id will be used.
+
+    :param client: an instance of tempest plugin BaremetalClient.
+    :param node_id: identifier (UUID or name) of the node.
+    :param instance_uuid: UUID of the instance.
+    :returns: the requested node.
+    :raises: AssertionError, if neither node_id nor instance_uuid was provided
+    """
+    assert node_id or instance_uuid, ('Either node or instance identifier '
+                                      'has to be provided.')
+    if node_id:
+        _, body = client.show_node(node_id)
+        return body
+    elif instance_uuid:
+        _, body = client.show_node_by_instance_uuid(instance_uuid)
+        if body['nodes']:
+            return body['nodes'][0]
diff --git a/ironic_tempest_plugin/common/waiters.py b/ironic_tempest_plugin/common/waiters.py
index 83aeeab..7cfc12e 100644
--- a/ironic_tempest_plugin/common/waiters.py
+++ b/ironic_tempest_plugin/common/waiters.py
@@ -12,12 +12,37 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-
-import time
-
+import six
+from tempest import config
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions as lib_exc
 
+from ironic_tempest_plugin.common import utils
+
+CONF = config.CONF
+
+
+def _determine_and_check_timeout_interval(timeout, default_timeout,
+                                          interval, default_interval):
+    if timeout is None:
+        timeout = default_timeout
+    if interval is None:
+        interval = default_interval
+    if (not isinstance(timeout, six.integer_types) or
+            not isinstance(interval, six.integer_types) or
+            timeout < 0 or interval < 0):
+        raise AssertionError(
+            'timeout and interval should be >= 0 or None, current values are: '
+            '%(timeout)s, %(interval)s respectively. If timeout and/or '
+            'interval are None, the default_timeout and default_interval are '
+            'used, and they should be integers >= 0, current values are: '
+            '%(default_timeout)s, %(default_interval)s respectively.' % dict(
+                timeout=timeout, interval=interval,
+                default_timeout=default_timeout,
+                default_interval=default_interval)
+        )
+    return timeout, interval
+
 
 def wait_for_bm_node_status(client, node_id, attr, status, timeout=None,
                             interval=None):
@@ -26,7 +51,7 @@
     :param client: an instance of tempest plugin BaremetalClient.
     :param node_id: identifier of the node.
     :param attr: node's API-visible attribute to check status of.
-    :param status: desired status.
+    :param status: desired status. Can be a list of statuses.
     :param timeout: the timeout after which the check is considered as failed.
         Defaults to client.build_timeout.
     :param interval: an interval between show_node calls for status check.
@@ -34,37 +59,54 @@
 
     The client should have a show_node(node_id) method to get the node.
     """
-    if timeout is None:
-        timeout = client.build_timeout
-    if interval is None:
-        interval = client.build_interval
-    if timeout < 0 or interval < 0:
-        raise lib_exc.InvalidConfiguration(
-            'timeout and interval should be >= 0 or None, current values are: '
-            '%(timeout)s, %(interval)s respectively.' % dict(timeout=timeout,
-                                                             interval=interval)
-        )
+    timeout, interval = _determine_and_check_timeout_interval(
+        timeout, client.build_timeout, interval, client.build_interval)
 
-    start = int(time.time())
-    _, node = client.show_node(node_id)
+    if not isinstance(status, list):
+        status = [status]
 
-    while node[attr] != status:
-        status_curr = node[attr]
-        if status_curr == status:
-            return
+    def is_attr_in_status():
+        node = utils.get_node(client, node_id=node_id)
+        if node[attr] in status:
+            return True
+        return False
 
-        if int(time.time()) - start >= timeout:
-            message = ('Node %(node_id)s failed to reach %(attr)s=%(status)s '
-                       'within the required time (%(timeout)s s).' %
-                       {'node_id': node_id,
-                        'attr': attr,
-                        'status': status,
-                        'timeout': client.build_timeout})
-            message += ' Current state of %s: %s.' % (attr, status_curr)
-            caller = test_utils.find_test_caller()
-            if caller:
-                message = '(%s) %s' % (caller, message)
-            raise lib_exc.TimeoutException(message)
+    if not test_utils.call_until_true(is_attr_in_status, timeout,
+                                      interval):
+        message = ('Node %(node_id)s failed to reach %(attr)s=%(status)s '
+                   'within the required time (%(timeout)s s).' %
+                   {'node_id': node_id,
+                    'attr': attr,
+                    'status': status,
+                    'timeout': timeout})
+        caller = test_utils.find_test_caller()
+        if caller:
+            message = '(%s) %s' % (caller, message)
+        raise lib_exc.TimeoutException(message)
 
-        time.sleep(interval)
-        _, node = client.show_node(node_id)
+
+def wait_node_instance_association(client, instance_uuid, timeout=None,
+                                   interval=None):
+    """Waits for a node to be associated with instance_id.
+
+    :param client: an instance of tempest plugin BaremetalClient.
+    :param instance_uuid: UUID of the instance.
+    :param timeout: the timeout after which the check is considered as failed.
+        Defaults to CONF.baremetal.association_timeout.
+    :param interval: an interval between show_node calls for status check.
+        Defaults to client.build_interval.
+    """
+    timeout, interval = _determine_and_check_timeout_interval(
+        timeout, CONF.baremetal.association_timeout,
+        interval, client.build_interval)
+
+    def is_some_node_associated():
+        node = utils.get_node(client, instance_uuid=instance_uuid)
+        return node is not None
+
+    if not test_utils.call_until_true(is_some_node_associated, timeout,
+                                      interval):
+        msg = ('Timed out waiting to get Ironic node by instance ID '
+               '%(instance_id)s within the required time (%(timeout)s s).'
+               % {'instance_id': instance_uuid, 'timeout': timeout})
+        raise lib_exc.TimeoutException(msg)
diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
index e7f7aae..a629364 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
@@ -16,11 +16,11 @@
 
 from tempest.common import waiters
 from tempest import config
-from tempest.lib.common.utils import test_utils
-from tempest.lib import exceptions as lib_exc
 from tempest.scenario import manager  # noqa
 
 from ironic_tempest_plugin import clients
+from ironic_tempest_plugin.common import utils
+from ironic_tempest_plugin.common import waiters as ironic_waiters
 
 CONF = config.CONF
 
@@ -73,58 +73,23 @@
         # allow any issues obtaining the node list to raise early
         cls.baremetal_client.list_nodes()
 
-    def _node_state_timeout(self, node_id, state_attr,
-                            target_states, timeout=10, interval=1):
-        if not isinstance(target_states, list):
-            target_states = [target_states]
-
-        def check_state():
-            node = self.get_node(node_id=node_id)
-            if node.get(state_attr) in target_states:
-                return True
-            return False
-
-        if not test_utils.call_until_true(check_state, timeout, interval):
-            msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
-                   (node_id, state_attr, target_states))
-            raise lib_exc.TimeoutException(msg)
-
-    def wait_provisioning_state(self, node_id, state, timeout, interval=1):
-        self._node_state_timeout(
-            node_id=node_id, state_attr='provision_state',
-            target_states=state, timeout=timeout, interval=interval)
+    def wait_provisioning_state(self, node_id, state, timeout=10, interval=1):
+        ironic_waiters.wait_for_bm_node_status(
+            self.baremetal_client, node_id=node_id, attr='provision_state',
+            status=state, timeout=timeout, interval=interval)
 
     def wait_power_state(self, node_id, state):
-        self._node_state_timeout(
-            node_id=node_id, state_attr='power_state',
-            target_states=state, timeout=CONF.baremetal.power_timeout)
+        ironic_waiters.wait_for_bm_node_status(
+            self.baremetal_client, node_id=node_id, attr='power_state',
+            status=state, timeout=CONF.baremetal.power_timeout)
 
     def wait_node(self, instance_id):
         """Waits for a node to be associated with instance_id."""
-
-        def _get_node():
-            node = None
-            try:
-                node = self.get_node(instance_id=instance_id)
-            except lib_exc.NotFound:
-                pass
-            return node is not None
-
-        if (not test_utils.call_until_true(
-            _get_node, CONF.baremetal.association_timeout, 1)):
-            msg = ('Timed out waiting to get Ironic node by instance id %s'
-                   % instance_id)
-            raise lib_exc.TimeoutException(msg)
+        ironic_waiters.wait_node_instance_association(self.baremetal_client,
+                                                      instance_id)
 
     def get_node(self, node_id=None, instance_id=None):
-        if node_id:
-            _, body = self.baremetal_client.show_node(node_id)
-            return body
-        elif instance_id:
-            _, body = self.baremetal_client.show_node_by_instance_uuid(
-                instance_id)
-            if body['nodes']:
-                return body['nodes'][0]
+        return utils.get_node(self.baremetal_client, node_id, instance_id)
 
     def get_ports(self, node_uuid):
         ports = []