Freeze lab-tested PostgreSQL plugin engine

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.
This commit is contained in:
Lyncoln
2026-09-13 01:57:04 -04:00
parent 488a1672d7
commit ec2077f979
41 changed files with 1925 additions and 331 deletions
+11 -13
View File
@@ -6,11 +6,12 @@ import re
import subprocess
import tempfile
import xml.etree.ElementTree as ET
from plugin_test_support import LOAD, restore_boundaries
ROOT = Path(__file__).resolve().parents[1]
REFERENCE = ROOT / 'reference/managed-container'
AGENT = ROOT / 'refactored/managed-container'
MODULES = sorted((AGENT.parent / 'managed-container.d').glob('*.sh'))
MODULES = sorted((AGENT.parent / 'managed-container.d').rglob('*.sh'))
SOURCE = REFERENCE.read_text()
PATTERN = r'^([a-z_]+)\(\)\n\{\n.*?^\}'
@@ -43,21 +44,16 @@ print('PASS both reference hashes')
current_functions = functions('\n'.join(p.read_text() for p in MODULES))
for name, definition in functions(SOURCE).items():
assert current_functions[name] == definition, name
assert set(current_functions) - set(functions(SOURCE)) == {
'mc_promotion_score_set', 'mc_promotion_score_from_monitor',
'mc_promotion_monitor', 'mc_promotion_action', 'mc_promotion_validate', 'mc_pg_promotion_eligible',
'mc_promotion_metadata', 'mc_pg_standby_observation', 'mc_pg_standby_score',
}
assert restore_boundaries(current_functions[name]) == definition, name
assert len(functions(SOURCE)) == 29
common = (AGENT.parent / 'managed-container.d/common.sh').read_text()
common = (AGENT.parent / 'managed-container.d/applications/postgresql/plugin.sh').read_text()
defaults = SOURCE[SOURCE.index('OCF_RESKEY_service_user_default='):SOURCE.index('\n###############################################################################\n# Metadata')].strip()
assert defaults in common
print('PASS all 29 original function bodies and defaults/globals identical; nine scoring helpers added')
print('PASS all 29 original function bodies identical after exact adapter-call reversal; defaults unchanged')
for path in [AGENT, *MODULES]:
subprocess.run(['bash', '-n', str(path)], check=True)
print('PASS syntax for entrypoint and all nine modules')
print('PASS syntax for entrypoint and all modules')
metadata = []
for path in (REFERENCE, AGENT):
@@ -69,6 +65,10 @@ parameters = extended.find('parameters')
added = parameters.find("parameter[@name='disconnected_max_age']")
assert added is not None and added.find('content').get('default') == '300'
parameters.remove(added)
for name, default in [('application', 'postgresql'), ('container_runtime', 'podman'), ('lifecycle_manager', 'systemd')]:
added = parameters.find("parameter[@name='" + name + "']")
assert added is not None and added.find('content').get('default') == default
parameters.remove(added)
def normalize(element):
return (element.tag, element.attrib, (element.text or '').strip(),
[normalize(child) for child in element])
@@ -80,9 +80,7 @@ print('PASS original metadata preserved plus freshness parameter and XML parsing
# Sourcing production modules in a fresh shell tests their real loading order.
# No dispatcher executes. All operational boundaries are overridden below.
modular_definitions = SOURCE[:SOURCE.index('\n###############################################################################')]
for name in ('common', 'container', 'postgres', 'auth', 'replication', 'notify', 'promote', 'demote', 'rejoin'):
modular_definitions += '\n. ' + "'" + str(AGENT.parent / 'managed-container.d' / (name + '.sh')).replace("'", "'\\''") + "'"
modular_definitions = LOAD
base = '''
ocf_log() { printf 'log %s\\n' "$*"; }
ocf_exit_reason() { printf 'reason %s\\n' "$*" >&2; }