diff --git a/CHANGELOG.md b/CHANGELOG.md index 5866dc4..d424070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,56 @@ ## [Unreleased][] -[Unreleased]: https://github.com/chaostoolkit/chaostoolkit-kubernetes/compare/0.38.2...HEAD +[Unreleased]: https://github.com/chaostoolkit/chaostoolkit-kubernetes/compare/0.39.0...HEAD + +## [0.39.0][] - 2024-05-06 + +[0.39.0]: https://github.com/chaostoolkit/chaostoolkit-kubernetes/compare/0.39.0...0.39.0 + +### Added + +* Added the `chaosk8s.pod.probes.should_be_found_in_logs` probe to search + into container logs: + + ```json + { + "title": "Check pod logs", + "description": "n/a", + "steady-state-hypothesis": { + "title": "extract-logs-with-regex", + "probes": [ + { + "name": "at-least-one-pod-should-have-restarted", + "type": "probe", + "tolerance": { + "name": "search-probe", + "type": "probe", + "provider": { + "type": "python", + "module": "chaosk8s.pod.probes", + "func": "should_be_found_in_logs", + "arguments": { + "pattern": "startup", + "all_containers": false + } + } + }, + "provider": { + "type": "python", + "module": "chaosk8s.pod.probes", + "func": "read_pod_logs", + "arguments": { + "last": "60s", + "label_selector": "app=producer", + "container_name": "producer" + } + } + } + ] + }, + "method": [] + } + ``` ## [0.38.2][] - 2024-04-18 diff --git a/chaosk8s/pod/probes.py b/chaosk8s/pod/probes.py index 5174085..b4e26ec 100644 --- a/chaosk8s/pod/probes.py +++ b/chaosk8s/pod/probes.py @@ -1,4 +1,5 @@ import logging +import re from datetime import datetime from typing import Dict, List, Union @@ -17,6 +18,7 @@ "count_pods", "pod_is_not_available", "count_min_pods", + "should_be_found_in_logs", ] logger = logging.getLogger("chaostoolkit") @@ -370,3 +372,43 @@ def count_min_pods( label_selector=label_selector, phase=phase, ns=ns, secrets=secrets ) return count >= min_count + + +def should_be_found_in_logs( + pattern: str, + all_containers: bool = True, + value: Dict[str, str] = None, + secrets: Secrets = None, +) -> bool: + """ + Lookup for the first occurence of `pattern` in the logs of each container + fetched by the `read_pod_logs` probe. + + If `all_containers` is set the match must occur on all continers. Otherwise, + allow for only a subset of containers to match the search. + """ + if not value: + raise ActivityFailed("no logs to search from") + + c_pattern = re.compile(pattern) + + matched: list[re.Match] = [] + + for container_name in value: + logs = value[container_name] + m = c_pattern.search(logs) + if m: + logger.debug( + f"Container '{container_name}' matched at position {m.span()}" + ) + matched.append(m) + continue + + logger.debug(f"Container '{container_name}' did not match") + + if all_containers and (len(matched) != len(value)): + return False + elif not matched: + return False + + return True