Files
managed-container/ocf/loxthelion/managed-container.d/common.sh
T

264 lines
10 KiB
Bash
Executable File

# Shared OCF scoring wrappers; application policy is supplied by the plugin.
# Promotion scores are scoped by Pacemaker to OCF_RESOURCE_INSTANCE and the
# executing node. Do not construct a legacy attribute name or cache locally:
# querying attrd on every observation also repairs a lost transient attribute.
mc_promotion_score_set()
{
local score="$1" current
local resource="${MC_PROMOTION_RESOURCE_INSTANCE:-}"
if [ -z "${resource}" ]; then
ocf_log warn "Cannot publish promotion score without OCF_RESOURCE_INSTANCE"
return 1
fi
case "${score}" in
1000|100|50|-INFINITY) ;;
*) return 1 ;;
esac
current="$(
timeout --kill-after=1s 2s crm_attribute \
--promotion="${resource}" --lifetime=reboot \
--query --quiet 2>/dev/null
)" || current=""
[ "${current}" = "${score}" ] && return 0
if ! timeout --kill-after=1s 2s crm_attribute \
--promotion="${resource}" --lifetime=reboot \
--update="${score}" >/dev/null 2>&1
then
ocf_log err "Unable to publish promotion score ${score} for ${resource}; prior score may remain"
return 1
fi
return 0
}
mc_promotion_score_from_monitor()
{
local monitor_rc="$1" score=-INFINITY
case "${monitor_rc}" in
"${OCF_RUNNING_PROMOTED}") score=1000 ;;
"${OCF_SUCCESS}")
score="$(mc_application_standby_score)" || score=-INFINITY
;;
esac
case "${score}" in
1000|100|50|-INFINITY) ;;
*) score=-INFINITY ;;
esac
mc_promotion_score_set "${score}"
}
# managed-containerd is authoritative for recurring monitor/status results.
# The packaged application monitor is used only when managed-containerd
# explicitly delegates monitoring to the container backend. Promotion scoring
# remains supplied by the existing application scoring hook.
mc_promotion_monitor()
{
local monitor_rc proxy_result proxy_rc cached_rc
proxy_result="$(mc_proxy_monitor)"
proxy_rc=$?
if [ "${proxy_rc}" -ne 0 ]; then
case "${proxy_rc}" in
"${MC_PROXY_RC_TRANSPORT}")
# Transport loss is a control-plane failure, not proof that
# the managed application itself has failed.
cached_rc="$(mc_proxy_cached_monitor 2>/dev/null)" || cached_rc=""
if [ -n "${cached_rc}" ]; then
monitor_rc="${cached_rc}"
case "${monitor_rc}" in
"${OCF_SUCCESS}")
# A standby with an unavailable control plane must
# never remain promotion-eligible during grace.
mc_promotion_score_set -INFINITY || true
;;
"${OCF_RUNNING_PROMOTED}")
# Preserve the established primary during a brief
# bounded transport outage.
;;
esac
ocf_log warn \
"managed-containerd unavailable; using recent cached authoritative rc=${monitor_rc} for bounded control-plane grace"
return "${monitor_rc}"
fi
mc_promotion_score_set -INFINITY || true
ocf_exit_reason \
"managed-containerd authoritative monitor transport failure and no valid control-plane grace observation exists"
return "${OCF_ERR_GENERIC}"
;;
*)
# Protocol/result/configuration/client failures are hard.
# Never conceal them behind cached authoritative health.
mc_proxy_monitor_cache_clear || true
mc_promotion_score_set -INFINITY || true
ocf_exit_reason \
"managed-containerd authoritative monitor hard failure; cached grace is not permitted"
return "${OCF_ERR_GENERIC}"
;;
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 <<EOF
${proxy_result}
EOF
if [ "${delegate_tag}" != delegate ] ||
[ "${delegate_backend}" != container ] ||
[ -z "${delegate_runtime}" ] ||
[ -n "${delegate_extra}" ]; then
mc_proxy_monitor_cache_clear || true
mc_promotion_score_set -INFINITY || true
ocf_exit_reason "managed-containerd returned invalid monitor delegation: ${proxy_result}"
return "${OCF_ERR_GENERIC}"
fi
configured_runtime="${OCF_RESKEY_container_runtime:-podman}"
if [ "${delegate_runtime}" != "${configured_runtime}" ]; then
mc_proxy_monitor_cache_clear || true
mc_promotion_score_set -INFINITY || true
ocf_exit_reason "managed-containerd delegated runtime ${delegate_runtime} does not match loaded RA runtime ${configured_runtime}"
return "${OCF_ERR_GENERIC}"
fi
ocf_log info "managed-containerd delegated authoritative monitor: resource=${OCF_RESOURCE_INSTANCE:-unknown} backend=${delegate_backend} runtime=${delegate_runtime}"
mc_application_monitor
monitor_rc=$?
case "${monitor_rc}" in
"${OCF_SUCCESS}"| "${OCF_ERR_GENERIC}"| "${OCF_NOT_RUNNING}"| "${OCF_RUNNING_PROMOTED}")
;;
*)
mc_proxy_monitor_cache_clear || true
mc_promotion_score_set -INFINITY || true
ocf_exit_reason "delegated application monitor returned invalid rc ${monitor_rc}"
return "${OCF_ERR_GENERIC}"
;;
esac
# The delegated application result is now the authoritative
# observation. Cache only healthy 0/8 results using the same
# control-plane grace mechanism as daemon-native monitoring.
mc_proxy_monitor_cache_write "${monitor_rc}" || {
ocf_log warn "Unable to update delegated authoritative monitor cache"
}
ocf_log info "managed-containerd delegated monitor result: resource=${OCF_RESOURCE_INSTANCE:-unknown} rc=${monitor_rc} backend=${delegate_backend} runtime=${delegate_runtime}"
mc_promotion_score_from_monitor "${monitor_rc}" || true
return "${monitor_rc}"
;;
esac
monitor_rc="${proxy_result%%|*}"
case "${monitor_rc}" in
"${OCF_SUCCESS}"|\
"${OCF_ERR_GENERIC}"|\
"${OCF_NOT_RUNNING}"|\
"${OCF_RUNNING_PROMOTED}")
;;
*)
mc_promotion_score_set -INFINITY || true
ocf_exit_reason "managed-containerd returned invalid monitor rc ${monitor_rc}"
return "${OCF_ERR_GENERIC}"
;;
esac
ocf_log info "managed-containerd authoritative monitor: resource=${OCF_RESOURCE_INSTANCE:-unknown} proxy=${proxy_result}"
# The daemon-native monitor is authoritative. The packaged application
# monitor is invoked only when managed-containerd explicitly delegates to
# the container backend.
mc_promotion_score_from_monitor "${monitor_rc}" || true
return "${monitor_rc}"
}
# Lifecycle functions retain their original checks, commands and return codes.
# Withdraw the previous score before any transition (including validation
# failure), then reassess only after success. Their internal monitor calls do
# not publish scores, so they cannot restore a positive score mid-transition.
mc_promotion_action()
{
local action="$1" action_rc monitor_rc
case "${action}" in
start|stop|promote|demote|rejoin) ;;
*) return "${OCF_ERR_UNIMPLEMENTED}" ;;
esac
# Any lifecycle mutation invalidates the previous daemon-observed role.
# A fresh daemon monitor must establish the next cache entry.
mc_proxy_monitor_cache_clear
mc_promotion_score_set -INFINITY || true
if [ "${action}" != stop ]; then
mc_promotion_validate
action_rc=$?
[ "${action_rc}" -eq "${OCF_SUCCESS}" ] || return "${action_rc}"
fi
mc_application_action "${action}"
action_rc=$?
if [ "${action_rc}" -eq "${OCF_SUCCESS}" ] && [ "${action}" != stop ]; then
mc_application_monitor
monitor_rc=$?
mc_promotion_score_from_monitor "${monitor_rc}" || true
fi
return "${action_rc}"
}
mc_promotion_validate()
{
local utility
if ! [[ "${OCF_RESKEY_proxy_failure_grace_seconds:-15}" =~ ^(0|[1-9][0-9]{0,2})$ ]]; then
ocf_exit_reason "proxy_failure_grace_seconds must be an integer from 0 to 999 seconds"
return "${OCF_ERR_CONFIGURED}"
fi
if [ "${OCF_RESKEY_proxy_failure_grace_seconds:-15}" -gt 300 ]; then
ocf_exit_reason "proxy_failure_grace_seconds must be between 0 and 300 seconds"
return "${OCF_ERR_CONFIGURED}"
fi
if ! [[ "${OCF_RESKEY_disconnected_max_age:-300}" =~ ^(0|[1-9][0-9]{0,4})$ ]]; then
ocf_exit_reason "disconnected_max_age must be an integer from 0 to 99999 seconds"
return "${OCF_ERR_CONFIGURED}"
fi
for utility in crm_attribute timeout python3; do
if ! command -v "${utility}" >/dev/null 2>&1; then
ocf_exit_reason "Required promotion scoring command '${utility}' is not installed"
return "${OCF_ERR_INSTALLED}"
fi
done
mc_application_validate
}