Manage image artifacts and show VM create progress
Stop relying on ad hoc rootfs handling by adding image promotion, managed work-seed fingerprint metadata, and lazy self-healing for older managed images after the first create. Rebuild guest images with baked SSH access, a guest NIC bootstrap, and default opencode services, and add the staged Void kernel/initramfs/modules workflow so void-exp uses a matching Void boot stack. Replace the opaque blocking vm.create RPC with a begin/status flow that prints live stages in the CLI while still waiting for vsock health and opencode on guest port 4096. Validate with GOCACHE=/tmp/banger-gocache go test ./... and live void-exp create/delete smoke runs.
This commit is contained in:
parent
9f09b0d25c
commit
30f0c0b54a
37 changed files with 2334 additions and 99 deletions
|
|
@ -170,6 +170,17 @@ func TestImageRegisterFlagsExist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestImagePromoteCommandExists(t *testing.T) {
|
||||
root := NewBangerCommand()
|
||||
image, _, err := root.Find([]string{"image"})
|
||||
if err != nil {
|
||||
t.Fatalf("find image: %v", err)
|
||||
}
|
||||
if _, _, err := image.Find([]string{"promote"}); err != nil {
|
||||
t.Fatalf("find promote: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMKillFlagsExist(t *testing.T) {
|
||||
root := NewBangerCommand()
|
||||
vm, _, err := root.Find([]string{"vm"})
|
||||
|
|
@ -304,6 +315,95 @@ func TestVMCreateParamsFromFlagsRejectsNonPositiveCPUAndMemory(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunVMCreatePollsUntilDone(t *testing.T) {
|
||||
origBegin := vmCreateBeginFunc
|
||||
origStatus := vmCreateStatusFunc
|
||||
origCancel := vmCreateCancelFunc
|
||||
t.Cleanup(func() {
|
||||
vmCreateBeginFunc = origBegin
|
||||
vmCreateStatusFunc = origStatus
|
||||
vmCreateCancelFunc = origCancel
|
||||
})
|
||||
|
||||
vm := model.VMRecord{
|
||||
ID: "vm-id",
|
||||
Name: "devbox",
|
||||
Spec: model.VMSpec{WorkDiskSizeBytes: model.DefaultWorkDiskSize},
|
||||
Runtime: model.VMRuntime{
|
||||
State: model.VMStateRunning,
|
||||
GuestIP: "172.16.0.2",
|
||||
DNSName: "devbox.vm",
|
||||
},
|
||||
}
|
||||
vmCreateBeginFunc = func(context.Context, string, api.VMCreateParams) (api.VMCreateBeginResult, error) {
|
||||
return api.VMCreateBeginResult{
|
||||
Operation: api.VMCreateOperation{
|
||||
ID: "op-1",
|
||||
Stage: "prepare_work_disk",
|
||||
Detail: "cloning work seed",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
statusCalls := 0
|
||||
vmCreateStatusFunc = func(context.Context, string, string) (api.VMCreateStatusResult, error) {
|
||||
statusCalls++
|
||||
if statusCalls == 1 {
|
||||
return api.VMCreateStatusResult{
|
||||
Operation: api.VMCreateOperation{
|
||||
ID: "op-1",
|
||||
Stage: "wait_opencode",
|
||||
Detail: "waiting for opencode on guest port 4096",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return api.VMCreateStatusResult{
|
||||
Operation: api.VMCreateOperation{
|
||||
ID: "op-1",
|
||||
Stage: "ready",
|
||||
Detail: "vm is ready",
|
||||
Done: true,
|
||||
Success: true,
|
||||
VM: &vm,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
vmCreateCancelFunc = func(context.Context, string, string) error {
|
||||
t.Fatal("cancel should not be called")
|
||||
return nil
|
||||
}
|
||||
|
||||
got, err := runVMCreate(context.Background(), "/tmp/bangerd.sock", &bytes.Buffer{}, api.VMCreateParams{Name: "devbox"})
|
||||
if err != nil {
|
||||
t.Fatalf("runVMCreate: %v", err)
|
||||
}
|
||||
if got.Name != vm.Name || got.Runtime.GuestIP != vm.Runtime.GuestIP {
|
||||
t.Fatalf("vm = %+v, want %+v", got, vm)
|
||||
}
|
||||
if statusCalls != 2 {
|
||||
t.Fatalf("statusCalls = %d, want 2", statusCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMCreateProgressRendererSuppressesDuplicateLines(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
renderer := &vmCreateProgressRenderer{out: &stderr, enabled: true}
|
||||
|
||||
renderer.render(api.VMCreateOperation{Stage: "prepare_work_disk", Detail: "cloning work seed"})
|
||||
renderer.render(api.VMCreateOperation{Stage: "prepare_work_disk", Detail: "cloning work seed"})
|
||||
renderer.render(api.VMCreateOperation{Stage: "wait_opencode", Detail: "waiting for opencode on guest port 4096"})
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(stderr.String()), "\n")
|
||||
if len(lines) != 2 {
|
||||
t.Fatalf("rendered lines = %q, want 2 lines", stderr.String())
|
||||
}
|
||||
if lines[0] != "[vm create] preparing work disk: cloning work seed" {
|
||||
t.Fatalf("first line = %q", lines[0])
|
||||
}
|
||||
if lines[1] != "[vm create] waiting for opencode: waiting for opencode on guest port 4096" {
|
||||
t.Fatalf("second line = %q", lines[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMSetParamsFromFlagsConflict(t *testing.T) {
|
||||
if _, err := vmSetParamsFromFlags("devbox", -1, -1, "", true, true); err == nil {
|
||||
t.Fatal("expected nat conflict error")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue