fix(storage): preserve retired Matrix containers during migration
This commit is contained in:
@@ -6,6 +6,7 @@ import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import tarfile
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
@@ -72,6 +73,15 @@ def run(case):
|
||||
try:
|
||||
docker(*base, "up", "-d", "--pull", "never", "--wait", "--wait-timeout", "90")
|
||||
seed(root, project, hidden=case == "hidden-prefix")
|
||||
retained = []
|
||||
if case.startswith("retired-"):
|
||||
(root / "matrix/postgres").mkdir(parents=True)
|
||||
(root / "matrix/postgres/retained.txt").write_text("retired matrix data")
|
||||
for service, mount in (("matrix-db", str(root / "matrix/postgres") + ":/var/lib/postgresql/data"),
|
||||
("matrix-redis", "/data")):
|
||||
retained.append(docker("create", "--pull", "never", "--restart=no", "--network", project + "-internal",
|
||||
"--label", "com.docker.compose.project=" + project, "--label", "com.docker.compose.service=" + service,
|
||||
"-v", mount, "node:22-alpine", "node", "-e", "process.exit(0)"))
|
||||
migration = m.Migration(root, "fedeo-storage-tools:pilot")
|
||||
with migration.engine.lock():
|
||||
if case == "hidden-prefix":
|
||||
@@ -85,12 +95,16 @@ def run(case):
|
||||
return
|
||||
migration.plan()
|
||||
assert migration.state["count"] == 1
|
||||
if retained:
|
||||
assert migration.state["retainedServices"] == ["matrix-db", "matrix-redis"]
|
||||
assert migration.state["matrixPaths"] == ["matrix"]
|
||||
assert not set(migration.state["retainedServices"]) & m.u.read_json(migration.home / "target.json")["services"].keys()
|
||||
migration.migrate(True)
|
||||
assert migration.state["phase"] == "verified"
|
||||
assert m.u.read_json(root / ".updates/status.json")["recoveryRequired"]
|
||||
# Simulate restart by reconstructing the process and persisted state.
|
||||
migration = m.Migration(root)
|
||||
if case == "rollback":
|
||||
if case.endswith("rollback"):
|
||||
migration.rollback()
|
||||
assert migration.state["phase"] == "rolled_back"
|
||||
assert not m.u.read_json(root / ".updates/status.json").get("recoveryRequired")
|
||||
@@ -107,6 +121,12 @@ def run(case):
|
||||
raise AssertionError("rollback after writes must fail")
|
||||
except m.Error:
|
||||
pass
|
||||
for cid in retained:
|
||||
old = migration.inspect(cid)
|
||||
assert not old["State"]["Running"] and old["HostConfig"]["RestartPolicy"]["Name"] == "no"
|
||||
if retained:
|
||||
with tarfile.open(migration.home / "backup/files.tar") as backup:
|
||||
assert backup.extractfile("matrix/postgres/retained.txt").read() == b"retired matrix data"
|
||||
print(json.dumps({"case": case, "result": "passed", "evidence": str(root)}), flush=True)
|
||||
except BaseException:
|
||||
print("Failure evidence: " + str(root), flush=True)
|
||||
@@ -122,7 +142,7 @@ def run(case):
|
||||
finally:
|
||||
ids = docker("ps", "-aq", "--filter", "label=com.docker.compose.project=" + project).split()
|
||||
if ids:
|
||||
docker("rm", "-f", *ids)
|
||||
docker("rm", "-f", "-v", *ids)
|
||||
remaining = docker("ps", "-aq", "--filter", "label=io.fedeo.storage-job=" + str(root / ".updates/storage-migration")).split()
|
||||
if remaining:
|
||||
docker("rm", "-f", *remaining)
|
||||
@@ -131,5 +151,5 @@ def run(case):
|
||||
|
||||
if __name__ == "__main__":
|
||||
for case in (sys.argv[1:] or ["hidden-prefix", "rollback", "activate"]):
|
||||
assert case in ("hidden-prefix", "rollback", "activate")
|
||||
assert case in ("hidden-prefix", "rollback", "activate", "retired-rollback", "retired-activate")
|
||||
run(case)
|
||||
|
||||
@@ -46,6 +46,45 @@ class HostSafetyTests(unittest.TestCase):
|
||||
with self.assertRaises(m.Error):
|
||||
m.validate_config(self.config, self.root)
|
||||
|
||||
def test_retired_matrix_remains_stopped_and_outside_compose(self):
|
||||
container = {"Config": {"Labels": {"com.docker.compose.service": "matrix-redis"}},
|
||||
"State": {"Running": False}, "HostConfig": {"RestartPolicy": {"Name": "no"}},
|
||||
"Mounts": [{"Type": "volume", "Source": "/var/lib/docker/volumes/old/_data", "Destination": "/data"}]}
|
||||
indexed, retained = m.index_containers(self.config, [container])
|
||||
self.assertEqual(set(indexed), {"matrix-redis"})
|
||||
self.assertEqual(retained, {"matrix-redis": {"volumes": []}})
|
||||
self.assertNotIn("matrix-redis", self.config["services"])
|
||||
mutations = [lambda c: c["State"].update(Running=True),
|
||||
lambda c: c["State"].update(Restarting=True),
|
||||
lambda c: c["State"].update(Paused=True),
|
||||
lambda c: c["HostConfig"]["RestartPolicy"].update(Name="unless-stopped"),
|
||||
lambda c: c["HostConfig"].update(AutoRemove=True),
|
||||
lambda c: c["Config"]["Labels"].update({"com.docker.compose.service": "unknown"}),
|
||||
lambda c: c["Config"]["Labels"].update({"com.docker.compose.oneoff": "True"}),
|
||||
lambda c: c["Mounts"][0].update(Destination="/unexpected")]
|
||||
for mutate in mutations:
|
||||
changed = copy.deepcopy(container)
|
||||
mutate(changed)
|
||||
with self.assertRaises(m.Error):
|
||||
m.index_containers(self.config, [changed])
|
||||
with self.assertRaises(m.Error):
|
||||
m.index_containers(self.config, [container, container])
|
||||
|
||||
def test_retired_matrix_bind_mounts_keep_strict_backup_validation(self):
|
||||
container = {"Config": {"Labels": {"com.docker.compose.service": "matrix-db"}},
|
||||
"State": {"Running": False}, "HostConfig": {"RestartPolicy": {"Name": "no"}},
|
||||
"Mounts": [{"Type": "bind", "Source": "/outside/postgres", "Destination": "/var/lib/postgresql/data"}]}
|
||||
migration = m.Migration(self.root)
|
||||
_, retained = m.index_containers(self.config, [container])
|
||||
migration.engine.current = {"services": retained}
|
||||
migration.engine.retired_services = list(retained)
|
||||
with self.assertRaises(m.Error):
|
||||
migration.engine.validate_legacy_matrix()
|
||||
(self.root / "matrix/postgres").mkdir(parents=True)
|
||||
retained["matrix-db"]["volumes"][0]["source"] = str(self.root / "matrix/postgres")
|
||||
migration.engine.validate_legacy_matrix()
|
||||
self.assertEqual(migration.engine.matrix_backup_paths, ["matrix"])
|
||||
|
||||
def test_cold_index_detects_objects_hidden_by_minio_listing(self):
|
||||
source = self.root / "minio"
|
||||
(source / ".minio.sys").mkdir()
|
||||
|
||||
Reference in New Issue
Block a user