vdi-cloud/platform/pkg/proxmox/gpu.go

129 lines
3.5 KiB
Go

package proxmox
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"regexp"
"strings"
)
type PCIDevice struct {
ID string `json:"id"`
Class string `json:"class"`
Vendor string `json:"vendor"`
VendorName string `json:"vendor_name"`
Device string `json:"device"`
DeviceName string `json:"device_name"`
IOMMUGroup *int `json:"iommugroup"`
}
type PCIMapping struct {
ID string `json:"id"`
Map []string `json:"map"`
}
type PendingOption struct {
Key string `json:"key"`
Value json.RawMessage `json:"value"`
Pending json.RawMessage `json:"pending"`
Delete json.RawMessage `json:"delete"`
}
func (c *Client) PCIDevices(ctx context.Context, node string) ([]PCIDevice, error) {
var items []PCIDevice
err := c.request(ctx, "GET", "/nodes/"+url.PathEscape(node)+"/hardware/pci?verbose=1", nil, &items)
return items, err
}
func (c *Client) PCIMappings(ctx context.Context) ([]PCIMapping, error) {
var items []PCIMapping
err := c.request(ctx, "GET", "/cluster/mapping/pci", nil, &items)
return items, err
}
func (c *Client) VMPending(ctx context.Context, node string, vmid int) ([]PendingOption, error) {
var items []PendingOption
err := c.request(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/pending", url.PathEscape(node), vmid), nil, &items)
return items, err
}
var pciAddressPattern = regexp.MustCompile(`^(?:[0-9a-f]{4}:)?[0-9a-f]{2}:[0-1][0-9a-f](?:\.[0-7])?$`)
var hostPCIKeyPattern = regexp.MustCompile(`^hostpci[0-9]+$`)
// A functionless address selects every function in that PCI slot.
func NormalizePCIAddress(value string) (string, bool) {
value = strings.ToLower(strings.TrimSpace(value))
if !pciAddressPattern.MatchString(value) {
return "", false
}
if strings.Count(value, ":") == 1 {
value = "0000:" + value
}
return value, true
}
func PCIAddressMatches(selector, address string) bool {
selector, ok := NormalizePCIAddress(selector)
if !ok {
return false
}
address, ok = NormalizePCIAddress(address)
return ok && (selector == address || !strings.Contains(selector, ".") && strings.HasPrefix(address, selector+"."))
}
func (d PCIDevice) IsGPU() bool {
class := strings.TrimPrefix(strings.ToLower(d.Class), "0x")
return len(class) == 6 && strings.HasPrefix(class, "03")
}
type HostPCI struct {
Addresses []string
Mapping string
Mediated bool
}
func IsHostPCIKey(key string) bool { return hostPCIKeyPattern.MatchString(key) }
func PCIProperties(value string) (map[string]string, error) {
properties := map[string]string{}
for i, part := range strings.Split(value, ",") {
key, val, found := strings.Cut(strings.TrimSpace(part), "=")
if !found && i == 0 {
key, val = "host", key
} else if !found {
return nil, errors.New("invalid PCI property")
}
if _, duplicate := properties[key]; key == "" || val == "" || duplicate {
return nil, errors.New("invalid PCI property")
}
properties[key] = val
}
return properties, nil
}
func ParseHostPCI(value string) (HostPCI, error) {
var out HostPCI
properties, err := PCIProperties(value)
if err != nil {
return out, err
}
out.Mapping = properties["mapping"]
out.Mediated = properties["mdev"] != ""
if (properties["host"] == "") == (out.Mapping == "") {
return out, errors.New("expected a host or mapping")
}
if properties["host"] != "" {
for _, address := range strings.Split(properties["host"], ";") {
normalized, ok := NormalizePCIAddress(address)
if !ok {
return out, errors.New("invalid PCI address")
}
out.Addresses = append(out.Addresses, normalized)
}
}
return out, nil
}