Skip to content

fix(ateom): convert a pre-per-volume durable snapshot on restore - #277

Merged
teemow merged 2 commits into
giantswarmfrom
fork/271-convert-legacy-durable-layout-2
Oct 11, 2026
Merged

teemow merged 2 commits into
giantswarmfrom
fork/271-convert-legacy-durable-layout-2

Conversation

@teemow

@teemow teemow commented Oct 11, 2026

Copy link
Copy Markdown
Member

Problem

Since the one-tar-per-volume layout (agent-substrate#2110, carried in #260) a gVisor snapshot keeps one durable-dir-<volume>.tar per durable volume. A snapshot written by 1.7 keeps them all in one durable-dir.tar, and #265 makes ateom refuse such a restore with FailedPrecondition. That is safe, but no upgrade path: every actor suspended on 1.7 loses its conversation and durable volumes on the step to 1.8, and so does every actor created from a 1.7 golden, unless the snapshots are reset, which loses them too.

Proposed solution

ateom's restore converts the old layout instead of refusing it:

  • tarutil.Split rewrites durable-dir.tar into one archive per declared volume, tar to tar with nothing extracted; the volume directory's own entry becomes the ./ root entry CreateWithRoot writes. Only the xattr PAX records are kept, so a stale path/linkpath record cannot override the new names. It is all or nothing (written beside the destination, renamed into place after the input was read to the end) and leaves its input alone. It is an exported function of an internal package, so the ActorTemplate reconciler can call it too.
  • convertLegacyDurableLayout (gVisor ateom) runs it when durable-dir.tar is present, before untarDurableVolumes extracts each volume as it does for a current snapshot: same os.Root, fd-relative extraction, same device and xattr restrictions of tarutil: refuse device nodes and unvetted xattrs on extract agent-substrate/substrate#2111. durable-dir.tar stays, so an interrupted conversion runs again; the next checkpoint writes the current layout.
  • A snapshot that does not convert (unreadable or truncated archive, an entry outside every declared volume, a top-level non-directory, a hardlink across volumes) is still refused with FailedPrecondition, now with its cause; the volumes stay untouched and the snapshot directory gains no partial conversion.
  • A snapshot in the current layout is restored as before, its directory untouched.

The micro-VM ateom on the line still writes and reads the single durable-dir.tar and needs no conversion. One upstream-ready commit, plus a docs(fork) commit for its FORK.md row; rebase-merge.

Acceptance criteria

  • TestUntarDurableVolumesConvertsLegacyLayout: a 1.7 snapshot (one durable-dir.tar written by tarutil.Create over the volumes directory, as the 1.7 checkpoint did) restores every volume's data, writes the per-volume tars, gives a volume without entries none, and converts again on a second restore.
  • TestUntarDurableVolumesCurrentLayoutUnchanged: a 1.8 snapshot restores and its directory is byte-for-byte unchanged.
  • TestUntarDurableVolumesRefusesUnconvertibleLegacyLayout: an entry outside every volume, a truncated archive and a non-tar are refused with FailedPrecondition wrapping the layout error and the cause; the volume and the snapshot directory are untouched.
  • TestSplitRoundTrip / TestSplitRefuses (internal/tarutil): names only PAX can hold, symlinks, hardlinks, user xattrs, the directory's mode and mtime survive the split; every refusal leaves nothing behind.
  • go test -race ./internal/tarutil/ ./cmd/ateom-gvisor/ ./cmd/ateom-microvm/ and golangci-lint on the touched packages pass locally; CI green.

Replaces #273, which conflicted with the line after a FORK.md row landed.

Closes #271

This pull request was written by an agent.

A gVisor snapshot written before the one-tar-per-volume layout keeps every
durable-dir volume in one durable-dir.tar, its entries named <volume>/... .
The restore refused it, so an actor suspended before the layout change lost
its durable volumes on the upgrade unless its snapshot was reset, which
loses them too.

The restore now converts such a snapshot instead: tarutil.Split rewrites
durable-dir.tar into one durable-dir-<volume>.tar per declared volume,
copying entries tar to tar with nothing extracted, the volume directory's
own entry becoming the root entry CreateWithRoot writes. Each volume is then
extracted like any snapshot in the current layout, through the same os.Root
and the same device and xattr checks. The split is all or nothing and
leaves durable-dir.tar in place, so a restore that did not finish converts
again; the next checkpoint writes the current layout.

A snapshot that does not convert, an archive the split cannot read or one
with an entry outside every declared volume or a hardlink across volumes,
is still refused with FailedPrecondition, now with the cause, so the actor
never resumes with empty volumes. A snapshot in the current layout is
restored as before, its directory untouched.
@teemow
teemow requested a review from a team as a code owner October 11, 2026 15:47
@teemow
teemow merged commit 12224ea into giantswarm Oct 11, 2026
17 checks passed
@teemow
teemow deleted the fork/271-convert-legacy-durable-layout-2 branch October 11, 2026 16:07
teemow added a commit that referenced this pull request Oct 11, 2026
The step from 1.7 to 1.8.1 converts each actor's 1.7-layout durable
snapshot on its first resume (#277) and takes every golden written in an
older layout again on the new workers, a create during the re-take
answering Unavailable and succeeding on retry (#282). It needs no
snapshot reset and no manual step.

The snapshot reset runbook says so and stays the remedy for a corrupted
snapshot store only. The rolling upgrade runbook gains the 1.7 to 1.8
step note with its floor 1.8.1: 1.8.0 refuses a 1.7 restore (#265).
FORK.md's layout row points at the step instead of the reset.

Refs #271, #272
teemow added a commit that referenced this pull request Oct 12, 2026
The step from 1.7 to 1.8.1 converts each actor's 1.7-layout durable
snapshot on its first resume (#277) and takes every golden written in an
older layout again on the new workers, a create during the re-take
answering Unavailable and succeeding on retry (#282). It needs no
snapshot reset and no manual step.

The snapshot reset runbook says so and stays the remedy for a corrupted
snapshot store only. The rolling upgrade runbook gains the 1.7 to 1.8
step note with its floor 1.8.1: 1.8.0 refuses a 1.7 restore (#265).
FORK.md's layout row points at the step instead of the reset.

Refs #271, #272
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ateom: convert a 1.7 durable snapshot to the per-volume layout on resume, instead of refusing it

1 participant