# Phase 4: Snapshot Management - Context

**Gathered:** 2026-03-23
**Status:** Ready for planning

<domain>
## Phase Boundary

Implement atomic cluster-wide snapshot management — `snapshot_create`, `snapshot_revert`, `snapshot_list`, and `snapshot_delete` — as deterministic, all-or-nothing operations across all cluster nodes. An agent can checkpoint running cluster state and restore any checkpoint reliably, making test resets fast and clean.

Requirements: SNAP-01, SNAP-02, SNAP-03, SNAP-04.

</domain>

<decisions>
## Implementation Decisions

### Snapshot Strategy
- **D-01:** Use **external disk-only snapshots** via libvirt's external snapshot mechanism (not internal QEMU snapshots). External snapshots create a new backing-chain overlay, leaving the base and previous overlays intact. Optimized for speed — no memory state capture by default.
- **D-02:** The `include_memory` flag on `snapshot_create` (default `False`) enables optional in-memory state capture when the caller needs in-flight state. Default path is disk-only for speed. External memory dump files follow the same cluster-namespaced layout.
- **D-03:** `snapshot_create` can run while the domain is live (external disk-only snapshots do not require guest shutdown). `snapshot_revert` requires shutting down all nodes first before reverting disk backing chains.

### Snapshot File Storage Layout
- **D-04:** Snapshot overlay files are stored as `{overlay_dir}/{cluster_name}/snapshots/{snapshot_name}/{node_name}.qcow2`. This is consistent with the D-10 cluster-namespaced layout established in Phase 2 for overlay disks.
- **D-05:** `snapshot_delete` removes the snapshot overlay file and any associated memory dump. The parent backing chain is not modified — callers cannot delete a snapshot that is the currently-active backing layer (planner must define the error contract for this edge case).

### Partial Failure and Atomicity Semantics
- **D-06:** `snapshot_create` is strict all-or-nothing: if any node's snapshot creation fails, `snapshot_create` returns `success=False` with per-node status. Any successfully-created snapshot files on other nodes are not automatically cleaned up — the error result reports what was and wasn't created. Consistent with D-04/D-05 from Phase 2.
- **D-07:** `snapshot_revert` is strict all-or-nothing: if any node's revert fails, return `success=False` with per-node status and do not leave silent partial-revert state. Consistent with D-04 from Phase 2.
- **D-08:** `snapshot_delete` is best-effort per node with full per-node reporting — deletion failures on individual nodes do not abort the entire operation, but are surfaced in the result.

### Concurrency Model
- **D-09:** All per-node operations (`snapshot_create`, `snapshot_revert`, `snapshot_delete`) use `asyncio.gather` to execute concurrently across nodes. Libvirt domain API calls (snapshotCreateXML, etc.) route through the existing single-worker `ThreadPoolExecutor` in `libvirt_client.py` — required because the libvirt C library is not thread-safe (D-08 from Phase 2).
- **D-10:** SSH wait after `snapshot_revert` with `restart_after=True` reuses the same wait-for-SSH polling loop pattern established in Phase 2 `cluster_start`. No new SSH mechanism needed.

### Snapshot Revert Behavior
- **D-11:** `snapshot_revert` shuts down all nodes (virDomainDestroy or graceful shutdown), reverts each node's disk to the named snapshot overlay, then — if `restart_after=True` — boots all nodes and blocks until every node is SSH-reachable (using Phase 2 SSH wait pattern). Default is `restart_after=True, wait_for_ssh=True`.
- **D-12:** The `snapshot_list` result uses the existing `SnapshotInfo` model (already defined in `models.py`). The `snapshot_create` return type `SnapshotStatus` from the design doc is not yet in `models.py` — researcher/planner should determine the appropriate return type (likely `SnapshotInfo` or a new `SnapshotCreateResult`).

