forked from lei.chen/myxce
wip(admin-config-tool): task 6.3 HA E2E test (blocked, flaky primary-push)
Standby-redirect half is done and correct: OP_upgradeConfig has no server-side redirect wiring today, matches design.md's own documented "Out of Boundary" gap, and this test locks in the real current rejection behavior. Primary-push success-path half is unreliable (~24/25 live fails in one investigation round, see bug-1485) -- the elected primary was observed as a <defunct> zombie shortly after promotion, but a dedicated debug pass could not reproduce it in 26+ clean runs on an idle host and captured no crash evidence either way. tasks.md marks 6.3 _Blocked:_ pending re-verification on an idle host with real stderr capture.
This commit is contained in:
parent
0206b9e166
commit
34bd934bc7
3 changed files with 603 additions and 53 deletions
|
|
@ -49,65 +49,20 @@ fix).
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SRC_DIR = Path(__file__).resolve().parents[2]
|
||||
REPO_CONFIGS_DIR = SRC_DIR / "configs"
|
||||
DEPLOY_HA_SH = SRC_DIR / "tools" / "deploy-ha.sh"
|
||||
|
||||
# Mirrors deploy-ha.sh's own xceadmin discovery order (src/tools/deploy-ha.sh)
|
||||
# -- xce.build (asan, the default profile) first, then release, then debug.
|
||||
_CONTROLLER_CANDIDATES = [
|
||||
SRC_DIR / "xce.build" / "controller" / "xce_controller",
|
||||
SRC_DIR / "build.release" / "controller" / "xce_controller",
|
||||
SRC_DIR / "build.dbg" / "controller" / "xce_controller",
|
||||
]
|
||||
_XCEADMIN_CANDIDATES = [
|
||||
SRC_DIR / "xce.build" / "xceadmin" / ".venv" / "bin" / "xceadmin",
|
||||
SRC_DIR / "build.release" / "xceadmin" / ".venv" / "bin" / "xceadmin",
|
||||
SRC_DIR / "build.dbg" / "xceadmin" / ".venv" / "bin" / "xceadmin",
|
||||
]
|
||||
|
||||
_READY_TIMEOUT_S = 20.0
|
||||
_VERSION_TAG_RE = re.compile(r"versionTag=(\d+)\s+ackedCount=(\d+)")
|
||||
|
||||
|
||||
def _find_first_executable(candidates: list[Path]) -> Path | None:
|
||||
for candidate in candidates:
|
||||
if candidate.is_file() and (candidate.stat().st_mode & 0o111):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _free_tcp_port() -> int:
|
||||
"""A port that was free at bind-time -- the same best-effort ephemeral-
|
||||
port trick used to avoid hardcoding a fixed port this test could
|
||||
collide on. The bind-then-immediately-close TOCTOU window is
|
||||
acceptable for a single-shot test deploy.
|
||||
"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
||||
probe.bind(("127.0.0.1", 0))
|
||||
return probe.getsockname()[1]
|
||||
|
||||
|
||||
def _wait_for_port(host: str, port: int, timeout_s: float) -> None:
|
||||
deadline = time.monotonic() + timeout_s
|
||||
last_err: OSError | None = None
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=1.0):
|
||||
return
|
||||
except OSError as exc:
|
||||
last_err = exc
|
||||
time.sleep(0.2)
|
||||
raise TimeoutError(f"controller never opened {host}:{port}: {last_err}")
|
||||
from _live_e2e_common import CONTROLLER_CANDIDATES as _CONTROLLER_CANDIDATES
|
||||
from _live_e2e_common import DEPLOY_HA_SH, REPO_CONFIGS_DIR
|
||||
from _live_e2e_common import READY_TIMEOUT_S as _READY_TIMEOUT_S
|
||||
from _live_e2e_common import VERSION_TAG_RE as _VERSION_TAG_RE
|
||||
from _live_e2e_common import XCEADMIN_CANDIDATES as _XCEADMIN_CANDIDATES
|
||||
from _live_e2e_common import find_first_executable as _find_first_executable
|
||||
from _live_e2e_common import free_tcp_port as _free_tcp_port
|
||||
from _live_e2e_common import wait_for_port as _wait_for_port
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue