test_service_lifecycle.py #1

  • //
  • guest/
  • russell_jackson/
  • sdp/
  • test/
  • test_service_lifecycle.py
  • View
  • Commits
  • Open Download .zip Download (5 KB)
#!/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