feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,46 @@
|
||||
# Offline contract test: no Docker daemon or production access needed.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$output = Join-Path $repoRoot 'artifacts/backend-baseline/offline-test.json'
|
||||
$mockState = @{ Calls = [Collections.Generic.List[object]]::new(); EmptyStats = $false }
|
||||
function docker {
|
||||
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DockerArgs)
|
||||
$mockState.Calls.Add($DockerArgs)
|
||||
$global:LASTEXITCODE = 0
|
||||
switch ($DockerArgs[0]) {
|
||||
'compose' {
|
||||
if ($DockerArgs -contains 'config') {
|
||||
'{"services":{"api":{"image":"example/api:test","environment":{"PASSWORD":"MUST_NOT_LEAK"},"command":"MUST_NOT_LEAK","deploy":{"resources":{"limits":{"memory":"280M","cpus":"0.85"}}}}}}'
|
||||
} else { 'aaaaaaaaaaaa'; 'bbbbbbbbbbbb' }
|
||||
}
|
||||
'info' { '{"server_version":"test","os_type":"linux","cpus":2,"memory_bytes":1073741824}' }
|
||||
'inspect' {
|
||||
'{"id":"' + $DockerArgs[-1] + '","image_id":"sha256:test","running":true,"memory_limit_bytes":293601280,"oom_killed":false,"restart_count":0}'
|
||||
}
|
||||
'stats' {
|
||||
if ($mockState.EmptyStats) { return }
|
||||
'{"id":"aaaaaaaaaaaa","cpu_percent":"1.0%","memory_usage":"20MiB / 280MiB"}'
|
||||
'{"id":"bbbbbbbbbbbb","cpu_percent":"2.0%","memory_usage":"30MiB / 280MiB"}'
|
||||
}
|
||||
default { throw 'unexpected Docker operation' }
|
||||
}
|
||||
}
|
||||
& (Join-Path $repoRoot 'scripts/collect-backend-baseline.ps1') -OutputPath $output -Samples 1
|
||||
$raw = Get-Content -LiteralPath $output -Raw
|
||||
$result = $raw | ConvertFrom-Json
|
||||
if ($result.status -ne 'collected') { throw 'collector did not succeed' }
|
||||
if ($result.containers.Count -ne 2 -or $result.samples[0].containers.Count -ne 2) { throw 'NDJSON multi-container stats were lost' }
|
||||
if ($raw.Contains('MUST_NOT_LEAK') -or $raw.Contains('PASSWORD')) { throw 'raw Compose secrets leaked' }
|
||||
if ($result.configured_services[0].memory_limit -ne '280M') { throw 'effective resource limit missing' }
|
||||
if ($mockState.Calls.Count -eq 0) { throw 'Docker mock was not exercised' }
|
||||
foreach ($call in $mockState.Calls) {
|
||||
if ($call[0] -notin @('compose', 'info', 'inspect', 'stats')) { throw 'unexpected operation' }
|
||||
if ($call[0] -eq 'compose' -and -not ($call -contains 'config' -or $call -contains 'ps')) { throw 'mutating Compose operation' }
|
||||
}
|
||||
$mockState.EmptyStats = $true
|
||||
& (Join-Path $repoRoot 'scripts/collect-backend-baseline.ps1') -OutputPath $output -Samples 1
|
||||
if ($LASTEXITCODE -ne 2) { throw 'missing stats must exit 2' }
|
||||
$partial = Get-Content -LiteralPath $output -Raw | ConvertFrom-Json
|
||||
if ($partial.status -ne 'partial' -or $partial.samples[0].complete -ne $false) { throw 'empty stats reported as complete' }
|
||||
Write-Output 'PASS: multi-container snapshot, resource projection, secret exclusion, read-only operations, incomplete stats detection'
|
||||
exit 0
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env sh
|
||||
# Offline behavioral checks for deploy/docker/mirror-base-images.sh.
|
||||
set -eu
|
||||
|
||||
repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
||||
script="$repo_root/deploy/docker/mirror-base-images.sh"
|
||||
test_dir="$(mktemp -d)"
|
||||
case_env="$test_dir/mirror.env"
|
||||
case_log="$test_dir/calls.log"
|
||||
case_out="$test_dir/output.log"
|
||||
copy_count_file="$test_dir/copy-count"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$test_dir/docker" "$test_dir/sleep" "$case_env" "$case_log" "$case_out" "$copy_count_file"
|
||||
rmdir "$test_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cat > "$test_dir/docker" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
case "$1 $2 $3" in
|
||||
'buildx imagetools inspect')
|
||||
if [ "${4:-}" = '--format' ]; then
|
||||
printf 'digest %s\n' "$6" >> "$CASE_LOG"
|
||||
[ "$TEST_BAD_DIGEST" = 0 ] || exit 1
|
||||
printf 'sha256:%s\n' 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
else
|
||||
printf 'inspect %s\n' "$4" >> "$CASE_LOG"
|
||||
[ "$TEST_EXISTING" = 1 ]
|
||||
fi
|
||||
;;
|
||||
'buildx imagetools create')
|
||||
n="$(cat "$COPY_COUNT_FILE")"
|
||||
n="$((n + 1))"
|
||||
printf '%s\n' "$n" > "$COPY_COUNT_FILE"
|
||||
printf 'copy %s\n' "$8" >> "$CASE_LOG"
|
||||
[ "$TEST_COPY_ALWAYS_FAIL" = 0 ] && [ "$n" -gt "$TEST_FAIL_FIRST" ]
|
||||
;;
|
||||
*)
|
||||
printf 'unexpected docker call: %s\n' "$*" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
cat > "$test_dir/sleep" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
printf 'sleep %s\n' "$1" >> "$CASE_LOG"
|
||||
EOF
|
||||
chmod +x "$test_dir/docker" "$test_dir/sleep"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
cat "$case_out" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
count_calls() {
|
||||
awk -v kind="$1" '$1 == kind { count++ } END { print count + 0 }' "$case_log"
|
||||
}
|
||||
|
||||
run_case() {
|
||||
label="$1"
|
||||
TEST_EXISTING="$2"
|
||||
MIRROR_REFRESH="$3"
|
||||
TEST_FAIL_FIRST="$4"
|
||||
TEST_COPY_ALWAYS_FAIL="$5"
|
||||
TEST_BAD_DIGEST="$6"
|
||||
expected_status="$7"
|
||||
export TEST_EXISTING MIRROR_REFRESH TEST_FAIL_FIRST TEST_COPY_ALWAYS_FAIL TEST_BAD_DIGEST
|
||||
: > "$case_log"
|
||||
printf '0\n' > "$copy_count_file"
|
||||
rm -f "$case_env"
|
||||
if REGISTRY=example.test/owner MIRROR_ENV_FILE="$case_env" MIRROR_RETRIES=2 \
|
||||
CASE_LOG="$case_log" COPY_COUNT_FILE="$copy_count_file" PATH="$test_dir:$PATH" \
|
||||
sh "$script" > "$case_out" 2>&1; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
if [ "$expected_status" = success ] && [ "$status" -ne 0 ]; then
|
||||
fail "$label returned $status, expected success"
|
||||
fi
|
||||
if [ "$expected_status" = failure ] && [ "$status" -eq 0 ]; then
|
||||
fail "$label succeeded, expected failure"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_calls() {
|
||||
kind="$1"
|
||||
expected="$2"
|
||||
actual="$(count_calls "$kind")"
|
||||
[ "$actual" -eq "$expected" ] || fail "expected $expected $kind calls, got $actual"
|
||||
}
|
||||
|
||||
assert_immutable_refs() {
|
||||
[ -f "$case_env" ] || fail 'mirror env file missing'
|
||||
lines="$(wc -l < "$case_env" | tr -d ' ')"
|
||||
[ "$lines" -eq 6 ] || fail "expected 6 mirror refs, got $lines"
|
||||
if grep -v -E '^BASE_[A-Z_]+=example\.test/owner/evobgp-buildcache@sha256:[a-f0-9]{64}$' "$case_env"; then
|
||||
fail 'mirror env contains mutable or malformed image ref'
|
||||
fi
|
||||
}
|
||||
|
||||
run_case existing 1 0 0 0 0 success
|
||||
assert_calls copy 0
|
||||
assert_calls sleep 0
|
||||
assert_immutable_refs
|
||||
|
||||
run_case new 0 0 0 0 0 success
|
||||
assert_calls copy 6
|
||||
assert_calls sleep 0
|
||||
assert_immutable_refs
|
||||
|
||||
run_case retry 0 0 1 0 0 success
|
||||
assert_calls copy 7
|
||||
assert_calls sleep 1
|
||||
assert_immutable_refs
|
||||
|
||||
run_case refresh 1 1 0 0 0 success
|
||||
assert_calls copy 6
|
||||
assert_calls sleep 0
|
||||
assert_immutable_refs
|
||||
|
||||
run_case refresh_copy_failure 1 1 0 1 0 failure
|
||||
assert_calls copy 2
|
||||
assert_calls sleep 1
|
||||
[ ! -f "$case_env" ] || fail 'failed refresh wrote mirror refs'
|
||||
|
||||
run_case refresh_digest_failure 1 1 0 0 1 failure
|
||||
assert_calls copy 1
|
||||
assert_calls sleep 0
|
||||
[ ! -f "$case_env" ] || fail 'failed digest lookup wrote mirror refs'
|
||||
|
||||
run_case fallback 0 0 0 1 0 success
|
||||
assert_calls copy 12
|
||||
assert_calls sleep 6
|
||||
[ ! -s "$case_env" ] || fail 'fallback wrote stale mirror refs'
|
||||
|
||||
run_case digest_fallback 1 0 0 0 1 success
|
||||
assert_calls copy 0
|
||||
assert_calls sleep 0
|
||||
[ ! -s "$case_env" ] || fail 'digest fallback wrote mutable mirror refs'
|
||||
|
||||
printf 'mirror-base-images offline tests passed\n'
|
||||
Reference in new issue
Block a user