Skip to content

Commit

Permalink
api: time.clock compatible code (ansible#70650)
Browse files Browse the repository at this point in the history
time.clock is removed in Python 3.8. Add time.clock
compatible code.

Fixes: ansible#70649

Signed-off-by: Abhijeet Kasurde <[email protected]>
  • Loading branch information
Akasurde authored Jul 16, 2020
1 parent 4a735ad commit 055871c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 25 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/70649_time_clock.yml
Original file line number Diff line number Diff line change
@@ -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).
36 changes: 11 additions & 25 deletions lib/ansible/module_utils/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, <[email protected]>
#
# 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, <[email protected]>
#
# 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
Expand All @@ -44,6 +26,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import sys
import time


Expand Down Expand Up @@ -91,12 +74,16 @@ def wrapper(f):
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

Expand All @@ -107,14 +94,13 @@ def ratelimited(*args, **kwargs):
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:
Expand Down
52 changes: 52 additions & 0 deletions test/units/module_utils/basic/test_argument_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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
Expand Down Expand Up @@ -88,6 +89,25 @@
({'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():
Expand Down Expand Up @@ -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'
Expand Down
48 changes: 48 additions & 0 deletions test/units/module_utils/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Abhijeet Kasurde <[email protected]>
# 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()

0 comments on commit 055871c

Please sign in to comment.