# services/cassandra/migration-jobs/db-copy-veles-to-cassandra-job.yaml # Suspended by default. Unsuspend after cassandra-postgres is ready and before # enabling generator/retention workers. This copies from Veles to Cassandra and # verifies table sets, row counts, and per-table content hashes. apiVersion: batch/v1 kind: Job metadata: name: cassandra-db-copy-from-veles-3 namespace: cassandra spec: suspend: true backoffLimit: 0 ttlSecondsAfterFinished: 86400 template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-init-first: "true" vault.hashicorp.com/agent-pre-populate-only: "true" vault.hashicorp.com/role: "cassandra-migration" vault.hashicorp.com/agent-inject-secret-source-db.sh: "kv/data/atlas/veles/veles-db" vault.hashicorp.com/agent-inject-template-source-db.sh: | {{- with secret "kv/data/atlas/veles/veles-db" }} export SOURCE_DATABASE_URL="{{ .Data.data.DATABASE_URL }}" {{- end }} vault.hashicorp.com/agent-inject-secret-target-db.sh: "kv/data/atlas/cassandra/cassandra-db" vault.hashicorp.com/agent-inject-template-target-db.sh: | {{- with secret "kv/data/atlas/cassandra/cassandra-db" }} export TARGET_DATABASE_URL="{{ .Data.data.DATABASE_URL }}" {{- end }} spec: serviceAccountName: cassandra-db-migration restartPolicy: Never nodeSelector: cassandra.bstein.dev/node-pool: oceanus kubernetes.io/arch: amd64 tolerations: - key: veles.bstein.dev/simulation operator: Equal value: "true" effect: NoSchedule containers: - name: copy-and-verify image: postgres:15 command: ["/bin/bash", "-c"] args: - | set -euo pipefail . /vault/secrets/source-db.sh . /vault/secrets/target-db.sh : "${SOURCE_DATABASE_URL:?missing source database url}" : "${TARGET_DATABASE_URL:?missing target database url}" export PGCONNECT_TIMEOUT=10 for attempt in $(seq 1 60); do if pg_isready -d "${SOURCE_DATABASE_URL}" >/dev/null 2>&1 \ && pg_isready -d "${TARGET_DATABASE_URL}" >/dev/null 2>&1; then break fi if [ "${attempt}" = "60" ]; then echo "source or target database did not become ready" >&2 exit 1 fi sleep 5 done dump_path=/tmp/veles-to-cassandra.dump pg_dump --format=custom --no-owner --no-acl \ --dbname="${SOURCE_DATABASE_URL}" \ --file="${dump_path}" psql -X --set=ON_ERROR_STOP=1 --dbname="${TARGET_DATABASE_URL}" \ --command="DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO cassandra; GRANT ALL ON SCHEMA public TO public;" pg_restore --no-owner --no-acl \ --dbname="${TARGET_DATABASE_URL}" \ "${dump_path}" table_sql="select schemaname || E'\t' || tablename from pg_tables where schemaname not in ('pg_catalog','information_schema') order by 1" psql -XAt --dbname="${SOURCE_DATABASE_URL}" --command="${table_sql}" > /tmp/source.tables psql -XAt --dbname="${TARGET_DATABASE_URL}" --command="${table_sql}" > /tmp/target.tables diff -u /tmp/source.tables /tmp/target.tables checksum_sql() { schema="$1" table="$2" printf '%s\n' \ "SELECT format(" \ " 'select count(*)::text || E''\t'' || coalesce(md5(string_agg(md5(row_to_json(t)::text), '''' order by md5(row_to_json(t)::text))), md5('''')) from %I.%I t'," \ " :'schema'," \ " :'table'" \ ")\\gexec" > /tmp/checksum.sql psql -XAt --set=ON_ERROR_STOP=1 \ --set=schema="${schema}" \ --set=table="${table}" \ --dbname="$3" \ --file=/tmp/checksum.sql } failures=0 while IFS="$(printf '\t')" read -r schema table; do [ -n "${schema}" ] || continue source_sum="$(checksum_sql "${schema}" "${table}" "${SOURCE_DATABASE_URL}")" target_sum="$(checksum_sql "${schema}" "${table}" "${TARGET_DATABASE_URL}")" if [ "${source_sum}" != "${target_sum}" ]; then echo "checksum mismatch for ${schema}.${table}: source=${source_sum} target=${target_sum}" >&2 failures=$((failures + 1)) else echo "verified ${schema}.${table}: ${source_sum}" fi done < /tmp/source.tables if [ "${failures}" -ne 0 ]; then echo "database copy integrity verification failed for ${failures} table(s)" >&2 exit 1 fi echo "database copy integrity verification passed" resources: requests: cpu: 500m memory: 1Gi limits: cpu: "2" memory: 4Gi