# 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 legacy application monitor remains temporarily available as a shadow # comparison only. Promotion scoring is still supplied by the existing # application scoring hook during this migration stage. mc_promotion_monitor() { local monitor_rc legacy_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" mc_application_monitor legacy_rc=$? mc_proxy_legacy_shadow_monitor \ "${monitor_rc}" \ "${legacy_rc}" || true 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 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}" # Legacy PostgreSQL monitoring remains observational only. mc_application_monitor legacy_rc=$? mc_proxy_legacy_shadow_monitor "${monitor_rc}" "${legacy_rc}" || true 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 }