package runtime import ( "context" "testing" "time" ) // TestPveVMRuntime_HappyPath verifies the qm lifecycle. func TestPveVMRuntime_HappyPath(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm create", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("qm start", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("qm shutdown", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("qm stop", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("qm status", func(cmd string) ([]byte, int) { return []byte("status: running\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "local:vmdir/disk.qcow2", "", srv.addr()) ctx, cancel := withTimeout(10 * time.Second) defer cancel() if err := v.Prepare(ctx, a); err != nil { t.Fatalf("Prepare: %v", err) } pid, err := v.Start(ctx, a) if err != nil { t.Fatalf("Start: %v", err) } if pid <= 0 { t.Fatalf("pid = %d, want > 0", pid) } st, err := v.Status(ctx, a) if err != nil { t.Fatalf("Status: %v", err) } if st != StateRunning { t.Errorf("Status = %q, want running", st) } if err := v.Stop(ctx, a); err != nil { t.Fatalf("Stop: %v", err) } } // TestPveVMRuntime_StatusStopped verifies Status maps "stopped". func TestPveVMRuntime_StatusStopped(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm status", func(cmd string) ([]byte, int) { return []byte("status: stopped\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) st, _ := v.Status(context.Background(), a) if st != StateStopped { t.Errorf("Status = %q, want stopped", st) } } // TestPveVMRuntime_StatusUnknown verifies Status returns pending on // unrecognized output. func TestPveVMRuntime_StatusUnknown(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm status", func(cmd string) ([]byte, int) { return []byte("weird state\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) st, _ := v.Status(context.Background(), a) if st != StatePending { t.Errorf("Status = %q, want pending", st) } } // TestPveVMRuntime_PrepareNoImage verifies Prepare errors with no disk. func TestPveVMRuntime_PrepareNoImage(t *testing.T) { v := NewPveVMRuntime(nil) a := allocNoImage("pve-vm") if err := v.Prepare(context.Background(), a); err == nil { t.Error("Prepare with no disk should error") } } // TestPveVMRuntime_PrepareNilRuntime verifies Prepare errors when // the Spec has no Runtime block at all. func TestPveVMRuntime_PrepareNilRuntime(t *testing.T) { v := NewPveVMRuntime(nil) a := &Alloc{ID: "x", Runtime: "pve-vm", Spec: nil} if err := v.Prepare(context.Background(), a); err == nil { t.Error("Prepare with nil spec should error") } } // TestPveVMRuntime_StartError verifies Start propagates qm start errors. func TestPveVMRuntime_StartError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm start", func(cmd string) ([]byte, int) { return []byte("already running\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) if _, err := v.Start(context.Background(), a); err == nil { t.Error("Start with qm error should error") } } // TestPveVMRuntime_StopError verifies Stop errors on qm stop failure. func TestPveVMRuntime_StopError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm shutdown", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("qm stop", func(cmd string) ([]byte, int) { return []byte("vm locked\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) if err := v.Stop(context.Background(), a); err == nil { t.Error("Stop with qm error should error") } } // TestPveVMRuntime_StatusError verifies Status returns failed on qm // status error. func TestPveVMRuntime_StatusError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm status", func(cmd string) ([]byte, int) { return []byte("no such vm\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) st, err := v.Status(context.Background(), a) if err == nil { t.Error("Status with qm error should error") } if st != StateFailed { t.Errorf("Status = %q, want failed", st) } } // TestPveVMRuntime_PrepareError verifies Prepare propagates qm create // errors. func TestPveVMRuntime_PrepareError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("qm create", func(cmd string) ([]byte, int) { return []byte("vmid already exists\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() v := NewPveVMRuntime(tr) a := allocWithNode("pve-vm", "img", "", srv.addr()) if err := v.Prepare(context.Background(), a); err == nil { t.Error("Prepare with qm create error should error") } } // --- PveCTRuntime --- func TestPveCTRuntime_HappyPath(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct create", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("pct start", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("pct shutdown", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("pct stop", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("pct status", func(cmd string) ([]byte, int) { return []byte("status: running\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "local:vztmpl/alpine.tar.xz", "", srv.addr()) ctx, cancel := withTimeout(10 * time.Second) defer cancel() if err := c.Prepare(ctx, a); err != nil { t.Fatalf("Prepare: %v", err) } pid, err := c.Start(ctx, a) if err != nil { t.Fatalf("Start: %v", err) } if pid <= 0 { t.Fatalf("pid = %d, want > 0", pid) } st, err := c.Status(ctx, a) if err != nil { t.Fatalf("Status: %v", err) } if st != StateRunning { t.Errorf("Status = %q, want running", st) } if err := c.Stop(ctx, a); err != nil { t.Fatalf("Stop: %v", err) } } // TestPveCTRuntime_StatusStopped verifies pct status stopped. func TestPveCTRuntime_StatusStopped(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct status", func(cmd string) ([]byte, int) { return []byte("status: stopped\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "tmpl", "", srv.addr()) st, _ := c.Status(context.Background(), a) if st != StateStopped { t.Errorf("Status = %q, want stopped", st) } } // TestPveCTRuntime_StatusUnknown verifies pending on unrecognized // output. func TestPveCTRuntime_StatusUnknown(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct status", func(cmd string) ([]byte, int) { return []byte("unknown\n"), 0 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "tmpl", "", srv.addr()) st, _ := c.Status(context.Background(), a) if st != StatePending { t.Errorf("Status = %q, want pending", st) } } // TestPveCTRuntime_PrepareNoImage verifies Prepare errors with no // template. func TestPveCTRuntime_PrepareNoImage(t *testing.T) { c := NewPveCTRuntime(nil) a := allocNoImage("pve-ct") if err := c.Prepare(context.Background(), a); err == nil { t.Error("Prepare with no template should error") } } // TestPveCTRuntime_PrepareNilRuntime verifies Prepare errors on nil // spec. func TestPveCTRuntime_PrepareNilRuntime(t *testing.T) { c := NewPveCTRuntime(nil) a := &Alloc{ID: "x", Runtime: "pve-ct", Spec: nil} if err := c.Prepare(context.Background(), a); err == nil { t.Error("Prepare with nil spec should error") } } // TestPveCTRuntime_StartError verifies Start errors on pct start fail. func TestPveCTRuntime_StartError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct start", func(cmd string) ([]byte, int) { return []byte("already running\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "tmpl", "", srv.addr()) if _, err := c.Start(context.Background(), a); err == nil { t.Error("Start with pct error should error") } } // TestPveCTRuntime_StopError verifies Stop errors on pct stop fail. func TestPveCTRuntime_StopError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct shutdown", func(cmd string) ([]byte, int) { return nil, 0 }) srv.setHandler("pct stop", func(cmd string) ([]byte, int) { return []byte("container locked\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "tmpl", "", srv.addr()) if err := c.Stop(context.Background(), a); err == nil { t.Error("Stop with pct error should error") } } // TestPveCTRuntime_StatusError verifies Status failed on pct status // error. func TestPveCTRuntime_StatusError(t *testing.T) { srv := newFakeServer(t) defer srv.close() srv.setHandler("pct status", func(cmd string) ([]byte, int) { return []byte("no such container\n"), 1 }) tr := realTransport(t, srv) defer tr.Close() c := NewPveCTRuntime(tr) a := allocWithNode("pve-ct", "tmpl", "", srv.addr()) st, err := c.Status(context.Background(), a) if err == nil { t.Error("Status with pct error should error") } if st != StateFailed { t.Errorf("Status = %q, want failed", st) } } // TestVMIDFor verifies the VMID is deterministic and within range. func TestVMIDFor(t *testing.T) { a := &Alloc{ID: "alloc-1"} id := vmidFor(a) if id < 100 || id > 99999 { t.Errorf("vmidFor = %d, want in [100, 99999]", id) } // stability if vmidFor(a) != id { t.Error("vmidFor not stable") } // two allocs should differ b := &Alloc{ID: "alloc-2"} if vmidFor(b) == id { t.Logf("note: two allocs collided on vmid (rare but allowed)") } }