Extract PostgreSQL, Podman runtime and systemd lifecycle adapters while preserving existing lifecycle and scoring behavior. Update RPM layout and add guarded operator-run lab harnesses. Record successful manual stop/start, promotion, demotion and rewind-only rejoin with pinned installed hashes. The unfenced lab does not validate automatic failover, Pacemaker-managed transitions or divergent-data rewind. Validation: 103 offline tests, original-function differential checks, checksum manifest and whitespace checks passed.
202 lines
5.8 KiB
Bash
202 lines
5.8 KiB
Bash
# 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}"
|
|
}
|