#!/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) class Cursor: def __enter__(self): return self def __exit__(self, *args): return False def execute(self, query): pass def fetchone(self): return { "in_recovery": True, "replay_paused": False, "receive_lsn": "14/1C07A910", "replay_lsn": "14/1C07A910", "receiver_status": "streaming", "slot_name": "ha_pg_david_eve", } class Connection: autocommit = False def cursor(self, cursor_factory=None): return Cursor() def close(self): pass plugin.monitor = lambda config, runtime_state: { "ha": {"ocf_rc": plugin.OCF_RUNNING_PROMOTED}, "primary": {"current_wal_lsn": "14/1C07A920"}, } plugin.read_secret = lambda path: "test-password" plugin.psycopg2.connect = lambda **kwargs: Connection() runtime_state = { "postgresql": { "notification": { "type": "pre", "operation": "demote", "demote_uname": "david-prodesk", "promote_uname": "david-eve", "received_at": time.time(), }, }, } config = { "PACEMAKER_NODE_NAME": "david-prodesk", "PG_REPLICATION_PEERS": ( "david-prodesk=192.168.1.4:5432," "david-eve=192.168.1.3:5432" ), "PGPASSWORD_FILE": "/run/secrets/postgres_password", "PGUSER": "david", "PGDATABASE": "david", } result = plugin.demote_database(config, runtime_state, {}) assert result["ocf_rc"] == 0 assert result["ready"] is True assert result["mutation"] is False assert result["final_handoff_required"] is True assert result["successor_node"] == "david-eve" assert result["successor_slot_name"] == "ha_pg_david_eve" assert result["checks"]["successor_streaming"] is True assert result["checks"]["successor_replay_not_paused"] is True assert result["wal_lag_bytes"] == 16 print("valid_database_probe accepted") print("wal_lag_reported accepted") print() print("DEMOTE_DATABASE_TEST_PASS=1") plugin.monitor = lambda config, runtime_state: { "ha": {"ocf_rc": plugin.OCF_SUCCESS}, "primary": None, } result = plugin.demote_database(config, runtime_state, {}) assert result["ocf_rc"] == 1 assert result["reason"] == "local_not_primary" print("local_not_primary rejected") plugin.monitor = lambda config, runtime_state: { "ha": {"ocf_rc": plugin.OCF_RUNNING_PROMOTED}, "primary": {"current_wal_lsn": "14/1C07A920"}, } def unavailable(**kwargs): raise plugin.psycopg2.OperationalError( "connection refused" ) plugin.psycopg2.connect = unavailable result = plugin.demote_database(config, runtime_state, {}) assert result["ocf_rc"] == 1 assert result["reason"] == "successor_unreachable" print("successor_unreachable rejected") print("DEMOTE_DATABASE_FAILURE_TEST_PASS=1")