diff --git a/changelogs/fragments/70649_time_clock.yml b/changelogs/fragments/70649_time_clock.yml new file mode 100644 index 00000000000..e9035c7b296 --- /dev/null +++ b/changelogs/fragments/70649_time_clock.yml @@ -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). diff --git a/lib/ansible/module_utils/api.py b/lib/ansible/module_utils/api.py index ca1d978f8f8..46a036d3747 100644 --- a/lib/ansible/module_utils/api.py +++ b/lib/ansible/module_utils/api.py @@ -4,27 +4,9 @@ # still belong to the author of the module, and may assign their own license # to the complete work. # -# (c) 2015 Brian Ccoa, -# -# 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. +# Copyright: (c) 2015, Brian Coca, # +# 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 @@ -44,6 +26,7 @@ The 'api' module provides the following common argument specs: from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import sys import time @@ -91,12 +74,16 @@ def rate_limit(rate=None, rate_limit=None): last = [0.0] 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: - elapsed = time.clock() - last[0] + elapsed = real_time() - last[0] left = minrate - elapsed if left > 0: time.sleep(left) - last[0] = time.clock() + last[0] = real_time() ret = f(*args, **kwargs) return ret @@ -107,14 +94,13 @@ def rate_limit(rate=None, rate_limit=None): def retry(retries=None, retry_pause=1): """Retry decorator""" def wrapper(f): - retry_count = 0 def retried(*args, **kwargs): + retry_count = 0 if retries is not None: ret = None while True: - # pylint doesn't understand this is a closure - retry_count += 1 # pylint: disable=undefined-variable + retry_count += 1 if retry_count >= retries: raise Exception("Retry limit exceeded: %d" % retries) try: diff --git a/test/units/module_utils/basic/test_argument_spec.py b/test/units/module_utils/basic/test_argument_spec.py index b0d4f1a8baf..6e297669561 100644 --- a/test/units/module_utils/basic/test_argument_spec.py +++ b/test/units/module_utils/basic/test_argument_spec.py @@ -14,6 +14,7 @@ import pytest from units.compat.mock import MagicMock 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.six import integer_types, string_types from ansible.module_utils.six.moves import builtins @@ -88,6 +89,25 @@ INVALID_SPECS = ( ({'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 def complex_argspec(): @@ -213,6 +233,38 @@ def test_validator_function(mocker, stdin): 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']) def test_validator_string_type(mocker, stdin): # Custom callable that is 'str' diff --git a/test/units/module_utils/test_api.py b/test/units/module_utils/test_api.py new file mode 100644 index 00000000000..0eaea046598 --- /dev/null +++ b/test/units/module_utils/test_api.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, Abhijeet Kasurde +# 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()