package server import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "vdi-cloud/platform/pkg/proxmox" ) func fakeGPUPVE(t *testing.T, f *fixture, mode string) { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("GPU endpoint attempted write: %s %s", r.Method, r.URL.Path) w.WriteHeader(500) return } path := strings.TrimPrefix(r.URL.Path, "/api2/json") var data any switch path { case "/cluster/resources": if mode == "cluster-failed" { w.WriteHeader(503) return } data = []map[string]any{{"type": "node", "node": "node-a", "name": "node-a", "status": "online"}, {"type": "qemu", "node": "node-a", "vmid": 8000, "name": "managed-windows", "status": "stopped"}, {"type": "qemu", "node": "node-a", "vmid": 8001, "name": "unmanaged-ubuntu", "status": "running"}} case "/nodes/node-a/hardware/pci": if mode == "pci-denied" { w.WriteHeader(403) return } data = []map[string]any{{"id": "0000:00:02.0", "class": "0x030000", "device_name": "Intel GPU", "vendor_name": "Intel", "iommugroup": 0}, {"id": "0000:01:00.0", "class": "0x030200", "device_name": "NVIDIA GPU", "vendor": "0x10de", "iommugroup": 2}, {"id": "0000:01:00.1", "class": "0x040300", "device_name": "Audio"}, {"id": "0000:02:00.0", "class": "0x030000", "device_name": "AMD GPU", "iommugroup": -1}} case "/access/permissions": allowed := 1 if mode == "partial-visibility" { allowed = 0 } data = map[string]any{"/": map[string]int{"VM.Audit": allowed}} case "/nodes/node-a/qemu/8000/pending": value := "01:00,pcie=1" if mode == "mapping" || mode == "mapping-missing" || mode == "mapping-candidates" { value = "mapping=gpu-map" } if mode == "malformed" { value = "host=invalid,SECRET=do-not-return" } rows := []map[string]any{{"key": "hostpci0", "value": value, "pending": "host=02:00.0", "delete": 1}} if mode == "custom" { rows = append(rows, map[string]any{"key": "args", "value": "-device vfio-pci,host=00:02.0 SECRET-do-not-return"}) } data = rows case "/nodes/node-a/qemu/8001/pending": if mode == "config-failed" { w.WriteHeader(403) return } data = []map[string]any{} if mode == "multiple" { data = []map[string]any{{"key": "hostpci0", "value": "0000:01:00.0,mdev=test"}} } case "/cluster/mapping/pci": if mode == "mapping-missing" { w.WriteHeader(403) return } entries := []string{"node=node-a,path=0000:01:00.0,id=10de:1234", "node=node-b,path=0000:03:00.0,id=10de:1234"} if mode == "mapping-candidates" { entries = append(entries, "node=node-a,path=0000:00:02.0,id=8086:1234") } data = []map[string]any{{"id": "gpu-map", "map": entries}} default: t.Errorf("unexpected GPU PVE read: %s", path) http.NotFound(w, r) return } _ = json.NewEncoder(w).Encode(map[string]any{"data": data}) })) t.Cleanup(srv.Close) f.server.PVE = &proxmox.Client{BaseURL: srv.URL, HTTP: srv.Client()} } func TestGPUInventoryReadOnlyCoverage(t *testing.T) { for _, mode := range []string{"normal", "partial-visibility", "config-failed", "mapping", "mapping-missing", "mapping-candidates", "custom", "malformed", "multiple"} { t.Run(mode, func(t *testing.T) { f := newFixtureWorker(t, false) fakeGPUPVE(t, f, mode) if _, err := f.db.Exec("UPDATE desktops SET provider='proxmox',node='node-a',vmid=8000 WHERE id='desk_demo_windows'"); err != nil { t.Fatal(err) } code, out := f.request("GET", "/resources/gpus?node=node-a", f.login("viewer"), nil, "") expect(t, 200, code, out) raw, _ := json.Marshal(out) if strings.Contains(string(raw), "SECRET") { t.Fatal("raw VM config leaked") } var view struct { Complete bool VisibleVMCount int ScannedVMCount int Items []gpuItem Issues []gpuIssue } if err := json.Unmarshal(raw, &view); err != nil { t.Fatal(err) } if len(view.Items) != 3 || view.VisibleVMCount != 2 || view.Items[0].IOMMUGroup == nil || *view.Items[0].IOMMUGroup != 0 || view.Items[2].IOMMUGroup != nil { t.Fatalf("incorrect inventory %s", raw) } complete := mode == "normal" || mode == "mapping" || mode == "multiple" if view.Complete != complete || (len(view.Issues) == 0) != complete { t.Fatalf("coverage state %s", raw) } if complete && view.Items[0].State != "unreferenced" { t.Fatalf("unreferenced classification %s", raw) } if mode == "config-failed" && view.ScannedVMCount != 1 { t.Fatalf("failed config counted as scanned %s", raw) } if mode == "custom" || mode == "partial-visibility" || mode == "config-failed" || mode == "mapping-missing" || mode == "malformed" { if view.Items[0].State != "unknown" { t.Fatalf("unknown classified as free %s", raw) } } if mode == "multiple" { if view.Items[1].State != "multiple" || len(view.Items[1].References) != 2 { t.Fatalf("multiple references lost %s", raw) } } if mode == "normal" || mode == "mapping" { owner := view.Items[1].References[0] if owner.DesktopID != "desk_demo_windows" || !owner.PendingRemoval || owner.Phase != "current" || owner.VMStatus != "stopped" { t.Fatalf("owner metadata %s", raw) } if view.Items[2].References[0].Phase != "pending" { t.Fatalf("pending reservation lost %s", raw) } if mode == "mapping" && (owner.Source != "mapping" || owner.Mapping != "gpu-map") { t.Fatalf("mapping owner %s", raw) } } var count int if err := f.db.Get(&count, "SELECT count(*) FROM tasks"); err != nil || count != 0 { t.Fatalf("read created tasks: %d %v", count, err) } }) } } func TestGPUInventoryErrorsAndAuthentication(t *testing.T) { f := newFixtureWorker(t, false) admin := f.login("admin") code, out := f.request("GET", "/resources/gpus?node=node-a", "", nil, "") expect(t, 401, code, out) code, out = f.request("GET", "/resources/gpus?node=node-a", admin, nil, "") expect(t, 503, code, out) for _, mode := range []string{"pci-denied", "cluster-failed", "normal"} { fakeGPUPVE(t, f, mode) code, out = f.request("GET", "/resources/gpus?node=node-a", admin, nil, "") want := 502 if mode == "normal" { want = 200 } expect(t, want, code, out) } code, out = f.request("GET", "/resources/gpus", admin, nil, "") expect(t, 400, code, out) code, out = f.request("GET", "/resources/gpus?node=unknown", admin, nil, "") expect(t, 404, code, out) code, out = f.request("GET", "/resources/gpus?node=..%2Fsecret", admin, nil, "") expect(t, 404, code, out) r := httptest.NewRequest("POST", "/api/v1/resources/gpus?node=node-a", nil) r.Header.Set("Authorization", "Bearer "+admin) w := httptest.NewRecorder() f.server.Router.ServeHTTP(w, r) if w.Code != 404 { t.Fatalf("GPU write route exists: %d", w.Code) } }