Handle PostgreSQL self-restart demotion preflight
This commit is contained in:
@@ -802,6 +802,47 @@ def demote_prepare(config, runtime_state, request_parameters):
|
|||||||
"successor_node": successor_node,
|
"successor_node": successor_node,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
promoted_nodes = {
|
||||||
|
str(name).strip().lower()
|
||||||
|
for name in str(
|
||||||
|
notification.get("promoted_uname", "")
|
||||||
|
).split()
|
||||||
|
if str(name).strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
if successor_node == node_name:
|
||||||
|
if node_name not in promoted_nodes:
|
||||||
|
return {
|
||||||
|
"ocf_rc": 1,
|
||||||
|
"ready": False,
|
||||||
|
"reason": "self_restart_not_current_primary",
|
||||||
|
"node": node_name,
|
||||||
|
"target_node": target_node,
|
||||||
|
"successor_node": successor_node,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ocf_rc": 0,
|
||||||
|
"ready": False,
|
||||||
|
"identity_validated": True,
|
||||||
|
"reason": "local_restart_checks_pending",
|
||||||
|
"mutation": False,
|
||||||
|
"node": node_name,
|
||||||
|
"target_node": target_node,
|
||||||
|
"successor_node": successor_node,
|
||||||
|
"handoff_mode": "local_restart",
|
||||||
|
"final_handoff_required": False,
|
||||||
|
"age_seconds": age,
|
||||||
|
"checks": {
|
||||||
|
"notification": True,
|
||||||
|
"local_target": True,
|
||||||
|
"self_successor": True,
|
||||||
|
"local_currently_promoted": True,
|
||||||
|
"postgresql_mutated": False,
|
||||||
|
"database_checks": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if not successor_node:
|
if not successor_node:
|
||||||
return {
|
return {
|
||||||
"ocf_rc": 1,
|
"ocf_rc": 1,
|
||||||
@@ -885,6 +926,7 @@ def demote_database(config, runtime_state, request_parameters):
|
|||||||
|
|
||||||
if preparation.get("reason") not in (
|
if preparation.get("reason") not in (
|
||||||
"database_checks_pending",
|
"database_checks_pending",
|
||||||
|
"local_restart_checks_pending",
|
||||||
):
|
):
|
||||||
return preparation
|
return preparation
|
||||||
|
|
||||||
@@ -898,6 +940,26 @@ def demote_database(config, runtime_state, request_parameters):
|
|||||||
"local": local["ha"],
|
"local": local["ha"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preparation.get("reason") == "local_restart_checks_pending":
|
||||||
|
return {
|
||||||
|
"ocf_rc": 0,
|
||||||
|
"ready": True,
|
||||||
|
"mutation": False,
|
||||||
|
"final_handoff_required": False,
|
||||||
|
"handoff_mode": "local_restart",
|
||||||
|
"reason": "local_restart_demotion_ready",
|
||||||
|
"node": preparation["node"],
|
||||||
|
"target_node": preparation["target_node"],
|
||||||
|
"successor_node": preparation["successor_node"],
|
||||||
|
"checks": {
|
||||||
|
"notification": True,
|
||||||
|
"local_target": True,
|
||||||
|
"local_primary": True,
|
||||||
|
"self_successor": True,
|
||||||
|
"remote_handoff_required": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
successor_node = preparation["successor_node"]
|
successor_node = preparation["successor_node"]
|
||||||
peers = parse_replication_peers(
|
peers = parse_replication_peers(
|
||||||
config.get("PG_REPLICATION_PEERS", "")
|
config.get("PG_REPLICATION_PEERS", "")
|
||||||
|
|||||||
@@ -137,3 +137,56 @@ assert result["reason"] == "successor_unreachable"
|
|||||||
print("successor_unreachable rejected")
|
print("successor_unreachable rejected")
|
||||||
|
|
||||||
print("DEMOTE_DATABASE_FAILURE_TEST_PASS=1")
|
print("DEMOTE_DATABASE_FAILURE_TEST_PASS=1")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("===== SELF-RESTART DATABASE GATE =====")
|
||||||
|
|
||||||
|
plugin.monitor = lambda config, runtime_state: {
|
||||||
|
"ha": {"ocf_rc": plugin.OCF_RUNNING_PROMOTED},
|
||||||
|
"primary": {"current_wal_lsn": "14/1C07A920"},
|
||||||
|
}
|
||||||
|
|
||||||
|
self_restart_state = {
|
||||||
|
"postgresql": {
|
||||||
|
"notification": {
|
||||||
|
"type": "pre",
|
||||||
|
"operation": "demote",
|
||||||
|
"demote_uname": "david-prodesk",
|
||||||
|
"promote_uname": "david-prodesk",
|
||||||
|
"promoted_uname": "david-prodesk",
|
||||||
|
"unpromoted_uname": "david-eve",
|
||||||
|
"received_at": time.time(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_called = False
|
||||||
|
|
||||||
|
def must_not_connect(**kwargs):
|
||||||
|
global connect_called
|
||||||
|
connect_called = True
|
||||||
|
raise AssertionError(
|
||||||
|
"self-restart demotion must not probe a remote successor"
|
||||||
|
)
|
||||||
|
|
||||||
|
plugin.psycopg2.connect = must_not_connect
|
||||||
|
|
||||||
|
result = plugin.demote_database(
|
||||||
|
config,
|
||||||
|
self_restart_state,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["ocf_rc"] == 0
|
||||||
|
assert result["ready"] is True
|
||||||
|
assert result["mutation"] is False
|
||||||
|
assert result["final_handoff_required"] is False
|
||||||
|
assert result["handoff_mode"] == "local_restart"
|
||||||
|
assert result["reason"] == "local_restart_demotion_ready"
|
||||||
|
assert result["successor_node"] == "david-prodesk"
|
||||||
|
assert result["checks"]["local_primary"] is True
|
||||||
|
assert result["checks"]["remote_handoff_required"] is False
|
||||||
|
assert connect_called is False
|
||||||
|
|
||||||
|
print("self_restart_database accepted")
|
||||||
|
print("remote_probe_skipped accepted")
|
||||||
|
|||||||
@@ -124,3 +124,63 @@ result = plugin.demote_prepare(
|
|||||||
assert result["ocf_rc"] == 1
|
assert result["ocf_rc"] == 1
|
||||||
assert result["reason"] == "successor_not_configured"
|
assert result["reason"] == "successor_not_configured"
|
||||||
print("unconfigured_successor rejected")
|
print("unconfigured_successor rejected")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("===== SELF-RESTART PREPARE =====")
|
||||||
|
|
||||||
|
self_restart_state = {
|
||||||
|
"postgresql": {
|
||||||
|
"notification": {
|
||||||
|
"type": "pre",
|
||||||
|
"operation": "demote",
|
||||||
|
"demote_uname": "david-eve",
|
||||||
|
"promote_uname": "david-eve",
|
||||||
|
"promoted_uname": "david-eve",
|
||||||
|
"unpromoted_uname": "david-prodesk david-macbookpro",
|
||||||
|
"received_at": time.time(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result = plugin.demote_prepare(
|
||||||
|
{
|
||||||
|
"PACEMAKER_NODE_NAME": "david-eve",
|
||||||
|
"PG_REPLICATION_PEERS": (
|
||||||
|
"david-prodesk=192.168.1.4:5432,"
|
||||||
|
"david-eve=192.168.1.3:5432"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
self_restart_state,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["ocf_rc"] == 0
|
||||||
|
assert result["ready"] is False
|
||||||
|
assert result["identity_validated"] is True
|
||||||
|
assert result["reason"] == "local_restart_checks_pending"
|
||||||
|
assert result["handoff_mode"] == "local_restart"
|
||||||
|
assert result["final_handoff_required"] is False
|
||||||
|
assert result["successor_node"] == "david-eve"
|
||||||
|
assert result["checks"]["self_successor"] is True
|
||||||
|
assert result["checks"]["local_currently_promoted"] is True
|
||||||
|
|
||||||
|
print("self_restart_prepare accepted")
|
||||||
|
|
||||||
|
self_restart_state["postgresql"]["notification"]["promoted_uname"] = ""
|
||||||
|
|
||||||
|
result = plugin.demote_prepare(
|
||||||
|
{
|
||||||
|
"PACEMAKER_NODE_NAME": "david-eve",
|
||||||
|
"PG_REPLICATION_PEERS": (
|
||||||
|
"david-prodesk=192.168.1.4:5432,"
|
||||||
|
"david-eve=192.168.1.3:5432"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
self_restart_state,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["ocf_rc"] == 1
|
||||||
|
assert result["reason"] == "self_restart_not_current_primary"
|
||||||
|
|
||||||
|
print("false_self_restart rejected")
|
||||||
|
|||||||
Reference in New Issue
Block a user