Move `call_until_true` to tempest/lib

This `call_until_true()` is handy and could be used in Tempest plugins.
Let's move it to tempest/lib.

Also add some unit tests.

Change-Id: Ie379030baa336239e6027c8f3cdbeb74c561f66b
This commit is contained in:
Jordan Pittier 2016-08-30 13:09:12 +02:00
parent 0ef4c8cf75
commit 35a6375fd1
14 changed files with 106 additions and 62 deletions

View File

@ -0,0 +1,5 @@
---
deprecations:
- The ``call_until_true`` function is moved from the ``tempest.test`` module
to the ``tempest.lib.common.utils.test_utils`` module. Backward
compatibilty is preserved until Ocata.

View File

@ -22,3 +22,4 @@ stevedore>=1.16.0 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
os-testr>=0.7.0 # Apache-2.0
urllib3>=1.15.1 # MIT
debtcollector>=1.2.0 # Apache-2.0

View File

@ -16,6 +16,7 @@
import datetime
from tempest.api.compute import base
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as e
from tempest import test
@ -59,8 +60,8 @@ class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
return True
except e.InvalidHTTPResponseBody:
return False
self.assertEqual(test.call_until_true(is_valid, duration, 1), True,
"%s not return valid response in %s secs" % (
self.assertEqual(test_utils.call_until_true(is_valid, duration, 1),
True, "%s not return valid response in %s secs" % (
func.__name__, duration))
return self.resp

View File

@ -14,6 +14,7 @@
# under the License.
import inspect
import re
import time
from oslo_log import log as logging
@ -83,3 +84,24 @@ def call_and_ignore_notfound_exc(func, *args, **kwargs):
return func(*args, **kwargs)
except exceptions.NotFound:
pass
def call_until_true(func, duration, sleep_for):
"""Call the given function until it returns True (and return True)
or until the specified duration (in seconds) elapses (and return False).
:param func: A zero argument callable that returns True on success.
:param duration: The number of seconds for which to attempt a
successful call of the function.
:param sleep_for: The number of seconds to sleep after an unsuccessful
invocation of the function.
"""
now = time.time()
timeout = now + duration
while now < timeout:
if func():
return True
time.sleep(sleep_for)
now = time.time()
return False

View File

@ -513,7 +513,7 @@ class ScenarioTest(tempest.test.BaseTestCase):
'should_succeed':
'reachable' if should_succeed else 'unreachable'
})
result = tempest.test.call_until_true(ping, timeout, 1)
result = test_utils.call_until_true(ping, timeout, 1)
LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
'ping result is %(result)s' % {
'caller': caller, 'ip': ip_address, 'timeout': timeout,
@ -857,9 +857,9 @@ class NetworkScenarioTest(ScenarioTest):
show_floatingip(floatingip_id)['floatingip'])
return status == result['status']
tempest.test.call_until_true(refresh,
CONF.network.build_timeout,
CONF.network.build_interval)
test_utils.call_until_true(refresh,
CONF.network.build_timeout,
CONF.network.build_interval)
floating_ip = self.floating_ips_client.show_floatingip(
floatingip_id)['floatingip']
self.assertEqual(status, floating_ip['status'],
@ -914,9 +914,9 @@ class NetworkScenarioTest(ScenarioTest):
return not should_succeed
return should_succeed
return tempest.test.call_until_true(ping_remote,
CONF.validation.ping_timeout,
1)
return test_utils.call_until_true(ping_remote,
CONF.validation.ping_timeout,
1)
def _create_security_group(self, security_group_rules_client=None,
tenant_id=None,
@ -1249,7 +1249,7 @@ class BaremetalScenarioTest(ScenarioTest):
return True
return False
if not tempest.test.call_until_true(
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))
@ -1273,7 +1273,7 @@ class BaremetalScenarioTest(ScenarioTest):
self.get_node, instance_id=instance_id)
return node is not None
if not tempest.test.call_until_true(
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)

View File

@ -17,6 +17,7 @@ from tempest.common import custom_matchers
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
from tempest.scenario import manager
from tempest import test
@ -88,9 +89,9 @@ class TestMinimumBasicScenario(manager.ScenarioTest):
['server'])
return {'name': secgroup['name']} in body['security_groups']
if not test.call_until_true(wait_for_secgroup_add,
CONF.compute.build_timeout,
CONF.compute.build_interval):
if not test_utils.call_until_true(wait_for_secgroup_add,
CONF.compute.build_timeout,
CONF.compute.build_interval):
msg = ('Timed out waiting for adding security group %s to server '
'%s' % (secgroup['id'], server['id']))
raise exceptions.TimeoutException(msg)

