# Generic managed-containerd client helpers. MC_PROXY_SOCKET_DEFAULT="/run/user/1000/managed-containerd/managed-containerd.sock" MC_PROXY_MODULE_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd )" MC_PROXY_CLIENT_DEFAULT="${MC_PROXY_MODULE_DIR}/proxy_client.py" # Query managed-containerd for the authoritative monitor result. # # stdout: # | # # return: # 0 valid classified daemon result # MC_PROXY_RC_TRANSPORT daemon transport/control-plane failure; bounded cache grace allowed # MC_PROXY_RC_HARD_FAILURE configuration/protocol/result failure; fail closed, no cache grace mc_proxy_monitor() { local socket_path="${MC_PROXY_SOCKET:-${MC_PROXY_SOCKET_DEFAULT}}" local resource="${OCF_RESOURCE_INSTANCE:-}" local response rc resource="${resource%%:*}" if [ -z "${resource}" ]; then ocf_log err \ "managed-containerd monitor requires OCF_RESOURCE_INSTANCE" return "${MC_PROXY_RC_HARD_FAILURE}" fi if [ ! -S "${socket_path}" ]; then ocf_log warn \ "managed-containerd socket is unavailable: ${socket_path}" return "${MC_PROXY_RC_TRANSPORT}" fi if ! command -v python3 >/dev/null 2>&1; then ocf_log err \ "python3 is required for managed-containerd monitor transport" return "${MC_PROXY_RC_HARD_FAILURE}" fi response="$( timeout --kill-after=1s 3s \ python3 \ "${MC_PROXY_CLIENT:-${MC_PROXY_CLIENT_DEFAULT}}" \ "${socket_path}" \ "${resource}" \ "monitor" \ "${MC_PROXY_RC_TRANSPORT}" \ "${MC_PROXY_RC_HARD_FAILURE}" )" rc=$? case "${rc}" in 0) ;; "${MC_PROXY_RC_TRANSPORT}"|124|137) # MC_PROXY_RC_TRANSPORT = classified Python transport failure. # 124/137 = timeout(1) expiry / forced termination. ocf_log warn \ "managed-containerd authoritative monitor transport failure" return "${MC_PROXY_RC_TRANSPORT}" ;; "${MC_PROXY_RC_HARD_FAILURE}") ocf_log err \ "managed-containerd authoritative monitor protocol or result failure" return "${MC_PROXY_RC_HARD_FAILURE}" ;; *) ocf_log err \ "managed-containerd authoritative monitor client failure rc=${rc}" return "${MC_PROXY_RC_HARD_FAILURE}" ;; esac if [ -z "${response}" ]; then ocf_log err \ "managed-containerd authoritative monitor returned an empty result" return "${MC_PROXY_RC_HARD_FAILURE}" fi # Delegated monitor execution is a valid control-plane response, but it is # not itself an OCF observation and must never be written to the healthy # role cache. The caller validates and executes the packaged runtime path. case "${response}" in delegate\|*) printf '%s\n' "${response}" return 0 ;; esac rc="${response%%|*}" # Cache only healthy daemon-authoritative role observations. An # application-level failure automatically invalidates any previous cache. mc_proxy_monitor_cache_write "${rc}" || { ocf_log warn \ "Unable to update managed-containerd authoritative monitor cache" } printf '%s\n' "${response}" return 0 } # Compare the legacy application monitor against a daemon-authoritative rc. # The legacy result is diagnostic only and can never change the returned # Pacemaker monitor status. mc_proxy_legacy_shadow_monitor() { local authoritative_rc="$1" local legacy_rc="$2" local resource="${OCF_RESOURCE_INSTANCE:-}" resource="${resource%%:*}" if [ "${legacy_rc}" -eq "${authoritative_rc}" ]; then ocf_log info \ "legacy application shadow monitor: resource=${resource} authoritative_rc=${authoritative_rc} legacy_rc=${legacy_rc} comparison=MATCH" else ocf_log err \ "legacy application shadow monitor MISMATCH: resource=${resource} authoritative_rc=${authoritative_rc} legacy_rc=${legacy_rc}" fi return 0 } # Generic short-lived cache of the last healthy daemon-authoritative OCF role. # This is control-plane state only; it contains no application-specific data. # # Use HA_RSCTMP when available because it is the standard resource-agent # runtime location and is preferable to inventing a persistent filesystem path. MC_PROXY_STATE_DIR_DEFAULT="${HA_RSCTMP:-/run/resource-agents}/managed-container" mc_proxy_state_file() { local resource safe_resource resource="${OCF_RESOURCE_INSTANCE:-}" resource="${resource%%:*}" [ -n "${resource}" ] || return 1 safe_resource="$( printf '%s' "${resource}" | tr -c 'A-Za-z0-9_.-' '_' )" printf '%s/%s.monitor\n' \ "${MC_PROXY_STATE_DIR:-${MC_PROXY_STATE_DIR_DEFAULT}}" \ "${safe_resource}" } mc_proxy_monitor_cache_clear() { local state_file state_file="$(mc_proxy_state_file)" || return 0 rm -f "${state_file}" 2>/dev/null || true } mc_proxy_monitor_cache_write() { local rc="$1" local state_file state_dir tmp now # Only healthy authoritative role observations are eligible for # temporary control-plane grace. case "${rc}" in "${OCF_SUCCESS}"|"${OCF_RUNNING_PROMOTED}") ;; *) mc_proxy_monitor_cache_clear return 0 ;; esac state_file="$(mc_proxy_state_file)" || return 1 state_dir="${state_file%/*}" mkdir -p -m 700 "${state_dir}" || return 1 now="$(date +%s)" tmp="${state_file}.$$" umask 077 printf '%s|%s|0 ' "${now}" "${rc}" > "${tmp}" || { rm -f "${tmp}" 2>/dev/null || true return 1 } chmod 600 "${tmp}" 2>/dev/null || true mv -f "${tmp}" "${state_file}" } # Print a cached healthy OCF rc when its observation is still inside the # configured daemon control-plane failure grace. mc_proxy_cached_monitor() { local state_file observed rc failure_started local now failure_age observation_age grace max_observation_age local tmp grace="${OCF_RESKEY_proxy_failure_grace_seconds:-15}" case "${grace}" in ""|*[!0-9]*) return 1 ;; esac [ "${grace}" -gt 0 ] || return 1 state_file="$(mc_proxy_state_file)" || return 1 [ -f "${state_file}" ] || return 1 IFS='|' read -r observed rc failure_started < "${state_file}" || { mc_proxy_monitor_cache_clear return 1 } case "${observed}" in ""|*[!0-9]*) mc_proxy_monitor_cache_clear return 1 ;; esac case "${failure_started}" in ""|*[!0-9]*) mc_proxy_monitor_cache_clear return 1 ;; esac case "${rc}" in "${OCF_SUCCESS}"|"${OCF_RUNNING_PROMOTED}") ;; *) mc_proxy_monitor_cache_clear return 1 ;; esac now="$(date +%s)" observation_age=$((now - observed)) # A cached role must come from a reasonably recent successful daemon # observation. This protects against resurrecting stale runtime state. max_observation_age=$((grace + 60)) if [ "${observation_age}" -lt 0 ] || [ "${observation_age}" -gt "${max_observation_age}" ]; then mc_proxy_monitor_cache_clear return 1 fi # First failed daemon observation starts the bounded grace period. if [ "${failure_started}" -eq 0 ]; then failure_started="${now}" tmp="${state_file}.$$" umask 077 printf '%s|%s|%s ' "${observed}" "${rc}" "${failure_started}" > "${tmp}" || { rm -f "${tmp}" 2>/dev/null || true return 1 } chmod 600 "${tmp}" 2>/dev/null || true mv -f "${tmp}" "${state_file}" || return 1 fi failure_age=$((now - failure_started)) if [ "${failure_age}" -lt 0 ] || [ "${failure_age}" -ge "${grace}" ]; then mc_proxy_monitor_cache_clear return 1 fi printf '%s ' "${rc}" return 0 }