diff --git a/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml b/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml new file mode 100644 index 0000000000..543cf7b435 --- /dev/null +++ b/releasenotes/notes/move-call-until-true-to-tempest-lib-c9ea70dd6fe9bd15.yaml @@ -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. diff --git a/requirements.txt b/requirements.txt index d698cdaf46..a773d167e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py index a4ed8dcebd..dbc22e07c8 100644 --- a/tempest/api/compute/admin/test_simple_tenant_usage.py +++ b/tempest/api/compute/admin/test_simple_tenant_usage.py @@ -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 diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py index 50a1a7d700..3b287016d8 100644 --- a/tempest/lib/common/utils/test_utils.py +++ b/tempest/lib/common/utils/test_utils.py @@ -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 diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py index fdccfc3989..8cd090ab7c 100644 --- a/tempest/scenario/manager.py +++ b/tempest/scenario/manager.py @@ -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) diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py index f7c7434b23..dba1c92d72 100644 --- a/tempest/scenario/test_minimum_basic.py +++ b/tempest/scenario/test_minimum_basic.py @@ -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) diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py index e0e120426e..519dbece09 100644 --- a/tempest/scenario/test_network_basic_ops.py +++ b/tempest/scenario/test_network_basic_ops.py @@ -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") diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py index 364b6f5af2..dd86d90578 100644 --- a/tempest/scenario/test_network_v6.py +++ b/tempest/scenario/test_network_v6.py @@ -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']) diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py index 60dca3d140..e031ff733d 100644 --- a/tempest/scenario/test_server_basic_ops.py +++ b/tempest/scenario/test_server_basic_ops.py @@ -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) diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py index e7223c7552..5fd934c3e8 100644 --- a/tempest/scenario/test_stamp_pattern.py +++ b/tempest/scenario/test_stamp_pattern.py @@ -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") diff --git a/tempest/stress/actions/ssh_floating.py b/tempest/stress/actions/ssh_floating.py index 4f8c6bd440..c9a4d38037 100644 --- a/tempest/stress/actions/ssh_floating.py +++ b/tempest/stress/actions/ssh_floating.py @@ -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): diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py index 8bbbfc4b17..6e530aa448 100644 --- a/tempest/stress/actions/volume_attach_verify.py +++ b/tempest/stress/actions/volume_attach_verify.py @@ -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", diff --git a/tempest/test.py b/tempest/test.py index 97ab25c421..609f1f69e7 100644 --- a/tempest/test.py +++ b/tempest/test.py @@ -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') diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py index 919e219b85..29c56841ef 100644 --- a/tempest/tests/lib/common/utils/test_test_utils.py +++ b/tempest/tests/lib/common/utils/test_test_utils.py @@ -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)