Freeze HA scoring and document reported production failover
This commit is contained in:
@@ -22,7 +22,7 @@ tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owne
|
||||
--exclude='__pycache__' --exclude='*.pyc' \
|
||||
--transform="s,^,ocf-managed-container-$package_version/," \
|
||||
-czf "$build_dir/SOURCES/ocf-managed-container-$package_version.tar.gz" \
|
||||
README.md HANDOFF.md refactored reference tests packaging scripts
|
||||
README.md HANDOFF.md docs refactored reference tests packaging scripts notes/PROMOTION-SCORING.md notes/DISCONNECTED-STANDBY.md
|
||||
# Freeze identity into the SRPM spec so rpmbuild --rebuild needs no Git metadata
|
||||
# or external macro definitions and retains the same version and release.
|
||||
sed -e "s/^Version:.*/Version: $package_version/" \
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# HISTORICAL LAB TOOL: not a production/scoring installer; see docs/operations.md.
|
||||
# One-time installation of the modular baseline on the confirmed lab standby.
|
||||
# Run with sudo in your terminal. No service or PostgreSQL lifecycle actions.
|
||||
set -euo pipefail
|
||||
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
project_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
provider_dir=/usr/lib/ocf/resource.d/loxthelion
|
||||
agent="$provider_dir/managed-container"
|
||||
expected_hash=5eae1f3dba0586dc06105e4dd7a9b8809a5c9961452abe7654dcfce77c3c5ce2
|
||||
|
||||
fail() { printf 'STOP: %s\n' "$*" >&2; exit 1; }
|
||||
[[ "$EUID" == 0 ]] || fail 'Run this installer with sudo in your terminal.'
|
||||
[[ "$(hostname -s)" == DAVID-ELITEDESK ]] || fail 'This installer is only for DAVID-ELITEDESK.'
|
||||
[[ "$(crm_attribute --type crm_config --name cluster-name --query --quiet)" == postgres-ra-test ]] || fail 'Unexpected cluster name.'
|
||||
[[ "$(crm_attribute --type crm_config --name maintenance-mode --query --quiet)" == true ]] || fail 'Pacemaker maintenance-mode must already be true.'
|
||||
[[ "$(sha256sum "$agent" | cut -d ' ' -f1)" == "$expected_hash" ]] || fail 'Installed agent differs from the confirmed reference.'
|
||||
[[ ! -e "$provider_dir/managed-container.d" ]] || fail 'A module directory already exists; review it before installing.'
|
||||
|
||||
cd "$project_dir"
|
||||
[[ "$(sha256sum refactored/managed-container | cut -d ' ' -f1)" == "c91f09adfdeb45a3be1a72df3f5ca5b02fd4555dec4397ad944eab439ace0b89" ]] || fail 'This installer is for the original modular baseline only; the scoring enhancement requires separate review.'
|
||||
sha256sum -c notes/REFACTOR-SHA256SUMS
|
||||
for file in refactored/managed-container refactored/managed-container.d/*.sh; do
|
||||
bash -n "$file"
|
||||
done
|
||||
|
||||
# Match the recorded pg-ha resource parameters and avoid inherited test overrides.
|
||||
run_ra() {
|
||||
env -i PATH="$PATH" OCF_ROOT=/usr/lib/ocf OCF_RESOURCE_INSTANCE=pg-ha \
|
||||
OCF_RESKEY_service_user=david OCF_RESKEY_service_name=postgres.service \
|
||||
OCF_RESKEY_container_name=postgres OCF_RESKEY_pg_os_user=postgres \
|
||||
OCF_RESKEY_db_user=david OCF_RESKEY_db_name=david \
|
||||
"$1" "$2"
|
||||
}
|
||||
run_ra "$agent" validate-all
|
||||
run_ra "$agent" monitor || fail 'The original agent must report a healthy unpromoted standby (rc=0).'
|
||||
|
||||
mkdir -p /var/backups
|
||||
backup_dir="$(mktemp -d /var/backups/managed-container-ELITEDESK.XXXXXXXX)"
|
||||
cp -a "$agent" "$backup_dir/managed-container"
|
||||
printf 'Original agent backed up to: %s\n' "$backup_dir"
|
||||
stage_dir="$(mktemp -d "$provider_dir/.managed-container-install.XXXXXXXX")"
|
||||
install -d -o root -g root -m 0755 "$stage_dir/managed-container.d"
|
||||
install -o root -g root -m 0755 refactored/managed-container "$stage_dir/managed-container"
|
||||
install -o root -g root -m 0644 refactored/managed-container.d/*.sh "$stage_dir/managed-container.d/"
|
||||
run_ra "$agent" meta-data > "$backup_dir/original-metadata.xml"
|
||||
run_ra "$stage_dir/managed-container" meta-data > "$backup_dir/staged-metadata.xml"
|
||||
cmp "$backup_dir/original-metadata.xml" "$backup_dir/staged-metadata.xml"
|
||||
run_ra "$stage_dir/managed-container" validate-all
|
||||
run_ra "$stage_dir/managed-container" monitor || fail 'Staged agent must report a healthy standby.'
|
||||
|
||||
published=false
|
||||
rollback_on_error() {
|
||||
local rc=$?
|
||||
trap - EXIT
|
||||
if [[ "$rc" != 0 && "$published" == true ]]; then
|
||||
printf 'Installation/check failed; restoring the original entrypoint.\n' >&2
|
||||
if install -o root -g root -m 0755 "$backup_dir/managed-container" "$stage_dir/rollback-agent" &&
|
||||
mv -f "$stage_dir/rollback-agent" "$agent" && restorecon "$agent"; then
|
||||
printf 'Original restored. New modules remain unused beside it for inspection.\n' >&2
|
||||
else
|
||||
printf 'Automatic restoration FAILED. Original backup: %s/managed-container\n' "$backup_dir" >&2
|
||||
fi
|
||||
fi
|
||||
exit "$rc"
|
||||
}
|
||||
trap rollback_on_error EXIT
|
||||
# Recheck immediately before changing the installed files.
|
||||
[[ "$(crm_attribute --type crm_config --name maintenance-mode --query --quiet)" == true ]] || fail 'Maintenance mode changed; refusing installation.'
|
||||
published=true
|
||||
mv "$stage_dir/managed-container.d" "$provider_dir/managed-container.d"
|
||||
mv -f "$stage_dir/managed-container" "$agent"
|
||||
restorecon -RF "$agent" "$provider_dir/managed-container.d"
|
||||
|
||||
run_ra "$agent" meta-data > "$backup_dir/installed-metadata.xml"
|
||||
cmp "$backup_dir/original-metadata.xml" "$backup_dir/installed-metadata.xml"
|
||||
run_ra "$agent" validate-all
|
||||
run_ra "$agent" monitor || fail 'Installed agent did not report a healthy standby.'
|
||||
[[ "$(crm_attribute --type crm_config --name maintenance-mode --query --quiet)" == true ]] || fail 'Maintenance mode is no longer true.'
|
||||
for file in refactored/managed-container refactored/managed-container.d/*.sh; do
|
||||
cmp "$file" "$provider_dir/${file#refactored/}"
|
||||
done
|
||||
sha256sum "$agent" "$provider_dir/managed-container.d/"*.sh > "$backup_dir/installed-SHA256SUMS"
|
||||
cat "$backup_dir/installed-SHA256SUMS"
|
||||
published=false
|
||||
trap - EXIT
|
||||
rmdir "$stage_dir"
|
||||
printf '\nPASS: refactored RA installed; metadata identical, validate-all=0, monitor=0.\n'
|
||||
printf 'Maintenance mode remains true. No service restart or HA transition was requested.\n'
|
||||
printf 'Backup and validation evidence: %s\n' "$backup_dir"
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print (never execute) exact cleanup commands from a supplied CIB snapshot."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def plan(path, clone_id, preferred_node):
|
||||
root = ET.parse(path).getroot()
|
||||
clones = [c for c in root.findall('./configuration/resources/clone') if c.get('id') == clone_id]
|
||||
if len(clones) != 1:
|
||||
raise ValueError('Expected exactly one clone with the supplied ID')
|
||||
for meta in clones[0].findall('meta_attributes'):
|
||||
if meta.get('id-ref') or meta.find('rule') is not None:
|
||||
raise ValueError('Clone metadata uses references/rules; inspect manually')
|
||||
matches = []
|
||||
for location in root.findall('./configuration/constraints/rsc_location'):
|
||||
if location.get('rsc') != clone_id:
|
||||
continue
|
||||
for rule in location.findall('rule'):
|
||||
children = list(rule)
|
||||
if (rule.get('role') == 'Promoted' and rule.get('score') == '200'
|
||||
and len(children) == 1 and children[0].tag == 'expression'
|
||||
and children[0].get('attribute') == '#uname'
|
||||
and children[0].get('operation') == 'eq'
|
||||
and children[0].get('value') == preferred_node):
|
||||
# Preserve unrelated rules if the location has more than one.
|
||||
target = location.get('id') if len(list(location)) == 1 else rule.get('id')
|
||||
if not target:
|
||||
raise ValueError('Matching constraint/rule lacks an ID')
|
||||
matches.append(target)
|
||||
if len(matches) != 1:
|
||||
raise ValueError('Expected exactly one simple Promoted/200/#uname preference; inspect the full CIB manually')
|
||||
target_roles = [p.get('value') for m in clones[0].findall('meta_attributes')
|
||||
for p in m.findall('nvpair') if p.get('name') == 'target-role']
|
||||
if target_roles != ['Promoted']:
|
||||
raise ValueError('Expected one direct clone target-role=Promoted; inspect inheritance/rules manually')
|
||||
# target-role=Promoted is an intentional production invariant, not a
|
||||
# temporary node preference. Never remove or rewrite it here.
|
||||
return [shlex.join(['sudo', 'pcs', 'constraint', 'remove', matches[0]])]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('cib', type=Path)
|
||||
parser.add_argument('--clone', required=True)
|
||||
parser.add_argument('--preferred-node', required=True)
|
||||
parser.add_argument('--lab-validated', action='store_true', help='operator acknowledgement that this candidate passed lab validation')
|
||||
args = parser.parse_args()
|
||||
if not args.lab_validated:
|
||||
parser.error('No rollout commands emitted: lab validation must be completed and acknowledged first')
|
||||
try:
|
||||
commands = plan(args.cib, args.clone, args.preferred_node)
|
||||
except (ValueError, ET.ParseError, OSError) as error:
|
||||
print('No commands emitted:', error, file=sys.stderr)
|
||||
return 1
|
||||
print('# Review against the current CIB before running. No commands have been executed.')
|
||||
print('\n'.join(commands))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user