Replace backoff looping call with oslo_service provided version
The code that comprises the backoff looping call moved to oslo.service in 0.10.0 and IPA can use that version so remove the local backoff code and just prefer the oslo.service maintained and supported version instead. Change-Id: Ifec3490f9e5c68859deff4a951dcdf59caa7ca3a
This commit is contained in:
parent
3ce3b16803
commit
df701c979c
ironic_python_agent
@ -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)
|
@ -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
|
||||
|
@ -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)
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user