fix(backup,restore): make restore drills replayable and leave swarm tasks alone

Restore fixes, both hit by the infinito svc-bkp e2e drill:

- mariadb --empty dropped tables one docker exec at a time with SET
  FOREIGN_KEY_CHECKS=0 issued in a separate client session, so the
  session-scoped toggle never applied and any FK-referenced parent table
  (mailu.users) died with ERROR 1451. Issue the toggle and all DROPs in
  one session.
- postgres --empty now drops only current_user-owned objects (extension
  members like pg_trgm's set_limit are superuser-owned) with IF EXISTS
  absorbing CASCADE fallout, and the replay skips superuser-only dump
  lines (COMMENT ON EXTENSION, ALTER DEFAULT PRIVILEGES) that abort an
  app-user psql run under ON_ERROR_STOP.

Backup fixes:

- pg_dump now runs with --no-owner --no-privileges so future dumps are
  replayable by the owning app user in the first place.
- Swarm task containers are never stopped or started manually: the
  orchestrator replaces a stopped task and a later docker start fails on
  the detached overlay network. filter_stoppable skips them visibly and
  the whitelist stop check ignores them.

Validated end to end against a live infinito compose stack: the full
svc-bkp-volume-2-local drill (verify, restore cycle, sql replay for
mailu, keycloak and one more db) passes with these patches applied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 08:23:11 +02:00
parent 57ea4592c1
commit e9030e8443
7 changed files with 129 additions and 56 deletions

View File

@@ -59,15 +59,24 @@ class TestE2EMariaDBAnonymousPreemption(unittest.TestCase):
# Boot WITHOUT MARIADB_USER/MARIADB_PASSWORD/MARIADB_DATABASE so the
# entrypoint does not auto-create '<u>'@'%'. We provision the user
# explicitly below to mirror the SQL path used by svc-db-mariadb.
run([
"docker", "run", "-d",
"--name", cls.db_container,
"-e", f"MARIADB_ROOT_PASSWORD={cls.root_password}",
"-v", f"{cls.db_volume}:/var/lib/mysql",
"mariadb:12.2",
])
run(
[
"docker",
"run",
"-d",
"--name",
cls.db_container,
"-e",
f"MARIADB_ROOT_PASSWORD={cls.root_password}",
"-v",
f"{cls.db_volume}:/var/lib/mysql",
"mariadb:12.2",
]
)
wait_for_mariadb(cls.db_container, root_password=cls.root_password, timeout_s=120)
wait_for_mariadb(
cls.db_container, root_password=cls.root_password, timeout_s=120
)
# Provision: '<u>'@'%' (the app/backup grant) + anonymous ''@'localhost'
# (the preemption trigger). Mirrors the production state that produced
@@ -81,10 +90,16 @@ class TestE2EMariaDBAnonymousPreemption(unittest.TestCase):
f"CREATE TABLE {cls.db_name}.t (id INT PRIMARY KEY, v VARCHAR(50));"
f"INSERT INTO {cls.db_name}.t VALUES (1,'ok');"
)
run([
"docker", "exec", cls.db_container, "sh", "-lc",
f'mariadb -uroot --protocol=socket -e "{bootstrap_sql}"',
])
run(
[
"docker",
"exec",
cls.db_container,
"sh",
"-lc",
f'mariadb -uroot --protocol=socket -e "{bootstrap_sql}"',
]
)
# Sanity: '<u>' can log in over TCP (matches '%'). If THIS fails,
# the precondition for the fix to even apply is broken.
@@ -104,7 +119,11 @@ class TestE2EMariaDBAnonymousPreemption(unittest.TestCase):
# ability to discriminate "fix works" vs "bug never reproduced".
p = run(
[
"docker", "exec", self.db_container, "sh", "-lc",
"docker",
"exec",
self.db_container,
"sh",
"-lc",
f"mariadb-dump -u{self.db_user} -p{self.db_password} {self.db_name}",
],
capture=True,