Skip to content

Commit

Permalink
User timeout-sampler module (#1551)
Browse files Browse the repository at this point in the history
  • Loading branch information
myakove authored Dec 27, 2023
1 parent a296473 commit 29b34e3
Show file tree
Hide file tree
Showing 21 changed files with 35 additions and 238 deletions.
2 changes: 1 addition & 1 deletion ocp_resources/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ocp_resources.constants import NOT_FOUND_ERROR_EXCEPTION_DICT
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler
from timeout_sampler import TimeoutSampler


class Benchmark(NamespacedResource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/cdi_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import Resource
from ocp_resources.utils import TimeoutSampler
from timeout_sampler import TimeoutSampler


class CDIConfig(Resource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/daemonset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler
from timeout_sampler import TimeoutSampler


class DaemonSet(NamespacedResource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/datavolume.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
)
from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
from ocp_resources.resource import NamespacedResource, Resource
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
from timeout_sampler import TimeoutExpiredError, TimeoutSampler


class DataVolume(NamespacedResource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/deployment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler, TimeoutWatch
from timeout_sampler import TimeoutSampler, TimeoutWatch


class Deployment(NamespacedResource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/machine_set.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ocp_resources.constants import TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
from timeout_sampler import TimeoutExpiredError, TimeoutSampler

TIMEOUT_5MINUTES = 300

Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/mtv.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
from timeout_sampler import TimeoutExpiredError, TimeoutSampler


def _get_status_condition_log_message(**status_condition):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/node_network_configuration_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
)
from ocp_resources.node_network_state import NodeNetworkState
from ocp_resources.resource import Resource, ResourceEditor
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler, TimeoutWatch
from timeout_sampler import TimeoutExpiredError, TimeoutSampler, TimeoutWatch

IPV4_STR = "ipv4"
IPV6_STR = "ipv6"
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/node_network_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ocp_resources.constants import TIMEOUT_4MINUTES
from ocp_resources.resource import Resource
from ocp_resources.utils import TimeoutSampler
from timeout_sampler import TimeoutSampler

SLEEP = 1

Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/pod.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ocp_resources.constants import TIMEOUT_4MINUTES
from ocp_resources.node import Node
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutWatch
from timeout_sampler import TimeoutWatch


class ExecOnPodError(Exception):
Expand Down
4 changes: 2 additions & 2 deletions ocp_resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
TIMEOUT_4MINUTES,
)
from ocp_resources.event import Event
from ocp_resources.utils import (
from timeout_sampler import (
TimeoutExpiredError,
TimeoutSampler,
TimeoutWatch,
skip_existing_resource_creation_teardown,
)
from ocp_resources.utils import skip_existing_resource_creation_teardown

LOGGER = get_logger(name=__name__)
MAX_SUPPORTED_API_VERSION = "v2"
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/sriov_network_node_state.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler, TimeoutWatch
from timeout_sampler import TimeoutExpiredError, TimeoutSampler, TimeoutWatch


class SriovNetworkNodeState(NamespacedResource):
Expand Down
217 changes: 0 additions & 217 deletions ocp_resources/utils.py
Original file line number Diff line number Diff line change
@@ -1,226 +1,9 @@
import datetime
import time

import yaml
from simple_logger.logger import get_logger

LOGGER = get_logger(name=__name__)


class TimeoutExpiredError(Exception):
def __init__(self, value):
super().__init__()
self.value = value

def __str__(self):
return f"Timed Out: {self.value}"


class TimeoutSampler:
"""
Samples the function output.
This is a generator object that at first yields the output of callable.
After the yield, it either raises instance of `TimeoutExpiredError` or
sleeps `sleep` seconds.
Yielding the output allows you to handle every value as you wish.
exceptions_dict should be in the following format:
{
exception0: [exception0_msg0],
exception1: [
exception1_msg0,
exception1_msg1
],
exception2: []
}
If an exception is raised within `func`:
Example exception inheritance:
class Exception
class AExampleError(Exception)
class BExampleError(AExampleError)
The raised exception's class will fall into one of three categories:
1. An exception class specifically declared in exceptions_dict
exceptions_dict: {BExampleError: []}
raise: BExampleError
result: continue
2. A child class inherited from an exception class in exceptions_dict
exceptions_dict: {AExampleError: []}
raise: BExampleError
result: continue
3. Everything else, this will always re-raise the exception
exceptions_dict: {BExampleError: []}
raise: AExampleError
result: raise
Args:
wait_timeout (int): Time in seconds to wait for func to return a value equating to True
sleep (int): Time in seconds between calls to func
func (Callable): to be wrapped by TimeoutSampler
exceptions_dict (dict): Exception handling definition
print_log (bool): Print elapsed time to log
"""

def __init__(
self,
wait_timeout,
sleep,
func,
exceptions_dict=None,
print_log=True,
**func_kwargs,
):
self.wait_timeout = wait_timeout
self.sleep = sleep
self.func = func
self.func_kwargs = func_kwargs
self.elapsed_time = None
self.print_log = print_log

self.exceptions_dict = exceptions_dict or {Exception: []}
self._exceptions = tuple(self.exceptions_dict.keys())

def _get_func_info(self, _func, type_):
# If func is partial function.
if getattr(_func, "func", None):
return self._get_func_info(_func=_func.func, type_=type_)

res = getattr(_func, type_, None)
if res:
# If func is lambda function.
if _func.__name__ == "<lambda>":
if type_ == "__module__":
return f"{res}.{_func.__qualname__.split('.')[1]}"

elif type_ == "__name__":
free_vars = _func.__code__.co_freevars
free_vars = f"{'.'.join(free_vars)}." if free_vars else ""
return f"lambda: {free_vars}{'.'.join(_func.__code__.co_names)}"
return res

@property
def _func_log(self):
"""
Returns:
string: `func` information to include in log message
"""
_func_kwargs = f"Kwargs: {self.func_kwargs}" if self.func_kwargs else ""
_func_module = self._get_func_info(_func=self.func, type_="__module__")
_func_name = self._get_func_info(_func=self.func, type_="__name__")
return f"Function: {_func_module}.{_func_name} {_func_kwargs}".strip()

def __iter__(self):
"""
Iterator
Yields:
any: Return value from `func`
"""
timeout_watch = TimeoutWatch(timeout=self.wait_timeout)
if self.print_log:
LOGGER.info(
f"Waiting for {self.wait_timeout} seconds"
f" [{datetime.timedelta(seconds=self.wait_timeout)}], retry every"
f" {self.sleep} seconds. ({self._func_log})"
)

last_exp = None
while timeout_watch.remaining_time() > 0:
try:
self.elapsed_time = self.wait_timeout - timeout_watch.remaining_time()
yield self.func(**self.func_kwargs)
self.elapsed_time = None

time.sleep(self.sleep)

except Exception as exp:
last_exp = exp
if self._is_raisable_exception(exp=last_exp):
raise TimeoutExpiredError(self._get_exception_log(exp=last_exp))

self.elapsed_time = None
time.sleep(self.sleep)

finally:
if self.elapsed_time and self.print_log:
LOGGER.info(
"Elapsed time:" f" {self.elapsed_time} [{datetime.timedelta(seconds=self.elapsed_time)}]"
)

raise TimeoutExpiredError(self._get_exception_log(exp=last_exp))

@staticmethod
def _is_exception_matched(exp, exception_messages):
"""
Args:
exp (Exception): Exception object raised by `func`
exception_messages (list): Either an empty list allowing all text,
or a list of allowed strings to match against the exception text.
Returns:
bool: True if exception text is allowed or no exception text given, False otherwise
"""
if not exception_messages:
return True

# Prevent match if provided with empty string
return any(msg and msg in str(exp) for msg in exception_messages)

def _is_raisable_exception(self, exp):
"""
Verify whether exception should be raised during execution of `func`
Args:
exp (Exception): Exception object raised by `func`
Returns:
bool: True if exp should be raised, False otherwise
"""

for entry in self.exceptions_dict:
if isinstance(exp, entry): # Check inheritance for raised exception
exception_messages = self.exceptions_dict.get(entry)
if self._is_exception_matched(exp=exp, exception_messages=exception_messages):
return False

return True

def _get_exception_log(self, exp):
"""
Args:
exp (any): Raised exception
Returns:
string: Log message for exception
"""
exp_name = exp.__class__.__name__ if exp else "N/A"

last_exception_log = f"Last exception: {exp_name}: {exp}"
return f"{self.wait_timeout}\n{self._func_log}\n{last_exception_log}"


class TimeoutWatch:
"""
A time counter allowing to determine the time remaining since the start
of a given interval
"""

def __init__(self, timeout):
self.timeout = timeout
self.start_time = time.time()

def remaining_time(self):
"""
Return the remaining part of timeout since the object was created.
"""
return self.start_time + self.timeout - time.time()


def skip_existing_resource_creation_teardown(resource, export_str, user_exported_args, check_exists=True):
"""
Args:
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
TIMEOUT_4MINUTES,
)
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler
from timeout_sampler import TimeoutSampler
from ocp_resources.virtual_machine_instance import VirtualMachineInstance


Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/virtual_machine_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
from timeout_sampler import TimeoutExpiredError, TimeoutSampler
from ocp_resources.virtual_machine import VirtualMachine


Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/virtual_machine_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ocp_resources.node import Node
from ocp_resources.pod import Pod
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutExpiredError, TimeoutSampler
from timeout_sampler import TimeoutExpiredError, TimeoutSampler


class VirtualMachineInstance(NamespacedResource):
Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/virtual_machine_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler, TimeoutWatch
from timeout_sampler import TimeoutSampler, TimeoutWatch
from ocp_resources.virtual_machine import VirtualMachine


Expand Down
2 changes: 1 addition & 1 deletion ocp_resources/virtual_machine_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ocp_resources.constants import PROTOCOL_ERROR_EXCEPTION_DICT, TIMEOUT_4MINUTES
from ocp_resources.resource import NamespacedResource
from ocp_resources.utils import TimeoutSampler, TimeoutWatch
from timeout_sampler import TimeoutSampler, TimeoutWatch
from ocp_resources.virtual_machine import VirtualMachine


Expand Down
Loading

0 comments on commit 29b34e3

Please sign in to comment.