fix: serialize Tower migration validation
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
- Added a calm Latest state/Evolution flow to the map-first explorer, including period selection, metric comparison and added/removed/modified overlays where source identities support them.
|
- Added a calm Latest state/Evolution flow to the map-first explorer, including period selection, metric comparison and added/removed/modified overlays where source identities support them.
|
||||||
- Added explicit operator provisioners for official Statbel Mol population snapshots (2021-2025) and Digitaal Vlaanderen historical land-use snapshots (1778, 1873 and 1969); no source is fetched during application startup.
|
- Added explicit operator provisioners for official Statbel Mol population snapshots (2021-2025) and Digitaal Vlaanderen historical land-use snapshots (1778, 1873 and 1969); no source is fetched during application startup.
|
||||||
- Preserved methodological honesty: partial statistical sectors are labelled area-weighted estimates, historical land-use identity changes are not fabricated and all source URLs, versions and processing limitations are persisted.
|
- Preserved methodological honesty: partial statistical sectors are labelled area-weighted estimates, historical land-use identity changes are not fabricated and all source URLs, versions and processing limitations are persisted.
|
||||||
|
- Serialized Tower startup and migration-smoke validation by waiting for container health, preventing concurrent Alembic upgrades from racing on the same PostGIS schema.
|
||||||
|
|
||||||
## Sprint 186 Map-first Mol geographic explorer (2026-07-14)
|
## Sprint 186 Map-first Mol geographic explorer (2026-07-14)
|
||||||
|
|
||||||
|
|||||||
@@ -248,3 +248,13 @@ def test_temporal_frontend_and_official_operator_contracts_exist() -> None:
|
|||||||
assert "provision_mol_population_history.py" in dockerfile
|
assert "provision_mol_population_history.py" in dockerfile
|
||||||
assert "provision_mol_historical_landuse.py" in dockerfile
|
assert "provision_mol_historical_landuse.py" in dockerfile
|
||||||
assert "fake" not in population.lower()
|
assert "fake" not in population.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_tower_deploy_waits_for_startup_migration_before_live_smoke() -> None:
|
||||||
|
for relative_path in ("scripts/deploy_tower.ps1", "scripts/deploy_tower.sh"):
|
||||||
|
script = (ROOT / relative_path).read_text(encoding="utf-8")
|
||||||
|
wait_position = script.index("wait_for_geointel_health")
|
||||||
|
invocation_position = script.index("\nwait_for_geointel_health", wait_position)
|
||||||
|
smoke_position = script.index("LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh")
|
||||||
|
assert "docker inspect --format" in script
|
||||||
|
assert invocation_position < smoke_position
|
||||||
|
|||||||
@@ -7794,3 +7794,8 @@ Validation before live deployment:
|
|||||||
|
|
||||||
Next:
|
Next:
|
||||||
- Run the complete readiness gate, deploy to Tower/PostGIS, provision the official snapshots and verify current/evolution selection end to end in the internal browser.
|
- Run the complete readiness gate, deploy to Tower/PostGIS, provision the official snapshots and verify current/evolution selection end to end in the internal browser.
|
||||||
|
|
||||||
|
Live deployment correction:
|
||||||
|
- The first Tower rollout exposed a deployment race: all-in-one startup and `live_migration_smoke.sh` both began `alembic upgrade head` after PostgreSQL became reachable.
|
||||||
|
- Startup committed head `202607140001`; the concurrent smoke transaction rolled back on a duplicate first column. Database contents and the successful migration remained healthy.
|
||||||
|
- Both Tower deploy entry points now wait for the `geointel` container healthcheck, which includes completed startup migrations and backend readiness, before launching the independent migration smoke.
|
||||||
|
|||||||
@@ -49,6 +49,30 @@ docker compose -f docker-compose.unraid.yml config >/dev/null
|
|||||||
docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
||||||
bash deploy/unraid/run-dockerman-container.sh
|
bash deploy/unraid/run-dockerman-container.sh
|
||||||
|
|
||||||
|
wait_for_geointel_health() {
|
||||||
|
local status=''
|
||||||
|
for attempt in $(seq 1 90); do
|
||||||
|
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' geointel 2>/dev/null || true)"
|
||||||
|
if [ "$status" = 'healthy' ]; then
|
||||||
|
echo "GeoIntel container is healthy after attempt $attempt."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$status" = 'unhealthy' ] || [ "$status" = 'exited' ] || [ "$status" = 'dead' ]; then
|
||||||
|
echo "GeoIntel container entered terminal state: $status" >&2
|
||||||
|
docker logs --tail 120 geointel >&2 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "GeoIntel container did not become healthy (last state: ${status:-missing})." >&2
|
||||||
|
docker logs --tail 120 geointel >&2 || true
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Container startup owns the migration. Waiting here prevents the validation
|
||||||
|
# smoke from racing a concurrent `alembic upgrade head` against the same DB.
|
||||||
|
wait_for_geointel_health
|
||||||
|
|
||||||
if [ -x scripts/live_migration_smoke.sh ]; then
|
if [ -x scripts/live_migration_smoke.sh ]; then
|
||||||
LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh
|
LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -51,6 +51,30 @@ docker compose -f docker-compose.unraid.yml config >/dev/null
|
|||||||
docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" -f deploy/unraid/Dockerfile.all-in-one -t geointel-all-in-one:latest .
|
||||||
bash deploy/unraid/run-dockerman-container.sh
|
bash deploy/unraid/run-dockerman-container.sh
|
||||||
|
|
||||||
|
wait_for_geointel_health() {
|
||||||
|
local status=''
|
||||||
|
for attempt in $(seq 1 90); do
|
||||||
|
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' geointel 2>/dev/null || true)"
|
||||||
|
if [[ "$status" == 'healthy' ]]; then
|
||||||
|
echo "GeoIntel container is healthy after attempt $attempt."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "$status" == 'unhealthy' || "$status" == 'exited' || "$status" == 'dead' ]]; then
|
||||||
|
echo "GeoIntel container entered terminal state: $status" >&2
|
||||||
|
docker logs --tail 120 geointel >&2 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "GeoIntel container did not become healthy (last state: ${status:-missing})." >&2
|
||||||
|
docker logs --tail 120 geointel >&2 || true
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Container startup owns the migration. Waiting here prevents the validation
|
||||||
|
# smoke from racing a concurrent `alembic upgrade head` against the same DB.
|
||||||
|
wait_for_geointel_health
|
||||||
|
|
||||||
if [[ -x scripts/live_migration_smoke.sh ]]; then
|
if [[ -x scripts/live_migration_smoke.sh ]]; then
|
||||||
LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh
|
LIVE_SMOKE_CONTAINER=geointel bash scripts/live_migration_smoke.sh
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user