Skip to content

Commit 7b37a06

Browse files
Merge branch 'kubernetes-sigs:main' into helm-chart-parity
2 parents 9b906a1 + f5d4e17 commit 7b37a06

4 files changed

Lines changed: 319 additions & 12 deletions

File tree

cloudbuild.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ options:
33
substitution_option: ALLOW_LOOSE
44
machineType: E2_HIGHCPU_32
55
steps:
6-
- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud
6+
- name: registry.k8s.io/releng/gcb-docker-gcloud:v20260806
77
entrypoint: make
88
env:
99
- REGISTRY=gcr.io/k8s-staging-networking

pkg/driver/dra_hooks.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ func (np *NetworkDriver) prepareResourceClaim(ctx context.Context, claim *resour
193193
}
194194

195195
var errorList []error
196-
charDevices := sets.New[string]()
197196
for _, result := range claim.Status.Allocation.Devices.Results {
198197
// A single ResourceClaim can have devices managed by distinct DRA
199198
// drivers. One common use case for this is device topology alignment
@@ -288,7 +287,7 @@ func (np *NetworkDriver) prepareResourceClaim(ctx context.Context, claim *resour
288287
errorList = append(errorList, fmt.Errorf("failed to get RDMA device name for IB-only device %s: %v", result.Device, err))
289288
continue
290289
}
291-
deviceCfg.RDMADevice = buildRDMAConfig(rdmaDevName, charDevices)
290+
deviceCfg.RDMADevice = buildRDMAConfig(rdmaDevName)
292291
if err := np.podConfigStore.SetDeviceConfig(podUID, result.Device, deviceCfg); err != nil {
293292
errorList = append(errorList, fmt.Errorf("failed to persist device config for pod %s device %s: %v", podUID, result.Device, err))
294293
}
@@ -442,7 +441,7 @@ func (np *NetworkDriver) prepareResourceClaim(ctx context.Context, claim *resour
442441
// Get RDMA configuration: link and char devices
443442
if rdmaDev, err := inventory.GetRdmaDevice(ifName); err == nil && rdmaDev != "" {
444443
klog.V(2).Infof("RunPodSandbox processing RDMA device: %s", rdmaDev)
445-
deviceCfg.RDMADevice = buildRDMAConfig(rdmaDev, charDevices)
444+
deviceCfg.RDMADevice = buildRDMAConfig(rdmaDev)
446445
}
447446

448447
// Remove the pinned programs before the NRI hooks since it
@@ -557,10 +556,10 @@ func formatDeviceNames(devices []resourceapi.Device, max int) string {
557556
}
558557

559558
// buildRDMAConfig populates an RDMAConfig for the given rdma device name.
560-
// It inserts the rdma_cm and per-device character device paths into charDevices,
561-
// then resolves each path to a LinuxDevice entry.
562-
func buildRDMAConfig(rdmaDevName string, charDevices sets.Set[string]) RDMAConfig {
559+
// It resolves the rdma_cm and per-device character device paths to LinuxDevice entries.
560+
func buildRDMAConfig(rdmaDevName string) RDMAConfig {
563561
cfg := RDMAConfig{LinkDev: rdmaDevName}
562+
charDevices := sets.New[string]()
564563
charDevices.Insert(rdmaCmPath)
565564
charDevices.Insert(rdmamap.GetRdmaCharDevices(rdmaDevName)...)
566565
for _, devpath := range charDevices.UnsortedList() {

pkg/driver/dra_hooks_test.go

Lines changed: 306 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,26 @@ package driver
1818

1919
import (
2020
"context"
21-
"fmt"
22-
"strings"
23-
"testing"
24-
2521
"encoding/json"
22+
"fmt"
2623
"net/http"
2724
"net/http/httptest"
25+
"strings"
26+
"syscall"
27+
"testing"
2828

2929
"github.com/google/go-cmp/cmp"
30+
"github.com/google/go-cmp/cmp/cmpopts"
3031
"github.com/prometheus/client_golang/prometheus/testutil"
32+
"github.com/vishvananda/netlink"
3133
resourcev1 "k8s.io/api/resource/v1"
3234
k8sresource "k8s.io/apimachinery/pkg/api/resource"
3335
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3436
"k8s.io/apimachinery/pkg/types"
3537
"k8s.io/client-go/tools/record"
3638
"k8s.io/dynamic-resource-allocation/kubeletplugin"
3739
"k8s.io/utils/ptr"
40+
userns "sigs.k8s.io/dranet/internal/testutils"
3841
"sigs.k8s.io/dranet/pkg/apis"
3942
"sigs.k8s.io/dranet/pkg/cloudprovider"
4043
"sigs.k8s.io/dranet/pkg/cloudprovider/webhook"
@@ -954,3 +957,302 @@ func TestMergeDevices(t *testing.T) {
954957
})
955958
}
956959
}
960+
961+
// TODO: To further improve test coverage, consider constructing and mounting fake
962+
// sysfs paths for RDMA character devices (e.g., /dev/infiniband/uverbs0). This
963+
// would allow testing of device discovery and character device aggregation logic
964+
// that currently depends on the host's physical hardware.
965+
func TestPrepareResourceClaim(t *testing.T) {
966+
userns.Run(t, testPrepareResourceClaim_Namespaced, syscall.CLONE_NEWNET)
967+
}
968+
969+
func testPrepareResourceClaim_Namespaced(t *testing.T) {
970+
ctx := t.Context()
971+
const testDriverName = "test.driver"
972+
973+
// We are in a fresh, isolated netns for all these test cases.
974+
// Create a shared dummy interface that tests can rely on.
975+
la := netlink.NewLinkAttrs()
976+
la.Name = "dummy0"
977+
dummy := &netlink.Dummy{LinkAttrs: la}
978+
if err := netlink.LinkAdd(dummy); err != nil && !strings.Contains(err.Error(), "file exists") {
979+
t.Fatalf("Failed to create shared dummy interface: %v", err)
980+
}
981+
982+
testCases := []struct {
983+
name string
984+
claim *resourcev1.ResourceClaim
985+
setupDB func(*fakeInventoryDB)
986+
wantErr string
987+
wantPodConfig *PodConfig
988+
}{
989+
{
990+
name: "single IB-only device builds RDMA config successfully",
991+
claim: &resourcev1.ResourceClaim{
992+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-ib-single", Namespace: "default", Name: "claim-ib-single"},
993+
Status: resourcev1.ResourceClaimStatus{
994+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
995+
{APIGroup: "", Resource: "pods", Name: "test-pod", UID: "pod-uid-ib-single"},
996+
},
997+
Allocation: &resourcev1.AllocationResult{
998+
Devices: resourcev1.DeviceAllocationResult{
999+
Results: []resourcev1.DeviceRequestAllocationResult{
1000+
{Driver: testDriverName, Device: "ib-dev-0", Request: "req-0"},
1001+
},
1002+
},
1003+
},
1004+
},
1005+
},
1006+
setupDB: func(db *fakeInventoryDB) {
1007+
db.IsIBOnlyDeviceFunc = func(deviceName string) bool { return true }
1008+
db.GetRDMADeviceNameFunc = func(deviceName string) (string, error) {
1009+
return "fake_mlx5_0", nil
1010+
}
1011+
db.GetDeviceFunc = func(deviceName string) (resourcev1.Device, bool) {
1012+
return resourcev1.Device{Name: deviceName}, true
1013+
}
1014+
},
1015+
wantPodConfig: &PodConfig{
1016+
DeviceConfigs: map[string]DeviceConfig{
1017+
"ib-dev-0": {
1018+
Claim: types.NamespacedName{
1019+
Namespace: "default",
1020+
Name: "claim-ib-single",
1021+
},
1022+
DeviceSnapshot: &resourcev1.Device{Name: "ib-dev-0"},
1023+
RDMADevice: RDMAConfig{
1024+
LinkDev: "fake_mlx5_0",
1025+
},
1026+
},
1027+
},
1028+
},
1029+
},
1030+
{
1031+
name: "multiple IB-only devices in single claim build independent RDMA configs without accumulation",
1032+
claim: &resourcev1.ResourceClaim{
1033+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-ib-multi", Namespace: "default", Name: "claim-ib-multi"},
1034+
Status: resourcev1.ResourceClaimStatus{
1035+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1036+
{APIGroup: "", Resource: "pods", Name: "test-pod", UID: "pod-uid-ib-multi"},
1037+
},
1038+
Allocation: &resourcev1.AllocationResult{
1039+
Devices: resourcev1.DeviceAllocationResult{
1040+
Results: []resourcev1.DeviceRequestAllocationResult{
1041+
// Two requests for two separate IB devices within the same claim
1042+
{Driver: testDriverName, Device: "ib-dev-0", Request: "req-0"},
1043+
{Driver: testDriverName, Device: "ib-dev-1", Request: "req-1"},
1044+
},
1045+
},
1046+
},
1047+
},
1048+
},
1049+
setupDB: func(db *fakeInventoryDB) {
1050+
db.IsIBOnlyDeviceFunc = func(deviceName string) bool { return true }
1051+
db.GetRDMADeviceNameFunc = func(deviceName string) (string, error) {
1052+
switch deviceName {
1053+
case "ib-dev-0":
1054+
return "fake_mlx5_0", nil
1055+
case "ib-dev-1":
1056+
return "fake_mlx5_1", nil
1057+
default:
1058+
return "", fmt.Errorf("unexpected device %s", deviceName)
1059+
}
1060+
}
1061+
db.GetDeviceFunc = func(deviceName string) (resourcev1.Device, bool) {
1062+
return resourcev1.Device{Name: deviceName}, true
1063+
}
1064+
},
1065+
wantPodConfig: &PodConfig{
1066+
DeviceConfigs: map[string]DeviceConfig{
1067+
"ib-dev-0": {
1068+
Claim: types.NamespacedName{
1069+
Namespace: "default",
1070+
Name: "claim-ib-multi",
1071+
},
1072+
DeviceSnapshot: &resourcev1.Device{Name: "ib-dev-0"},
1073+
RDMADevice: RDMAConfig{
1074+
LinkDev: "fake_mlx5_0",
1075+
},
1076+
},
1077+
"ib-dev-1": {
1078+
Claim: types.NamespacedName{
1079+
Namespace: "default",
1080+
Name: "claim-ib-multi",
1081+
},
1082+
DeviceSnapshot: &resourcev1.Device{Name: "ib-dev-1"},
1083+
RDMADevice: RDMAConfig{
1084+
LinkDev: "fake_mlx5_1",
1085+
},
1086+
},
1087+
},
1088+
},
1089+
},
1090+
{
1091+
name: "single network device builds config successfully",
1092+
claim: &resourcev1.ResourceClaim{
1093+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-net-single", Namespace: "default", Name: "claim-net-single"},
1094+
Status: resourcev1.ResourceClaimStatus{
1095+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1096+
{APIGroup: "", Resource: "pods", Name: "test-pod", UID: "pod-uid-net-single"},
1097+
},
1098+
Allocation: &resourcev1.AllocationResult{
1099+
Devices: resourcev1.DeviceAllocationResult{
1100+
Results: []resourcev1.DeviceRequestAllocationResult{
1101+
{Driver: testDriverName, Device: "net-dev-0", Request: "req-0"},
1102+
},
1103+
},
1104+
},
1105+
},
1106+
},
1107+
setupDB: func(db *fakeInventoryDB) {
1108+
db.IsIBOnlyDeviceFunc = func(deviceName string) bool { return false }
1109+
// Return the shared 'dummy0' created at the start of the test
1110+
db.GetNetInterfaceNameFunc = func(deviceName string) (string, error) {
1111+
return "dummy0", nil
1112+
}
1113+
db.GetDeviceFunc = func(deviceName string) (resourcev1.Device, bool) {
1114+
return resourcev1.Device{Name: deviceName}, true
1115+
}
1116+
},
1117+
wantPodConfig: &PodConfig{
1118+
DeviceConfigs: map[string]DeviceConfig{
1119+
"net-dev-0": {
1120+
Claim: types.NamespacedName{
1121+
Namespace: "default",
1122+
Name: "claim-net-single",
1123+
},
1124+
DeviceSnapshot: &resourcev1.Device{Name: "net-dev-0"},
1125+
NetworkInterfaceConfigInHost: apis.NetworkConfig{
1126+
Interface: apis.InterfaceConfig{
1127+
Name: "dummy0",
1128+
},
1129+
},
1130+
NetworkInterfaceConfigInPod: apis.NetworkConfig{
1131+
Interface: apis.InterfaceConfig{
1132+
Name: "dummy0",
1133+
},
1134+
},
1135+
},
1136+
},
1137+
},
1138+
},
1139+
{
1140+
name: "no pods allocated to claim",
1141+
claim: &resourcev1.ResourceClaim{
1142+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-empty", Namespace: "default", Name: "claim-empty"},
1143+
Status: resourcev1.ResourceClaimStatus{
1144+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{},
1145+
},
1146+
},
1147+
},
1148+
{
1149+
name: "multiple pods allocated to claim returns error",
1150+
claim: &resourcev1.ResourceClaim{
1151+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-multi-pod", Namespace: "default", Name: "claim-multi-pod"},
1152+
Status: resourcev1.ResourceClaimStatus{
1153+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1154+
{APIGroup: "", Resource: "pods", Name: "pod-1", UID: "pod-uid-1"},
1155+
{APIGroup: "", Resource: "pods", Name: "pod-2", UID: "pod-uid-2"},
1156+
},
1157+
},
1158+
},
1159+
wantErr: "driver only supports one pod per claim, got 2",
1160+
},
1161+
{
1162+
name: "unsupported consumer reference returns error",
1163+
claim: &resourcev1.ResourceClaim{
1164+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-unsupported-ref", Namespace: "default", Name: "claim-unsupported-ref"},
1165+
Status: resourcev1.ResourceClaimStatus{
1166+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1167+
{APIGroup: "apps", Resource: "deployments", Name: "dep-1", UID: "dep-uid-1"},
1168+
},
1169+
},
1170+
},
1171+
wantErr: "driver only supports Pods",
1172+
},
1173+
{
1174+
name: "devices managed by other drivers are ignored",
1175+
claim: &resourcev1.ResourceClaim{
1176+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-other-driver", Namespace: "default", Name: "claim-other-driver"},
1177+
Status: resourcev1.ResourceClaimStatus{
1178+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1179+
{APIGroup: "", Resource: "pods", Name: "test-pod", UID: "pod-uid-1"},
1180+
},
1181+
Allocation: &resourcev1.AllocationResult{
1182+
Devices: resourcev1.DeviceAllocationResult{
1183+
Results: []resourcev1.DeviceRequestAllocationResult{
1184+
// This result specifies a different driver, so it should be safely ignored
1185+
{Driver: "other.driver.io", Device: "gpu-0", Request: "gpu-req"},
1186+
},
1187+
},
1188+
},
1189+
},
1190+
},
1191+
},
1192+
{
1193+
name: "device interface lookup failure returns error",
1194+
claim: &resourcev1.ResourceClaim{
1195+
ObjectMeta: metav1.ObjectMeta{UID: "claim-uid-net-fail", Namespace: "default", Name: "claim-net-fail"},
1196+
Status: resourcev1.ResourceClaimStatus{
1197+
ReservedFor: []resourcev1.ResourceClaimConsumerReference{
1198+
{APIGroup: "", Resource: "pods", Name: "test-pod", UID: "pod-uid-net-fail"},
1199+
},
1200+
Allocation: &resourcev1.AllocationResult{
1201+
Devices: resourcev1.DeviceAllocationResult{
1202+
Results: []resourcev1.DeviceRequestAllocationResult{
1203+
{Driver: testDriverName, Device: "net-dev-0", Request: "req-0"},
1204+
},
1205+
},
1206+
},
1207+
},
1208+
},
1209+
setupDB: func(db *fakeInventoryDB) {
1210+
db.IsIBOnlyDeviceFunc = func(deviceName string) bool { return false }
1211+
// Simulate failure when retrieving the interface name
1212+
db.GetNetInterfaceNameFunc = func(deviceName string) (string, error) {
1213+
return "", fmt.Errorf("interface not found in inventory")
1214+
}
1215+
},
1216+
wantErr: "failed to get network interface name for device net-dev-0",
1217+
},
1218+
}
1219+
1220+
for _, tc := range testCases {
1221+
t.Run(tc.name, func(t *testing.T) {
1222+
fakeDB := newFakeInventoryDB()
1223+
if tc.setupDB != nil {
1224+
tc.setupDB(fakeDB)
1225+
}
1226+
1227+
np := &NetworkDriver{
1228+
netdb: fakeDB,
1229+
driverName: testDriverName,
1230+
podConfigStore: mustNewPodConfigStore(),
1231+
eventRecorder: record.NewFakeRecorder(100),
1232+
}
1233+
1234+
gotResult := np.prepareResourceClaim(ctx, tc.claim)
1235+
1236+
if tc.wantErr != "" {
1237+
if gotResult.Err == nil || !strings.Contains(gotResult.Err.Error(), tc.wantErr) {
1238+
t.Fatalf("prepareResourceClaim() error = %v, want error containing %q", gotResult.Err, tc.wantErr)
1239+
}
1240+
} else if gotResult.Err != nil {
1241+
t.Fatalf("prepareResourceClaim() unexpected error = %v", gotResult.Err)
1242+
}
1243+
1244+
var gotPodConfig *PodConfig
1245+
if len(tc.claim.Status.ReservedFor) > 0 {
1246+
podUID := tc.claim.Status.ReservedFor[0].UID
1247+
if podCfg, ok := np.podConfigStore.GetPodConfig(podUID); ok {
1248+
gotPodConfig = &podCfg
1249+
}
1250+
}
1251+
1252+
opts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(PodConfig{}, "LastNRIActivity")}
1253+
if diff := cmp.Diff(tc.wantPodConfig, gotPodConfig, opts...); diff != "" {
1254+
t.Errorf("PodConfig mismatch (-want +got):\n%s", diff)
1255+
}
1256+
})
1257+
}
1258+
}

0 commit comments

Comments
 (0)