# Sourced by managed-container; uses the shared OCF and parameter environment. node_to_addr() { local target="${1:-}" [ -n "${target}" ] || return 1 corosync-cmapctl 2>/dev/null | awk -v target="${target}" ' /nodelist\.node\.[0-9]+\.name / { key=$1 sub(/\.name$/, "", key) names[key]=$NF } /nodelist\.node\.[0-9]+\.ring0_addr / { key=$1 sub(/\.ring0_addr$/, "", key) addrs[key]=$NF } END { for (key in names) { if (names[key] == target && addrs[key] != "") { print addrs[key] exit 0 } } exit 1 } ' } managed_demote() { local monitor_rc local target_node target_addr local local_node slot_name target_port local role receiver_status i managed_monitor monitor_rc=$? case "${monitor_rc}" in "${OCF_RUNNING_PROMOTED}") ;; "${OCF_SUCCESS}") ocf_log info \ "PostgreSQL is already unpromoted" return "${OCF_SUCCESS}" ;; "${OCF_NOT_RUNNING}") ocf_exit_reason \ "Cannot complete demotion because PostgreSQL is stopped" return "${OCF_ERR_GENERIC}" ;; *) ocf_exit_reason \ "Cannot demote unhealthy PostgreSQL instance (monitor rc=${monitor_rc})" return "${monitor_rc}" ;; esac target_node="$(demote_target)" || { ocf_exit_reason \ "No fresh valid Pacemaker pre-demote target is available" return "${OCF_ERR_GENERIC}" } target_addr="$(node_to_addr "${target_node}")" || { ocf_exit_reason \ "Unable to resolve Pacemaker promotion target ${target_node} through Corosync" return "${OCF_ERR_GENERIC}" } target_port="${REJOIN_SOURCE_PORT:-5432}" local_node="$(hostname)" slot_name="ha_$(printf '%s' "${local_node}" | tr '[:upper:]-' '[:lower:]_')" ocf_log info \ "Preparing controlled demotion of ${local_node}; successor=${target_node} (${target_addr})" if ! candidate_is_standby "${target_addr}"; then ocf_exit_reason \ "Promotion target ${target_node} is not a reachable PostgreSQL standby" return "${OCF_ERR_GENERIC}" fi if ! candidate_caught_up "${target_node}"; then ocf_exit_reason \ "Promotion target ${target_node} is not streaming and fully replayed to the current flushed WAL position" return "${OCF_ERR_GENERIC}" fi if ! ensure_remote_slot "${target_addr}" "${slot_name}"; then ocf_exit_reason \ "Unable to ensure physical replication slot ${slot_name} on ${target_node}" return "${OCF_ERR_GENERIC}" fi ocf_log info \ "Demotion safety checks passed; stopping PostgreSQL primary" if ! pg_command ' BINDIR="$(pg_config --bindir)" || exit 1 exec "$BINDIR/pg_ctl" \ -D "$PGDATA" \ stop -m fast -w ' then ocf_exit_reason \ "pg_ctl stop failed during controlled demotion" return "${OCF_ERR_GENERIC}" fi if pg_running; then ocf_exit_reason \ "PostgreSQL remained running after pg_ctl stop" return "${OCF_ERR_GENERIC}" fi if ! service_running || ! container_running; then ocf_exit_reason \ "Maintenance service/container stopped unexpectedly during demotion" return "${OCF_ERR_GENERIC}" fi ocf_log info \ "Configuring ${local_node} as cascading standby of ${target_node}" if ! mc_runtime_exec \ --user "${PG_OS_USER}" \ --env "RA_PRIMARY_HOST=${target_addr}" \ --env "RA_PRIMARY_PORT=${target_port}" \ --env "RA_REPLICATION_USER=${REPLICATION_USER}" \ --env "RA_REPLICATION_SLOT=${slot_name}" \ --env "RA_APPLICATION_NAME=${local_node}" \ "${CONTAINER_NAME}" \ sh -c ' set -e sed -i \ -e "/^[[:space:]]*primary_conninfo[[:space:]]*=/d" \ -e "/^[[:space:]]*primary_slot_name[[:space:]]*=/d" \ "$PGDATA/postgresql.auto.conf" printf "%s " \ "primary_conninfo = '\''host=${RA_PRIMARY_HOST} port=${RA_PRIMARY_PORT} user=${RA_REPLICATION_USER} application_name=${RA_APPLICATION_NAME}'\''" \ "primary_slot_name = '\''${RA_REPLICATION_SLOT}'\''" \ >> "$PGDATA/postgresql.auto.conf" touch "$PGDATA/standby.signal" chmod 600 \ "$PGDATA/postgresql.auto.conf" \ "$PGDATA/standby.signal" ' then ocf_exit_reason \ "Failed to configure PostgreSQL as standby of ${target_node}" return "${OCF_ERR_GENERIC}" fi ocf_log info \ "Starting PostgreSQL as cascading standby of ${target_node}" if ! pg_command ' BINDIR="$(pg_config --bindir)" || exit 1 exec "$BINDIR/pg_ctl" \ -D "$PGDATA" \ -l "$PGDATA/postgresql.log" \ start -w ' then ocf_exit_reason \ "PostgreSQL failed to start as cascading standby" return "${OCF_ERR_GENERIC}" fi for i in $(seq 1 30); do role="$(pg_role 2>/dev/null || true)" receiver_status="$(pg_wal_receiver_status 2>/dev/null || true)" if [ "${role}" = "t" ] && [ "${receiver_status}" = "streaming" ] then ocf_log info \ "Controlled demotion complete: ${local_node} is streaming from ${target_node}" return "${OCF_SUCCESS}" fi sleep 1 done ocf_exit_reason \ "PostgreSQL started after demotion but did not become a streaming standby" return "${OCF_ERR_GENERIC}" }