feat: schedule persistent archive jobs with console management

This commit is contained in:
2026-09-23 11:29:32 +08:00
parent 6eb429e21f
commit 0140c78ea7
32 changed files with 2077 additions and 20 deletions

View File

@@ -21,6 +21,15 @@ cleanup() {
done
rmdir "$E2E_DIR/archive-job" >/dev/null 2>&1 || true
fi
if [ -d "$E2E_DIR/archive-volume" ]; then
python3 - "$E2E_DIR/archive-volume" <<'PY_CLEAN'
import pathlib, shutil, sys
path = pathlib.Path(sys.argv[1])
# 仅清理由该测试 mktemp 目录下创建的独立归档卷。
if path.name == "archive-volume" and path.parent.name.startswith("orbit-console-e2e."):
shutil.rmtree(path)
PY_CLEAN
fi
for e2e_artifact in "$E2E_DIR"/*; do
if [ -f "$e2e_artifact" ]; then
unlink "$e2e_artifact"
@@ -90,11 +99,17 @@ print(queue)
PY
)
mkdir -m 700 "$E2E_DIR/archive-volume"
E2E_STORAGE_ID=$(openssl rand -hex 16)
printf '%s\n' "[4/8] start Orbit container"
"$E2E_DOCKER" run -d --name "$E2E_ORBIT" --network bridge --link "$E2E_MYSQL:mysql" \
--env ORBIT_MYSQL_DSN="$E2E_DSN" \
--env ORBIT_HTTP_ADDR=0.0.0.0:8080 \
--env ORBIT_CONSOLE_QUEUE_ID="$E2E_QUEUE" \
--env ORBIT_ARCHIVE_ROOT=/var/lib/orbit/archive \
--env ORBIT_ARCHIVE_STORAGE_ID="$E2E_STORAGE_ID" \
--env ORBIT_ARCHIVE_TICK_INTERVAL=1s \
--volume "$E2E_DIR/archive-volume:/var/lib/orbit/archive" \
--publish 127.0.0.1::8080 "$E2E_IMAGE" >/dev/null
E2E_PORT=
for e2e_attempt in $(seq 1 30); do
@@ -247,12 +262,39 @@ assert content["source_retained"] is True and content["root_count"] == 0
status, _ = request("/console/retention/export", "POST", {"kind": "tasks", "expected_revision": "0"})
assert status == 409, status
print("retention_policy=ok replay=ok stale_revision=409 preview_categories=7 empty_export_digest=ok")
status, body = request("/console/archive/plans")
assert status == 200, (status, body)
plans = json.loads(body)["data"]
assert len(plans["items"]) == 7 and plans["executor_configured"] is True
plan = dict(expected_revision="0", policy_revision="1", enabled=True, interval_seconds=3600, batch_size=20, max_bytes=1048576, reason="console scheduler e2e")
plan_headers = {"Idempotency-Key": "archive-plan-e2e"}
status, body = request("/console/archive/plans/tasks", "PUT", plan, {**plan_headers, "Origin": "https://different-origin.invalid"})
assert status == 403, (status, body)
status, body = request("/console/archive/plans/tasks", "PUT", plan, plan_headers)
assert status == 200 and json.loads(body)["data"]["revision"] == "1", (status, body)
status, body = request("/console/archive/plans/tasks", "PUT", plan, plan_headers)
assert status == 200 and json.loads(body)["data"]["revision"] == "1", (status, body)
import time
for attempt in range(30):
status, body = request("/console/archive/jobs")
assert status == 200, (status, body)
jobs = json.loads(body)["data"]["items"]
if jobs and jobs[0]["state"] in ("SUCCEEDED", "FAILED"):
break
time.sleep(1)
assert len(jobs) == 1 and jobs[0]["state"] == "SUCCEEDED", jobs
assert jobs[0]["batches"] == 1 and jobs[0]["roots"] == 0 and len(jobs[0]["last_sha256"]) == 64
assert "lease_id" not in jobs[0]
print("archive_scheduler=ok plan_replay=ok cross_origin=403 durable_job=SUCCEEDED")
status, _ = request("/console/auth/logout", "POST", {})
assert status == 200, status
status, _ = request("/v1/tasks?" + urllib.parse.urlencode({"queue_id": queue, "limit": 1}))
assert status == 401, status
status_retention, _ = request("/console/retention")
assert status_retention == 401, status_retention
status_archive, _ = request("/console/archive/jobs")
assert status_archive == 401, status_archive
print("console_task_data=ok succeeded_tasks=%d logout=ok unauthorized_after_logout=%d" % (sum(item.get("state") == "SUCCEEDED" for item in items), status))
PY