mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
The migration hook Job gets CURRENT_VERSION once, at render time, and a failed pod is retried with that same environment. Each migration that finished has stamped the cozystack-version ConfigMap by then, yet the retry started from the rendered version and ran those migrations again. Migrations are written to run once, so a re-run is not safe in general. The runner now starts from the version stamped in the ConfigMap. A failed read aborts the pod, since falling back would bring the re-run back and the Job retries anyway. The rendered value is kept only when the ConfigMap holds no integer version, which is also the case where the chart could not derive a real one from it. An all-digit stamp past the shell's integer range aborts too: its comparison fails inside an if, out of reach of set -e, and the Job would go green having run nothing, while the chart renders that stamp as 0. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
163 lines
4.9 KiB
Bash
163 lines
4.9 KiB
Bash
#!/usr/bin/env bats
|
|
# -----------------------------------------------------------------------------
|
|
# Unit tests for packages/core/platform/images/migrations/run-migrations.sh.
|
|
#
|
|
# The migration hook Job retries a failed pod with the environment rendered for
|
|
# the first one, so CURRENT_VERSION is what the cluster was on before any
|
|
# migration of this upgrade ran. Every migration that finished has stamped the
|
|
# cozystack-version ConfigMap since, and a retried pod must start from that
|
|
# stamp, or it runs finished migrations a second time.
|
|
#
|
|
# The runner executes in the migrations image's alpine base (busybox ash, the
|
|
# interpreter it ships with) against stand-in migrations and a fake kubectl
|
|
# that keeps the stamped version in a file, so one pod's stamps are what the
|
|
# next pod reads. The harness shape is hack/migration-58-http-cache-freeze.bats's.
|
|
#
|
|
# Run with: hack/cozytest.sh hack/run-migrations.bats
|
|
# -----------------------------------------------------------------------------
|
|
|
|
FAKEBIN="$PWD/hack/testdata/run-migrations"
|
|
RUNNER="$PWD/packages/core/platform/images/migrations/run-migrations.sh"
|
|
LIB="$PWD/packages/core/platform/images/migrations/migrations/lib"
|
|
ALPINE=$(sed -n 's/^FROM \(alpine:[^ ]*\).*$/\1/p' \
|
|
"$PWD/packages/core/platform/images/migrations/Dockerfile" | head -1)
|
|
WORKROOT="${TMPDIR:-/tmp}/cozy-run-migrations-$$"
|
|
|
|
cozy_cleanup() {
|
|
rm -rf "$WORKROOT"
|
|
return 0
|
|
}
|
|
|
|
# prep <stamped version, or "none" for no ConfigMap>
|
|
prep() {
|
|
docker info >/dev/null 2>&1 || {
|
|
echo "docker is required: these tests run run-migrations.sh inside $ALPINE," >&2
|
|
echo "the migrations image's base." >&2
|
|
return 1
|
|
}
|
|
mkdir -p "$WORKROOT"
|
|
WORK=$(mktemp -d "$WORKROOT/XXXXXX")
|
|
mkdir -p "$WORK/migrations"
|
|
cp -R "$LIB" "$WORK/migrations/lib"
|
|
for n in 57 58 59; do
|
|
cp "$FAKEBIN/migration" "$WORK/migrations/$n"
|
|
done
|
|
: > "$WORK/ran"
|
|
[ "$1" = none ] || printf '%s' "$1" > "$WORK/state"
|
|
chmod +x "$FAKEBIN/kubectl"
|
|
unset FAKE_FAIL_AT FAKE_READ_FAIL || true
|
|
return 0
|
|
}
|
|
|
|
# run_pod <CURRENT_VERSION> <TARGET_VERSION> -- one pod of the hook Job.
|
|
run_pod() {
|
|
_run_pod_rc=0
|
|
docker run --rm --network none \
|
|
--user "$(id -u):$(id -g)" \
|
|
-v "$RUNNER:/usr/bin/run-migrations.sh:ro" \
|
|
-v "$WORK/migrations:/migrations" \
|
|
-v "$FAKEBIN:/fakebin:ro" \
|
|
-v "$WORK:/work" \
|
|
-e PATH=/fakebin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
|
-e FAKE_STATE=/work/state \
|
|
-e FAKE_FAIL_AT="${FAKE_FAIL_AT-}" \
|
|
-e FAKE_READ_FAIL="${FAKE_READ_FAIL-}" \
|
|
-e NAMESPACE=cozy-system \
|
|
-e CURRENT_VERSION="$1" \
|
|
-e TARGET_VERSION="$2" \
|
|
"$ALPINE" /bin/sh /usr/bin/run-migrations.sh || _run_pod_rc=$?
|
|
return "$_run_pod_rc"
|
|
}
|
|
|
|
ran() {
|
|
tr '\n' ' ' < "$WORK/ran" | sed 's/ $//'
|
|
return 0
|
|
}
|
|
|
|
@test "a retried pod starts from the version the failed pod stamped" {
|
|
prep 57
|
|
export FAKE_FAIL_AT=59
|
|
rc=0
|
|
run_pod 57 60 || rc=$?
|
|
[ "$rc" -ne 0 ]
|
|
[ "$(cat "$WORK/state")" = 59 ]
|
|
|
|
unset FAKE_FAIL_AT
|
|
rc=0
|
|
run_pod 57 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -eq 0 ]
|
|
[ "$(ran)" = "57 58 59 59" ]
|
|
[ "$(cat "$WORK/state")" = 60 ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
@test "a pod that finds the target already stamped runs nothing" {
|
|
prep 60
|
|
rc=0
|
|
run_pod 57 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -eq 0 ]
|
|
[ -z "$(ran)" ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
@test "a ConfigMap with no version falls back to the rendered one" {
|
|
prep ""
|
|
rc=0
|
|
run_pod 58 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -eq 0 ]
|
|
[ "$(ran)" = "58 59" ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
# A non-integer start fails the -ge test inside an if and makes seq fail
|
|
# inside the for list, and neither trips set -e: without the fallback such a
|
|
# stamp gives a green Job that ran nothing and moved no stamp.
|
|
@test "a ConfigMap with a non-integer version falls back to the rendered one" {
|
|
prep v58
|
|
rc=0
|
|
run_pod 58 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -eq 0 ]
|
|
[ "$(ran)" = "58 59" ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
# The same unguarded -ge and empty seq turn an all-digit stamp past the shell's
|
|
# integer range into a green Job that ran nothing. Falling back is no answer:
|
|
# the chart's int renders such a stamp as 0, which would replay every migration.
|
|
@test "a stamped version outside the shell's integer range stops before any migration" {
|
|
prep 999999999999999999999999999999
|
|
rc=0
|
|
run_pod 0 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -ne 0 ]
|
|
[ -z "$(ran)" ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
@test "a failed read of the stamped version stops before any migration" {
|
|
prep 58
|
|
export FAKE_READ_FAIL="Error from server (Timeout): the server was unable to return a response in the time allotted"
|
|
rc=0
|
|
run_pod 57 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -ne 0 ]
|
|
[ -z "$(ran)" ]
|
|
[ "$(cat "$WORK/state")" = 58 ]
|
|
rm -rf "$WORK"
|
|
}
|
|
|
|
@test "with no ConfigMap it stamps the target and runs nothing" {
|
|
prep none
|
|
rc=0
|
|
run_pod 57 60 || rc=$?
|
|
echo "ran: $(ran)"
|
|
[ "$rc" -eq 0 ]
|
|
[ -z "$(ran)" ]
|
|
[ "$(cat "$WORK/state")" = 60 ]
|
|
rm -rf "$WORK"
|
|
}
|