Files
krkn/tests/test_rollback.py
LIU ZHE YOU 816363d151
Some checks failed
Functional & Unit Tests / Functional & Unit Tests (push) Failing after 9m18s
Functional & Unit Tests / Generate Coverage Badge (push) Has been skipped
[Rollback Scenarios] Perform rollback (#879)
* Add rollback config

* Inject rollback handler to scenario plugin

* Add Serializer

* Add decorator

* Add test with SimpleRollbackScenarioPlugin

* Add logger for verbose debug flow

* Resolve review comment

- remove additional rollback config in config.yaml
- set KUBECONFIG to ~/.kube/config in test_rollback

* Simplify set_rollback_context_decorator

* Fix integration of rollback_handler in __load_plugins

* Refactor rollback.config module

  - make it singleton class with register method to construct
  - RollbackContext ( <timestamp>-<run_uuid> )
  - add get_rollback_versions_directory for moduling the directory
    format

* Adapt new rollback.config

* Refactor serialization

- respect rollback_callable_name
- refactor _parse_rollback_callable_code
- refine VERSION_FILE_TEMPLATE

* Add get_scenario_rollback_versions_directory in RollbackConfig

* Add rollback in ApplicationOutageScenarioPlugin

* Add RollbackCallable and RollbackContent for type annotation

* Refactor rollback_handler with limited arguments

* Refactor the serialization for rollback

- limited arguments: callback and rollback_content just these two!
- always constuct lib_openshift and lib_telemetry in version file
- add _parse_rollback_content_definition for retrieving scenaio specific
  rollback_content
- remove utils for formating variadic function

* Refactor applicaton outage scenario

* Fix test_rollback

* Make RollbackContent with static fields

* simplify serialization

  - Remove all unused format dynamic arguments utils
  - Add jinja template for version file
  - Replace set_context for serialization with passing version to serialize_callable

* Add rollback for hogs scenario

* Fix version file full path based on feedback

- {versions_directory}/<timestamp(ns)>-<run_uuid>/{scenario_type}-<timestamp(ns)>-<random_hash>.py

* Fix scenario plugins after rebase

* Add rollback config

* Inject rollback handler to scenario plugin

* Add test with SimpleRollbackScenarioPlugin

* Resolve review comment

- remove additional rollback config in config.yaml
- set KUBECONFIG to ~/.kube/config in test_rollback

* Fix integration of rollback_handler in __load_plugins

* Refactor rollback.config module

  - make it singleton class with register method to construct
  - RollbackContext ( <timestamp>-<run_uuid> )
  - add get_rollback_versions_directory for moduling the directory
    format

* Adapt new rollback.config

* Add rollback in ApplicationOutageScenarioPlugin

* Add RollbackCallable and RollbackContent for type annotation

* Refactor applicaton outage scenario

* Fix test_rollback

* Make RollbackContent with static fields

* simplify serialization

  - Remove all unused format dynamic arguments utils
  - Add jinja template for version file
  - Replace set_context for serialization with passing version to serialize_callable

* Add rollback for hogs scenario

* Fix version file full path based on feedback

- {versions_directory}/<timestamp(ns)>-<run_uuid>/{scenario_type}-<timestamp(ns)>-<random_hash>.py

* Fix scenario plugins after rebase

* Add execute rollback

* Add CLI for list and execute rollback

* Replace subprocess with importlib

* Fix error after rebase

* fixup! Fix docstring

- Add telemetry_ocp in execute_rollback docstring
- Remove rollback_config in create_plugin docstring
- Remove scenario_types in set_rollback_callable docsting

* fixup! Replace os.urandom with krkn_lib.utils.get_random_string

* fixup! Add missing telemetry_ocp for execute_rollback_version_files

* fixup! Remove redundant import

- Remove duplicate TYPE_CHECKING in handler module
- Remove cast in signal module
- Remove RollbackConfig in scenario_plugin_factory

* fixup! Replace sys.exit(1) with return

* fixup! Remove duplicate rollback_network_policy

* fixup! Decouple Serializer initialization

* fixup! Rename callback to rollback_callable

* fixup! Refine comment for constructing AbstractScenarioPlugin with
placeholder value

* fixup! Add version in docstring

* fixup! Remove uv.lock
2025-08-20 16:50:52 +02:00

161 lines
5.8 KiB
Python

import pytest
import logging
import os
import sys
import uuid
import subprocess
from krkn_lib.k8s import KrknKubernetes
from krkn_lib.ocp import KrknOpenshift
from krkn_lib.telemetry.ocp import KrknTelemetryOpenshift
from krkn_lib.models.telemetry import ScenarioTelemetry
from krkn_lib.utils import SafeLogger
from krkn.rollback.config import RollbackConfig
sys.path.append(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
) # Adjust path to include krkn
TEST_LOGS_DIR = "/tmp/krkn_test_rollback_logs_directory"
TEST_VERSIONS_DIR = "/tmp/krkn_test_rollback_versions_directory"
class TestRollbackScenarioPlugin:
def validate_rollback_directory(
self, run_uuid: str, scenario: str, versions: int = 1
) -> list[str]:
"""
Validate that the rollback directory exists and contains version files.
:param run_uuid: The UUID for current run, used to identify the rollback context directory.
:param scenario: The name of the scenario to validate.
:param versions: The expected number of version files.
:return: List of version files in full path.
"""
rollback_context_directories = [
dirname for dirname in os.listdir(TEST_VERSIONS_DIR) if run_uuid in dirname
]
assert len(rollback_context_directories) == 1, (
f"Expected one directory for run UUID {run_uuid}, found: {rollback_context_directories}"
)
scenario_rollback_versions_directory = os.path.join(
TEST_VERSIONS_DIR, rollback_context_directories[0]
)
version_files = os.listdir(scenario_rollback_versions_directory)
assert len(version_files) == versions, (
f"Expected {versions} version files, found: {len(version_files)}"
)
for version_file in version_files:
assert version_file.startswith(scenario), (
f"Version file {version_file} does not start with '{scenario}'"
)
assert version_file.endswith(".py"), (
f"Version file {version_file} does not end with '.py'"
)
return [
os.path.join(scenario_rollback_versions_directory, vf)
for vf in version_files
]
def execute_version_file(self, version_file: str):
"""
Execute a rollback version file using subprocess.
:param version_file: The path to the version file to execute.
"""
print(f"Executing rollback version file: {version_file}")
result = subprocess.run(
[sys.executable, version_file],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"Rollback version file {version_file} failed with return code {result.returncode}. "
f"Output: {result.stdout}, Error: {result.stderr}"
)
print(
f"Rollback version file executed successfully: {version_file} with output: {result.stdout}"
)
@pytest.fixture(autouse=True)
def setup_logging(self):
os.makedirs(TEST_LOGS_DIR, exist_ok=True)
# setup logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(os.path.join(TEST_LOGS_DIR, "test_rollback.log")),
logging.StreamHandler(),
],
)
@pytest.fixture(scope="module")
def kubeconfig_path(self):
# Provide the path to the kubeconfig file for testing
return os.getenv("KUBECONFIG", "~/.kube/config")
@pytest.fixture(scope="module")
def safe_logger(self):
os.makedirs(TEST_LOGS_DIR, exist_ok=True)
with open(os.path.join(TEST_LOGS_DIR, "telemetry.log"), "w") as f:
pass # Create the file if it doesn't exist
yield SafeLogger(filename=os.path.join(TEST_LOGS_DIR, "telemetry.log"))
@pytest.fixture(scope="module")
def kubecli(self, kubeconfig_path):
yield KrknKubernetes(kubeconfig_path=kubeconfig_path)
@pytest.fixture(scope="module")
def lib_openshift(self, kubeconfig_path):
yield KrknOpenshift(kubeconfig_path=kubeconfig_path)
@pytest.fixture(scope="module")
def lib_telemetry(self, lib_openshift, safe_logger):
yield KrknTelemetryOpenshift(
safe_logger=safe_logger,
lib_openshift=lib_openshift,
)
@pytest.fixture(scope="module")
def scenario_telemetry(self):
yield ScenarioTelemetry()
@pytest.fixture(scope="module")
def setup_rollback_config(self):
RollbackConfig.register(
auto=False,
versions_directory=TEST_VERSIONS_DIR,
)
@pytest.mark.usefixtures("setup_rollback_config")
def test_simple_rollback_scenario_plugin(self, lib_telemetry, scenario_telemetry):
from tests.rollback_scenario_plugins.simple import SimpleRollbackScenarioPlugin
scenario_type = "simple_rollback_scenario"
simple_rollback_scenario_plugin = SimpleRollbackScenarioPlugin(
scenario_type=scenario_type,
)
run_uuid = str(uuid.uuid4())
simple_rollback_scenario_plugin.run(
run_uuid=run_uuid,
scenario="test_scenario",
krkn_config={
"key1": "value",
"key2": False,
"key3": 123,
"key4": ["value1", "value2", "value3"],
},
lib_telemetry=lib_telemetry,
scenario_telemetry=scenario_telemetry,
)
# Validate the rollback directory and version files do exist
version_files = self.validate_rollback_directory(
run_uuid,
scenario_type,
)
# Execute the rollback version file
for version_file in version_files:
self.execute_version_file(version_file)