diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go deleted file mode 100644 index 41b4129823d..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package deviceattribute - -import resourceapi "k8s.io/dynamic-resource-allocation/api" - -type StandardDeviceAttributesOption func(args *StandardDeviceAttributesOptions) - -// WithStandardPCIDeviceAttributesOpts sets the standard PCI device attributes options -func WithStandardPCIDeviceAttributesOpts(opts ...StandardPCIDeviceAttributesOption) StandardDeviceAttributesOption { - return func(args *StandardDeviceAttributesOptions) { - args.pciDeviceAttributeOpts = append(args.pciDeviceAttributeOpts, opts...) - } -} - -type StandardDeviceAttributesOptions struct { - pciDeviceAttributeOpts []StandardPCIDeviceAttributesOption -} - -// StandardDeviceAttributes generates standard device attributes based on the provided options. -// It currently supports standard PCI device attributes if options are provided. -func StandardDeviceAttributes( - opts ...StandardDeviceAttributesOption, -) (map[resourceapi.QualifiedName]resourceapi.DeviceAttribute, error) { - options := &StandardDeviceAttributesOptions{} - for _, opt := range opts { - opt(options) - } - - attrs := map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{} - - if len(options.pciDeviceAttributeOpts) > 0 { - // Standard PCI device attributes - pciAttrs, err := StandardPCIDeviceAttributes(options.pciDeviceAttributeOpts...) - if err != nil { - return nil, err - } - for k, v := range pciAttrs { - attrs[k] = v - } - } - - return attrs, nil -} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go deleted file mode 100644 index ae04b5f2a51..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/attribute_test.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package deviceattribute - -import ( - "os" - "reflect" - "testing" - - resourceapi "k8s.io/dynamic-resource-allocation/api" - dratesting "k8s.io/dynamic-resource-allocation/testing" - "k8s.io/dynamic-resource-allocation/utils" - "k8s.io/utils/ptr" -) - -func TestAddStandardDeviceAttributes(t *testing.T) { - // Create mock sysfs for testing - testSysfsDir, err := os.MkdirTemp("", "test-sysfs") - if err != nil { - t.Fatalf("failed to create temp sysfs directory: %v", err) - } - t.Cleanup(func() { - if err := os.RemoveAll(testSysfsDir); err != nil { - t.Fatalf("failed to clean up temp sysfs directory: %v", err) - } - }) - - testSysFs := utils.NewSysfsWithRoot(testSysfsDir) - // "0123:45:1e.7" - testPCIAddress := utils.MustNewPCIAddress(0x0123, 0x45, 0x1e, 0x7) - // "1234:56" - testPCIRoot := utils.MustNewPCIRoot(0x1234, 0x56) - - // /sys/bus/pci/devices/0123:45:1e.7 --> /sys/devices/pci1234:56/0123:45:1e.7 - testPCIDevicePath := testSysFs.Devices("pci" + testPCIRoot.String() + "/" + testPCIAddress.String()) - dratesting.TouchFile(t, testPCIDevicePath) - testPCIBusPath := testSysFs.Bus("pci/devices/" + testPCIAddress.String()) - dratesting.CreateSymlink(t, testPCIDevicePath, testPCIBusPath) - - tests := map[string]struct { - opts []StandardDeviceAttributesOption - expectedAttrs map[resourceapi.QualifiedName]resourceapi.DeviceAttribute - }{ - "pci attrs": { - opts: []StandardDeviceAttributesOption{ - WithStandardPCIDeviceAttributesOpts( - WithPCIDeviceAddress(testPCIAddress), - withSysfs(testSysFs), // Use the mock sysfs - ), - }, - expectedAttrs: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ - qualifiedNamePCIeRoot: { - StringValue: ptr.To("pci" + testPCIRoot.String()), - }, - }, - }, - "nil attrs": { - opts: nil, - expectedAttrs: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, - }, - "empty attrs": { - opts: []StandardDeviceAttributesOption{}, - expectedAttrs: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, - }, - "empty pci attrs": { - opts: []StandardDeviceAttributesOption{WithStandardPCIDeviceAttributesOpts()}, - expectedAttrs: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - attrs, err := StandardDeviceAttributes(test.opts...) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !reflect.DeepEqual(attrs, test.expectedAttrs) { - t.Errorf("expected %v, got %v", test.expectedAttrs, attrs) - } - }) - } -} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci.go index 2d9afef6644..9b20430b295 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci.go @@ -17,62 +17,83 @@ limitations under the License. package deviceattribute import ( - "k8s.io/utils/ptr" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" resourceapi "k8s.io/dynamic-resource-allocation/api" - utils "k8s.io/dynamic-resource-allocation/utils" ) var ( - qualifiedNamePCIeRoot = resourceapi.QualifiedName(resourceapi.StandardDeviceAttributePCIeRoot) + bdfRegexp = regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`) ) -type StandardPCIDeviceAttributesOption func(args *StandardPCIDeviceAttributeArgs) - -// WithPCIDeviceAddress sets the PCI address for the PCI device attributes. -func WithPCIDeviceAddress(pciAddress *utils.PCIAddress) StandardPCIDeviceAttributesOption { - return func(args *StandardPCIDeviceAttributeArgs) { - args.pciAddress = pciAddress - } -} - -// StandardPCIDeviceAttributeArgs holds the arguments for generating standard PCI device attributes. -type StandardPCIDeviceAttributeArgs struct { - pciAddress *utils.PCIAddress - sysfs utils.Sysfs -} - -// StandardPCIDeviceAttributes returns standard device attributes for a PCI device. -func StandardPCIDeviceAttributes(opts ...StandardPCIDeviceAttributesOption) (map[resourceapi.QualifiedName]resourceapi.DeviceAttribute, error) { - args := &StandardPCIDeviceAttributeArgs{ - sysfs: utils.NewSysfs(), +// GetPCIeRootAttributeByPCIBusID retrieves the PCIe Root Complex for a given PCI Bus ID. +// in BDF (Bus-Device-Function) format, e.g., "0123:45:1e.7". +// +// It returns a DeviceAttribute with the PCIe Root Complex information("pci:") +// as a string value or an error if the PCI Bus ID is invalid or the root complex cannot be determined. +// +// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +func GetPCIeRootAttributeByPCIBusID(pciBusID string) (resourceapi.DeviceAttribute, error) { + if pciBusID == "" { + return resourceapi.DeviceAttribute{}, fmt.Errorf("PCI Bus ID cannot be empty") } - for _, opt := range opts { - opt(args) + if !bdfRegexp.MatchString(pciBusID) { + return resourceapi.DeviceAttribute{}, fmt.Errorf("invalid PCI Bus ID format: %s", pciBusID) } - if args.pciAddress == nil { - return nil, nil - } - - attrs := make(map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) - - pciRoot, err := args.pciAddress.ResolvePCIRoot(args.sysfs) + // e.g. /sys/devices/pci0000:01/....../0000:00:1f.0, + sysDevicesPath, err := resolveSysDevicesPath(pciBusID) if err != nil { - return nil, err + return resourceapi.DeviceAttribute{}, fmt.Errorf("failed to resolve sysfs path for PCI Bus ID %s: %w", pciBusID, err) } - attrs[qualifiedNamePCIeRoot] = resourceapi.DeviceAttribute{ - StringValue: ptr.To("pci" + pciRoot.String()), - } + pciRootPart := strings.Split(strings.TrimPrefix(sysDevicesPath, sysfs.Devices("")+"/"), "/")[0] - return attrs, nil + return resourceapi.DeviceAttribute{ + StringValue: &pciRootPart, + }, nil } -// This is only for testing purposes. -func withSysfs(sysfs utils.Sysfs) StandardPCIDeviceAttributesOption { - return func(args *StandardPCIDeviceAttributeArgs) { - args.sysfs = sysfs +// resolveSysDevicesPath resolves the /sys/devices path from the PCI Bus ID +// in BDF (Bus-Device-Function) format, e.g., "0123:45:1e.7". +// +// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation +// +// /sys/devices has directory structure which reflects the hardware hierarchy in the system. +// Therefore, the device path may contains intermediate directories(devices). +// Thus, we can not simply find the device path from the PCIAddress. +// Fortunately, /sys/bus/pci/devices/
is a symlink to the actual device path in /sys/devices. +// So we can resolve the actual device path by reading the symlink at /sys/bus/pci/devices/
. +// +// For example, if the PCIAddress is "0000:00:1f.0", +// /sys/bus/pci/devices/0000:00:1f.0 points to +// /sys/devices/pci0000:01/....../0000:00:1f.0, +func resolveSysDevicesPath(pciBusID string) (string, error) { + // e.g. /sys/bus/pci/devices/0000:00:1f.0 + sysBusPath := sysfs.Bus(filepath.Join("pci", "devices", pciBusID)) + + targetRelative, err := os.Readlink(sysBusPath) + if err != nil { + return "", fmt.Errorf("failed to read symlink for PCI Bus ID %s: %w", sysBusPath, err) } + var targetAbs string + if filepath.IsAbs(targetRelative) { + targetAbs = targetRelative + } else { + // If the target is a relative path, we need to resolve it relative to the symlink's directory. + targetAbs = filepath.Join(filepath.Dir(sysBusPath), targetRelative) + } + + // targetAbs must be /sys/devices/pci0000:01/....../0000:00:1f.0 + devicePathPrefix := sysfs.Devices("pci") + if !strings.HasPrefix(targetAbs, devicePathPrefix) || filepath.Base(targetAbs) != pciBusID { + return "", fmt.Errorf("invalid symlink target for PCI Bus ID %s: %s", sysBusPath, targetAbs) + } + + return targetAbs, nil } diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go index 3f4b4ffe54a..28a5fa65ed4 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/pci_test.go @@ -26,133 +26,117 @@ import ( "k8s.io/utils/ptr" resourceapi "k8s.io/dynamic-resource-allocation/api" - dratesting "k8s.io/dynamic-resource-allocation/testing" - utils "k8s.io/dynamic-resource-allocation/utils" ) -func TestStandardPCIDeviceAttributes(t *testing.T) { - pcieRootAttrName := resourceapi.QualifiedName(resourceapi.StandardDeviceAttributePCIeRoot) - - address := utils.MustNewPCIAddress(0x0, 0x1, 0x2, 0x3) - root := utils.MustNewPCIRoot(0x0, 0x1) - standardAttributes := map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ - pcieRootAttrName: { - StringValue: ptr.To("pci" + root.String()), - }, +func TestGetPCIeRootBAttributeyPCIBusID(t *testing.T) { + pciBusID := "0000:01:02.3" + pcieRoot := "pci0000:01" + expectedAttribute := &resourceapi.DeviceAttribute{ + StringValue: ptr.To(pcieRoot), } tests := map[string]struct { - pciAddress *utils.PCIAddress - sysfs func(*testing.T, string) utils.Sysfs - expectedAttributes map[resourceapi.QualifiedName]resourceapi.DeviceAttribute - expectsErr bool - expectedErrMsg string + smockSysfsSetup func(t *testing.T, mockSysfs sysfsPath) + address string + expectedAttribute *resourceapi.DeviceAttribute + expectsError bool + expectedErrMsg string }{ - "nil address": { - pciAddress: nil, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - return utils.NewSysfs() + "valid": { + smockSysfsSetup: func(t *testing.T, mockSysfs sysfsPath) { + devicePath := mockSysfs.Devices(filepath.Join(pcieRoot, "0000:00:13.1", pciBusID)) + TouchFile(t, devicePath) + busPath := mockSysfs.Bus(filepath.Join("pci", "devices", pciBusID)) + CreateSymlink(t, devicePath, busPath) }, - expectedAttributes: nil, + address: pciBusID, + expectedAttribute: expectedAttribute, + expectsError: false, }, - "valid address": { - pciAddress: address, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - sysfs := utils.NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci"+root.String(), address.String())) - dratesting.TouchFile(t, devicePath) - - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, devicePath, busPath) - return sysfs - }, - expectedAttributes: standardAttributes, + "invalid empty PCI Bus ID": { + address: "", + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "PCI Bus ID cannot be empty", }, - "non-existent address": { - pciAddress: address, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - return utils.NewSysfsWithRoot(testSysfsRoot) - }, - expectsErr: true, - expectedErrMsg: "no such file or directory", + "invalid PCI Bus ID format": { + address: "invalid-pci-id", + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "invalid PCI Bus ID format: invalid-pci-id", }, - "devices path not a symlink": { - pciAddress: address, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - sysfs := utils.NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci"+root.String(), address.String())) - dratesting.TouchFile(t, devicePath) - - // Create a file instead of a symlink - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.TouchFile(t, busPath) - return sysfs - }, - expectsErr: true, - expectedErrMsg: "invalid argument", + "invalid no exist PCI Bus ID": { + address: pciBusID, + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "no such file or directory", }, - "invalid symlink target": { - pciAddress: address, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - sysfs := utils.NewSysfsWithRoot(testSysfsRoot) - invalidDevicePath := sysfs.Devices(filepath.Join("invalid", "pci"+root.String(), address.String())) - dratesting.TouchFile(t, invalidDevicePath) - - // Create a symlink that points to an invalid path - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, invalidDevicePath, busPath) - return sysfs + "invalid symlink": { + smockSysfsSetup: func(t *testing.T, mockSysfs sysfsPath) { + devicePath := mockSysfs.Devices(filepath.Join("invalid-pci-root", "0000:00:13.1", pciBusID)) + TouchFile(t, devicePath) + busPath := mockSysfs.Bus(filepath.Join("pci", "devices", pciBusID)) + CreateSymlink(t, devicePath, busPath) }, - expectsErr: true, - expectedErrMsg: "invalid symlink target for PCI device", - }, - "invalid root format": { - pciAddress: address, - sysfs: func(t *testing.T, testSysfsRoot string) utils.Sysfs { - sysfs := utils.NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci-invalid-root", address.String())) - dratesting.TouchFile(t, devicePath) - - // Create a symlink with an invalid root format - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, devicePath, busPath) - return sysfs - }, - expectsErr: true, - expectedErrMsg: "invalid PCIRoot format", + address: pciBusID, + expectedAttribute: nil, + expectsError: true, + expectedErrMsg: "invalid symlink target for PCI Bus ID", }, } - for name, test := range tests { t.Run(name, func(t *testing.T) { - testSysfsRoot, err := os.MkdirTemp("", "sysfs-test") - if err != nil { - t.Fatalf("Failed to create temp sysfs directory: %v", err) + mockSysfsPath := t.TempDir() + mockSysfs := sysfsPath(mockSysfsPath) + if test.smockSysfsSetup != nil { + test.smockSysfsSetup(t, mockSysfs) } + sysfs = mockSysfs t.Cleanup(func() { - if err := os.RemoveAll(testSysfsRoot); err != nil { - t.Errorf("Failed to remove temp sysfs directory: %v", err) + sysfs = sysfsPath(sysfsRoot) + if err := os.RemoveAll(mockSysfsPath); err != nil { + t.Errorf("Failed to clean up mock sysfs path %s: %v", mockSysfsPath, err) } }) - result, err := StandardPCIDeviceAttributes( - WithPCIDeviceAddress(test.pciAddress), - withSysfs(test.sysfs(t, testSysfsRoot)), - ) - if test.expectsErr { + + got, err := GetPCIeRootAttributeByPCIBusID(test.address) + if test.expectsError { if err == nil { - t.Fatalf("Expected error but got none") + t.Errorf("Expected error but got none") + return } if !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Fatalf("Expected error message %q contains %q", 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(result, test.expectedAttributes) { - t.Fatalf("Expected attributes %v, got %v", test.expectedAttributes, result) + 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 { + t.Fatalf("Failed to create directory %s: %v", filepath.Dir(path), err) + } + if _, err := os.Create(path); err != nil { + t.Fatalf("Failed to create file %s: %v", path, err) + } +} + +func CreateSymlink(t *testing.T, target, link string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(link), 0755); err != nil { + t.Fatalf("Failed to create directory for symlink %s: %v", filepath.Dir(link), err) + } + if err := os.Symlink(target, link); err != nil { + t.Fatalf("Failed to create symlink from %s to %s: %v", target, link, err) + } +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/sysfs.go b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/sysfs.go new file mode 100644 index 00000000000..0c81f72998d --- /dev/null +++ b/staging/src/k8s.io/dynamic-resource-allocation/deviceattribute/sysfs.go @@ -0,0 +1,63 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package deviceattribute + +import "path/filepath" + +const ( + sysfsRoot = "/sys" +) + +var ( + sysfs sysfsPath = sysfsPath(sysfsRoot) +) + +// sysfsPath provides methods to construct sysfs paths for various subsystems. +// It is used to abstract the sysfs path construction +// and can be replaced with a mock in tests. +type sysfsPath string + +func (s sysfsPath) Devices(path string) string { + return filepath.Join(string(s), "devices", path) +} + +func (s sysfsPath) Bus(path string) string { + return filepath.Join(string(s), "bus", path) +} + +func (s sysfsPath) Block(path string) string { + return filepath.Join(string(s), "block", path) +} + +func (s sysfsPath) Class(path string) string { + return filepath.Join(string(s), "class", path) +} + +func (s sysfsPath) Dev(path string) string { + return filepath.Join(string(s), "dev", path) +} + +func (s sysfsPath) Firmware(path string) string { + return filepath.Join(string(s), "firmware", path) +} +func (s sysfsPath) Kernel(path string) string { + return filepath.Join(string(s), "kernel", path) +} + +func (s sysfsPath) Module(path string) string { + return filepath.Join(string(s), "module", path) +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/testing/file.go b/staging/src/k8s.io/dynamic-resource-allocation/testing/file.go deleted file mode 100644 index 0c68c6955b3..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/testing/file.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package testing - -import ( - "os" - "path/filepath" - "testing" -) - -func TouchFile(t *testing.T, path string) { - t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - t.Fatalf("Failed to create directory %s: %v", filepath.Dir(path), err) - } - if _, err := os.Create(path); err != nil { - t.Fatalf("Failed to create file %s: %v", path, err) - } -} - -func CreateSymlink(t *testing.T, target, link string) { - t.Helper() - if err := os.MkdirAll(filepath.Dir(link), 0755); err != nil { - t.Fatalf("Failed to create directory for symlink %s: %v", filepath.Dir(link), err) - } - if err := os.Symlink(target, link); err != nil { - t.Fatalf("Failed to create symlink from %s to %s: %v", target, link, err) - } -} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go deleted file mode 100644 index a0404b5e2af..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci.go +++ /dev/null @@ -1,301 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "fmt" - "math" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" -) - -const ( - // PCI address consists of: - // - domain: 16 bits, it can be represented in 4-digit hexadecimal number - // - bus: 8 bits, it can be represented in 2-digit hexadecimal number - // - device: 5 bits, it can be represented in 2-digit hexadecimal number - // - function: 3 bits, it can be represented in 1-digit hexadecimal number - // - // ref: "Chapter 12. PCI Drivers - PCI Addressing" - // Linux Device Drivers, 3rd Edition by Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman - // https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch12.html - PCIDomainBits = uint16(16) - PCIDomainMax = uint16(math.MaxUint16) - PCIBusBits = uint16(8) - PCIBusMax = uint16((1 << PCIBusBits) - 1) - PCIDeviceBits = uint16(5) - PCIDeviceMax = uint16((1 << PCIDeviceBits) - 1) - PCIFunctionBits = uint16(3) - PCIFunctionMax = uint16((1 << PCIFunctionBits) - 1) -) - -var ( - // bdfRegexp matches PCI address in BDF notation. - // The format is ::. - // where: - // - domain: 4-digit hexadecimal number representing the PCI domain (16 bits) - // - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) - // - device: 2-digit hexadecimal number representing the PCI device (5 bits) - // - function: 1-digit hexadecimal number representing the PCI function (3 bits) - // - // Example: "0000:0e:1f.0" represents domain 0, bus 14, device 31, function 0. - // - // ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation - bdfRegexp = regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`) - - // pciRootRegexp matches PCI root address in the format "pci:". - // The format is: - // - domain: 4-digit hexadecimal number representing the PCI domain (32 bits) - // - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) - pciRootRegexp = regexp.MustCompile(`^([0-9a-f]{4}):([0-9a-f]{2})$`) -) - -// PCIRoot represents a PCI root address in the combination of Domain and Bus. -// This represents the top-level element in sysfs PCI device hierarchy. -// (i.e. /sys/devices/pci:) -// -// ref: https://docs.kernel.org/PCI/sysfs-pci.html -type PCIRoot struct { - domain uint16 - bus uint16 -} - -func NewPCIRoot(domain, bus uint16) (*PCIRoot, error) { - // no validation for domain, as it uses full 16 bits - - if bus > PCIBusMax { - return nil, fmt.Errorf("invalid PCI bus number: %02x, must be in range 0-%d (%dbits)", bus, PCIBusMax, PCIBusBits) - } - return &PCIRoot{ - domain: domain, - bus: bus, - }, nil -} - -func MustNewPCIRoot(domain, bus uint16) *PCIRoot { - root, err := NewPCIRoot(domain, bus) - if err != nil { - panic(fmt.Sprintf("failed to create PCIRoot: %v", err)) - } - return root -} - -// String returns the string representation of the PCIRoot in the format "domain:bus". -func (p *PCIRoot) String() string { - return fmt.Sprintf("%04x:%02x", p.domain, p.bus) -} - -// The PCIAddress holds PCI address components in BDF notation. -// ::. -// where: -// - domain: 4-digit hexadecimal number representing the PCI domain (16 bits) -// - bus: 2-digit hexadecimal number representing the PCI bus (8 bits) -// - device: 2-digit hexadecimal number representing the PCI device (5 bits) -// - function: 1-digit hexadecimal number representing the PCI function (3 bits) -// -// Example: "0000:0e:1f.0" represents domain 0, bus 14, device 31, function 0. -// -// ref: -// - BDF Notation: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation -// - PCI Addressing: https://docs.kernel.org/PCI/sysfs-pci.html -type PCIAddress struct { - domain uint16 - bus uint16 - device uint16 - function uint16 -} - -func NewPCIAddress(domain, bus, device, function uint16) (*PCIAddress, error) { - // no validation for domain, as it uses full 16 bits - if bus > PCIBusMax { - return nil, fmt.Errorf("invalid PCI bus number: %02x, must be in range 0-%d (%dbits)", bus, PCIBusMax, PCIBusBits) - } - if device > PCIDeviceMax { - return nil, fmt.Errorf("invalid PCI device number: %02x, must be in range 0-%d (%dbits)", device, PCIDeviceMax, PCIDeviceBits) - } - if function > PCIFunctionMax { - return nil, fmt.Errorf("invalid PCI function number: %01x, must be in range 0-%d (%dbits)", function, PCIFunctionMax, PCIFunctionBits) - } - return &PCIAddress{ - domain: domain, - bus: bus, - device: device, - function: function, - }, nil -} - -func MustNewPCIAddress(domain, bus, device, function uint16) *PCIAddress { - addr, err := NewPCIAddress(domain, bus, device, function) - if err != nil { - panic(fmt.Sprintf("failed to create PCIAddress: %v", err)) - } - return addr -} - -func (p *PCIAddress) String() string { - return fmt.Sprintf("%04x:%02x:%02x.%01x", p.domain, p.bus, p.device, p.function) -} - -// ResolvePCIRoot resolves the PCI root for the PCIAddress. -// It returns a PCIRoot object that contains the domain and bus of the PCI root. -// The PCIRoot is derived from the sysfs path of the PCIAddress. -// -// sysfs argument is optional and only for testing purposes. -func (p *PCIAddress) ResolvePCIRoot(sysfs Sysfs) (*PCIRoot, error) { - if sysfs == nil { - sysfs = NewSysfs() - } - - // e.g. /sys/devices/pci0000:01/....../0000:00:1f.0, - sysDevicesPath, err := p.resolveSysDevicesPath(sysfs) - if err != nil { - return nil, fmt.Errorf("failed to resolve sysfs path for PCI device %s: %w", p.String(), err) - } - - pciRootPart := strings.Split(strings.TrimPrefix(sysDevicesPath, sysfs.Devices("")+"/"), "/")[0] - pciRoot, err := ParsePCIRoot( - pciRootPart[3:], // skip "pci" prefix - ) - if err != nil { - return nil, fmt.Errorf("failed to resolve PCI root for %s: %w", p.String(), err) - } - return pciRoot, nil -} - -// resolveSysDevicesPath resolves the /sys/devices path for the PCIAddress. -// -// /sys/devices has directory structure which reflects the hardware hierarchy in the system. -// Therefore, the device path may contains intermediate directories(devices). -// Thus, we can not simply find the device path from the PCIAddress. -// Fortunately, /sys/bus/pci/devices/
is a symlink to the actual device path in /sys/devices. -// So we can resolve the actual device path by reading the symlink at /sys/bus/pci/devices/
. -// -// For example, if the PCIAddress is "0000:00:1f.0", -// /sys/bus/pci/devices/0000:00:1f.0 points to -// /sys/devices/pci0000:01/....../0000:00:1f.0, -func (p *PCIAddress) resolveSysDevicesPath(sysfs Sysfs) (string, error) { - // e.g. /sys/bus/pci/devices/0000:00:1f.0 - sysBusPath := sysfs.Bus(filepath.Join("pci", "devices", p.String())) - - targetRelative, err := os.Readlink(sysBusPath) - if err != nil { - return "", fmt.Errorf("failed to read symlink for PCI device %s: %w", sysBusPath, err) - } - var targetAbs string - if filepath.IsAbs(targetRelative) { - targetAbs = targetRelative - } else { - // If the target is a relative path, we need to resolve it relative to the symlink's directory. - targetAbs = filepath.Join(filepath.Dir(sysBusPath), targetRelative) - } - - // targetAbs must be /sys/devices/pci0000:01/....../0000:00:1f.0 - devicePathPrefix := sysfs.Devices("pci") - if !strings.HasPrefix(targetAbs, devicePathPrefix) || filepath.Base(targetAbs) != p.String() { - return "", fmt.Errorf("invalid symlink target for PCI device %s: %s", sysBusPath, targetAbs) - } - - return targetAbs, nil -} - -// ParsePCIRoot parses a PCIRoot from a string in the format "domain:bus". -// The format is: -// - domain: 4-digit hexadecimal number representing the PCI domain -// - bus: 2-digit hexadecimal number representing the PCI bus -// Example: "0000:01" represents domain 0, bus 1. -func ParsePCIRoot(str string) (*PCIRoot, error) { - match := pciRootRegexp.FindStringSubmatch(str) - - if len(match) == 0 { - return nil, fmt.Errorf("invalid PCIRoot format: %s", str) - } - - parsePart := func(name, part string) (uint16, error) { - value, err := parseHexTouint16(part) - if err != nil { - return 0, fmt.Errorf("invalid value %s in PCIRoot %s: %w", name, str, err) - } - return uint16(value), nil - } - - var domain, bus uint16 - var err error - if domain, err = parsePart("domain", match[1]); err != nil { - return nil, err - } - if bus, err = parsePart("bus", match[2]); err != nil { - return nil, err - } - - return NewPCIRoot(domain, bus) -} - -// ParsePCIAddress parses a PCI address in BDF notation. -// The format is ::. -// where: -// - domain: 4-digit hexadecimal number representing the PCI domain -// - bus: 2-digit hexadecimal number representing the PCI bus -// - device: 2-digit hexadecimal number representing the PCI device -// - function: 1-digit hexadecimal number representing the PCI function -// The function returns a PCIAddress object or an error if the format is invalid. -// -// Example: "0000:0e:1f.0" represents domain 0, bus 14, device 31, function 0. -// -// ref: https://wiki.xenproject.org/wiki/Bus:Device.Function_(BDF)_Notation -func ParsePCIAddress(bdfString string) (*PCIAddress, error) { - match := bdfRegexp.FindStringSubmatch(bdfString) - - if len(match) == 0 { - return nil, fmt.Errorf("invalid PCI address format: %s", bdfString) - } - - parsePart := func(name, part string) (uint16, error) { - value, err := parseHexTouint16(part) - if err != nil { - return 0, fmt.Errorf("invalid value %s in PCI address %s: %w", name, bdfString, err) - } - return uint16(value), nil - } - - var domain, bus, device, function uint16 - var err error - if domain, err = parsePart("domain", match[1]); err != nil { - return nil, err - } - if bus, err = parsePart("bus", match[2]); err != nil { - return nil, err - } - if device, err = parsePart("device", match[3]); err != nil { - return nil, err - } - if function, err = parsePart("function", match[4]); err != nil { - return nil, err - } - - return NewPCIAddress(domain, bus, device, function) -} - -func parseHexTouint16(s string) (uint16, error) { - value, err := strconv.ParseUint(s, 16, 32) - if err != nil { - return 0, err - } - return uint16(value), nil -} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go b/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go deleted file mode 100644 index 1c45612f76a..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/utils/pci_test.go +++ /dev/null @@ -1,395 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "os" - "path/filepath" - "reflect" - "strings" - "testing" - - dratesting "k8s.io/dynamic-resource-allocation/testing" -) - -func TestNewPCIAddress(t *testing.T) { - tests := map[string]struct { - domain, bus, device, function uint16 - expectsErr bool - expectedErrMsg string - }{ - "valid zero address": {0, 0, 0, 0, false, ""}, - "valid simple address": {1, 2, 3, 4, false, ""}, - "valid complex address": {0x1234, 0x56, 0x1e, 0x5, false, ""}, - // no invalid domain case because domain uses full 16 bits - "invalid bus": {0, PCIBusMax + 1, 0, 0, true, "invalid PCI bus number"}, - "invalid device": {0, 0, PCIDeviceMax + 1, 0, true, "invalid PCI device number"}, - "invalid function": {0, 0, 0, PCIFunctionMax + 1, true, "invalid PCI function number"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - address, err := NewPCIAddress(test.domain, test.bus, test.device, test.function) - if test.expectsErr { - if err == nil { - t.Errorf("Expected error but got none") - return - } - if !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) - return - } - return - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - if address.domain != test.domain || address.bus != test.bus || address.device != test.device || address.function != test.function { - t.Errorf("Expected PCIAddress(%d, %d, %d, %d), got (%d, %d, %d, %d)", - test.domain, test.bus, test.device, test.function, - address.domain, address.bus, address.device, address.function) - return - } - }) - } -} - -func TestPCIAddressString(t *testing.T) { - tests := map[string]struct { - address *PCIAddress - expected string - }{ - "zero": {MustNewPCIAddress(0, 0, 0, 0), "0000:00:00.0"}, - "simple": {MustNewPCIAddress(0x0001, 0x02, 0x03, 0x4), "0001:02:03.4"}, - "complex": {MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x5), "1234:56:1e.5"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if got := test.address.String(); got != test.expected { - t.Errorf("Expected %s, got %s", test.expected, got) - } - }) - } -} - -func TestParsePCIAddress(t *testing.T) { - tests := map[string]struct { - input string - expected *PCIAddress - expectsErr bool - expectedErrMsg string - }{ - "valid zero": {"0000:00:00.0", MustNewPCIAddress(0x0, 0x0, 0x0, 0x0), false, ""}, - "valid simple": {"0001:02:03.4", MustNewPCIAddress(0x1, 0x2, 0x3, 0x4), false, ""}, - "valid complex": {"1234:56:1e.7", MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x7), false, ""}, - "invalid format": {"0000-00-00-0", nil, true, "invalid PCI address forma"}, - "too less parts": {"0000:00:00", nil, true, "invalid PCI address forma"}, - "too many parts": {"0000:00:00.0.1", nil, true, "invalid PCI address forma"}, - "invalid hex value": {"0000:00:00.0g", nil, true, "invalid PCI address format"}, - "completely invalid": {"invalid", nil, true, "invalid PCI address forma"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - result, err := ParsePCIAddress(test.input) - if test.expectsErr { - if err == nil { - t.Errorf("Expected error but got none for input %s", test.input) - return - } - if test.expectedErrMsg != "" && !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Errorf("Expected error message %q contains %q for input %s", err.Error(), test.expectedErrMsg, test.input) - return - } - return - } - if err != nil { - t.Errorf("Unexpected error for input %s: %v", test.input, err) - return - } - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("ParsePCIAddress(%s) = %+v, want %+v", test.input, result, test.expected) - return - } - }) - } -} - -func TestParsePCIAddressRoundTrip(t *testing.T) { - tests := map[string]struct { - addressStr string - expected *PCIAddress - }{ - "zero": {"0000:00:00.0", MustNewPCIAddress(0x0, 0x0, 0x0, 0x0)}, - "simple": {"0001:02:03.4", MustNewPCIAddress(0x1, 0x2, 0x3, 0x4)}, - "complex": {"1234:56:1e.5", MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x5)}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - address, err := ParsePCIAddress(test.addressStr) - if err != nil { - t.Errorf("ParsePCIAddress(%s) returned error: %v", test.addressStr, err) - return - } - if !reflect.DeepEqual(address, test.expected) { - t.Errorf("ParsePCIAddress(%s) = %+v, want %+v", test.addressStr, address, test.expected) - return - } - if address.String() != test.expected.String() { - t.Errorf("Expected %s, got %s", test.expected.String(), address.String()) - } - }) - } -} - -func TestNewPCIRoot(t *testing.T) { - tests := map[string]struct { - domain uint16 - bus uint16 - address *PCIRoot - expectsErr bool - expectedErrMsg string - }{ - "valid zero root": {0, 0, MustNewPCIRoot(0x0, 0x0), false, ""}, - "valid simple root": {1, 2, MustNewPCIRoot(0x1, 0x2), false, ""}, - "valid complex root": {0x1234, 0x56, MustNewPCIRoot(0x1234, 0x56), false, ""}, - // no invalid domain case because domain uses full 16 bits - "invalid bus": {0, PCIBusMax + 1, nil, true, "invalid PCI bus number"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - root, err := NewPCIRoot(test.domain, test.bus) - if test.expectsErr { - if err == nil { - t.Errorf("Expected error but got none") - return - } - if !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) - return - } - return - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - if root.String() != test.address.String() { - t.Errorf("Expected PCIRoot %s, got %s", test.address.String(), root.String()) - return - } - }) - } -} - -func TestPCIRootString(t *testing.T) { - tests := map[string]struct { - root *PCIRoot - expected string - }{ - "zero": {MustNewPCIRoot(0x0, 0x0), "0000:00"}, - "simple": {MustNewPCIRoot(0x1, 0x2), "0001:02"}, - "complex": {MustNewPCIRoot(0x1234, 0x56), "1234:56"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if got := test.root.String(); got != test.expected { - t.Errorf("Expected %s, got %s", test.expected, got) - } - }) - } -} - -func TestParsePCIRoot(t *testing.T) { - tests := map[string]struct { - input string - expected *PCIRoot - expectsErr bool - expectedErrMsg string - }{ - "valid zero": {"0000:00", MustNewPCIRoot(0x0, 0x0), false, ""}, - "valid simple": {"0001:02", MustNewPCIRoot(0x1, 0x2), false, ""}, - "valid complex": {"1234:56", MustNewPCIRoot(0x1234, 0x56), false, ""}, - "invalid format": {"0000-00", nil, true, "invalid PCIRoot format"}, - "invalid too less parts": {"0000", nil, true, "invalid PCIRoot format"}, - "invalid too many parts": {"0000:00:00", nil, true, "invalid PCIRoot format"}, - "invalid hex value": {"0000:0g", nil, true, "invalid PCIRoot format"}, - "invalid completely invalid": {"invalid", nil, true, "invalid PCIRoot format"}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - result, err := ParsePCIRoot(test.input) - if test.expectsErr { - if err == nil { - t.Errorf("Expected error but got none") - return - } - if test.expectedErrMsg != "" && !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) - return - } - return - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("Expected PCIRoot %s, got %s", test.expected.String(), result.String()) - return - } - }) - } -} - -func TestParsePCIRootRoundTrip(t *testing.T) { - tests := map[string]struct { - rootStr string - expected *PCIRoot - }{ - "zero": {"0000:00", MustNewPCIRoot(0x0, 0x0)}, - "simple": {"0001:02", MustNewPCIRoot(0x1, 0x2)}, - "complex": {"1234:56", MustNewPCIRoot(0x1234, 0x56)}, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - root, err := ParsePCIRoot(test.rootStr) - if err != nil { - t.Errorf("ParsePCIRoot(%s) returned error: %v", test.rootStr, err) - return - } - if !reflect.DeepEqual(root, test.expected) { - t.Errorf("ParsePCIRoot(%s) = %+v, want %+v", test.rootStr, root, test.expected) - return - } - if root.String() != test.expected.String() { - t.Errorf("Expected %s, got %s", test.expected.String(), root.String()) - } - }) - } -} - -func TestPCIAddressResolvePCIRoot(t *testing.T) { - address := MustNewPCIAddress(0x1234, 0x56, 0x1e, 0x7) - root := MustNewPCIRoot(0x2345, 0x67) - - tests := map[string]struct { - sysfs func(*testing.T, string) Sysfs - expectsErr bool - expectedErrMsg string - }{ - "valid address": { - sysfs: func(t *testing.T, testSysfsRoot string) Sysfs { - sysfs := NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci"+root.String(), address.String())) - dratesting.TouchFile(t, devicePath) - - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, devicePath, busPath) - return sysfs - }, - }, - "non-existent address": { - sysfs: func(t *testing.T, testSysfsRoot string) Sysfs { - return NewSysfsWithRoot(testSysfsRoot) - }, - expectsErr: true, - expectedErrMsg: "no such file or directory", - }, - "devices path not a symlink": { - sysfs: func(t *testing.T, testSysfsRoot string) Sysfs { - sysfs := NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci"+root.String(), address.String())) - dratesting.TouchFile(t, devicePath) - - // Create a file instead of a symlink - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.TouchFile(t, busPath) - return sysfs - }, - expectsErr: true, - expectedErrMsg: "invalid argument", - }, - "invalid symlink target": { - sysfs: func(t *testing.T, testSysfsRoot string) Sysfs { - sysfs := NewSysfsWithRoot(testSysfsRoot) - invalidDevicePath := sysfs.Devices(filepath.Join("invalid", "pci"+root.String(), address.String())) - dratesting.TouchFile(t, invalidDevicePath) - - // Create a symlink that points to an invalid path - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, invalidDevicePath, busPath) - return sysfs - }, - expectsErr: true, - expectedErrMsg: "invalid symlink target for PCI device", - }, - "invalid root format": { - sysfs: func(t *testing.T, testSysfsRoot string) Sysfs { - sysfs := NewSysfsWithRoot(testSysfsRoot) - devicePath := sysfs.Devices(filepath.Join("pci-invalid-root", address.String())) - dratesting.TouchFile(t, devicePath) - - // Create a symlink with an invalid root format - busPath := sysfs.Bus(filepath.Join("pci", "devices", address.String())) - dratesting.CreateSymlink(t, devicePath, busPath) - return sysfs - }, - expectsErr: true, - expectedErrMsg: "invalid PCIRoot format", - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - testSysfsRoot, err := os.MkdirTemp("", "sysfs_test") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - t.Cleanup(func() { - if err := os.RemoveAll(testSysfsRoot); err != nil { - t.Errorf("Failed to remove temp sysfs directory: %v", err) - } - }) - - ret, err := address.ResolvePCIRoot(test.sysfs(t, testSysfsRoot)) - - if test.expectsErr { - if err == nil { - t.Errorf("Expected error but got none") - return - } - if !strings.Contains(err.Error(), test.expectedErrMsg) { - t.Errorf("Expected error message %q contains %q", err.Error(), test.expectedErrMsg) - return - } - return - } - - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - if ret == nil { - t.Errorf("Expected non-nil PCIRoot, got nil") - return - } - if ret.String() != root.String() { - t.Errorf("Expected PCIRoot %s, got %s", root.String(), ret.String()) - return - } - }) - } -} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/utils/sysfs.go b/staging/src/k8s.io/dynamic-resource-allocation/utils/sysfs.go deleted file mode 100644 index b0c732a900a..00000000000 --- a/staging/src/k8s.io/dynamic-resource-allocation/utils/sysfs.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import "path/filepath" - -const ( - sysfsPath = "/sys" -) - -// Sysfs provides methods to access various sysfs paths. -// It abstracts the sysfs directory structure, allowing for easy retrieval of paths -// ref: https://linux-kernel-labs.github.io/refs/heads/master/labs/device_model.html#sysfs -type Sysfs interface { - // Devices returns the full path to the devices directory with the specified path appended. - // The path is relative to /sys/devices. - Devices(path string) string - - // Bus returns the full path to the bus directory with the specified path appended. - // The path is relative to /sys/bus. - Bus(path string) string - - // Block returns the full path to the block directory with the specified path appended. - // The path is relative to /sys/block. - Block(path string) string - - // Class returns the full path to the class directory with the specified path appended. - // The path is relative to /sys/class. - Class(path string) string - - // Dev returns the full path to the dev directory with the specified path appended. - // The path is relative to /sys/dev. - Dev(path string) string - - // Firmware returns the full path to the firmware directory with the specified path appended. - // The path is relative to /sys/firmware. - Firmware(path string) string - - // Kernel returns the full path to the kernel directory with the specified path appended. - // The path is relative to /sys/kernel. - Kernel(path string) string - - // Module returns the full path to the module directory with the specified path appended. - // The path is relative to /sys/module. - Module(path string) string -} - -// sysfs provides methods to access sysfs paths -// root is the root path of the sysfs filesystem, typically "/sys". -// Setting non-"/sys" root is only for testing purposes. -type sysfs struct { - root string -} - -// NewSysfs creates a new Sysfs instance(/sys) -func NewSysfs() Sysfs { - return &sysfs{root: sysfsPath} -} - -// NewSysfsWithRoot creates a new Sysfs instance with the specified root path. -// If the root path is empty, it defaults to "/sys". -// This method is only for testing purposes. -func NewSysfsWithRoot(root string) Sysfs { - if root == "" { - root = sysfsPath - } - return &sysfs{root: root} -} - -func (s *sysfs) Devices(path string) string { - return filepath.Join(s.root, "devices", path) -} - -func (s *sysfs) Bus(path string) string { - return filepath.Join(s.root, "bus", path) -} - -func (s *sysfs) Block(path string) string { - return filepath.Join(s.root, "block", path) -} - -func (s *sysfs) Class(path string) string { - return filepath.Join(s.root, "class", path) -} - -func (s *sysfs) Dev(path string) string { - return filepath.Join(s.root, "dev", path) -} - -func (s *sysfs) Firmware(path string) string { - return filepath.Join(s.root, "firmware", path) -} -func (s *sysfs) Kernel(path string) string { - return filepath.Join(s.root, "kernel", path) -} - -func (s *sysfs) Module(path string) string { - return filepath.Join(s.root, "module", path) -}