api: time.clock compatible code (#70650)

time.clock is removed in Python 3.8. Add time.clock
compatible code.

Fixes: #70649

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
pull/70679/head
Abhijeet Kasurde 4 years ago committed by GitHub
parent 4a735adc21
commit 055871cbb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- api - time.clock is removed in Python 3.8, add backward compatible code (https://github.com/ansible/ansible/issues/70649).

@ -4,27 +4,9 @@
# still belong to the author of the module, and may assign their own license # still belong to the author of the module, and may assign their own license
# to the complete work. # to the complete work.
# #
# (c) 2015 Brian Ccoa, <bcoca@ansible.com> # Copyright: (c) 2015, Brian Coca, <bcoca@ansible.com>
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# #
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
""" """
This module adds shared support for generic api modules This module adds shared support for generic api modules
@ -44,6 +26,7 @@ The 'api' module provides the following common argument specs:
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import sys
import time import time
@ -91,12 +74,16 @@ def rate_limit(rate=None, rate_limit=None):
last = [0.0] last = [0.0]
def ratelimited(*args, **kwargs): def ratelimited(*args, **kwargs):
if sys.version_info >= (3, 8):
real_time = time.process_time
else:
real_time = time.clock
if minrate is not None: if minrate is not None:
elapsed = time.clock() - last[0] elapsed = real_time() - last[0]
left = minrate - elapsed left = minrate - elapsed
if left > 0: if left > 0:
time.sleep(left) time.sleep(left)
last[0] = time.clock() last[0] = real_time()
ret = f(*args, **kwargs) ret = f(*args, **kwargs)
return ret return ret
@ -107,14 +94,13 @@ def rate_limit(rate=None, rate_limit=None):
def retry(retries=None, retry_pause=1): def retry(retries=None, retry_pause=1):
"""Retry decorator""" """Retry decorator"""
def wrapper(f): def wrapper(f):
retry_count = 0
def retried(*args, **kwargs): def retried(*args, **kwargs):
retry_count = 0
if retries is not None: if retries is not None:
ret = None ret = None
while True: while True:
# pylint doesn't understand this is a closure retry_count += 1
retry_count += 1 # pylint: disable=undefined-variable
if retry_count >= retries: if retry_count >= retries:
raise Exception("Retry limit exceeded: %d" % retries) raise Exception("Retry limit exceeded: %d" % retries)
try: try:

@ -14,6 +14,7 @@ import pytest
from units.compat.mock import MagicMock from units.compat.mock import MagicMock
from ansible.module_utils import basic from ansible.module_utils import basic
from ansible.module_utils.api import basic_auth_argument_spec, rate_limit_argument_spec, retry_argument_spec
from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages
from ansible.module_utils.six import integer_types, string_types from ansible.module_utils.six import integer_types, string_types
from ansible.module_utils.six.moves import builtins from ansible.module_utils.six.moves import builtins
@ -88,6 +89,25 @@ INVALID_SPECS = (
({'arg': {'required': True}}, {}, 'missing required arguments: arg'), ({'arg': {'required': True}}, {}, 'missing required arguments: arg'),
) )
BASIC_AUTH_VALID_ARGS = [
{'api_username': 'user1', 'api_password': 'password1', 'api_url': 'http://example.com', 'validate_certs': False},
{'api_username': 'user1', 'api_password': 'password1', 'api_url': 'http://example.com', 'validate_certs': True},
]
RATE_LIMIT_VALID_ARGS = [
{'rate': 1, 'rate_limit': 1},
{'rate': '1', 'rate_limit': 1},
{'rate': 1, 'rate_limit': '1'},
{'rate': '1', 'rate_limit': '1'},
]
RETRY_VALID_ARGS = [
{'retries': 1, 'retry_pause': 1.5},
{'retries': '1', 'retry_pause': '1.5'},
{'retries': 1, 'retry_pause': '1.5'},
{'retries': '1', 'retry_pause': 1.5},
]
@pytest.fixture @pytest.fixture
def complex_argspec(): def complex_argspec():
@ -213,6 +233,38 @@ def test_validator_function(mocker, stdin):
assert am.params['arg'] == 27 assert am.params['arg'] == 27
@pytest.mark.parametrize('stdin', BASIC_AUTH_VALID_ARGS, indirect=['stdin'])
def test_validate_basic_auth_arg(mocker, stdin):
kwargs = dict(
argument_spec=basic_auth_argument_spec()
)
am = basic.AnsibleModule(**kwargs)
assert isinstance(am.params['api_username'], string_types)
assert isinstance(am.params['api_password'], string_types)
assert isinstance(am.params['api_url'], string_types)
assert isinstance(am.params['validate_certs'], bool)
@pytest.mark.parametrize('stdin', RATE_LIMIT_VALID_ARGS, indirect=['stdin'])
def test_validate_rate_limit_argument_spec(mocker, stdin):
kwargs = dict(
argument_spec=rate_limit_argument_spec()
)
am = basic.AnsibleModule(**kwargs)
assert isinstance(am.params['rate'], integer_types)
assert isinstance(am.params['rate_limit'], integer_types)
@pytest.mark.parametrize('stdin', RETRY_VALID_ARGS, indirect=['stdin'])
def test_validate_retry_argument_spec(mocker, stdin):
kwargs = dict(
argument_spec=retry_argument_spec()
)
am = basic.AnsibleModule(**kwargs)
assert isinstance(am.params['retries'], integer_types)
assert isinstance(am.params['retry_pause'], float)
@pytest.mark.parametrize('stdin', [{'arg': '123'}, {'arg': 123}], indirect=['stdin']) @pytest.mark.parametrize('stdin', [{'arg': '123'}, {'arg': 123}], indirect=['stdin'])
def test_validator_string_type(mocker, stdin): def test_validator_string_type(mocker, stdin):
# Custom callable that is 'str' # Custom callable that is 'str'

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Abhijeet Kasurde <akasurde@redhat.com>
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.module_utils.api import rate_limit, retry
import pytest
class TestRateLimit:
def test_ratelimit(self):
@rate_limit(rate=1, rate_limit=1)
def login_database():
return "success"
r = login_database()
assert r == 'success'
class TestRetry:
def test_no_retry_required(self):
self.counter = 0
@retry(retries=4, retry_pause=2)
def login_database():
self.counter += 1
return 'success'
r = login_database()
assert r == 'success'
assert self.counter == 1
def test_catch_exception(self):
@retry(retries=1)
def login_database():
return 'success'
with pytest.raises(Exception):
login_database()
Loading…
Cancel
Save