package proxmox import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestPCIAddressAndHostPCIParsing(t *testing.T) { for _, value := range []string{"01:00.0", "0000:01:00.0", " 0000:01:00.0 "} { got, ok := NormalizePCIAddress(value) if !ok || got != "0000:01:00.0" { t.Fatalf("normalization %q: %q %v", value, got, ok) } } for _, value := range []string{"01:20.0", "01:00.8", "../../config", "01:00.0,pcie=1", "000000:01:00.0"} { if _, ok := NormalizePCIAddress(value); ok { t.Fatalf("accepted invalid address %q", value) } } if !PCIAddressMatches("01:00", "0000:01:00.1") || PCIAddressMatches("01:00.0", "0000:01:00.1") || PCIAddressMatches("0001:01:00", "0000:01:00.0") { t.Fatal("function/domain matching incorrect") } parsed, err := ParseHostPCI("host=01:00;0001:02:00.0,pcie=1,x-vga=1") if err != nil || len(parsed.Addresses) != 2 || parsed.Addresses[0] != "0000:01:00" { t.Fatalf("parse multi-function PCI: %+v %v", parsed, err) } parsed, err = ParseHostPCI("mapping=gpu-pool,mdev=nvidia-1") if err != nil || parsed.Mapping != "gpu-pool" || !parsed.Mediated { t.Fatalf("parse mediated mapping: %+v %v", parsed, err) } for _, value := range []string{"", "mapping=x,host=01:00", "host=01:00,host=02:00", "host=invalid", "mapping=", "pcie=1"} { if _, err := ParseHostPCI(value); err == nil { t.Fatalf("accepted invalid hostpci %q", value) } } } func TestGPUReadOnlyPVEContract(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("unexpected write: %s", r.Method) } var data any switch r.URL.Path { case "/api2/json/nodes/node-a/hardware/pci": if r.URL.Query().Get("verbose") != "1" { t.Error("missing verbose PCI properties") } data = []map[string]any{{"id": "0000:00:02.0", "class": "0x030000", "iommugroup": 0}, {"id": "0000:01:00.1", "class": "0x040300", "iommugroup": 2}} case "/api2/json/nodes/node-a/qemu/8000/pending": data = []map[string]any{{"key": "hostpci0", "value": "01:00.0", "pending": "02:00.0", "delete": 1}} case "/api2/json/cluster/mapping/pci": data = []map[string]any{{"id": "gpu-map", "map": []string{"node=node-a,path=0000:01:00.0,id=10de:1234"}}} default: t.Errorf("unexpected path: %s", r.URL.Path) http.NotFound(w, r) return } _ = json.NewEncoder(w).Encode(map[string]any{"data": data}) })) defer srv.Close() c := &Client{BaseURL: srv.URL, HTTP: srv.Client()} devices, err := c.PCIDevices(context.Background(), "node-a") if err != nil || len(devices) != 2 || !devices[0].IsGPU() || devices[1].IsGPU() || devices[0].IOMMUGroup == nil || *devices[0].IOMMUGroup != 0 { t.Fatalf("PCI decode: %+v %v", devices, err) } pending, err := c.VMPending(context.Background(), "node-a", 8000) if err != nil || len(pending) != 1 || string(pending[0].Delete) != "1" { t.Fatalf("pending decode: %+v %v", pending, err) } mappings, err := c.PCIMappings(context.Background()) if err != nil || len(mappings) != 1 || len(mappings[0].Map) != 1 { t.Fatalf("mapping decode: %+v %v", mappings, err) } }