diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go index 73021df3d50..5472af2b086 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go @@ -30,6 +30,13 @@ const ( // referring to a PCIe (Peripheral Component Interconnect Express) Root Complex. // This attribute can be used to identify devices that share the same PCIe Root Complex. StandardDeviceAttributePCIeRoot resourceapi.QualifiedName = StandardDeviceAttributePrefix + "pcieRoot" + + // StandardDeviceAttributePCIBusID is a standard device attribute name + // which describes the PCI Bus address of the PCI device. + // The value is a string value in the extended BDF notation (Domain:Bus:Device.Function), + // referring to a PCI (Peripheral Component Interconnect) device. + // This attribute can be used to identify PCI devices. + StandardDeviceAttributePCIBusID resourceapi.QualifiedName = StandardDeviceAttributePrefix + "pciBusID" ) // DeviceAttribute represents a device attribute name and its value diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux.go index bed59d52a34..5576f7d43dd 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux.go @@ -34,13 +34,8 @@ import ( // // ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation func GetPCIeRootAttributeByPCIBusID(pciBusID string) (DeviceAttribute, error) { - if pciBusID == "" { - return DeviceAttribute{}, fmt.Errorf("PCI Bus ID cannot be empty") - } - - bdfRegexp := regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`) - if !bdfRegexp.MatchString(pciBusID) { - return DeviceAttribute{}, fmt.Errorf("invalid PCI Bus ID format: %s", pciBusID) + if err := verifyPCIBDFFormat(pciBusID); err != nil { + return DeviceAttribute{}, err } pcieRoot, err := resolvePCIeRoot(pciBusID) @@ -100,3 +95,39 @@ func resolvePCIeRoot(pciBusID string) (string, error) { return pcieRootPart, nil } + +// GetPCIBusIDAttribute returns a DeviceAttribute with the PCI Bus Address("::.") +// of a PCI device as a string value. +// +// It returns an error if the PCI Bus ID is empty or is not in +// extended BDF (Domain:Bus:Device.Function) format, e.g., "0123:45:1e.7" +// +// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +func GetPCIBusIDAttribute(pciBusID string) (DeviceAttribute, error) { + if err := verifyPCIBDFFormat(pciBusID); err != nil { + return DeviceAttribute{}, err + } + + attr := DeviceAttribute{ + Name: StandardDeviceAttributePCIBusID, + Value: resourceapi.DeviceAttribute{StringValue: &pciBusID}, + } + + return attr, nil +} + +// verifyPCIBDFFormat verifies that the PCI Bus ID is in extended BDF (Domain:Bus:Device.Function) format. +// +// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +func verifyPCIBDFFormat(pciBusID string) error { + if pciBusID == "" { + return fmt.Errorf("PCI Bus ID cannot be empty") + } + + bdfRegexp := regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`) + if !bdfRegexp.MatchString(pciBusID) { + return fmt.Errorf("invalid PCI Bus ID format: %s", pciBusID) + } + + return nil +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux_test.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux_test.go index d3fdf3ed159..0af01530787 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_linux_test.go @@ -136,6 +136,61 @@ func TestGetPCIeRootAttributePCIBusID(t *testing.T) { } } +func TestGetPCIBusIDAttribute(t *testing.T) { + pciBusID := "0000:02:00.0" + expectedAttribute := DeviceAttribute{ + Name: StandardDeviceAttributePCIBusID, + Value: resourceapi.DeviceAttribute{StringValue: ptr.To(pciBusID)}, + } + + tests := map[string]struct { + address string + expectedAttribute *DeviceAttribute + expectsError bool + expectedErrMsg string + }{ + "valid": { + address: pciBusID, + expectedAttribute: &expectedAttribute, + expectsError: false, + }, + "invalid empty PCI Bus ID": { + address: "", + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "PCI Bus ID cannot be empty", + }, + "invalid PCI Bus ID format": { + address: "invalid-pci-id", + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "invalid PCI Bus ID format: invalid-pci-id", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + got, err := GetPCIBusIDAttribute(test.address) + if test.expectsError { + if err == nil { + t.Errorf("Expected error but got none") + return + } + if !strings.Contains(err.Error(), test.expectedErrMsg) { + t.Errorf("Expected error message to contain %q, got %q", test.expectedErrMsg, err.Error()) + return + } + return + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(got, *test.expectedAttribute) { + t.Errorf("Expected attribute %v, got %v", test.expectedAttribute, got) + } + }) + } +} + func touchFile(t *testing.T, path string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {