From 7d271c3f221d5fade656cd94d2a56fab0fc3b928 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Thu, 18 Feb 2021 17:39:14 +0000 Subject: [PATCH 1/9] Update dsub version to 0.4.5.dev0 PiperOrigin-RevId: 358198209 --- dsub/_dsub_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsub/_dsub_version.py b/dsub/_dsub_version.py index c1c51da..118272f 100644 --- a/dsub/_dsub_version.py +++ b/dsub/_dsub_version.py @@ -26,4 +26,4 @@ 0.1.3.dev0 -> 0.1.3 -> 0.1.4.dev0 -> ... """ -DSUB_VERSION = '0.4.4' +DSUB_VERSION = '0.4.5.dev0' From f2a0a040ac250a7d85a363b604ed26fda7972015 Mon Sep 17 00:00:00 2001 From: cym <cym@google.com> Date: Thu, 18 Feb 2021 20:50:18 +0000 Subject: [PATCH 2/9] Quiet a warning about 'oauth2client' (which dsub no longer uses). PiperOrigin-RevId: 358242992 --- dsub/lib/dsub_util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsub/lib/dsub_util.py b/dsub/lib/dsub_util.py index 996ce7e..dd29e0f 100644 --- a/dsub/lib/dsub_util.py +++ b/dsub/lib/dsub_util.py @@ -140,8 +140,10 @@ def get_storage_service(credentials): 'ignore', 'Your application has authenticated using end user credentials') if credentials is None: credentials, _ = google.auth.default() + # Set cache_discovery to False because we use google-auth + # See https://github.com/googleapis/google-api-python-client/issues/299 return googleapiclient.discovery.build( - 'storage', 'v1', credentials=credentials) + 'storage', 'v1', credentials=credentials, cache_discovery=False) # Exponential backoff retrying downloads of GCS object chunks. From 9ead9bb2251f162ccf337575c234c5dd2218a2b4 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Thu, 18 Feb 2021 21:23:46 +0000 Subject: [PATCH 3/9] Update Travis Python3 version from 3.7 to 3.8 PiperOrigin-RevId: 358250178 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 16a497f..7f1b3ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python python: - - "3.7" + - "3.8" # command to install dependencies install: "python setup.py install" # command to run tests From 8e3c5d74cccbc3adc85d16b3cdb2ede221df9e71 Mon Sep 17 00:00:00 2001 From: cym <cym@google.com> Date: Fri, 26 Feb 2021 20:55:34 +0000 Subject: [PATCH 4/9] Fix one other instance of cache_discovery=True raising ImportError. PiperOrigin-RevId: 359819958 --- dsub/providers/google_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsub/providers/google_base.py b/dsub/providers/google_base.py index 32ef403..1423e75 100644 --- a/dsub/providers/google_base.py +++ b/dsub/providers/google_base.py @@ -449,8 +449,10 @@ def setup_service(api_name, api_version, credentials=None): 'ignore', 'Your application has authenticated using end user credentials') if not credentials: credentials, _ = google.auth.default() + # Set cache_discovery to False because we use google-auth + # See https://github.com/googleapis/google-api-python-client/issues/299 return googleapiclient.discovery.build( - api_name, api_version, credentials=credentials) + api_name, api_version, cache_discovery=False, credentials=credentials) def credentials_from_service_account_info(credentials_file): From c03d4c6412ac4c500ed07cfe2c10eb332c9155fe Mon Sep 17 00:00:00 2001 From: cym <cym@google.com> Date: Wed, 24 Mar 2021 01:15:34 +0000 Subject: [PATCH 5/9] Add `flush` method to _Printer. PiperOrigin-RevId: 364691499 --- dsub/lib/dsub_util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dsub/lib/dsub_util.py b/dsub/lib/dsub_util.py index dd29e0f..5e4aac8 100644 --- a/dsub/lib/dsub_util.py +++ b/dsub/lib/dsub_util.py @@ -56,6 +56,9 @@ def __init__(self, fileobj): def write(self, buf): self._fileobj.write(buf) + def flush(self): + self._fileobj.flush() + @contextlib.contextmanager def replace_print(fileobj=sys.stderr): From 154ec6101b46c75d67f3ea3b3d3384285006ffb9 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Wed, 18 Aug 2021 19:59:08 +0000 Subject: [PATCH 6/9] setup.py: Update dsub dependent libraries to pick up newer versions. PiperOrigin-RevId: 391591605 --- setup.py | 20 ++++++++------------ test/integration/script_python.py | 2 +- test/unit/batch_handling_test.py | 8 +++++++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index deb0bc8..f421f52 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ """ import os -import sys import unittest # Always prefer setuptools over distutils from setuptools import find_packages @@ -15,22 +14,22 @@ # dependencies for dsub, ddel, dstat # Pin to known working versions to prevent episodic breakage from library # version mismatches. - # This version list generated: 02/01/2021 + # This version list generated: 08/18/2021 # direct dependencies - 'google-api-python-client<=1.12.8', - 'google-auth<=1.24.0', - 'python-dateutil<=2.8.1', + 'google-api-python-client<=2.17.0', + 'google-auth<=1.35.0', + 'python-dateutil<=2.8.2', 'pytz<=2021.1', 'pyyaml<=5.4.1', 'tenacity<=5.0.4', - 'tabulate<=0.8.7', + 'tabulate<=0.8.9', # downstream dependencies 'funcsigs<=1.0.2', - 'google-api-core<=1.25.1', - 'google-auth-httplib2<=0.0.4', - 'httplib2<=0.19.0', + 'google-api-core<=1.31.2', + 'google-auth-httplib2<=0.1.0', + 'httplib2<=0.19.1', 'pyasn1<=0.4.8', 'pyasn1-modules<=0.2.8', 'rsa<=4.7', @@ -41,9 +40,6 @@ 'mock<=4.0.3', ] -if sys.version_info[0] == 2: - _DEPENDENCIES.append('cachetools==3.1.1') - def unittest_suite(): """Get test suite (Python unit tests only).""" diff --git a/test/integration/script_python.py b/test/integration/script_python.py index 164f3bc..fff5042 100755 --- a/test/integration/script_python.py +++ b/test/integration/script_python.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 """Minimal Python script.""" from __future__ import print_function diff --git a/test/unit/batch_handling_test.py b/test/unit/batch_handling_test.py index 4aab5bf..ad9b5cc 100644 --- a/test/unit/batch_handling_test.py +++ b/test/unit/batch_handling_test.py @@ -23,13 +23,19 @@ def callback_mock(request_id, response, exception): raise exception +class ResponseMock(object): + + def __init__(self): + self.reason = None + + class CancelMock(object): def __init__(self): pass def execute(self): - raise apiclient.errors.HttpError(None, b'test_exception') + raise apiclient.errors.HttpError(ResponseMock(), b'test_exception') class TestBatchHandling(unittest.TestCase): From 025e83def7e46477775c6ba1b3f56680d8e35444 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Fri, 20 Aug 2021 00:00:22 +0000 Subject: [PATCH 7/9] Run pytype on dsub, and fix type errors PiperOrigin-RevId: 391876236 --- dsub/commands/dsub.py | 15 ++++++++------- dsub/lib/job_model.py | 2 +- dsub/lib/output_formatter.py | 2 +- dsub/lib/retry_util.py | 4 ++-- dsub/providers/base.py | 2 +- dsub/providers/google_base.py | 2 +- dsub/providers/google_v2_base.py | 2 +- dsub/providers/provider_base.py | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/dsub/commands/dsub.py b/dsub/commands/dsub.py index 23b24de..005054b 100644 --- a/dsub/commands/dsub.py +++ b/dsub/commands/dsub.py @@ -25,15 +25,14 @@ import sys import time import uuid -from dateutil.tz import tzlocal +import dateutil from ..lib import dsub_errors from ..lib import dsub_util from ..lib import job_model from ..lib import output_formatter from ..lib import param_util from ..lib import resources -from ..lib.dsub_util import print_error from ..providers import google_base from ..providers import provider_base @@ -660,7 +659,8 @@ def _get_job_metadata(provider, user_id, job_name, script, task_ids, Returns: A dictionary of job-specific metadata (such as job id, name, etc.) """ - create_time = dsub_util.replace_timezone(datetime.datetime.now(), tzlocal()) + create_time = dsub_util.replace_timezone(datetime.datetime.now(), + dateutil.tz.tzlocal()) user_id = user_id or dsub_util.get_os_user() job_metadata = provider.prepare_job_metadata(script.name, job_name, user_id) if unique_job_id: @@ -811,7 +811,7 @@ def _wait_after(provider, job_ids, poll_interval, stop_on_failure, summary): jobs_not_found = jobs_completed.difference(jobs_found) for j in jobs_not_found: error = '%s: not found' % j - print_error(' %s' % error) + dsub_util.print_error(' %s' % error) error_messages += [error] # Print the dominant task for the completed jobs @@ -997,7 +997,8 @@ def _importance_of_task(task): return (importance[task.get_field('task-status')], task.get_field( 'end-time', - dsub_util.replace_timezone(datetime.datetime.max, tzlocal()))) + dsub_util.replace_timezone(datetime.datetime.max, + dateutil.tz.tzlocal()))) def _wait_for_any_job(provider, job_ids, poll_interval, summary): @@ -1289,7 +1290,7 @@ def run(provider, summary) if error_messages: for msg in error_messages: - print_error(msg) + dsub_util.print_error(msg) raise dsub_errors.PredecessorJobFailureError( 'One or more predecessor jobs completed but did not succeed.', error_messages, None) @@ -1331,7 +1332,7 @@ def run(provider, poll_interval, False, summary) if error_messages: for msg in error_messages: - print_error(msg) + dsub_util.print_error(msg) raise dsub_errors.JobExecutionError( 'One or more jobs finished with status FAILURE or CANCELED' ' during wait.', error_messages, launched_job) diff --git a/dsub/lib/job_model.py b/dsub/lib/job_model.py index c262248..1438e51 100644 --- a/dsub/lib/job_model.py +++ b/dsub/lib/job_model.py @@ -595,7 +595,7 @@ def get_complete_descriptor(cls, task_metadata, task_params, task_resources): return task_descriptor def __str__(self): - return 'task-id: {}'.format(self.job_metadata.get('task-id')) + return 'task-id: {}'.format(self.task_metadata.get('task-id')) def __repr__(self): return ('task_metadata: {}, task_params: {}').format( diff --git a/dsub/lib/output_formatter.py b/dsub/lib/output_formatter.py index 946fb0a..80522a6 100644 --- a/dsub/lib/output_formatter.py +++ b/dsub/lib/output_formatter.py @@ -271,7 +271,7 @@ def prepare_summary_table(rows): # Use the original table as the driver in order to preserve the order. new_rows = [] for job_key in sorted(grouped.keys()): - group = grouped.get(job_key, None) + group = grouped[job_key] canonical_status = ['RUNNING', 'SUCCESS', 'FAILURE', 'CANCEL'] # Written this way to ensure that if somehow a new status is introduced, # it shows up in our output. diff --git a/dsub/lib/retry_util.py b/dsub/lib/retry_util.py index ff33bad..70baaa7 100644 --- a/dsub/lib/retry_util.py +++ b/dsub/lib/retry_util.py @@ -21,7 +21,7 @@ import sys import googleapiclient.errors -from httplib2 import ServerNotFoundError +import httplib2 import tenacity import google.auth @@ -127,7 +127,7 @@ def retry_api_check(retry_state: tenacity.RetryCallState) -> bool: # This has been observed as a transient error: # ServerNotFoundError: Unable to find the server at genomics.googleapis.com - if isinstance(exception, ServerNotFoundError): + if isinstance(exception, httplib2.ServerNotFoundError): _print_retry_error(attempt_number, MAX_API_ATTEMPTS, exception) return True diff --git a/dsub/providers/base.py b/dsub/providers/base.py index 3fa967b..e4872ea 100644 --- a/dsub/providers/base.py +++ b/dsub/providers/base.py @@ -184,7 +184,7 @@ def get_tasks_completion_messages(self, tasks): raise NotImplementedError() -class Task(object): +class Task(object, metaclass=abc.ABCMeta): """Basic container for task metadata.""" @abc.abstractmethod diff --git a/dsub/providers/google_base.py b/dsub/providers/google_base.py index 1423e75..a47ed3d 100644 --- a/dsub/providers/google_base.py +++ b/dsub/providers/google_base.py @@ -300,7 +300,7 @@ def parse_rfc3339_utc_string(rfc3339_utc_string): # When nanoseconds are provided, we round micros = int(round(int(fraction) // 1000)) else: - assert False, 'Fraction length not 0, 6, or 9: {}'.len(fraction) + assert False, 'Fraction length not 0, 6, or 9: {}'.format(len(fraction)) try: return datetime.datetime( diff --git a/dsub/providers/google_v2_base.py b/dsub/providers/google_v2_base.py index d5fe1ad..b56fb86 100644 --- a/dsub/providers/google_v2_base.py +++ b/dsub/providers/google_v2_base.py @@ -409,7 +409,7 @@ def get_filtered_normalized_events(self): continue if name == 'pulling-image': - if match.group(1) != user_image: + if match and match.group(1) != user_image: continue events[name] = mapped diff --git a/dsub/providers/provider_base.py b/dsub/providers/provider_base.py index e2bbce5..7d791c8 100644 --- a/dsub/providers/provider_base.py +++ b/dsub/providers/provider_base.py @@ -110,7 +110,7 @@ def parse_args(parser, provider_required_args, argv): # For the selected provider, check the required arguments for arg in provider_required_args[args.provider]: - if not args.__getattribute__(arg): + if not vars(args)[arg]: parser.error('argument --%s is required' % arg) return args From 56e1f61853095c44348e6663d3b09f62af074da1 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Fri, 20 Aug 2021 18:33:27 +0000 Subject: [PATCH 8/9] Update tenacity version PiperOrigin-RevId: 392033960 --- dsub/lib/dsub_util.py | 21 +++-- dsub/providers/google_base.py | 8 +- setup.py | 2 +- test/unit/retrying_test.py | 158 +++++++++++++++++----------------- 4 files changed, 96 insertions(+), 93 deletions(-) diff --git a/dsub/lib/dsub_util.py b/dsub/lib/dsub_util.py index 5e4aac8..e724c5d 100644 --- a/dsub/lib/dsub_util.py +++ b/dsub/lib/dsub_util.py @@ -21,15 +21,14 @@ import pwd import sys import warnings -from . import retry_util +from . import retry_util +import google.auth import googleapiclient.discovery import googleapiclient.errors import googleapiclient.http import tenacity -import google.auth - # this is the Job ID for jobs that are skipped. NO_JOB = 'NO_JOB' @@ -154,14 +153,14 @@ def get_storage_service(credentials): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def _downloader_next_chunk(downloader): """Downloads the next chunk.""" @@ -219,14 +218,14 @@ def load_file(file_path, credentials=None): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def _file_exists_in_gcs(gcs_file_path, credentials=None, storage_service=None): """Check whether the file exists, in GCS. @@ -257,14 +256,14 @@ def _file_exists_in_gcs(gcs_file_path, credentials=None, storage_service=None): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def _prefix_exists_in_gcs(gcs_prefix, credentials=None, storage_service=None): """Check whether there is a GCS object whose name starts with the prefix. @@ -307,14 +306,14 @@ def folder_exists(folder_path, credentials=None, storage_service=None): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def simple_pattern_exists_in_gcs(file_pattern, credentials=None, diff --git a/dsub/providers/google_base.py b/dsub/providers/google_base.py index a47ed3d..a1fe773 100644 --- a/dsub/providers/google_base.py +++ b/dsub/providers/google_base.py @@ -424,14 +424,14 @@ def cancel(batch_fn, cancel_fn, ops): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def setup_service(api_name, api_version, credentials=None): """Configures genomics API client. @@ -469,14 +469,14 @@ class Api(object): @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_API_ATTEMPTS), retry=retry_util.retry_api_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=64), + wait=tenacity.wait_exponential(multiplier=1, max=64), retry_error_callback=retry_util.on_give_up) # For API errors dealing with auth, we want to retry, but not as often # Maximum 4 retries. Wait 1, 2, 4, 8 seconds. @tenacity.retry( stop=tenacity.stop_after_attempt(retry_util.MAX_AUTH_ATTEMPTS), retry=retry_util.retry_auth_check, - wait=tenacity.wait_exponential(multiplier=0.5, max=8), + wait=tenacity.wait_exponential(multiplier=1, max=8), retry_error_callback=retry_util.on_give_up) def execute(self, api): """Executes operation. diff --git a/setup.py b/setup.py index f421f52..bbe8874 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ 'python-dateutil<=2.8.2', 'pytz<=2021.1', 'pyyaml<=5.4.1', - 'tenacity<=5.0.4', + 'tenacity<=7.0.0', 'tabulate<=0.8.9', # downstream dependencies diff --git a/test/unit/retrying_test.py b/test/unit/retrying_test.py index a652397..8d87682 100644 --- a/test/unit/retrying_test.py +++ b/test/unit/retrying_test.py @@ -15,19 +15,25 @@ import errno import socket -import time import unittest import apiclient.errors from dsub.lib import retry_util from dsub.providers import google_base +import fake_time +import google.auth +from mock import patch import parameterized -import google.auth +def chronology(): + # Simulates the passing of time for fake_time. + while True: + yield 1 # Simulates 1 second passing. -def current_time_ms(): - return int(round(time.time() * 1000)) + +def elapsed_time_in_seconds(fake_time_object): + return int(round(fake_time_object.now())) class GoogleApiMock(object): @@ -56,41 +62,38 @@ def __init__(self, status, reason): class TestRetrying(unittest.TestCase): def test_success(self): - exception_list = [] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + exception_list = [] + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() + api_wrapper_to_test.execute(mock_api_object) - # No expected retries, and no expected wait time - self.assertEqual(mock_api_object.retry_counter, 0) - self.assertLess(end - start, 1000) + # No expected retries, and no expected wait time + self.assertEqual(mock_api_object.retry_counter, 0) + self.assertLess(elapsed_time_in_seconds(ft), 1) @parameterized.parameterized.expand( - [(error_code,) - for error_code in list(retry_util.TRANSIENT_HTTP_ERROR_CODES) + - list(retry_util.HTTP_AUTH_ERROR_CODES)] + [(error_code,) for error_code in list(retry_util.TRANSIENT_HTTP_ERROR_CODES) + list(retry_util.HTTP_AUTH_ERROR_CODES)]) def test_retry_once(self, error_code): - exception_list = [ - apiclient.errors.HttpError( - ResponseMock(error_code, None), b'test_exception'), - ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + exception_list = [ + apiclient.errors.HttpError( + ResponseMock(error_code, None), b'test_exception'), + ] + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() + api_wrapper_to_test.execute(mock_api_object) - # Expected to retry once, for about 1 second - self.assertEqual(mock_api_object.retry_counter, 1) - self.assertGreaterEqual(end - start, 1000) - self.assertLess(end - start, 1500) + # Expected to retry once, for about 1 second + self.assertEqual(mock_api_object.retry_counter, 1) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 1) + self.assertLess(elapsed_time_in_seconds(ft), 1.5) def test_auth_failure(self): exception_list = [ @@ -100,32 +103,32 @@ def test_auth_failure(self): apiclient.errors.HttpError(ResponseMock(401, None), b'test_exception'), apiclient.errors.HttpError(ResponseMock(403, None), b'test_exception'), ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) - # We don't want to retry auth errors as aggressively, so we expect - # this exception to be raised after 4 retries, - # for a total of 1 + 2 + 4 + 8 = 15 seconds - start = current_time_ms() - with self.assertRaises(apiclient.errors.HttpError): - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() - self.assertGreaterEqual(end - start, 15000) - self.assertLess(end - start, 15500) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) + # We don't want to retry auth errors as aggressively, so we expect + # this exception to be raised after 4 retries, + # for a total of 1 + 2 + 4 + 8 = 15 seconds + with self.assertRaises(apiclient.errors.HttpError): + api_wrapper_to_test.execute(mock_api_object) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 15) + self.assertLess(elapsed_time_in_seconds(ft), 15.5) def test_transient_retries(self): exception_list = [ apiclient.errors.HttpError(ResponseMock(500, None), b'test_exception'), apiclient.errors.HttpError(ResponseMock(503, None), b'test_exception'), ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() - # Expected to retry twice, for a total of 1 + 2 = 3 seconds - self.assertEqual(mock_api_object.retry_counter, 2) - self.assertGreaterEqual(end - start, 3000) - self.assertLess(end - start, 3500) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) + api_wrapper_to_test.execute(mock_api_object) + # Expected to retry twice, for a total of 1 + 2 = 3 seconds + self.assertEqual(mock_api_object.retry_counter, 2) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 3) + self.assertLess(elapsed_time_in_seconds(ft), 3.5) def test_broken_pipe_retries(self): # To construct a BrokenPipeError, we pass errno.EPIPE (32) to OSError @@ -134,30 +137,30 @@ def test_broken_pipe_retries(self): OSError(errno.EPIPE, 'broken pipe exception test'), OSError(errno.EPIPE, 'broken pipe exception test'), ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() - # Expected to retry twice, for a total of 1 + 2 = 3 seconds - self.assertEqual(mock_api_object.retry_counter, 2) - self.assertGreaterEqual(end - start, 3000) - self.assertLess(end - start, 3500) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) + api_wrapper_to_test.execute(mock_api_object) + # Expected to retry twice, for a total of 1 + 2 = 3 seconds + self.assertEqual(mock_api_object.retry_counter, 2) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 3) + self.assertLess(elapsed_time_in_seconds(ft), 3.5) def test_socket_timeout(self): exception_list = [ socket.timeout(), socket.timeout(), ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() - # Expected to retry twice, for a total of 1 + 2 = 3 seconds - self.assertEqual(mock_api_object.retry_counter, 2) - self.assertGreaterEqual(end - start, 3000) - self.assertLess(end - start, 3500) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) + api_wrapper_to_test.execute(mock_api_object) + # Expected to retry twice, for a total of 1 + 2 = 3 seconds + self.assertEqual(mock_api_object.retry_counter, 2) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 3) + self.assertLess(elapsed_time_in_seconds(ft), 3.5) @parameterized.parameterized.expand([ (apiclient.errors.HttpError(ResponseMock(500, None), b'test_exception'), @@ -178,16 +181,17 @@ def test_retry_then_succeed(self): apiclient.errors.HttpError(ResponseMock(500, None), b'test_exception'), apiclient.errors.HttpError(ResponseMock(500, None), b'test_exception'), ] - api_wrapper_to_test = google_base.Api() - mock_api_object = GoogleApiMock(exception_list) - start = current_time_ms() - api_wrapper_to_test.execute(mock_api_object) - end = current_time_ms() - # Expected to retry 5 times, for a total of 1 + 2 + 4 + 8 + 16 = 31 seconds - # At the end we expect a recovery message to be emitted. - self.assertEqual(mock_api_object.retry_counter, 5) - self.assertGreaterEqual(end - start, 31000) - self.assertLess(end - start, 31500) + ft = fake_time.FakeTime(chronology()) + with patch('time.sleep', new=ft.sleep): + api_wrapper_to_test = google_base.Api() + mock_api_object = GoogleApiMock(exception_list) + api_wrapper_to_test.execute(mock_api_object) + # Expected to retry 5 times, + # for a total of 1 + 2 + 4 + 8 + 16 = 31 seconds + # At the end we expect a recovery message to be emitted. + self.assertEqual(mock_api_object.retry_counter, 5) + self.assertGreaterEqual(elapsed_time_in_seconds(ft), 31) + self.assertLess(elapsed_time_in_seconds(ft), 31.5) if __name__ == '__main__': From ad1c654422413b90c78614f66af61c1410cc1a77 Mon Sep 17 00:00:00 2001 From: willyn <willyn@google.com> Date: Thu, 26 Aug 2021 17:32:25 +0000 Subject: [PATCH 9/9] Update dsub version to 0.4.5 PiperOrigin-RevId: 393155372 --- dsub/_dsub_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsub/_dsub_version.py b/dsub/_dsub_version.py index 118272f..64869f1 100644 --- a/dsub/_dsub_version.py +++ b/dsub/_dsub_version.py @@ -26,4 +26,4 @@ 0.1.3.dev0 -> 0.1.3 -> 0.1.4.dev0 -> ... """ -DSUB_VERSION = '0.4.5.dev0' +DSUB_VERSION = '0.4.5'