From cd96214b6dc69dd090d32f7a52a00007122e35f6 Mon Sep 17 00:00:00 2001 From: Ondřej Nosek Date: Feb 07 2024 01:15:11 +0000 Subject: Retries for clone command JIRA: RHELCMP-12562 Signed-off-by: Ondřej Nosek --- diff --git a/pyrpkg/__init__.py b/pyrpkg/__init__.py index 875dea3..20d7569 100644 --- a/pyrpkg/__init__.py +++ b/pyrpkg/__init__.py @@ -13,6 +13,7 @@ from __future__ import print_function import errno import fnmatch +import functools import getpass import glob import io @@ -1632,7 +1633,7 @@ class Commands(object): self.log.debug('Cloning into: %s', target) cmd.append(target) - self._run_command(cmd, cwd=path) + self.retry_clone()(self._run_command)(cmd, cwd=path, return_text=True, return_stderr=True) # Set configuration base_repo = self.get_base_repo(repo) @@ -1647,6 +1648,51 @@ class Commands(object): return + def retry_clone(self, attempts=None, delay_between_attempts=None, raises=None): + """A decorator that allows to retry a section of code until success or counter elapses + """ + + def wrapper(function): + @functools.wraps(function) + def inner(*args, **kwargs): + + attempts_all = attempts or self.lookaside_attempts + attempts_left = attempts_all + delay = delay_between_attempts or self.lookaside_delay + stop_patterns = [ + r"destination path.+already exists", + r"No such repository", + ] + + while attempts_left > 0: + code, _, stderr = function(*args, **kwargs) + # delayed print - this error was originally suppressed within + # the function. Display it now. + self.log.error(stderr) + if code and stop_patterns: + for pattern in stop_patterns: + if re.search(pattern, stderr, flags=re.IGNORECASE): + raise rpkgError('Failed to execute command.') + else: + return + attempts_left -= 1 + self.log.debug("Attempt %d/%d has failed." + % (attempts_all - attempts_left, attempts_all)) + if attempts_left: + self.log.info("The operation will be retried in %ds." % (delay)) + time.sleep(delay) + delay *= 2 + self.log.info("Retrying ...") + else: + if raises is None: + raise rpkgError('Failed to execute command.') + else: + raise raises(e) + + return inner + + return wrapper + def get_base_repo(self, repo): # Handle namespaced repositories # Example: