Files
scylladb/test/cluster/dtest/tools/log_utils.py
Evgeniy Naydanov 145c2fed97 test.py: dtest: add wait_for_any_log() to tools/log_utils.py
Copy wait_for_any_log() function from dtest tools/log_utils.py
with few modifications:

 - Add type hints;
 - Change timeout for node.watch_log_for() calls from 0 to 0.1
   because dtest shim's implementation uses asyncio.timeout()
   and 0 means not "one time" but "never run";
 - Use set() instead of list() for `ret` variable;
 - Remove redundant `found` variable.
 - Remove `remaining` variable and use shallow copies to make
   the code more correct.  As a side effect this makes the
   TimeoutError message more correct too;
 - Use f-string formatting for TimeoutError message;
2025-06-02 05:14:41 +00:00

84 lines
2.8 KiB
Python

#
# Copyright (C) 2025-present ScyllaDB
#
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
#
from __future__ import annotations
import itertools
import logging
import re
import time
from datetime import datetime
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from test.cluster.dtest.ccmlib.scylla_node import ScyllaNode
control_chars = "".join(map(chr, itertools.chain(range(0x20), range(0x7F, 0xA0))))
control_char_re = re.compile("[%s]" % re.escape(control_chars))
def remove_control_chars(s):
return control_char_re.sub("", s)
class DisableLogger:
def __init__(self, logger_name):
self.logger_name = logger_name
self.level = 0
def __enter__(self):
self.level = logging.getLogger(self.logger_name).level
logging.getLogger(self.logger_name).setLevel(logging.WARNING)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
logging.getLogger(self.logger_name).setLevel(self.level)
def wait_for_any_log(nodes: list[ScyllaNode],
patterns: str | list[str],
timeout: int,
dispersed: bool = False) -> ScyllaNode | list[ScyllaNode]:
"""Look for a pattern in the system.log of any in a given list of nodes.
:param nodes: The list of nodes whose logs to scan
:param patterns: The target pattern (a string, or a list of strings)
:param timeout: How long to wait for the pattern. Note that
strictly speaking, timeout is not really a timeout,
but a maximum number of attempts. This implies that
the all the grepping takes no time at all, so it is
somewhat inaccurate, but probably close enough.
:return: The first node in whose log the pattern was found, if not dispersed.
Otherwise, if dispersed=True, return a list of all nodes with any of the patterns.
"""
if dispersed:
patterns = patterns.copy()
ret = set()
for _ in range(timeout):
for node in nodes:
for p in patterns.copy():
try:
if node.watch_log_for(p, timeout=0.1):
patterns.remove(p)
ret.add(node)
except TimeoutError:
pass
if not patterns:
return list(ret)
time.sleep(1)
else:
for _ in range(timeout):
for node in nodes:
try:
if node.watch_log_for(patterns, timeout=0.1):
return node
except TimeoutError:
pass
time.sleep(1)
raise TimeoutError(f"{datetime.now():%d %b %Y %H:%M:%S} Unable to find :{patterns} in any node log within {timeout}s")