Add read-only PostgreSQL demotion preparation

This commit is contained in:
Lyncoln
2026-09-17 17:33:28 -04:00
parent 942457e48f
commit 89aaec5f96
3 changed files with 202 additions and 1 deletions
+111
View File
@@ -529,3 +529,114 @@ def notify_target(config, runtime_state, request_parameters):
"node": node_name,
"age_seconds": age,
}
def demote_prepare(config, runtime_state, request_parameters):
"""
Read-only demotion preparation.
Validates the latest Pacemaker demotion notification against this local
daemon and reports the successor. PostgreSQL mutation remains outside this
action until candidate and WAL checks are migrated.
"""
del request_parameters
node_name = str(
config.get("PACEMAKER_NODE_NAME", "")
).strip().lower()
if not node_name:
raise ValueError(
"PACEMAKER_NODE_NAME is required for demote.prepare"
)
notification = _postgres_runtime_state(
runtime_state
).get("notification")
if not isinstance(notification, dict):
return {
"ocf_rc": 1,
"ready": False,
"reason": "notification_missing",
"node": node_name,
}
if notification.get("type") != "pre":
return {
"ocf_rc": 1,
"ready": False,
"reason": "notification_type_invalid",
"node": node_name,
}
if notification.get("operation") != "demote":
return {
"ocf_rc": 1,
"ready": False,
"reason": "notification_operation_invalid",
"node": node_name,
}
received_at = notification.get("received_at")
if not isinstance(received_at, (int, float)):
return {
"ocf_rc": 1,
"ready": False,
"reason": "notification_timestamp_invalid",
"node": node_name,
}
age = time.time() - received_at
if age < 0 or age > NOTIFY_TARGET_MAX_AGE_SECONDS:
return {
"ocf_rc": 1,
"ready": False,
"reason": "notification_stale",
"age_seconds": age,
"node": node_name,
}
target_node = str(
notification.get("demote_uname", "")
).strip().lower()
successor_node = str(
notification.get("promote_uname", "")
).strip().lower()
if target_node != node_name:
return {
"ocf_rc": 1,
"ready": False,
"reason": "local_node_not_target",
"node": node_name,
"target_node": target_node,
"successor_node": successor_node,
}
if not successor_node:
return {
"ocf_rc": 1,
"ready": False,
"reason": "successor_missing",
"node": node_name,
}
return {
"ocf_rc": 0,
"ready": True,
"mutation": False,
"node": node_name,
"target_node": target_node,
"successor_node": successor_node,
"age_seconds": age,
"checks": {
"notification": True,
"local_target": True,
"successor_selected": True,
"postgresql_mutated": False,
},
}