Extract managed-containerd proxy client
This commit is contained in:
@@ -1,99 +1,13 @@
|
||||
# Generic managed-containerd client helpers.
|
||||
# Shadow calls are observational only and must never affect OCF action results.
|
||||
|
||||
MC_PROXY_SOCKET_DEFAULT="/run/user/1000/managed-containerd/managed-containerd.sock"
|
||||
|
||||
mc_proxy_shadow_monitor()
|
||||
{
|
||||
local authoritative_rc="$1"
|
||||
local socket_path="${MC_PROXY_SOCKET:-${MC_PROXY_SOCKET_DEFAULT}}"
|
||||
local resource="${OCF_RESOURCE_INSTANCE:-}"
|
||||
local response
|
||||
MC_PROXY_MODULE_DIR="$(
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" &&
|
||||
pwd
|
||||
)"
|
||||
|
||||
resource="${resource%%:*}"
|
||||
|
||||
[ -n "${resource}" ] || return 0
|
||||
[ -S "${socket_path}" ] || return 0
|
||||
command -v python3 >/dev/null 2>&1 || return 0
|
||||
|
||||
response="$(
|
||||
timeout --kill-after=1s 3s python3 - "${socket_path}" "${resource}" "${authoritative_rc}" <<'PY'
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
|
||||
path, resource, authoritative_raw = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
authoritative_rc = int(authoritative_raw)
|
||||
request = {"action": "monitor", "resource": resource}
|
||||
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.settimeout(2.0)
|
||||
s.connect(path)
|
||||
s.sendall(json.dumps(request).encode("utf-8") + b"\n")
|
||||
|
||||
raw = b""
|
||||
while b"\n" not in raw:
|
||||
chunk = s.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
raw += chunk
|
||||
s.close()
|
||||
|
||||
obj = json.loads(raw.decode("utf-8").strip())
|
||||
if not obj.get("ok"):
|
||||
print("ERROR|" + str(obj.get("error", "unknown proxy error")))
|
||||
raise SystemExit(0)
|
||||
|
||||
result = obj.get("result") or {}
|
||||
ha = result.get("ha") or {}
|
||||
proxy_rc = ha.get("ocf_rc")
|
||||
|
||||
if proxy_rc is None:
|
||||
comparison = "UNCLASSIFIED"
|
||||
else:
|
||||
comparison = "MATCH" if int(proxy_rc) == authoritative_rc else "MISMATCH"
|
||||
|
||||
receiver = result.get("wal_receiver") or {}
|
||||
print("OK|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}".format(
|
||||
comparison,
|
||||
"null" if proxy_rc is None else proxy_rc,
|
||||
ha.get("health", "unknown"),
|
||||
ha.get("ha_role", "unknown"),
|
||||
ha.get("state", "unknown"),
|
||||
str(ha.get("promotion_eligible", "unknown")).lower(),
|
||||
result.get("role", "unknown"),
|
||||
str(result.get("streaming", "unknown")).lower(),
|
||||
receiver.get("slot_name") or "",
|
||||
receiver.get("sender_host") or "",
|
||||
))
|
||||
PY
|
||||
)" || response="TRANSPORT_ERROR"
|
||||
|
||||
case "${response}" in
|
||||
OK\|MATCH\|*)
|
||||
ocf_log info \
|
||||
"managed-containerd shadow monitor: resource=${resource} authoritative_rc=${authoritative_rc} proxy=${response}"
|
||||
;;
|
||||
OK\|UNCLASSIFIED\|*)
|
||||
ocf_log warn \
|
||||
"managed-containerd shadow monitor unclassified: resource=${resource} authoritative_rc=${authoritative_rc} proxy=${response}"
|
||||
;;
|
||||
OK\|MISMATCH\|*)
|
||||
ocf_log err \
|
||||
"managed-containerd shadow monitor MISMATCH: resource=${resource} authoritative_rc=${authoritative_rc} proxy=${response}"
|
||||
;;
|
||||
ERROR\|*)
|
||||
ocf_log warn \
|
||||
"managed-containerd shadow monitor failed: resource=${resource} authoritative_rc=${authoritative_rc} proxy=${response}"
|
||||
;;
|
||||
*)
|
||||
ocf_log warn \
|
||||
"managed-containerd shadow monitor transport failure: resource=${resource} authoritative_rc=${authoritative_rc}"
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
MC_PROXY_CLIENT_DEFAULT="${MC_PROXY_MODULE_DIR}/proxy_client.py"
|
||||
|
||||
|
||||
# Query managed-containerd for the authoritative monitor result.
|
||||
@@ -133,116 +47,12 @@ mc_proxy_monitor()
|
||||
|
||||
response="$(
|
||||
timeout --kill-after=1s 3s \
|
||||
python3 - \
|
||||
python3 \
|
||||
"${MC_PROXY_CLIENT:-${MC_PROXY_CLIENT_DEFAULT}}" \
|
||||
"${socket_path}" \
|
||||
"${resource}" \
|
||||
"${MC_PROXY_RC_TRANSPORT}" \
|
||||
"${MC_PROXY_RC_HARD_FAILURE}" <<'PY'
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
|
||||
path, resource = sys.argv[1], sys.argv[2]
|
||||
TRANSPORT_FAILURE = int(sys.argv[3])
|
||||
PROTOCOL_FAILURE = int(sys.argv[4])
|
||||
|
||||
PROTOCOL_VERSION = 1
|
||||
|
||||
request = {
|
||||
"protocol_version": PROTOCOL_VERSION,
|
||||
"action": "monitor",
|
||||
"resource": resource,
|
||||
}
|
||||
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.settimeout(2.0)
|
||||
|
||||
try:
|
||||
s.connect(path)
|
||||
s.sendall(json.dumps(request).encode("utf-8") + b"\n")
|
||||
|
||||
raw = b""
|
||||
|
||||
while b"\n" not in raw:
|
||||
chunk = s.recv(65536)
|
||||
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
raw += chunk
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
except (socket.timeout, ConnectionError, OSError):
|
||||
# Local daemon/control-plane reachability failure.
|
||||
# Recent authoritative state may be used for bounded grace.
|
||||
raise SystemExit(TRANSPORT_FAILURE)
|
||||
|
||||
# Transport succeeded. Invalid daemon output is therefore a
|
||||
# protocol/result failure and must never be hidden by cached health.
|
||||
if not raw:
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
try:
|
||||
obj = json.loads(raw.decode("utf-8").strip())
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
# The daemon is reachable at this point, so any protocol mismatch is a hard
|
||||
# control-plane failure. It must never be hidden by cached application health.
|
||||
if obj.get("protocol_version") != PROTOCOL_VERSION:
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
if not obj.get("ok"):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
if obj.get("resource") != resource:
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
if obj.get("action") != "monitor":
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
result = obj.get("result")
|
||||
if not isinstance(result, dict):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
ha = result.get("ha")
|
||||
if not isinstance(ha, dict):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
rc = ha.get("ocf_rc")
|
||||
|
||||
try:
|
||||
rc = int(rc)
|
||||
except (TypeError, ValueError):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
if rc not in (0, 1, 7, 8):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
receiver = result.get("wal_receiver") or {}
|
||||
|
||||
if not isinstance(receiver, dict):
|
||||
raise SystemExit(PROTOCOL_FAILURE)
|
||||
|
||||
payload = "|".join([
|
||||
str(rc),
|
||||
str(ha.get("health", "unknown")),
|
||||
str(ha.get("ha_role", "unknown")),
|
||||
str(ha.get("state", "unknown")),
|
||||
str(ha.get("promotion_eligible", "unknown")).lower(),
|
||||
str(result.get("role", "unknown")),
|
||||
str(result.get("streaming", "unknown")).lower(),
|
||||
str(receiver.get("slot_name") or ""),
|
||||
str(receiver.get("sender_host") or ""),
|
||||
])
|
||||
|
||||
print(payload)
|
||||
PY
|
||||
"${MC_PROXY_RC_HARD_FAILURE}"
|
||||
)"
|
||||
rc=$?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user