Files
wrenn/internal/layout/layout_test.go
pptx704 c164aadfc9 feat(templates): multi-distro system base images + paused-state snapshotting
- Replace single 'minimal' system template with four well-known distro
  base images: minimal-ubuntu (id 0), minimal-alpine (id 1), minimal-arch
  (id 2), minimal-fedora (id 3) — all platform-owned, reserved IDs 0–1024
- Add id.IsReservedTemplateID() guard and well-known ID constants
- Seed the four system base templates via a goose migration
- On-disk layout: images/teams/{b36(team)}/{b36(tid)}/rootfs.ext4
- Rebuild scripts (update-minimal-rootfs.sh, build-*.sh) handle all four
- rootfs-from-container.sh: fix usr-merged distro tini install, flatten
  IMAGE_NAME slashes for tar path

Snapshotting:
- Allow snapshots from 'paused' state (not just 'running')
- Implement flattenPausedCow for paused sandboxes: re-attach dm-snapshot,
  flatten CoW, tear down; distinct dm name to avoid colliding with Resume
- copyMemorySnapshotFiles + linkOrCopyFile: hardlink CH memory artefacts
  with sparse-preserving cp --sparse=always fallback across filesystems
- Promote staging dir atomically with rename(2)
- Track origStatus through snapshotInBackground so badge returns to
  paused (not running) after paused snapshot
- Expand CleanupOrphanPauseDirs to clean up .stage- prefixes too

Build service:
- Look up all base templates (including system ones) via DB query instead
  of hardcoding the minimal template path
- Insert a sandbox DB record for builder sandboxes
- Destroy builder sandbox + mark DB record stopped on finalize
- Default base template to minimal-ubuntu instead of minimal

Chores:
- Remove stale recipe files (code-runner-beta, jupyter test)
- Remove prepare-wrenn-user.sh (replaced by setup-host.sh)
- Remove old build-rootfs.sh / docker-to-rootfs.sh / per-template build.sh
- Update CLAUDE.md, README.md, envd-rs README with new template info
- Frontend: update admin templates page, admin capsule view, snapshot
  dialog to support paused-state snapshots
2026-05-23 01:58:51 +06:00

140 lines
4.1 KiB
Go

package layout
import (
"path/filepath"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"git.omukk.dev/wrenn/wrenn/pkg/id"
)
func TestIsSystemTemplate(t *testing.T) {
tests := []struct {
name string
teamID pgtype.UUID
templateID pgtype.UUID
want bool
}{
{
name: "ubuntu (zeros, zeros)",
teamID: id.PlatformTeamID,
templateID: id.UbuntuTemplateID,
want: true,
},
{
name: "fedora (platform, id 3)",
teamID: id.PlatformTeamID,
templateID: id.FedoraTemplateID,
want: true,
},
{
name: "platform, max reserved id",
teamID: id.PlatformTeamID,
templateID: pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x00}, Valid: true}, // 1024
want: true,
},
{
name: "platform, above reserved range",
teamID: id.PlatformTeamID,
templateID: pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04, 0x01}, Valid: true}, // 1025
want: false,
},
{
name: "non-platform team, reserved id",
teamID: pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Valid: true},
templateID: id.UbuntuTemplateID,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsSystemTemplate(tt.teamID, tt.templateID); got != tt.want {
t.Errorf("IsSystemTemplate() = %v, want %v", got, tt.want)
}
})
}
}
func TestTemplateDir(t *testing.T) {
wrennDir := "/var/lib/wrenn"
t.Run("system base template (ubuntu) lives under teams", func(t *testing.T) {
got := TemplateDir(wrennDir, id.PlatformTeamID, id.UbuntuTemplateID)
want := filepath.Join(wrennDir, "images", "teams",
id.UUIDToBase36(id.PlatformTeamID.Bytes),
id.UUIDToBase36(id.UbuntuTemplateID.Bytes))
if got != want {
t.Errorf("TemplateDir() = %q, want %q", got, want)
}
})
t.Run("team template", func(t *testing.T) {
teamID := pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Valid: true}
tmplID := pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, Valid: true}
got := TemplateDir(wrennDir, teamID, tmplID)
want := filepath.Join(wrennDir, "images", "teams",
id.UUIDToBase36(teamID.Bytes),
id.UUIDToBase36(tmplID.Bytes))
if got != want {
t.Errorf("TemplateDir() = %q, want %q", got, want)
}
})
t.Run("global template (platform team, non-zero template)", func(t *testing.T) {
tmplID := pgtype.UUID{Bytes: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5}, Valid: true}
got := TemplateDir(wrennDir, id.PlatformTeamID, tmplID)
want := filepath.Join(wrennDir, "images", "teams",
id.UUIDToBase36(id.PlatformTeamID.Bytes),
id.UUIDToBase36(tmplID.Bytes))
if got != want {
t.Errorf("TemplateDir() = %q, want %q", got, want)
}
})
}
func TestTemplateRootfs(t *testing.T) {
wrennDir := "/var/lib/wrenn"
got := TemplateRootfs(wrennDir, id.PlatformTeamID, id.UbuntuTemplateID)
want := filepath.Join(wrennDir, "images", "teams",
id.UUIDToBase36(id.PlatformTeamID.Bytes),
id.UUIDToBase36(id.UbuntuTemplateID.Bytes),
"rootfs.ext4")
if got != want {
t.Errorf("TemplateRootfs() = %q, want %q", got, want)
}
}
func TestPauseSnapshotDir(t *testing.T) {
got := PauseSnapshotDir("/var/lib/wrenn", "cl-abc123")
want := "/var/lib/wrenn/sandboxes/cl-abc123"
if got != want {
t.Errorf("PauseSnapshotDir() = %q, want %q", got, want)
}
}
func TestPauseStagingDir(t *testing.T) {
got := PauseStagingDir("/var/lib/wrenn", "cl-abc123")
prefix := "/var/lib/wrenn/sandboxes/cl-abc123.staging-"
if len(got) <= len(prefix) || got[:len(prefix)] != prefix {
t.Errorf("PauseStagingDir() = %q, want prefix %q", got, prefix)
}
}
func TestSandboxesDir(t *testing.T) {
got := SandboxesDir("/var/lib/wrenn")
want := "/var/lib/wrenn/sandboxes"
if got != want {
t.Errorf("SandboxesDir() = %q, want %q", got, want)
}
}
func TestKernelPath(t *testing.T) {
got := KernelPath("/var/lib/wrenn")
want := "/var/lib/wrenn/kernels/vmlinux"
if got != want {
t.Errorf("KernelPath() = %q, want %q", got, want)
}
}