v0.29.0: OS/Docker-data storage split — golden + provision (Phase 1)

build-golden.sh bakes a small OS rootfs + a dedicated /var/lib/docker volume
(mp0, backup=1) carrying the baked images, plus Docker log rotation. bringup.go
grows the golden's data volume to the per-customer target (DataVolGrowGB) and
emits backup=1 on data mounts (GuestMount.Backup) — closing the spike-B3 silent
DB-loss trap. CLI gains -rootfs-grow/-datavol-grow/-datavol-mount. New
RUNBOOK-provisioning-storage.md. Phase 2 = felhom-controller v0.58.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:38:26 +02:00
parent 5ab159521d
commit d7d68fdd83
6 changed files with 275 additions and 11 deletions
+52
View File
@@ -74,6 +74,58 @@ func TestRunBringUp_ProvisionHappyPath(t *testing.T) {
}
}
// A data-bearing additive mount must carry backup=1 (so its DBs stay in PBS — storage-split B3);
// a non-backup mount must NOT. Pure-function check on buildBringUpConfig.
func TestBuildBringUpConfig_BackupFlagOnDataMount(t *testing.T) {
params := buildBringUpConfig(BringUpSpec{
Mode: ModeProvision,
Mounts: []GuestMount{
{Storage: "local-lvm", SizeGB: 2, MountPoint: "/mnt/data", Backup: true},
{Storage: "local-lvm", SizeGB: 1, MountPoint: "/mnt/scratch"}, // no backup
},
}, scratchCfg())
if params["mp0"] != "local-lvm:2,mp=/mnt/data,backup=1" {
t.Errorf("data mount must carry backup=1: mp0=%q", params["mp0"])
}
if params["mp1"] != "local-lvm:1,mp=/mnt/scratch" {
t.Errorf("non-backup mount must NOT carry backup=1: mp1=%q", params["mp1"])
}
}
// The golden-carried Docker-data volume is grown via a SEPARATE resize on its mpN slot (B4),
// alongside (but distinct from) the rootfs grow.
func TestRunBringUp_StorageSplit_DataVolGrow(t *testing.T) {
const vmid = 8050
api := &fakeAPI{cfg: map[int]proxmox.GuestConfig{vmid: scratchCfg()}}
e, _, q := newEngine(t, api, EmptyProvider{})
defer q.Close()
res := e.RunBringUp(context.Background(), BringUpSpec{
Mode: ModeProvision, Archive: "local:backup/golden.tar.zst", VMID: vmid,
RestoreStorage: "local-lvm", Hostname: "felhom-prov-8050",
RootfsGrowGB: 8, DataVolGrowGB: 240, // grows mp0 (DefaultDataVolMount)
})
if res.Err != nil || !res.Pass {
t.Fatalf("provision must pass, got %+v", res)
}
// TWO resizes: rootfs +8G and the Docker-data volume mp0 +240G.
if len(api.resizes) != 2 {
t.Fatalf("expected rootfs + data-volume resizes, got %+v", api.resizes)
}
var sawRootfs, sawData bool
for _, r := range api.resizes {
if r.disk == "rootfs" && r.size == "+8G" {
sawRootfs = true
}
if r.disk == "mp0" && r.size == "+240G" {
sawData = true
}
}
if !sawRootfs || !sawData {
t.Errorf("want rootfs +8G AND mp0 +240G, got %+v", api.resizes)
}
}
func TestRunBringUp_CompensatingRollback(t *testing.T) {
const vmid = 8000
lockBackoffFast(t)