Application recovery plans and restore drills
tools/atlas_recovery is an operator-run, Python 3.10+ tool using Atlas's existing
REST API. It needs no Python packages, server migration, privileges on the gateway,
or additional network listener. It restores snapshots into new volumes, verifies
them, and cleans them up. It never rolls back or deletes a source volume.
Quickstart
From the repository root:
export PYTHONPATH="$PWD/tools"
# Supply an operator token through ATLAS_TOKEN in your process environment.
python3 -m atlas_recovery validate --plan examples/recovery/native-block.json
python3 -m atlas_recovery preflight --plan /etc/atlas-recovery/plan.json \
--base-url https://atlas.example.com/api/atlas/v1
python3 -m atlas_recovery drill --plan /etc/atlas-recovery/plan.json \
--base-url https://atlas.example.com/api/atlas/v1 \
--report-dir /var/lib/atlas-recovery/reports
Replace the example resource IDs and checksum with your actual resources. The
Native example checks the 20-byte atlas-recovery-proof marker at offset zero;
it is illustrative, not a digest for arbitrary user data. Write it only to a
throwaway test volume. Checks hash restored bytes against a pre-established
expected SHA-256 value, never against a changing production volume.
Remote gateways require HTTPS with normal CA and hostname verification. Use
--ca-file /path/to/ca.pem for a private CA. HTTP is accepted only on literal
loopback addresses or localhost. Redirects are refused. The token is neither
printed nor included in reports or hook subprocess environments.
Exit codes: 0 verified success, 1 failed drill/capture or missed objective,
2 input, transport or preflight error. An input/preflight error may happen
before any report is created. Monitoring should check the process exit code.
Plan contract
See examples/recovery/application.json for a database followed by an API.
version:1.name: lowercase identifier, at most 30 characters.tenant_id: the tenant owning every source volume and snapshot.target_namespace: a DNS label beginning withatlas-drill-, distinct from every source namespace. Provision this namespace before a Ceph/PVC drill.rpo_seconds: maximum age of the oldest selected snapshot, default 3600.rto_seconds: maximum elapsed clone/start/verification time, default 600. It excludes cleanup and read-only preflight. This is a drill recovery duration, not a promise about recovery from a site-wide outage.capture_timeout_seconds: capture budget, default 120, maximum 600. The runner checks the remaining budget between hooks/requests and limits each snapshot request/job wait to the remaining budget. Resume hooks always run separately.components: 1–64 components, unique IDs, optionaldepends_on, 1–16 volumes per component. Unknown dependencies, cycles and reused source volumes fail validation. Restoration and start follow dependency order; stop is reversed.- Each volume supplies
volume_idandsnapshot_id. Use"latest"to choose the newest ready snapshot for that exact volume and tenant at preflight. The selection is pinned for the run; resolved IDs are recorded in the report. - Every volume must have one or more
checks, or its component must have averify_hook. An inventory-only check can never pass a drill.
Checksum ranges contain offset, length (1 byte–4 MiB), and lowercase sha256.
They currently work only for Native block volumes through Atlas's raw data
API. Reports identify the exact ranges tested; a sampled digest is not a full
volume scrub. Use application verification hooks for Ceph/PVCs and Native
filesystems. Backend capability preflight currently permits Ceph and Native;
read-only Longhorn/NFS/ZFS/WEKA integrations are refused.
Snapshots are validated against the tenant-filtered volume inventory and snapshot catalog. Missing, unready, foreign, undated, or implausibly future-dated snapshots fail before restoring. SQLite timestamps without offsets are interpreted as UTC. An RPO violation creates a failed report without making mutations. RTO violations also fail the drill even if restored data was otherwise valid.
Application hooks
Hook commands are installed by the operator and referenced by name in plans.
examples/recovery/hooks.json shows the registry; these executable paths are
placeholders to replace, not built-in database implementations.
Each registry entry contains an argv array with an absolute executable path,
allowed phases, and timeout_seconds (default 30, maximum 600). There is no
shell expansion, interpolation, or command text from the plan. Keep the registry
and executables operator-owned and unwritable by untrusted plan authors.
The runner sends one JSON object on stdin: phase, run_id, tenant_id,
namespace, component_id, source IDs, and this component's restored resource
records (IDs, names, snapshot IDs, job IDs). Hooks return zero only on success.
Output is discarded to prevent credentials appearing in reports. The environment
contains only PATH/locale/platform basics and ATLAS_RECOVERY_PHASE. Hooks needing
Kubernetes or application credentials should load dedicated files explicitly.
Implementations must use the supplied clone IDs and target namespace for
start/verify/stop, and validate the actual application state (for example, database
recovery completed plus a known query). start_hook requires a paired stop_hook.
A partially failed start still invokes stop. If any stop fails, clones are retained
and the drill fails, rather than deleting volumes still used by a workload.
Namespace naming and separate clones do not establish network isolation. Before using application hooks, provision deny-by-default ingress/egress policies and restricted service accounts, and avoid production Service names, credentials, external side effects and source-volume mounts. Native volumes do not live in a Kubernetes namespace: the hook must attach their distinct clone IDs explicitly. The runner cannot prove that a custom executable honors these contracts.
Coordinated snapshot capture
python3 -m atlas_recovery capture --plan /etc/atlas-recovery/source-plan.json \
--hooks /etc/atlas-recovery/hooks.json \
--base-url https://atlas.example.com/api/atlas/v1 \
--report /var/lib/atlas-recovery/capture-001.json \
--captured-plan /var/lib/atlas-recovery/captured-001.json
Capture allows volume entries without snapshot IDs. It quiesces consumers before
providers, creates snapshots, then resumes providers before consumers, including
a component whose quiesce hook failed partway. Quiesce requires a paired resume.
Capture can pause source applications: schedule it as a maintenance operation
with reviewed hooks. For normal recurring drills, use already scheduled Atlas
snapshots and snapshot_id: "latest".
A successful all-hook capture is reported as hook_coordinated; without a hook
for every component it is crash. Snapshots are taken sequentially and remain
marked crash-consistent by Atlas. Hook coordination is not a storage-atomic group
snapshot or proof of database consistency. Verification must establish that.
Created snapshots are retained, including partial captures, so an operator can
inspect or apply retention policies. Capture reports list every known resource.
A resume failure always fails the capture, and no captured-plan output is emitted.
SIGTERM attempts orderly cleanup. SIGKILL, power loss or host loss cannot execute
resume hooks; quiesce implementations should have their own bounded leases or
external watchdog. Capture hooks must fit the configured capture budget.
Durable reports and failure handling
Reports use private files, atomic replacement, and fsync of file contents after
phase changes. Each run reserves a unique report path; an existing path is refused.
Use a durable local volume for journals. Reports include resources, hook/check
outcomes, RPO/RTO results, cleanup failures, unresolved mutations and timestamps.
The JSON schema version is 1. Checks and hooks are recorded separately; byte
checks alone do not establish application startup.
The mutation intent is persisted before sending a create. Mutations are not
retried after ambiguous responses. uncertain_operations means a resource may
have been created even though its ID was not received. An async clone timeout
also leaves the resource/job in the report; a still-running clone job is never
followed by a premature volume deletion. Operators should reconcile the recorded
unique names and job IDs through Atlas, stop any drill workloads, and remove
only their drill resources after jobs reach terminal states. Source snapshots
are never deleted by drill cleanup. Cleanup uses recorded clone IDs, rechecks
names, excludes all plan source IDs, and never uses force deletion. A failed
cleanup makes the whole drill fail.
Reports are a local journal, not an HA workflow engine. A process crash does not automatically resume a run. Keep sensitive hooks/configuration out of reports; local administrators can inspect resource metadata in the journal. Apply your own report retention policy.
Scheduling and verification
The example systemd service/timer runs a daily drill at 02:00 UTC with a unique
report per invocation. Install Python, the repository, dedicated credentials,
operator-owned hooks/config, the service user and writable reports directory
first. Replace the gateway URL and provision hook-specific access. Use
snapshot_id: "latest" for recurring drills. Configure monitoring for nonzero
service exits and stale/missing reports; the timer does not send notifications.
PYTHONPATH=tools python3 -m unittest discover -s tools/atlas_recovery/tests -v
cargo test -p atlas-io --locked
cargo clippy -p atlas-io --all-targets --locked -- -D warnings
The Python suite uses an HTTP contract fixture and real subprocess hooks; it does
not claim Ceph, Kubernetes network isolation or database-engine certification.
tests/live_native.py additionally exercises the actual gateway and Native node
binaries on loopback using temporary storage; build the binaries first and run
it with PYTHONPATH=tools python3 tools/atlas_recovery/tests/live_native.py.
I/O diagnosis
GET /io/rca keeps its existing response schema and p50/p99 service histogram.
Notes now distinguish observed scheduler queue pressure, device service latency,
and mixed pressure. insert → issue wait is added to issue → complete
service averages; it is never subtracted from the service histogram or presented
as an end-to-end percentile. Fewer than 20 samples do not infer a queue cause;
missing queue observations and inconsistent queue counters are explicit. The
hottest issuer is restricted to the attributed volume when known. These are
observational hints, not assertions that a particular disk or OSD failed.