#!/usr/bin/env python3
"""Exercise broker/proxy lifecycle failures and cache authentication offline."""
from pathlib import Path
import os
import shutil
import subprocess
import tempfile
import unittest
SDP = Path(__file__).resolve().parents[1]
BIN = SDP / "Server/Unix/p4/common/bin"
BASH = os.environ.get("SDP_TEST_BASH", shutil.which("bash"))
class ServiceLifecycle(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="sdp-service-")
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.common = self.root / "common"
self.common.mkdir()
self.env = dict(os.environ, TEST_ROOT=str(self.root))
(self.common / "p4_vars").write_text('''
P4CBIN="$TEST_ROOT/common"
P4CCFG="$TEST_ROOT/common"
P4SERVER=p4_1
P4MASTER_ID=master
P4BROKERBIN="$TEST_ROOT/daemon"
P4PBIN="$TEST_ROOT/daemon"
OSUSER=perforce
LOGS="$TEST_ROOT"
PROXY_TARGET=127.0.0.1:19999
PROXY_PORT=19998
PROXY_MON_LEVEL=1
P4PCACHE="$TEST_ROOT/cache"
P4SERVICEUSER=svc
GREP=grep
''')
(self.common / "p4_1.broker.master.cfg").write_text('target = "127.0.0.1:19999";\n')
(self.common / "ps_functions.sh").write_text('''
# Fake processes only: no real signals or accounts are used.
id () { if [[ $* == '-u' ]]; then echo 1000; else echo perforce; fi; }
get_pids () { [[ ${RUNNING:-0} == 1 ]] && echo 999999; return 0; }
kill () { echo attempted >> "$TEST_ROOT/signals"; }
sleep () { :; }
''')
self.executable(self.root / "daemon", 'echo started >> "$TEST_ROOT/starts"\nexit "${START_RC:-0}"\n')
def executable(self, path, body):
path.write_text("#!" + BASH + "\n" + body)
path.chmod(0o755)
def fixture(self, name):
target = self.root / name
self.executable(target, (BIN / name).read_text().replace("/p4/common/bin/", str(self.common) + "/"))
return target
def run_service(self, path, operation, **env):
return subprocess.run([BASH, str(path), "1", operation], env=dict(self.env, **env),
capture_output=True, text=True, timeout=10)
def test_start_failure_is_reported_for_both_services(self):
for name in ("p4broker_base", "p4p_base"):
with self.subTest(name=name):
result = self.run_service(self.fixture(name), "start", START_RC="7")
self.assertEqual(result.returncode, 7, result.stdout + result.stderr)
def test_stop_timeout_never_claims_success(self):
for name in ("p4broker_base", "p4p_base"):
with self.subTest(name=name):
result = self.run_service(self.fixture(name), "stop", RUNNING="1")
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
self.assertIn("did not stop after 60 seconds", result.stderr)
self.assertNotIn(" stopped.", result.stdout)
def test_restart_never_starts_when_old_process_cannot_stop(self):
for name in ("p4broker_base", "p4p_base"):
with self.subTest(name=name):
result = self.run_service(self.fixture(name), "restart", RUNNING="1")
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
self.assertFalse((self.root / "starts").exists())
def test_restart_stopped_service_starts_and_reports_failure(self):
for name in ("p4broker_base", "p4p_base"):
for rc in (0, 7):
with self.subTest(name=name, rc=rc):
result = self.run_service(self.fixture(name), "restart", START_RC=str(rc))
self.assertEqual(result.returncode, rc, result.stdout + result.stderr)
self.assertTrue((self.root / "starts").exists())
def test_cache_purge_stops_after_failed_login(self):
with (self.common / "p4_vars").open("a") as output:
output.write('P4BIN="$TEST_ROOT/p4"\nP4PORT=127.0.0.1:19999\nP4USER=admin\nKEEPLOGS=7\n')
(self.common / "backup_functions.sh").write_text('''
check_vars () { :; }; set_vars () { :; }; get_journalnum () { :; }
log_init () { :; }; sdp_log_init () { :; }; rotate_log_file () { :; }; remove_log_files () { :; }
die () { echo "$*" >&2; exit 1; }
''')
self.executable(self.common / "p4login", 'echo login-denied >&2\nexit 5\n')
self.executable(self.root / "p4", 'touch "$TEST_ROOT/purged"\n')
result = self.run_service(self.fixture("p4cachepurge.sh"), "unused")
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
self.assertFalse((self.root / "purged").exists())
self.assertIn("login-denied", (self.root / "p4cachepurge.log").read_text())
self.assertIn("Cache purge login failed", result.stderr)
if __name__ == "__main__":
unittest.main()
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 34004 | Russell C. Jackson (Rusty) |
Improve SDP backup/recovery, Maintenance, logging and cloud operation. Add owned checkpoint locks, recovery-aware retention, preserved database switches, bounded cleanup and coordinated AWS/Azure snapshots. Fix service-user password initialization, edge staging, standby promotion state parsing, proxy cache setup, typed depot verification and historical-server configuration/error handling. Remove unused OTP programs; keep Windows removed, server.locks cleanup and the journal-count condition unchanged. Add no global shell strict modes. Update operational documentation and add reproducible mkdirs.sh/mkdirs.cfg container assurance. Validate Ubuntu 26.04 and UBI 8.10/9.8/10.2 across both Python runtimes: 44 stages; 428 offline tests per runtime/OS, zero offline skips; 19 deployed-archive comparisons per combination. Three six-node topology runs pass all 30 recovery/failure scenarios. P4D 2023.1 through 2026.1 on UBI 8/9 pass all 14 cells and 98 scenarios. Static, documentation and provenance checks pass. Containers do not certify every fleet kernel, patch build or integration. Tested source manifest SHA-256: e1a23e17b8ced8c1148d9a250589977c6f6e98606bf2a81dc3ff5d7c909e3695 Deployed comparison archive SHA-256: 1bd23217356ee3808e040d39aea5be9519bda7c8f2b4924656a70aff8ad7293a |