Add read-only PostgreSQL promotion database preflight
This commit is contained in:
Executable
+225
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
fake_psycopg2 = types.ModuleType("psycopg2")
|
||||
fake_psycopg2.Error = Exception
|
||||
fake_psycopg2.OperationalError = OSError
|
||||
|
||||
fake_extras = types.ModuleType("psycopg2.extras")
|
||||
fake_extras.RealDictCursor = object
|
||||
fake_psycopg2.extras = fake_extras
|
||||
|
||||
sys.modules["psycopg2"] = fake_psycopg2
|
||||
sys.modules["psycopg2.extras"] = fake_extras
|
||||
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"postgresql_plugin",
|
||||
Path("plugins/postgresql/plugin.py"),
|
||||
)
|
||||
|
||||
plugin = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(plugin)
|
||||
|
||||
|
||||
def standby_monitor(*, eligible=True):
|
||||
return {
|
||||
"ha": {
|
||||
"ocf_rc": plugin.OCF_SUCCESS,
|
||||
"health": "healthy",
|
||||
"ha_role": "unpromoted",
|
||||
"state": "streaming",
|
||||
"promotion_eligible": eligible,
|
||||
},
|
||||
"role": "standby",
|
||||
"in_recovery": True,
|
||||
"streaming": True,
|
||||
}
|
||||
|
||||
|
||||
def peer_state(*primaries):
|
||||
return {
|
||||
"peers": [],
|
||||
"writable_primaries": list(primaries),
|
||||
"writable_primary_count": len(primaries),
|
||||
}
|
||||
|
||||
|
||||
config = {
|
||||
"PACEMAKER_NODE_NAME": "david-eve",
|
||||
"PGPASSWORD_FILE": "/run/secrets/postgres_password",
|
||||
"PG_REPLICATION_PEERS": (
|
||||
"david-prodesk=192.168.1.4:5432,"
|
||||
"david-macbookpro=192.168.1.5:5432,"
|
||||
"david-eve=192.168.1.3:5432"
|
||||
),
|
||||
"PGUSER": "david",
|
||||
"PGDATABASE": "david",
|
||||
}
|
||||
|
||||
|
||||
def fresh_state():
|
||||
return {
|
||||
"postgresql": {
|
||||
"notification": {
|
||||
"type": "pre",
|
||||
"operation": "promote",
|
||||
"promote_uname": "david-eve",
|
||||
"demote_uname": "david-prodesk",
|
||||
"received_at": time.time(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
plugin.read_secret = lambda path: "test-password"
|
||||
plugin.monitor = lambda config, runtime_state: standby_monitor()
|
||||
plugin.probe_replication_peers = (
|
||||
lambda config, password: peer_state()
|
||||
)
|
||||
|
||||
|
||||
print("===== PROMOTE DATABASE ACTION =====")
|
||||
|
||||
runtime_state = fresh_state()
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
runtime_state,
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == plugin.OCF_SUCCESS
|
||||
assert result["ready"] is True
|
||||
assert result["mutation"] is False
|
||||
assert result["reason"] == "promotion_preflight_ready"
|
||||
assert result["target_node"] == "david-eve"
|
||||
assert result["previous_primary_node"] == "david-prodesk"
|
||||
assert result["writable_primary_count"] == 0
|
||||
assert result["checks"]["local_target"] is True
|
||||
assert result["checks"]["local_standby"] is True
|
||||
assert result["checks"]["promotion_eligible"] is True
|
||||
assert result["checks"]["no_reachable_writable_primary"] is True
|
||||
|
||||
print("valid_promotion_preflight accepted")
|
||||
|
||||
|
||||
runtime_state = fresh_state()
|
||||
runtime_state["postgresql"]["notification"]["promote_uname"] = (
|
||||
"david-macbookpro"
|
||||
)
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
runtime_state,
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == 1
|
||||
assert result["ready"] is False
|
||||
assert result["reason"] == "local_node_not_target"
|
||||
|
||||
print("wrong_target rejected")
|
||||
|
||||
|
||||
runtime_state = fresh_state()
|
||||
runtime_state["postgresql"]["notification"]["received_at"] = (
|
||||
time.time() - 121
|
||||
)
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
runtime_state,
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == 1
|
||||
assert result["reason"] == "notification_stale"
|
||||
|
||||
print("stale_notification rejected")
|
||||
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
{},
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == 1
|
||||
assert result["reason"] == "notification_missing"
|
||||
|
||||
print("missing_notification rejected")
|
||||
|
||||
|
||||
runtime_state = fresh_state()
|
||||
|
||||
plugin.monitor = lambda config, runtime_state: {
|
||||
"ha": {
|
||||
"ocf_rc": plugin.OCF_RUNNING_PROMOTED,
|
||||
"promotion_eligible": True,
|
||||
},
|
||||
"role": "primary",
|
||||
"in_recovery": False,
|
||||
}
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
runtime_state,
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == 1
|
||||
assert result["ready"] is False
|
||||
assert result["reason"] == "local_not_promotion_eligible"
|
||||
assert result["checks"]["local_standby"] is False
|
||||
|
||||
print("local_primary rejected")
|
||||
|
||||
|
||||
plugin.monitor = lambda config, runtime_state: standby_monitor(
|
||||
eligible=False
|
||||
)
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
fresh_state(),
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == 1
|
||||
assert result["ready"] is False
|
||||
assert result["checks"]["promotion_eligible"] is False
|
||||
|
||||
print("ineligible_standby rejected")
|
||||
|
||||
|
||||
plugin.monitor = lambda config, runtime_state: standby_monitor()
|
||||
plugin.probe_replication_peers = (
|
||||
lambda config, password: peer_state("david-prodesk")
|
||||
)
|
||||
|
||||
result = plugin.promote_database(
|
||||
config,
|
||||
fresh_state(),
|
||||
{},
|
||||
)
|
||||
|
||||
assert result["ocf_rc"] == plugin.OCF_ERR_GENERIC
|
||||
assert result["ready"] is False
|
||||
assert result["mutation"] is False
|
||||
assert result["reason"] == "reachable_writable_primary"
|
||||
assert result["writable_primary_count"] == 1
|
||||
assert result["writable_primaries"] == ["david-prodesk"]
|
||||
assert result["checks"]["no_reachable_writable_primary"] is False
|
||||
|
||||
print("existing_primary rejected")
|
||||
|
||||
|
||||
print()
|
||||
print("PROMOTE_DATABASE_TEST_PASS=1")
|
||||
@@ -179,8 +179,8 @@ production_manifest = json.loads(
|
||||
)
|
||||
|
||||
require(
|
||||
production_manifest.get("version") == 3,
|
||||
"PostgreSQL manifest version is not 3",
|
||||
production_manifest.get("version") == 4,
|
||||
"PostgreSQL manifest version is not 4",
|
||||
)
|
||||
require(
|
||||
"PG_REPLICATION_PEERS"
|
||||
|
||||
Reference in New Issue
Block a user