### Carried Forward (consistent with Phase 2)
- **D-13:** `@structured_tool_handler` wraps every tool handler — no undecorated tools.
- **D-14:** Pydantic v2 models for all return types. `SnapshotNodeInfo` and `SnapshotInfo` are already defined in `vmcluster_mcp/models.py` and must be used.
- **D-15:** Logging via `logging.getLogger("vmcluster_mcp.snapshot")` — consistent module-level logger naming.

### Agent's Discretion
- Exact libvirt virDomainSnapshot XML format and flags for external disk-only snapshots — researcher determines based on libvirt API.
- Whether to call virDomainSnapshotCreateXML or use qemu-img externally for snapshot creation — researcher evaluates; libvirt's virDomainSnapshotCreateXML with DISK_ONLY flag is preferred for consistency.
- Return type for `snapshot_create` (`SnapshotStatus` in design doc, not yet in models.py) — planner defines model.
- Disk size reporting in `SnapshotNodeInfo.size_bytes` — whether to report allocated size or virtual size — researcher determines.
- Graceful vs. forced shutdown when reverting (ACPI shutdown first with timeout, then destroy) — planner decides based on revert time requirements.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Design Specification
- `docs/vmcluster-mcp-design.md` §2.3 — `snapshot_create`, `snapshot_revert`, `snapshot_list`, `snapshot_delete` tool signatures, parameters, and behavioral contracts
- `docs/vmcluster-mcp-design.md` §4 — Open design decisions: snapshot strategy resolved as external disk-only, optimize for speed (see D-01 above)

### Data Models
- `vmcluster_mcp/models.py` — `SnapshotNodeInfo`, `SnapshotInfo` already defined; `SnapshotStatus`/`SnapshotCreateResult` not yet defined — must be added

### Existing Infrastructure to Reuse
- `vmcluster_mcp/cluster/libvirt_client.py` — LibvirtClient with single-worker executor; all libvirt domain calls must go through this executor
- `vmcluster_mcp/server.py` — FastMCP `mcp` instance + `structured_tool_handler` decorator + `config` global

### Phase 2 Patterns (apply consistently)
- `.planning/phases/02-core-vm-lifecycle/02-CONTEXT.md` — Prior decisions D-04 through D-11 that carry forward to this phase

### Requirements
- `.planning/REQUIREMENTS.md` — SNAP-01..SNAP-04 acceptance criteria

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `SnapshotNodeInfo` (models.py): `node_name`, `disk_path`, `size_bytes` — return type for per-node snapshot data
- `SnapshotInfo` (models.py): `snapshot_name`, `cluster_name`, `created_at`, `per_node: dict[str, SnapshotNodeInfo]` — return type for `snapshot_list`
- `LibvirtClient` (cluster/libvirt_client.py): single-worker executor wrapping unsafe libvirt C library calls — must be reused for all snapshot domain API calls
- `structured_tool_handler` (server.py): decorator for all tool registrations
- `config` global (server.py): provides `libvirt_uri`, `overlay_dir`, `ssh_key_path`, `ssh_user`
- SSH wait loop (cluster wait_for_ssh pattern from Phase 2): reuse for `restart_after=True` in snapshot_revert

### Established Patterns
- **Pydantic v2 BaseModel** for all return types — no dicts returned to agents
- **`from __future__ import annotations`** in every module
- **`asyncio.gather`** for parallel per-node fan-out (established in Phase 3 node_exec_all)
- **`@structured_tool_handler`** wraps every tool function
- **Cluster-namespaced paths**: `{overlay_dir}/{cluster_name}/...` for all file artifacts

### Integration Points
- `snapshot/__init__.py` is currently empty — implement in `snapshot/manager.py` (per design doc §3.2 directory layout)
- Tools register on the `mcp` FastMCP instance from `server.py`
- Snapshot operations depend on `cluster_define` having been run (overlay disks must exist)

</code_context>

<specifics>
## Specific Ideas

No specific requirements — open to standard libvirt external snapshot approaches based on performance benchmarks and libvirt best practices.

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope.

</deferred>

---

*Phase: 04-snapshot-management*
*Context gathered: 2026-03-23*
