diff --git a/ironic_python_agent/backoff.py b/ironic_python_agent/backoff.py deleted file mode 100644 index 80105b2e7..000000000 --- a/ironic_python_agent/backoff.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# -# 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. - -import random - -from oslo_service import loopingcall - - -# TODO(JoshNang) move to oslo, i18n -class LoopingCallTimeOut(Exception): - """Exception for a timed out LoopingCall. - - The LoopingCall will raise this exception when a timeout is provided - and it is exceeded. - """ - pass - - -class BackOffLoopingCall(loopingcall.LoopingCallBase): - """Run a method in a loop with backoff on error. - - The passed in function should return True (no error, return to - initial_interval), - False (error, start backing off), or raise LoopingCallDone(retvalue=None) - (quit looping, return retvalue if set). - - When there is an error, the call will backoff on each failure. The - backoff will be equal to double the previous base interval times some - jitter. If a backoff would put it over the timeout, it halts immediately, - so the call will never take more than timeout, but may and likely will - take less time. - - When the function return value is True or False, the interval will be - multiplied by a random jitter. If min_jitter or max_jitter is None, - there will be no jitter (jitter=1). If min_jitter is below 0.5, the code - may not backoff and may increase its retry rate. - - If func constantly returns True, this function will not return. - - To run a func and wait for a call to finish (by raising a LoopingCallDone): - - timer = BackOffLoopingCall(func) - response = timer.start().wait() - - :param initial_delay: delay before first running of function - :param starting_interval: initial interval in seconds between calls to - function. When an error occurs and then a - success, the interval is returned to - starting_interval - :param timeout: time in seconds before a LoopingCallTimeout is raised. - The call will never take longer than timeout, but may quit - before timeout. - :param max_interval: The maximum interval between calls during errors - :param jitter: Used to vary when calls are actually run to avoid group of - calls all coming at the exact same time. Uses - random.gauss(jitter, 0.1), with jitter as the mean for the - distribution. If set below .5, it can cause the calls to - come more rapidly after each failure. - :raises: LoopingCallTimeout if time spent doing error retries would exceed - timeout. - """ - - _RNG = random.SystemRandom() - _KIND = 'Dynamic backoff interval looping call' - _RUN_ONLY_ONE_MESSAGE = ("A dynamic backoff interval looping call can" - " only run one function at a time") - - def __init__(self, f=None, *args, **kw): - super(BackOffLoopingCall, self).__init__(f=f, *args, **kw) - self._error_time = 0 - self._interval = 1 - - def start(self, initial_delay=None, starting_interval=1, timeout=300, - max_interval=300, jitter=0.75): - if self._thread is not None: - raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE) - - # Reset any prior state. - self._error_time = 0 - self._interval = starting_interval - - def _idle_for(success, _elapsed): - random_jitter = self._RNG.gauss(jitter, 0.1) - if success: - # Reset error state now that it didn't error... - self._interval = starting_interval - self._error_time = 0 - return self._interval * random_jitter - else: - # Perform backoff - self._interval = idle = min( - self._interval * 2 * random_jitter, max_interval) - # Don't go over timeout, end early if necessary. If - # timeout is 0, keep going. - if timeout > 0 and self._error_time + idle > timeout: - raise LoopingCallTimeOut( - 'Looping call timed out after %.02f seconds' - % self._error_time) - self._error_time += idle - return idle - - return self._start(_idle_for, initial_delay=initial_delay) diff --git a/ironic_python_agent/ironic_api_client.py b/ironic_python_agent/ironic_api_client.py index 5816b95af..4be6849ec 100644 --- a/ironic_python_agent/ironic_api_client.py +++ b/ironic_python_agent/ironic_api_client.py @@ -18,7 +18,6 @@ from oslo_log import log from oslo_service import loopingcall import requests -from ironic_python_agent import backoff from ironic_python_agent import encoding from ironic_python_agent import errors @@ -80,14 +79,14 @@ class APIClient(object): def lookup_node(self, hardware_info, timeout, starting_interval, node_uuid=None): - timer = backoff.BackOffLoopingCall( + timer = loopingcall.BackOffLoopingCall( self._do_lookup, hardware_info=hardware_info, node_uuid=node_uuid) try: node_content = timer.start(starting_interval=starting_interval, timeout=timeout).wait() - except backoff.LoopingCallTimeOut: + except loopingcall.LoopingCallTimeOut: raise errors.LookupNodeError('Could not look up node info. Check ' 'logs for details.') return node_content diff --git a/ironic_python_agent/tests/unit/test_backoff.py b/ironic_python_agent/tests/unit/test_backoff.py deleted file mode 100644 index bed034ca8..000000000 --- a/ironic_python_agent/tests/unit/test_backoff.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2014 Rackspace, Inc. -# -# 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. - -import unittest - -import mock -from oslo_service import loopingcall - -from ironic_python_agent import backoff - - -class TestBackOffLoopingCall(unittest.TestCase): - @mock.patch('random.SystemRandom.gauss') - @mock.patch('eventlet.greenthread.sleep') - def test_exponential_backoff(self, sleep_mock, random_mock): - def false(): - return False - - random_mock.return_value = .8 - - self.assertRaises(backoff.LoopingCallTimeOut, - backoff.BackOffLoopingCall(false).start() - .wait) - - expected_times = [mock.call(1.6000000000000001), - mock.call(2.5600000000000005), - mock.call(4.096000000000001), - mock.call(6.5536000000000021), - mock.call(10.485760000000004), - mock.call(16.777216000000006), - mock.call(26.843545600000013), - mock.call(42.949672960000022), - mock.call(68.719476736000033), - mock.call(109.95116277760006)] - self.assertEqual(expected_times, sleep_mock.call_args_list) - - @mock.patch('random.SystemRandom.gauss') - @mock.patch('eventlet.greenthread.sleep') - def test_no_backoff(self, sleep_mock, random_mock): - random_mock.return_value = 1 - func = mock.Mock() - # func.side_effect - func.side_effect = [True, True, True, loopingcall.LoopingCallDone( - retvalue='return value')] - - retvalue = backoff.BackOffLoopingCall(func).start().wait() - - expected_times = [mock.call(1), mock.call(1), mock.call(1)] - self.assertEqual(expected_times, sleep_mock.call_args_list) - self.assertTrue(retvalue, 'return value') - - @mock.patch('random.SystemRandom.gauss') - @mock.patch('eventlet.greenthread.sleep') - def test_no_sleep(self, sleep_mock, random_mock): - # Any call that executes properly the first time shouldn't sleep - random_mock.return_value = 1 - func = mock.Mock() - # func.side_effect - func.side_effect = loopingcall.LoopingCallDone(retvalue='return value') - - retvalue = backoff.BackOffLoopingCall(func).start().wait() - self.assertFalse(sleep_mock.called) - self.assertTrue(retvalue, 'return value') - - @mock.patch('random.SystemRandom.gauss') - @mock.patch('eventlet.greenthread.sleep') - def test_max_interval(self, sleep_mock, random_mock): - def false(): - return False - - random_mock.return_value = .8 - - self.assertRaises(backoff.LoopingCallTimeOut, - backoff.BackOffLoopingCall(false).start( - max_interval=60) - .wait) - - expected_times = [mock.call(1.6000000000000001), - mock.call(2.5600000000000005), - mock.call(4.096000000000001), - mock.call(6.5536000000000021), - mock.call(10.485760000000004), - mock.call(16.777216000000006), - mock.call(26.843545600000013), - mock.call(42.949672960000022), - mock.call(60), - mock.call(60), - mock.call(60)] - self.assertEqual(expected_times, sleep_mock.call_args_list) diff --git a/ironic_python_agent/tests/unit/test_ironic_api_client.py b/ironic_python_agent/tests/unit/test_ironic_api_client.py index f860c95fb..bad882d69 100644 --- a/ironic_python_agent/tests/unit/test_ironic_api_client.py +++ b/ironic_python_agent/tests/unit/test_ironic_api_client.py @@ -18,7 +18,6 @@ import mock from oslo_service import loopingcall from oslotest import base as test_base -from ironic_python_agent import backoff from ironic_python_agent import errors from ironic_python_agent import hardware from ironic_python_agent import ironic_api_client @@ -111,7 +110,7 @@ class TestBaseIronicPythonAgent(test_base.BaseTestCase): @mock.patch('eventlet.greenthread.sleep') @mock.patch('ironic_python_agent.ironic_api_client.APIClient._do_lookup') def test_lookup_timeout(self, lookup_mock, sleep_mock): - lookup_mock.side_effect = backoff.LoopingCallTimeOut() + lookup_mock.side_effect = loopingcall.LoopingCallTimeOut() self.assertRaises(errors.LookupNodeError, self.api_client.lookup_node, hardware_info=self.hardware_info,