From ea4f784f4b13a2fa5e71a11a894bac715629b9c6 Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Wed, 14 Jan 2026 11:43:55 +0000 Subject: [PATCH] test(ut/dra): add unit test for pod requesting prepared and new claims --- pkg/kubelet/cm/dra/manager_test.go | 170 ++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 39 deletions(-) diff --git a/pkg/kubelet/cm/dra/manager_test.go b/pkg/kubelet/cm/dra/manager_test.go index ab10ca41417..58fe6ab5337 100644 --- a/pkg/kubelet/cm/dra/manager_test.go +++ b/pkg/kubelet/cm/dra/manager_test.go @@ -65,8 +65,9 @@ const ( ) var ( - shareID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - shareUID = types.UID(shareID) + shareID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + shareUID = types.UID(shareID) + testPodCounter atomic.Uint32 ) type fakeDRADriverGRPCServer struct { @@ -300,6 +301,23 @@ func setupFakeDRADriverGRPCServer(ctx context.Context, shouldTimeout bool, plugi }, nil } +func genPrepareResourcesResponse(claimUID types.UID) *drapb.NodePrepareResourcesResponse { + return &drapb.NodePrepareResourcesResponse{ + Claims: map[string]*drapb.NodePrepareResourceResponse{ + string(claimUID): { + Devices: []*drapb.Device{ + { + PoolName: poolName, + DeviceName: deviceName, + RequestNames: []string{requestName}, + CdiDeviceIds: []string{cdiID}, + }, + }, + }, + }, + } +} + func TestNewManagerImpl(t *testing.T) { kubeClient := fake.NewSimpleClientset() for _, test := range []struct { @@ -363,6 +381,45 @@ func genTestPod() *v1.Pod { } } +func genTestPodWithClaims(claimNames ...string) *v1.Pod { + podCounter := testPodCounter.Add(1) + + podName := fmt.Sprintf("test-pod-%d", podCounter) + podUID := types.UID(fmt.Sprintf("test-pod-uid-%d", podCounter)) + + resourceClaims := make([]v1.PodResourceClaim, 0, len(claimNames)) + containerClaims := make([]v1.ResourceClaim, 0, len(claimNames)) + + for _, claimName := range claimNames { + cn := claimName + resourceClaims = append(resourceClaims, v1.PodResourceClaim{ + Name: cn, + ResourceClaimName: &cn, + }) + containerClaims = append(containerClaims, v1.ResourceClaim{ + Name: cn, + }) + } + + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + UID: podUID, + }, + Spec: v1.PodSpec{ + ResourceClaims: resourceClaims, + Containers: []v1.Container{ + { + Resources: v1.ResourceRequirements{ + Claims: containerClaims, + }, + }, + }, + }, + } +} + // genTestPodWithExtendedResource generates pod object func genTestPodWithExtendedResource() *v1.Pod { return &v1.Pod{ @@ -787,18 +844,7 @@ dra_operations_duration_seconds_sum{is_error="false",operation_name="PrepareReso dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareResources"} 1 `, expectedClaimInfoState: genClaimInfoState(cdiID), - resp: &drapb.NodePrepareResourcesResponse{Claims: map[string]*drapb.NodePrepareResourceResponse{ - string(claimUID): { - Devices: []*drapb.Device{ - { - PoolName: poolName, - DeviceName: deviceName, - RequestNames: []string{requestName}, - CdiDeviceIds: []string{cdiID}, - }, - }, - }, - }}, + resp: genPrepareResourcesResponse(claimUID), }, { description: "resource already prepared", @@ -807,18 +853,7 @@ dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareRe claim: genTestClaim(claimName, driverName, deviceName, podUID), claimInfo: genTestClaimInfo(claimUID, []string{podUID}, true), expectedClaimInfoState: genClaimInfoState(cdiID), - resp: &drapb.NodePrepareResourcesResponse{Claims: map[string]*drapb.NodePrepareResourceResponse{ - string(claimUID): { - Devices: []*drapb.Device{ - { - PoolName: poolName, - DeviceName: deviceName, - RequestNames: []string{requestName}, - CdiDeviceIds: []string{cdiID}, - }, - }, - }, - }}, + resp: genPrepareResourcesResponse(claimUID), expectedMetric: `# HELP dra_operations_duration_seconds [ALPHA] Latency histogram in seconds for the duration of handling all ResourceClaims referenced by a pod when the pod starts or stops. Identified by the name of the operation (PrepareResources or UnprepareResources) and separated by the success of the operation. The number of failed operations is provided through the histogram's overall count. # TYPE dra_operations_duration_seconds histogram dra_operations_duration_seconds_bucket{is_error="false",operation_name="PrepareResources",le="+Inf"} 1 @@ -852,19 +887,8 @@ dra_operations_duration_seconds_count{is_error="true",operation_name="PrepareRes pod: genTestPod(), claim: genTestClaim(claimName, driverName, deviceName, podUID), expectedClaimInfoState: genClaimInfoState(cdiID), - resp: &drapb.NodePrepareResourcesResponse{Claims: map[string]*drapb.NodePrepareResourceResponse{ - string(claimUID): { - Devices: []*drapb.Device{ - { - PoolName: poolName, - DeviceName: deviceName, - RequestNames: []string{requestName}, - CdiDeviceIds: []string{cdiID}, - }, - }, - }, - }}, - expectedPrepareCalls: 1, + resp: genPrepareResourcesResponse(claimUID), + expectedPrepareCalls: 1, expectedMetric: `# HELP dra_grpc_operations_duration_seconds [ALPHA] Duration in seconds of the DRA gRPC operations # TYPE dra_grpc_operations_duration_seconds histogram dra_grpc_operations_duration_seconds_bucket{driver_name="test-driver",grpc_status_code="OK",method_name="/k8s.io.kubelet.pkg.apis.dra.v1.DRAPlugin/NodePrepareResources",le="+Inf"} 1 @@ -1053,6 +1077,74 @@ dra_operations_duration_seconds_count{is_error="false",operation_name="PrepareRe } } +// TestPrepareResourcesWithPreparedAndNewClaim verifies that PrepareResources +// correctly handles a pod that references a mix of ResourceClaims: +// - first claim already prepared by a previous pod +// - second claim is new and needs to be prepared +func TestPrepareResourcesWithPreparedAndNewClaim(t *testing.T) { + logger, tCtx := ktesting.NewTestContext(t) + fakeKubeClient := fake.NewClientset() + + manager, err := NewManager(logger, fakeKubeClient, t.TempDir()) + require.NoError(t, err) + manager.initDRAPluginManager(tCtx, getFakeNode, time.Second) + + secondClaimName := fmt.Sprintf("%s-second", claimName) + + // Generate two pods where the second pod reuses an existing claim and adds a new one + firstPod := genTestPodWithClaims(claimName) + secondPod := genTestPodWithClaims(claimName, secondClaimName) + + firstClaim := genTestClaim(claimName, driverName, deviceName, string(firstPod.ObjectMeta.UID)) + secondClaim := genTestClaim(secondClaimName, driverName, deviceName, string(secondPod.ObjectMeta.UID)) + + // Make firstClaim reserved for first and second pod + firstClaim.Status.ReservedFor = append( + firstClaim.Status.ReservedFor, + resourceapi.ResourceClaimConsumerReference{UID: secondPod.ObjectMeta.UID}, + ) + + _, err = fakeKubeClient.ResourceV1().ResourceClaims(namespace).Create(tCtx, firstClaim, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = fakeKubeClient.ResourceV1().ResourceClaims(namespace).Create(tCtx, secondClaim, metav1.CreateOptions{}) + require.NoError(t, err) + + respFirst := genPrepareResourcesResponse(firstClaim.UID) + draServerInfo, err := setupFakeDRADriverGRPCServer(tCtx, false, nil, respFirst, nil, nil) + require.NoError(t, err) + defer draServerInfo.teardownFn() + + plg := manager.GetWatcherHandler() + require.NoError(t, plg.RegisterPlugin(driverName, draServerInfo.socketName, []string{drapb.DRAPluginService}, nil)) + + err = manager.PrepareResources(tCtx, firstPod) + require.NoError(t, err) + + assert.Equal(t, uint32(1), + draServerInfo.server.prepareResourceCalls.Load(), + "first pod should trigger one prepare call", + ) + + respSecond := genPrepareResourcesResponse(secondClaim.UID) + draServerInfo.server.prepareResourcesResponse = respSecond + + err = manager.PrepareResources(tCtx, secondPod) + require.NoError(t, err) + + // second pod triggered exactly one prepare call (new claim only) + previous one call + assert.Equal(t, uint32(2), + draServerInfo.server.prepareResourceCalls.Load(), + "second pod should trigger one prepare call for the new claim", + ) + + for _, claimName := range []string{firstClaim.Name, secondClaim.Name} { + claimInfo, exists := manager.cache.get(claimName, namespace) + require.True(t, exists, "claim %s should exist in cache", claimName) + assert.True(t, claimInfo.prepared, "claim %s should be marked as prepared", claimName) + } +} + func TestUnprepareResources(t *testing.T) { fakeKubeClient := fake.NewSimpleClientset() for _, test := range []struct {