diff --git a/ocf/loxthelion/managed-container.d/common.sh b/ocf/loxthelion/managed-container.d/common.sh index e1fa717..4557346 100755 --- a/ocf/loxthelion/managed-container.d/common.sh +++ b/ocf/loxthelion/managed-container.d/common.sh @@ -119,6 +119,70 @@ mc_promotion_monitor() esac fi + # A successful managed-containerd response may explicitly delegate + # monitoring to the RA's already-loaded packaged container/runtime path. + # + # The daemon remains authoritative for backend selection: this path is + # reachable only after managed-containerd successfully requested it. + case "${proxy_result}" in + delegate\|*) + local delegate_tag delegate_backend delegate_runtime delegate_extra + local configured_runtime + + IFS='|' read -r delegate_tag delegate_backend delegate_runtime delegate_extra <&2 + return 99 +} + +mc_application_monitor() +{ + application_calls=$((application_calls + 1)) + return "${application_rc}" +} + +mc_promotion_score_set() +{ + score="$1" + return 0 +} + +mc_promotion_score_from_monitor() +{ + case "$1" in + 0|8) + score="healthy:$1" + ;; + *) + score="-INFINITY" + ;; + esac + + return 0 +} + +echo "===== PODMAN DELEGATION SUCCESS =====" + +application_rc=0 +proxy_result_value="delegate|container|podman" +cache_rc="" +score="" +application_calls=0 + +mc_promotion_monitor +rc=$? + +[ "$rc" -eq 0 ] +[ "$application_calls" -eq 1 ] +[ "$cache_rc" = 0 ] +[ "$score" = "healthy:0" ] + +echo "podman_success accepted" + + +echo +echo "===== PROMOTED DELEGATION =====" + +application_rc=8 +proxy_result_value="delegate|container|podman" +cache_rc="" +score="" +application_calls=0 + +mc_promotion_monitor +rc=$? + +[ "$rc" -eq 8 ] +[ "$application_calls" -eq 1 ] +[ "$cache_rc" = 8 ] +[ "$score" = "healthy:8" ] + +echo "podman_promoted accepted" + + +echo +echo "===== RUNTIME MISMATCH =====" + +application_rc=0 +proxy_result_value="delegate|container|docker" +cache_rc="" +score="" +application_calls=0 + +mc_promotion_monitor +rc=$? + +[ "$rc" -eq 1 ] +[ "$application_calls" -eq 0 ] +[ "$cache_rc" = "cleared" ] +[ "$score" = "-INFINITY" ] + +echo "runtime_mismatch rejected" + + +echo +echo "===== MALFORMED DELEGATION =====" + +proxy_result_value="delegate|container|podman|extra" +cache_rc="" +score="" +application_calls=0 + +mc_promotion_monitor +rc=$? + +[ "$rc" -eq 1 ] +[ "$application_calls" -eq 0 ] +[ "$cache_rc" = "cleared" ] +[ "$score" = "-INFINITY" ] + +echo "malformed_delegation rejected" + + +echo +echo "MONITOR_DELEGATION_SHELL_TEST_PASS=1" diff --git a/tests/test-proxy-monitor-delegation.py b/tests/test-proxy-monitor-delegation.py new file mode 100755 index 0000000..3d157a3 --- /dev/null +++ b/tests/test-proxy-monitor-delegation.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import importlib.util +from pathlib import Path + + +spec = importlib.util.spec_from_file_location( + "proxy_client", + Path( + "ocf/loxthelion/" + "managed-container.d/" + "proxy_client.py" + ), +) + +proxy = importlib.util.module_from_spec(spec) +spec.loader.exec_module(proxy) + + +print("===== NORMAL API MONITOR RESULT =====") + +normal = { + "ha": { + "ocf_rc": 0, + "health": "healthy", + "ha_role": "unpromoted", + "state": "streaming", + "promotion_eligible": True, + }, + "role": "standby", + "streaming": True, + "wal_receiver": { + "slot_name": "ha_pg_node1", + "sender_host": "192.0.2.10", + }, +} + +formatted = proxy.format_monitor_result(normal) + +expected = ( + "0|healthy|unpromoted|streaming|true|" + "standby|true|ha_pg_node1|192.0.2.10" +) + +assert formatted == expected, formatted + +print("api_monitor_format accepted") + + +print() +print("===== PODMAN DELEGATION =====") + +delegated = { + "delegated": True, + "backend": "container", + "container_runtime": "podman", +} + +formatted = proxy.format_monitor_result(delegated) + +assert formatted == "delegate|container|podman", formatted + +print("podman_delegation accepted") + + +print() +print("===== DOCKER DELEGATION =====") + +delegated = { + "delegated": True, + "backend": "container", + "container_runtime": "docker", +} + +formatted = proxy.format_monitor_result(delegated) + +assert formatted == "delegate|container|docker", formatted + +print("docker_delegation accepted") + + +print() +print("===== INVALID DELEGATED BACKEND =====") + +bad = { + "delegated": True, + "backend": "api", + "container_runtime": "podman", +} + +try: + proxy.format_monitor_result(bad) +except proxy.ProtocolFailure: + print("invalid_backend rejected") +else: + raise AssertionError( + "delegation with non-container backend was accepted" + ) + + +print() +print("===== INVALID DELEGATED RUNTIME =====") + +bad = { + "delegated": True, + "backend": "container", + "container_runtime": "lxc", +} + +try: + proxy.format_monitor_result(bad) +except proxy.ProtocolFailure: + print("invalid_runtime rejected") +else: + raise AssertionError( + "delegation with invalid runtime was accepted" + ) + + +print() +print("===== MALFORMED DELEGATION =====") + +bad = { + "delegated": True, + "backend": "container", +} + +try: + proxy.format_monitor_result(bad) +except proxy.ProtocolFailure: + print("missing_runtime rejected") +else: + raise AssertionError( + "delegation without runtime was accepted" + ) + + +print() +print("PROXY_MONITOR_DELEGATION_TEST_PASS=1") diff --git a/tests/test-proxy-protocol.sh b/tests/test-proxy-protocol.sh index f27c72b..a176201 100755 --- a/tests/test-proxy-protocol.sh +++ b/tests/test-proxy-protocol.sh @@ -150,6 +150,13 @@ with conn: elif mode == "bad_rc": response["result"]["ha"]["ocf_rc"] = 42 + elif mode == "delegate": + response["result"] = { + "delegated": True, + "backend": "container", + "container_runtime": "podman", + } + conn.sendall( json.dumps(response).encode("utf-8") + b"\n" ) @@ -248,6 +255,7 @@ PY_REFUSED echo "===== PROXY PROTOCOL CLASSIFICATION =====" run_case valid valid 0 +run_case delegation delegate 0 run_case missing_protocol missing_protocol 11 run_case wrong_protocol wrong_protocol 11 run_case wrong_resource wrong_resource 11