复核 R-d2(起不来的库容器回退后还在一遍遍重启):stop_pg 对在跑的和在重启的(restarting)都停;migrate-to-postgres 回退时先 docker update --restart=no 再停,再起库时(start_pg)恢复 --restart unless-stopped;./deploy.sh stop 用同一个函数一起好。假 docker 单测:镜像起不来、容器一直 restarting 时回退后停在 exited、重启策略是 no,换回能起的镜像重跑照常,stop 也停得下在重启的

This commit is contained in:
ThearchyHelios
2026-10-19 16:09:19 +08:00
parent 35bb634460
commit 8b5b7f86b2
3 changed files with 52 additions and 4 deletions
+18 -2
View File
@@ -11,6 +11,7 @@ FAKE_COPY_FAIL 成功或失败。
FAKE_PSQL_FAIL=1 docker exec … psql 失败
FAKE_UNHEALTHY_ENV=<变量名> 带这个环境变量(-e)起的容器健康检查不过
FAKE_UNHEALTHY_IMAGE=<镜像> 用这个镜像起的容器健康检查不过
FAKE_RESTARTING_IMAGE=<镜像> 用这个镜像起的容器一直在重启(restarting)
"""
from __future__ import annotations
@@ -272,14 +273,29 @@ def main(argv: list[str]) -> int:
fail(f'docker: Error response from daemon: Conflict. The container name "/{name}" is already in use.', 125)
for vol in opts["volumes"]:
state["volumes"].setdefault(vol, {})
flapping = os.environ.get("FAKE_RESTARTING_IMAGE") == image
state["containers"][name] = {
"image": image, "state": "running", "env": opts["env"], "labels": opts["labels"],
"mounts": opts["mounts"], "ports": opts["ports"], "network": network or "bridge",
"image": image, "state": "restarting" if flapping else "running", "env": opts["env"],
"labels": opts["labels"], "mounts": opts["mounts"], "ports": opts["ports"], "network": network or "bridge",
"restart": opts.get("--restart", "no"),
}
save(state)
print("f" * 64)
return 0
if cmd == "update":
name = args[-1]
c = state["containers"].get(name)
if c is None:
fail(f"Error response from daemon: No such container: {name}")
for i, a in enumerate(args[:-1]):
if a.startswith("--restart="):
c["restart"] = a.split("=", 1)[1]
elif a == "--restart":
c["restart"] = args[i + 1]
save(state)
return 0
if cmd in ("start", "stop", "restart"):
names = [a for a in args if not a.startswith("-") and not a.isdigit()]
for name in names:
+24
View File
@@ -560,6 +560,30 @@ def test_a_failed_copy_leaves_the_sqlite_service_as_it_was_and_rerunning_works(d
assert deploy.mode() == "builtin"
def test_a_database_that_keeps_restarting_is_stopped_and_left_without_a_restart_policy(deploy):
"""复核 R-d2:库的镜像起不来、容器一遍遍重启(restarting)时,回退要真停下它、关掉重启策略;再起时恢复。"""
_sqlite_deployment(deploy)
(deploy.pkg / "postgres-image.tar").write_text(f"alpine:latest \n{PG_IMAGE} \n")
out = deploy("migrate-to-postgres", ok=False, DEPLOY_POSTGRES_IMAGE="alpine:latest", FAKE_RESTARTING_IMAGE="alpine:latest")
assert "没有切换,仍用 SQLite" in out.stderr
pg = deploy.containers()[PG]
assert pg["state"] == "exited" and pg["restart"] == "no", "停下了、不会再被拉起来"
calls = deploy.calls()
assert index_of(calls, lambda a: a == ["update", "--restart=no", PG]) < index_of(calls, is_cmd("stop", PG))
# 换回能起的镜像重跑:库的容器按新镜像重建,重启策略照常
deploy("migrate-to-postgres")
assert deploy.containers()[PG]["restart"] == "unless-stopped" and deploy.mode() == "builtin"
# ./deploy.sh stop 同一个函数:在重启的也停
state = deploy.state()
state["containers"][PG]["state"] = "restarting"
deploy.state_file.write_text(json.dumps(state))
deploy("stop")
assert deploy.containers()[PG]["state"] == "exited"
deploy("start")
calls = deploy.calls()
assert index_of(calls, lambda a: a == ["update", "--restart", "unless-stopped", PG]) < index_of(calls, is_cmd("start", PG))
def test_a_stopped_service_stays_stopped_after_a_failed_migration(deploy):
_sqlite_deployment(deploy)
deploy("stop")
+10 -2
View File
@@ -873,6 +873,8 @@ start_pg() {
fi
if [ "$(container_state "$POSTGRES_CONTAINER")" != running ]; then
say "启动内置 PostgreSQL(${POSTGRES_CONTAINER})"
# 回退时关掉过重启策略(stop_pg --no-restart),起的时候恢复
docker update --restart unless-stopped "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true
docker start "$POSTGRES_CONTAINER" >/dev/null || die "内置 PostgreSQL 的容器起不来,见上面 docker 的报错"
fi
else
@@ -885,9 +887,15 @@ start_pg() {
fi
}
# 在跑的、在反复重启的(镜像起不来、--restart unless-stopped 一遍遍拉)都停。--no-restart:再关掉重启策略(回退时用:
# 一个起不来的库容器不该在后台一直重启),下次 start_pg 起它时恢复
stop_pg() {
container_exists "$POSTGRES_CONTAINER" || return 0
[ "$(container_state "$POSTGRES_CONTAINER")" = running ] || return 0
[ "${1:-}" != --no-restart ] || docker update --restart=no "$POSTGRES_CONTAINER" >/dev/null 2>&1 || true
case "$(container_state "$POSTGRES_CONTAINER")" in
running|restarting) ;;
*) return 0 ;;
esac
say "停止内置 PostgreSQL(${POSTGRES_CONTAINER},数据保留)"
docker stop "$POSTGRES_CONTAINER" >/dev/null
}
@@ -1040,7 +1048,7 @@ migrate_rollback() {
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
fi
fi
stop_pg
stop_pg --no-restart
die "没有切换,仍用 SQLite(数据目录里的 agentlab.db 没动过)。照上面的报错处理好之后重新运行 ./deploy.sh migrate-to-postgres,
重复执行是安全的:内置库里这次导了一半的会先清空再导"
}