View File

@ -263,8 +263,9 @@ class TestNetworkBasicOps(manager.NetworkScenarioTest):
if port['id'] != old_port['id']]
return len(self.new_port_list) == 1
if not test.call_until_true(check_ports, CONF.network.build_timeout,
CONF.network.build_interval):
if not test_utils.call_until_true(
check_ports, CONF.network.build_timeout,
CONF.network.build_interval):
raise exceptions.TimeoutException(
"No new port attached to the server in time (%s sec)! "
"Old port: %s. Number of new ports: %d" % (
@ -277,8 +278,9 @@ class TestNetworkBasicOps(manager.NetworkScenarioTest):
self.diff_list = [n for n in new_nic_list if n not in old_nic_list]
return len(self.diff_list) == 1
if not test.call_until_true(check_new_nic, CONF.network.build_timeout,
CONF.network.build_interval):
if not test_utils.call_until_true(
check_new_nic, CONF.network.build_timeout,
CONF.network.build_interval):
raise exceptions.TimeoutException("Interface not visible on the "
"guest after %s sec"
% CONF.network.build_timeout)
@ -593,9 +595,9 @@ class TestNetworkBasicOps(manager.NetworkScenarioTest):
return False
return True
self.assertTrue(test.call_until_true(check_new_dns_server,
renew_timeout,
renew_delay),
self.assertTrue(test_utils.call_until_true(check_new_dns_server,
renew_timeout,
renew_delay),
msg="DHCP renewal failed to fetch "
"new DNS nameservers")

View File

@ -187,10 +187,10 @@ class TestGettingAddress(manager.NetworkScenarioTest):
srv2_v6_addr_assigned = functools.partial(
guest_has_address, sshv4_2, ips_from_api_2['6'][i])
self.assertTrue(test.call_until_true(srv1_v6_addr_assigned,
self.assertTrue(test_utils.call_until_true(srv1_v6_addr_assigned,
CONF.validation.ping_timeout, 1))
self.assertTrue(test.call_until_true(srv2_v6_addr_assigned,
self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
CONF.validation.ping_timeout, 1))
self._check_connectivity(sshv4_1, ips_from_api_2['4'])

View File

@ -18,6 +18,7 @@ import re
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
from tempest.scenario import manager
from tempest import test
@ -70,9 +71,9 @@ class TestServerBasicOps(manager.ScenarioTest):
self.assertEqual(self.fip, result, msg)
return 'Verification is successful!'
if not test.call_until_true(exec_cmd_and_verify_output,
CONF.compute.build_timeout,
CONF.compute.build_interval):
if not test_utils.call_until_true(exec_cmd_and_verify_output,
CONF.compute.build_timeout,
CONF.compute.build_interval):
raise exceptions.TimeoutException('Timed out while waiting to '
'verify metadata on server. '
'%s is empty.' % md_url)

View File

@ -22,6 +22,7 @@ from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
@ -89,9 +90,9 @@ class TestStampPattern(manager.ScenarioTest):
LOG.debug("Partitions:%s" % part)
return CONF.compute.volume_device_name in part
if not test.call_until_true(_func,
CONF.compute.build_timeout,
CONF.compute.build_interval):
if not test_utils.call_until_true(_func,
CONF.compute.build_timeout,
CONF.compute.build_interval):
raise exceptions.TimeoutException
@decorators.skip_because(bug="1205344")

View File

@ -16,8 +16,8 @@ import subprocess
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import test_utils
import tempest.stress.stressaction as stressaction
import tempest.test
CONF = config.CONF
@ -52,8 +52,8 @@ class FloatingStress(stressaction.StressAction):
def check_port_ssh(self):
def func():
return self.tcp_connect_scan(self.floating['ip'], 22)
if not tempest.test.call_until_true(func, self.check_timeout,
self.check_interval):
if not test_utils.call_until_true(func, self.check_timeout,
self.check_interval):
raise RuntimeError("Cannot connect to the ssh port.")
def check_icmp_echo(self):
@ -62,8 +62,8 @@ class FloatingStress(stressaction.StressAction):
def func():
return self.ping_ip_address(self.floating['ip'])
if not tempest.test.call_until_true(func, self.check_timeout,
self.check_interval):
if not test_utils.call_until_true(func, self.check_timeout,
self.check_interval):
raise RuntimeError("%s(%s): Cannot ping the machine.",
self.server_id, self.floating['ip'])
self.logger.info("%s(%s): pong :)",
@ -153,8 +153,8 @@ class FloatingStress(stressaction.StressAction):
['floating_ip'])
return floating['instance_id'] is None
if not tempest.test.call_until_true(func, self.check_timeout,
self.check_interval):
if not test_utils.call_until_true(func, self.check_timeout,
self.check_interval):
raise RuntimeError("IP disassociate timeout!")
def run_core(self):

View File

@ -16,8 +16,8 @@ from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import test_utils
import tempest.stress.stressaction as stressaction
import tempest.test
CONF = config.CONF
@ -105,8 +105,8 @@ class VolumeVerifyStress(stressaction.StressAction):
['floating_ip'])
return floating['instance_id'] is None
if not tempest.test.call_until_true(func, CONF.compute.build_timeout,
CONF.compute.build_interval):
if not test_utils.call_until_true(func, CONF.compute.build_timeout,
CONF.compute.build_interval):
raise RuntimeError("IP disassociate timeout!")
def new_server_ops(self):
@ -179,9 +179,9 @@ class VolumeVerifyStress(stressaction.StressAction):
if self.part_line_re.match(part_line):
matching += 1
return matching == num_match
if tempest.test.call_until_true(_part_state,
CONF.compute.build_timeout,
CONF.compute.build_interval):
if test_utils.call_until_true(_part_state,
CONF.compute.build_timeout,
CONF.compute.build_interval):
return
else:
raise RuntimeError("Unexpected partitions: %s",

View File

@ -18,8 +18,8 @@ import functools
import os
import re
import sys
import time
import debtcollector.moves
import fixtures
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
@ -38,6 +38,7 @@ import tempest.common.validation_resources as vresources
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@ -866,22 +867,6 @@ def SimpleNegativeAutoTest(klass):
return klass
def call_until_true(func, duration, sleep_for):
"""Call the given function until it returns True (and return True)
or until the specified duration (in seconds) elapses (and return False).
:param func: A zero argument callable that returns True on success.
:param duration: The number of seconds for which to attempt a
successful call of the function.
:param sleep_for: The number of seconds to sleep after an unsuccessful
invocation of the function.
"""
now = time.time()
timeout = now + duration
while now < timeout:
if func():
return True
time.sleep(sleep_for)
now = time.time()
return False
call_until_true = debtcollector.moves.moved_function(
test_utils.call_until_true, 'call_until_true', __name__,
version='Newton', removal_version='Ocata')

View File

@ -17,6 +17,7 @@ import mock
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions
from tempest.tests import base
from tempest.tests import utils
class TestTestUtils(base.TestCase):
@ -76,3 +77,27 @@ class TestTestUtils(base.TestCase):
self.assertEqual(
42, test_utils.call_and_ignore_notfound_exc(m, *args, **kwargs))
m.assert_called_once_with(*args, **kwargs)
@mock.patch('time.sleep')
@mock.patch('time.time')
def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep):
timeout = 42 # The value doesn't matter as we mock time.time()
sleep = 60 # The value doesn't matter as we mock time.sleep()
m_time.side_effect = utils.generate_timeout_series(timeout)
self.assertEqual(
False, test_utils.call_until_true(lambda: False, timeout, sleep)
)
m_sleep.call_args_list = [mock.call(sleep)] * 2
m_time.call_args_list = [mock.call()] * 2
@mock.patch('time.sleep')
@mock.patch('time.time')
def test_call_until_true_when_f_returns_true(self, m_time, m_sleep):
timeout = 42 # The value doesn't matter as we mock time.time()
sleep = 60 # The value doesn't matter as we mock time.sleep()
m_time.return_value = 0
self.assertEqual(
True, test_utils.call_until_true(lambda: True, timeout, sleep)
)
self.assertEqual(0, m_sleep.call_count)
self.assertEqual(1, m_time.call_count)