k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/cm/dra/manager_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package dra
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"google.golang.org/grpc"
    32  	v1 "k8s.io/api/core/v1"
    33  	resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/client-go/kubernetes/fake"
    38  	"k8s.io/dynamic-resource-allocation/resourceclaim"
    39  	drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1alpha3"
    40  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin"
    41  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
    42  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    43  )
    44  
    45  const (
    46  	driverName      = "test-cdi-device"
    47  	driverClassName = "test"
    48  )
    49  
    50  type fakeDRADriverGRPCServer struct {
    51  	drapbv1.UnimplementedNodeServer
    52  	driverName                 string
    53  	timeout                    *time.Duration
    54  	prepareResourceCalls       atomic.Uint32
    55  	unprepareResourceCalls     atomic.Uint32
    56  	prepareResourcesResponse   *drapbv1.NodePrepareResourcesResponse
    57  	unprepareResourcesResponse *drapbv1.NodeUnprepareResourcesResponse
    58  }
    59  
    60  func (s *fakeDRADriverGRPCServer) NodePrepareResources(ctx context.Context, req *drapbv1.NodePrepareResourcesRequest) (*drapbv1.NodePrepareResourcesResponse, error) {
    61  	s.prepareResourceCalls.Add(1)
    62  
    63  	if s.timeout != nil {
    64  		time.Sleep(*s.timeout)
    65  	}
    66  
    67  	if s.prepareResourcesResponse == nil {
    68  		deviceName := "claim-" + req.Claims[0].Uid
    69  		result := s.driverName + "/" + driverClassName + "=" + deviceName
    70  		return &drapbv1.NodePrepareResourcesResponse{
    71  			Claims: map[string]*drapbv1.NodePrepareResourceResponse{
    72  				req.Claims[0].Uid: {
    73  					CDIDevices: []string{result},
    74  				},
    75  			},
    76  		}, nil
    77  	}
    78  
    79  	return s.prepareResourcesResponse, nil
    80  }
    81  
    82  func (s *fakeDRADriverGRPCServer) NodeUnprepareResources(ctx context.Context, req *drapbv1.NodeUnprepareResourcesRequest) (*drapbv1.NodeUnprepareResourcesResponse, error) {
    83  	s.unprepareResourceCalls.Add(1)
    84  
    85  	if s.timeout != nil {
    86  		time.Sleep(*s.timeout)
    87  	}
    88  
    89  	if s.unprepareResourcesResponse == nil {
    90  		return &drapbv1.NodeUnprepareResourcesResponse{
    91  			Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{
    92  				req.Claims[0].Uid: {},
    93  			},
    94  		}, nil
    95  	}
    96  
    97  	return s.unprepareResourcesResponse, nil
    98  }
    99  
   100  type tearDown func()
   101  
   102  type fakeDRAServerInfo struct {
   103  	// fake DRA server
   104  	server *fakeDRADriverGRPCServer
   105  	// fake DRA plugin socket name
   106  	socketName string
   107  	// teardownFn stops fake gRPC server
   108  	teardownFn tearDown
   109  }
   110  
   111  func setupFakeDRADriverGRPCServer(shouldTimeout bool, pluginClientTimeout *time.Duration, prepareResourcesResponse *drapbv1.NodePrepareResourcesResponse, unprepareResourcesResponse *drapbv1.NodeUnprepareResourcesResponse) (fakeDRAServerInfo, error) {
   112  	socketDir, err := os.MkdirTemp("", "dra")
   113  	if err != nil {
   114  		return fakeDRAServerInfo{
   115  			server:     nil,
   116  			socketName: "",
   117  			teardownFn: nil,
   118  		}, err
   119  	}
   120  
   121  	socketName := filepath.Join(socketDir, "server.sock")
   122  	stopCh := make(chan struct{})
   123  
   124  	teardown := func() {
   125  		close(stopCh)
   126  		os.RemoveAll(socketName)
   127  	}
   128  
   129  	l, err := net.Listen("unix", socketName)
   130  	if err != nil {
   131  		teardown()
   132  		return fakeDRAServerInfo{
   133  			server:     nil,
   134  			socketName: "",
   135  			teardownFn: nil,
   136  		}, err
   137  	}
   138  
   139  	s := grpc.NewServer()
   140  	fakeDRADriverGRPCServer := &fakeDRADriverGRPCServer{
   141  		driverName:                 driverName,
   142  		prepareResourcesResponse:   prepareResourcesResponse,
   143  		unprepareResourcesResponse: unprepareResourcesResponse,
   144  	}
   145  	if shouldTimeout {
   146  		timeout := *pluginClientTimeout * 2
   147  		fakeDRADriverGRPCServer.timeout = &timeout
   148  	}
   149  
   150  	drapbv1.RegisterNodeServer(s, fakeDRADriverGRPCServer)
   151  
   152  	go func() {
   153  		go s.Serve(l)
   154  		<-stopCh
   155  		s.GracefulStop()
   156  	}()
   157  
   158  	return fakeDRAServerInfo{
   159  		server:     fakeDRADriverGRPCServer,
   160  		socketName: socketName,
   161  		teardownFn: teardown,
   162  	}, nil
   163  }
   164  
   165  func TestNewManagerImpl(t *testing.T) {
   166  	kubeClient := fake.NewSimpleClientset()
   167  	for _, test := range []struct {
   168  		description        string
   169  		stateFileDirectory string
   170  		wantErr            bool
   171  	}{
   172  		{
   173  			description:        "invalid directory path",
   174  			stateFileDirectory: "",
   175  			wantErr:            true,
   176  		},
   177  		{
   178  			description:        "valid directory path",
   179  			stateFileDirectory: t.TempDir(),
   180  		},
   181  	} {
   182  		t.Run(test.description, func(t *testing.T) {
   183  			manager, err := NewManagerImpl(kubeClient, test.stateFileDirectory, "worker")
   184  			if test.wantErr {
   185  				assert.Error(t, err)
   186  				return
   187  			}
   188  
   189  			assert.NoError(t, err)
   190  			assert.NotNil(t, manager.cache)
   191  			assert.NotNil(t, manager.kubeClient)
   192  		})
   193  	}
   194  }
   195  
   196  func TestGetResources(t *testing.T) {
   197  	kubeClient := fake.NewSimpleClientset()
   198  	resourceClaimName := "test-pod-claim-1"
   199  
   200  	for _, test := range []struct {
   201  		description string
   202  		container   *v1.Container
   203  		pod         *v1.Pod
   204  		claimInfo   *ClaimInfo
   205  		wantErr     bool
   206  	}{
   207  		{
   208  			description: "claim info with annotations",
   209  			container: &v1.Container{
   210  				Name: "test-container",
   211  				Resources: v1.ResourceRequirements{
   212  					Claims: []v1.ResourceClaim{
   213  						{
   214  							Name: "test-pod-claim-1",
   215  						},
   216  					},
   217  				},
   218  			},
   219  			pod: &v1.Pod{
   220  				ObjectMeta: metav1.ObjectMeta{
   221  					Name:      "test-pod",
   222  					Namespace: "test-namespace",
   223  				},
   224  				Spec: v1.PodSpec{
   225  					ResourceClaims: []v1.PodResourceClaim{
   226  						{
   227  							Name:   "test-pod-claim-1",
   228  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
   229  						},
   230  					},
   231  				},
   232  			},
   233  			claimInfo: &ClaimInfo{
   234  				annotations: map[string][]kubecontainer.Annotation{
   235  					"test-plugin": {
   236  						{
   237  							Name:  "test-annotation",
   238  							Value: "123",
   239  						},
   240  					},
   241  				},
   242  				ClaimInfoState: state.ClaimInfoState{
   243  					ClaimName: "test-pod-claim-1",
   244  					CDIDevices: map[string][]string{
   245  						driverName: {"123"},
   246  					},
   247  					Namespace: "test-namespace",
   248  				},
   249  			},
   250  		},
   251  		{
   252  			description: "claim info without annotations",
   253  			container: &v1.Container{
   254  				Name: "test-container",
   255  				Resources: v1.ResourceRequirements{
   256  					Claims: []v1.ResourceClaim{
   257  						{
   258  							Name: "test-pod-claim-1",
   259  						},
   260  					},
   261  				},
   262  			},
   263  			pod: &v1.Pod{
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Name:      "test-pod",
   266  					Namespace: "test-namespace",
   267  				},
   268  				Spec: v1.PodSpec{
   269  					ResourceClaims: []v1.PodResourceClaim{
   270  						{
   271  							Name:   "test-pod-claim-1",
   272  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
   273  						},
   274  					},
   275  				},
   276  			},
   277  			claimInfo: &ClaimInfo{
   278  				ClaimInfoState: state.ClaimInfoState{
   279  					ClaimName: "test-pod-claim-1",
   280  					CDIDevices: map[string][]string{
   281  						driverName: {"123"},
   282  					},
   283  					Namespace: "test-namespace",
   284  				},
   285  			},
   286  		},
   287  		{
   288  			description: "no claim info",
   289  			container: &v1.Container{
   290  				Name: "test-container",
   291  				Resources: v1.ResourceRequirements{
   292  					Claims: []v1.ResourceClaim{
   293  						{
   294  							Name: "test-pod-claim-1",
   295  						},
   296  					},
   297  				},
   298  			},
   299  			pod: &v1.Pod{
   300  				ObjectMeta: metav1.ObjectMeta{
   301  					Name:      "test-pod",
   302  					Namespace: "test-namespace",
   303  				},
   304  				Spec: v1.PodSpec{
   305  					ResourceClaims: []v1.PodResourceClaim{
   306  						{
   307  							Name: "test-pod-claim-1",
   308  						},
   309  					},
   310  				},
   311  			},
   312  			wantErr: true,
   313  		},
   314  	} {
   315  		t.Run(test.description, func(t *testing.T) {
   316  			manager, err := NewManagerImpl(kubeClient, t.TempDir(), "worker")
   317  			assert.NoError(t, err)
   318  
   319  			if test.claimInfo != nil {
   320  				manager.cache.add(test.claimInfo)
   321  			}
   322  
   323  			containerInfo, err := manager.GetResources(test.pod, test.container)
   324  			if test.wantErr {
   325  				assert.Error(t, err)
   326  				return
   327  			}
   328  
   329  			assert.NoError(t, err)
   330  			assert.Equal(t, test.claimInfo.CDIDevices[driverName][0], containerInfo.CDIDevices[0].Name)
   331  		})
   332  	}
   333  }
   334  
   335  func getFakeNode() (*v1.Node, error) {
   336  	return &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "worker"}}, nil
   337  }
   338  
   339  func TestPrepareResources(t *testing.T) {
   340  	fakeKubeClient := fake.NewSimpleClientset()
   341  
   342  	for _, test := range []struct {
   343  		description          string
   344  		driverName           string
   345  		pod                  *v1.Pod
   346  		claimInfo            *ClaimInfo
   347  		resourceClaim        *resourcev1alpha2.ResourceClaim
   348  		resp                 *drapbv1.NodePrepareResourcesResponse
   349  		wantErr              bool
   350  		wantTimeout          bool
   351  		wantResourceSkipped  bool
   352  		expectedCDIDevices   []string
   353  		ExpectedPrepareCalls uint32
   354  	}{
   355  		{
   356  			description: "failed to fetch ResourceClaim",
   357  			driverName:  driverName,
   358  			pod: &v1.Pod{
   359  				ObjectMeta: metav1.ObjectMeta{
   360  					Name:      "test-pod",
   361  					Namespace: "test-namespace",
   362  					UID:       "test-reserved",
   363  				},
   364  				Spec: v1.PodSpec{
   365  					ResourceClaims: []v1.PodResourceClaim{
   366  						{
   367  							Name: "test-pod-claim-0",
   368  							Source: v1.ClaimSource{
   369  								ResourceClaimName: func() *string {
   370  									s := "test-pod-claim-0"
   371  									return &s
   372  								}(),
   373  							},
   374  						},
   375  					},
   376  				},
   377  			},
   378  			wantErr: true,
   379  		},
   380  		{
   381  			description: "plugin does not exist",
   382  			driverName:  "this-plugin-does-not-exist",
   383  			pod: &v1.Pod{
   384  				ObjectMeta: metav1.ObjectMeta{
   385  					Name:      "test-pod",
   386  					Namespace: "test-namespace",
   387  					UID:       "test-reserved",
   388  				},
   389  				Spec: v1.PodSpec{
   390  					ResourceClaims: []v1.PodResourceClaim{
   391  						{
   392  							Name: "test-pod-claim-1",
   393  							Source: v1.ClaimSource{
   394  								ResourceClaimName: func() *string {
   395  									s := "test-pod-claim-1"
   396  									return &s
   397  								}(),
   398  							},
   399  						},
   400  					},
   401  					Containers: []v1.Container{
   402  						{
   403  							Resources: v1.ResourceRequirements{
   404  								Claims: []v1.ResourceClaim{
   405  									{
   406  										Name: "test-pod-claim-1",
   407  									},
   408  								},
   409  							},
   410  						},
   411  					},
   412  				},
   413  			},
   414  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   415  				ObjectMeta: metav1.ObjectMeta{
   416  					Name:      "test-pod-claim-1",
   417  					Namespace: "test-namespace",
   418  					UID:       "test-reserved",
   419  				},
   420  				Spec: resourcev1alpha2.ResourceClaimSpec{
   421  					ResourceClassName: "test-class",
   422  				},
   423  				Status: resourcev1alpha2.ResourceClaimStatus{
   424  					DriverName: driverName,
   425  					Allocation: &resourcev1alpha2.AllocationResult{
   426  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   427  							{Data: "test-data", DriverName: driverName},
   428  						},
   429  					},
   430  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   431  						{UID: "test-reserved"},
   432  					},
   433  				},
   434  			},
   435  			wantErr: true,
   436  		},
   437  		{
   438  			description: "should prepare resources, driver returns nil value",
   439  			driverName:  driverName,
   440  			pod: &v1.Pod{
   441  				ObjectMeta: metav1.ObjectMeta{
   442  					Name:      "test-pod",
   443  					Namespace: "test-namespace",
   444  					UID:       "test-reserved",
   445  				},
   446  				Spec: v1.PodSpec{
   447  					ResourceClaims: []v1.PodResourceClaim{
   448  						{
   449  							Name: "test-pod-claim-nil",
   450  							Source: v1.ClaimSource{
   451  								ResourceClaimName: func() *string {
   452  									s := "test-pod-claim-nil"
   453  									return &s
   454  								}(),
   455  							},
   456  						},
   457  					},
   458  					Containers: []v1.Container{
   459  						{
   460  							Resources: v1.ResourceRequirements{
   461  								Claims: []v1.ResourceClaim{
   462  									{
   463  										Name: "test-pod-claim-nil",
   464  									},
   465  								},
   466  							},
   467  						},
   468  					},
   469  				},
   470  			},
   471  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   472  				ObjectMeta: metav1.ObjectMeta{
   473  					Name:      "test-pod-claim-nil",
   474  					Namespace: "test-namespace",
   475  					UID:       "test-reserved",
   476  				},
   477  				Spec: resourcev1alpha2.ResourceClaimSpec{
   478  					ResourceClassName: "test-class",
   479  				},
   480  				Status: resourcev1alpha2.ResourceClaimStatus{
   481  					DriverName: driverName,
   482  					Allocation: &resourcev1alpha2.AllocationResult{
   483  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   484  							{Data: "test-data", DriverName: driverName},
   485  						},
   486  					},
   487  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   488  						{UID: "test-reserved"},
   489  					},
   490  				},
   491  			},
   492  			resp:                 &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{"test-reserved": nil}},
   493  			expectedCDIDevices:   []string{},
   494  			ExpectedPrepareCalls: 1,
   495  		},
   496  		{
   497  			description: "should prepare resources, driver returns empty result",
   498  			driverName:  driverName,
   499  			pod: &v1.Pod{
   500  				ObjectMeta: metav1.ObjectMeta{
   501  					Name:      "test-pod",
   502  					Namespace: "test-namespace",
   503  					UID:       "test-reserved",
   504  				},
   505  				Spec: v1.PodSpec{
   506  					ResourceClaims: []v1.PodResourceClaim{
   507  						{
   508  							Name: "test-pod-claim-empty",
   509  							Source: v1.ClaimSource{
   510  								ResourceClaimName: func() *string {
   511  									s := "test-pod-claim-empty"
   512  									return &s
   513  								}(),
   514  							},
   515  						},
   516  					},
   517  					Containers: []v1.Container{
   518  						{
   519  							Resources: v1.ResourceRequirements{
   520  								Claims: []v1.ResourceClaim{
   521  									{
   522  										Name: "test-pod-claim-empty",
   523  									},
   524  								},
   525  							},
   526  						},
   527  					},
   528  				},
   529  			},
   530  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   531  				ObjectMeta: metav1.ObjectMeta{
   532  					Name:      "test-pod-claim-empty",
   533  					Namespace: "test-namespace",
   534  					UID:       "test-reserved",
   535  				},
   536  				Spec: resourcev1alpha2.ResourceClaimSpec{
   537  					ResourceClassName: "test-class",
   538  				},
   539  				Status: resourcev1alpha2.ResourceClaimStatus{
   540  					DriverName: driverName,
   541  					Allocation: &resourcev1alpha2.AllocationResult{
   542  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   543  							{Data: "test-data", DriverName: driverName},
   544  						},
   545  					},
   546  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   547  						{UID: "test-reserved"},
   548  					},
   549  				},
   550  			},
   551  			resp:                 &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{"test-reserved": nil}},
   552  			expectedCDIDevices:   []string{},
   553  			ExpectedPrepareCalls: 1,
   554  		},
   555  		{
   556  			description: "pod is not allowed to use resource claim",
   557  			driverName:  driverName,
   558  			pod: &v1.Pod{
   559  				ObjectMeta: metav1.ObjectMeta{
   560  					Name:      "test-pod",
   561  					Namespace: "test-namespace",
   562  					UID:       "test-reserved",
   563  				},
   564  				Spec: v1.PodSpec{
   565  					ResourceClaims: []v1.PodResourceClaim{
   566  						{
   567  							Name: "test-pod-claim-2",
   568  							Source: v1.ClaimSource{
   569  								ResourceClaimName: func() *string {
   570  									s := "test-pod-claim-2"
   571  									return &s
   572  								}(),
   573  							},
   574  						},
   575  					},
   576  				},
   577  			},
   578  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   579  				ObjectMeta: metav1.ObjectMeta{
   580  					Name:      "test-pod-claim-2",
   581  					Namespace: "test-namespace",
   582  					UID:       "test-reserved",
   583  				},
   584  				Spec: resourcev1alpha2.ResourceClaimSpec{
   585  					ResourceClassName: "test-class",
   586  				},
   587  				Status: resourcev1alpha2.ResourceClaimStatus{
   588  					DriverName: driverName,
   589  					Allocation: &resourcev1alpha2.AllocationResult{
   590  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   591  							{Data: "test-data", DriverName: driverName},
   592  						},
   593  					},
   594  				},
   595  			},
   596  			wantErr: true,
   597  		},
   598  		{
   599  			description: "no container actually uses the claim",
   600  			driverName:  driverName,
   601  			pod: &v1.Pod{
   602  				ObjectMeta: metav1.ObjectMeta{
   603  					Name:      "test-pod",
   604  					Namespace: "test-namespace",
   605  					UID:       "test-reserved",
   606  				},
   607  				Spec: v1.PodSpec{
   608  					ResourceClaims: []v1.PodResourceClaim{
   609  						{
   610  							Name: "test-pod-claim-3",
   611  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   612  								s := "test-pod-claim-3"
   613  								return &s
   614  							}()},
   615  						},
   616  					},
   617  				},
   618  			},
   619  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   620  				ObjectMeta: metav1.ObjectMeta{
   621  					Name:      "test-pod-claim-3",
   622  					Namespace: "test-namespace",
   623  					UID:       "test-reserved",
   624  				},
   625  				Spec: resourcev1alpha2.ResourceClaimSpec{
   626  					ResourceClassName: "test-class",
   627  				},
   628  				Status: resourcev1alpha2.ResourceClaimStatus{
   629  					DriverName: driverName,
   630  					Allocation: &resourcev1alpha2.AllocationResult{
   631  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   632  							{Data: "test-data", DriverName: driverName},
   633  						},
   634  					},
   635  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   636  						{UID: "test-reserved"},
   637  					},
   638  				},
   639  			},
   640  			wantResourceSkipped: true,
   641  		},
   642  		{
   643  			description: "resource already prepared",
   644  			driverName:  driverName,
   645  			pod: &v1.Pod{
   646  				ObjectMeta: metav1.ObjectMeta{
   647  					Name:      "test-pod",
   648  					Namespace: "test-namespace",
   649  					UID:       "test-reserved",
   650  				},
   651  				Spec: v1.PodSpec{
   652  					ResourceClaims: []v1.PodResourceClaim{
   653  						{
   654  							Name: "test-pod-claim-4",
   655  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   656  								s := "test-pod-claim-4"
   657  								return &s
   658  							}()},
   659  						},
   660  					},
   661  					Containers: []v1.Container{
   662  						{
   663  							Resources: v1.ResourceRequirements{
   664  								Claims: []v1.ResourceClaim{
   665  									{
   666  										Name: "test-pod-claim-4",
   667  									},
   668  								},
   669  							},
   670  						},
   671  					},
   672  				},
   673  			},
   674  			claimInfo: &ClaimInfo{
   675  				ClaimInfoState: state.ClaimInfoState{
   676  					DriverName: driverName,
   677  					ClaimName:  "test-pod-claim-4",
   678  					Namespace:  "test-namespace",
   679  					PodUIDs:    sets.Set[string]{"test-another-pod-reserved": sets.Empty{}},
   680  				},
   681  				prepared: true,
   682  			},
   683  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   684  				ObjectMeta: metav1.ObjectMeta{
   685  					Name:      "test-pod-claim-4",
   686  					Namespace: "test-namespace",
   687  					UID:       "test-reserved",
   688  				},
   689  				Spec: resourcev1alpha2.ResourceClaimSpec{
   690  					ResourceClassName: "test-class",
   691  				},
   692  				Status: resourcev1alpha2.ResourceClaimStatus{
   693  					DriverName: driverName,
   694  					Allocation: &resourcev1alpha2.AllocationResult{
   695  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   696  							{Data: "test-data", DriverName: driverName},
   697  						},
   698  					},
   699  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   700  						{UID: "test-reserved"},
   701  					},
   702  				},
   703  			},
   704  			expectedCDIDevices:  []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)},
   705  			wantResourceSkipped: true,
   706  		},
   707  		{
   708  			description: "should timeout",
   709  			driverName:  driverName,
   710  			pod: &v1.Pod{
   711  				ObjectMeta: metav1.ObjectMeta{
   712  					Name:      "test-pod",
   713  					Namespace: "test-namespace",
   714  					UID:       "test-reserved",
   715  				},
   716  				Spec: v1.PodSpec{
   717  					ResourceClaims: []v1.PodResourceClaim{
   718  						{
   719  							Name: "test-pod-claim-5",
   720  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   721  								s := "test-pod-claim-5"
   722  								return &s
   723  							}()},
   724  						},
   725  					},
   726  					Containers: []v1.Container{
   727  						{
   728  							Resources: v1.ResourceRequirements{
   729  								Claims: []v1.ResourceClaim{
   730  									{
   731  										Name: "test-pod-claim-5",
   732  									},
   733  								},
   734  							},
   735  						},
   736  					},
   737  				},
   738  			},
   739  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   740  				ObjectMeta: metav1.ObjectMeta{
   741  					Name:      "test-pod-claim-5",
   742  					Namespace: "test-namespace",
   743  					UID:       "test-reserved",
   744  				},
   745  				Spec: resourcev1alpha2.ResourceClaimSpec{
   746  					ResourceClassName: "test-class",
   747  				},
   748  				Status: resourcev1alpha2.ResourceClaimStatus{
   749  					DriverName: driverName,
   750  					Allocation: &resourcev1alpha2.AllocationResult{
   751  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   752  							{Data: "test-data", DriverName: driverName},
   753  						},
   754  					},
   755  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   756  						{UID: "test-reserved"},
   757  					},
   758  				},
   759  			},
   760  			resp: &drapbv1.NodePrepareResourcesResponse{
   761  				Claims: map[string]*drapbv1.NodePrepareResourceResponse{
   762  					"test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}},
   763  				},
   764  			},
   765  			wantErr:              true,
   766  			wantTimeout:          true,
   767  			ExpectedPrepareCalls: 1,
   768  		},
   769  		{
   770  			description: "should prepare resource, claim not in cache",
   771  			driverName:  driverName,
   772  			pod: &v1.Pod{
   773  				ObjectMeta: metav1.ObjectMeta{
   774  					Name:      "test-pod",
   775  					Namespace: "test-namespace",
   776  					UID:       "test-reserved",
   777  				},
   778  				Spec: v1.PodSpec{
   779  					ResourceClaims: []v1.PodResourceClaim{
   780  						{
   781  							Name: "test-pod-claim-6",
   782  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   783  								s := "test-pod-claim-6"
   784  								return &s
   785  							}()},
   786  						},
   787  					},
   788  					Containers: []v1.Container{
   789  						{
   790  							Resources: v1.ResourceRequirements{
   791  								Claims: []v1.ResourceClaim{
   792  									{
   793  										Name: "test-pod-claim-6",
   794  									},
   795  								},
   796  							},
   797  						},
   798  					},
   799  				},
   800  			},
   801  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   802  				ObjectMeta: metav1.ObjectMeta{
   803  					Name:      "test-pod-claim-6",
   804  					Namespace: "test-namespace",
   805  					UID:       "test-reserved",
   806  				},
   807  				Spec: resourcev1alpha2.ResourceClaimSpec{
   808  					ResourceClassName: "test-class",
   809  				},
   810  				Status: resourcev1alpha2.ResourceClaimStatus{
   811  					DriverName: driverName,
   812  					Allocation: &resourcev1alpha2.AllocationResult{
   813  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   814  							{Data: "test-data", DriverName: driverName},
   815  						},
   816  					},
   817  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   818  						{UID: "test-reserved"},
   819  					},
   820  				},
   821  			},
   822  			resp: &drapbv1.NodePrepareResourcesResponse{
   823  				Claims: map[string]*drapbv1.NodePrepareResourceResponse{
   824  					"test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}},
   825  				},
   826  			},
   827  			expectedCDIDevices:   []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)},
   828  			ExpectedPrepareCalls: 1,
   829  		},
   830  		{
   831  			description: "should prepare resource. claim in cache, manager did not prepare resource",
   832  			driverName:  driverName,
   833  			pod: &v1.Pod{
   834  				ObjectMeta: metav1.ObjectMeta{
   835  					Name:      "test-pod",
   836  					Namespace: "test-namespace",
   837  					UID:       "test-reserved",
   838  				},
   839  				Spec: v1.PodSpec{
   840  					ResourceClaims: []v1.PodResourceClaim{
   841  						{
   842  							Name: "test-pod-claim",
   843  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   844  								s := "test-pod-claim"
   845  								return &s
   846  							}()},
   847  						},
   848  					},
   849  					Containers: []v1.Container{
   850  						{
   851  							Resources: v1.ResourceRequirements{
   852  								Claims: []v1.ResourceClaim{
   853  									{
   854  										Name: "test-pod-claim",
   855  									},
   856  								},
   857  							},
   858  						},
   859  					},
   860  				},
   861  			},
   862  			claimInfo: &ClaimInfo{
   863  				ClaimInfoState: state.ClaimInfoState{
   864  					DriverName:      driverName,
   865  					ClassName:       "test-class",
   866  					ClaimName:       "test-pod-claim",
   867  					ClaimUID:        "test-reserved",
   868  					Namespace:       "test-namespace",
   869  					PodUIDs:         sets.Set[string]{"test-reserved": sets.Empty{}},
   870  					ResourceHandles: []resourcev1alpha2.ResourceHandle{{Data: "test-data", DriverName: driverName}},
   871  				},
   872  				annotations: make(map[string][]kubecontainer.Annotation),
   873  				prepared:    false,
   874  			},
   875  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   876  				ObjectMeta: metav1.ObjectMeta{
   877  					Name:      "test-pod-claim",
   878  					Namespace: "test-namespace",
   879  					UID:       "test-reserved",
   880  				},
   881  				Spec: resourcev1alpha2.ResourceClaimSpec{
   882  					ResourceClassName: "test-class",
   883  				},
   884  				Status: resourcev1alpha2.ResourceClaimStatus{
   885  					DriverName: driverName,
   886  					Allocation: &resourcev1alpha2.AllocationResult{
   887  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   888  							{Data: "test-data", DriverName: driverName},
   889  						},
   890  					},
   891  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   892  						{UID: "test-reserved"},
   893  					},
   894  				},
   895  			},
   896  			resp: &drapbv1.NodePrepareResourcesResponse{
   897  				Claims: map[string]*drapbv1.NodePrepareResourceResponse{
   898  					"test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}},
   899  				},
   900  			},
   901  			expectedCDIDevices:   []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)},
   902  			ExpectedPrepareCalls: 1,
   903  		},
   904  	} {
   905  		t.Run(test.description, func(t *testing.T) {
   906  			cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
   907  			if err != nil {
   908  				t.Fatalf("failed to newClaimInfoCache, err:%v", err)
   909  			}
   910  
   911  			manager := &ManagerImpl{
   912  				kubeClient: fakeKubeClient,
   913  				cache:      cache,
   914  			}
   915  
   916  			if test.resourceClaim != nil {
   917  				if _, err := fakeKubeClient.ResourceV1alpha2().ResourceClaims(test.pod.Namespace).Create(context.Background(), test.resourceClaim, metav1.CreateOptions{}); err != nil {
   918  					t.Fatalf("failed to create ResourceClaim %s: %+v", test.resourceClaim.Name, err)
   919  				}
   920  			}
   921  
   922  			var pluginClientTimeout *time.Duration
   923  			if test.wantTimeout {
   924  				timeout := time.Millisecond * 20
   925  				pluginClientTimeout = &timeout
   926  			}
   927  
   928  			draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout, pluginClientTimeout, test.resp, nil)
   929  			if err != nil {
   930  				t.Fatal(err)
   931  			}
   932  			defer draServerInfo.teardownFn()
   933  
   934  			plg := plugin.NewRegistrationHandler(nil, getFakeNode)
   935  			if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}, pluginClientTimeout); err != nil {
   936  				t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err)
   937  			}
   938  			defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests
   939  
   940  			if test.claimInfo != nil {
   941  				manager.cache.add(test.claimInfo)
   942  			}
   943  
   944  			err = manager.PrepareResources(test.pod)
   945  
   946  			assert.Equal(t, test.ExpectedPrepareCalls, draServerInfo.server.prepareResourceCalls.Load())
   947  
   948  			if test.wantErr {
   949  				assert.Error(t, err)
   950  				return // PrepareResources returned an error so stopping the subtest here
   951  			} else if test.wantResourceSkipped {
   952  				assert.NoError(t, err)
   953  				return // resource skipped so no need to continue
   954  			}
   955  
   956  			assert.NoError(t, err)
   957  			// check the cache contains the expected claim info
   958  			claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0])
   959  			if err != nil {
   960  				t.Fatal(err)
   961  			}
   962  			claimInfo, ok := manager.cache.get(*claimName, test.pod.Namespace)
   963  			if !ok {
   964  				t.Fatalf("claimInfo not found in cache for claim %s", *claimName)
   965  			}
   966  			if claimInfo.DriverName != test.resourceClaim.Status.DriverName {
   967  				t.Fatalf("driverName mismatch: expected %s, got %s", test.resourceClaim.Status.DriverName, claimInfo.DriverName)
   968  			}
   969  			if claimInfo.ClassName != test.resourceClaim.Spec.ResourceClassName {
   970  				t.Fatalf("resourceClassName mismatch: expected %s, got %s", test.resourceClaim.Spec.ResourceClassName, claimInfo.ClassName)
   971  			}
   972  			if len(claimInfo.PodUIDs) != 1 || !claimInfo.PodUIDs.Has(string(test.pod.UID)) {
   973  				t.Fatalf("podUIDs mismatch: expected [%s], got %v", test.pod.UID, claimInfo.PodUIDs)
   974  			}
   975  			assert.ElementsMatchf(t, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName], test.expectedCDIDevices,
   976  				"cdiDevices mismatch: expected [%v], got %v", test.expectedCDIDevices, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName])
   977  		})
   978  	}
   979  }
   980  
   981  func TestUnprepareResources(t *testing.T) {
   982  	fakeKubeClient := fake.NewSimpleClientset()
   983  
   984  	for _, test := range []struct {
   985  		description            string
   986  		driverName             string
   987  		pod                    *v1.Pod
   988  		claimInfo              *ClaimInfo
   989  		resp                   *drapbv1.NodeUnprepareResourcesResponse
   990  		wantErr                bool
   991  		wantTimeout            bool
   992  		wantResourceSkipped    bool
   993  		expectedUnprepareCalls uint32
   994  	}{
   995  		{
   996  			description: "plugin does not exist",
   997  			driverName:  "this-plugin-does-not-exist",
   998  			pod: &v1.Pod{
   999  				ObjectMeta: metav1.ObjectMeta{
  1000  					Name:      "test-pod",
  1001  					Namespace: "test-namespace",
  1002  					UID:       "test-reserved",
  1003  				},
  1004  				Spec: v1.PodSpec{
  1005  					ResourceClaims: []v1.PodResourceClaim{
  1006  						{
  1007  							Name: "another-claim-test",
  1008  							Source: v1.ClaimSource{
  1009  								ResourceClaimName: func() *string {
  1010  									s := "another-claim-test"
  1011  									return &s
  1012  								}(),
  1013  							},
  1014  						},
  1015  					},
  1016  					Containers: []v1.Container{
  1017  						{
  1018  							Resources: v1.ResourceRequirements{
  1019  								Claims: []v1.ResourceClaim{
  1020  									{
  1021  										Name: "another-claim-test",
  1022  									},
  1023  								},
  1024  							},
  1025  						},
  1026  					},
  1027  				},
  1028  			},
  1029  			claimInfo: &ClaimInfo{
  1030  				ClaimInfoState: state.ClaimInfoState{
  1031  					DriverName: driverName,
  1032  					ClaimName:  "another-claim-test",
  1033  					Namespace:  "test-namespace",
  1034  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1035  						{
  1036  							DriverName: driverName,
  1037  							Data:       "test data",
  1038  						},
  1039  					},
  1040  				},
  1041  			},
  1042  			wantErr: true,
  1043  		},
  1044  		{
  1045  			description: "resource claim referenced by other pod(s)",
  1046  			driverName:  driverName,
  1047  			pod: &v1.Pod{
  1048  				ObjectMeta: metav1.ObjectMeta{
  1049  					Name:      "test-pod",
  1050  					Namespace: "test-namespace",
  1051  					UID:       "test-reserved",
  1052  				},
  1053  				Spec: v1.PodSpec{
  1054  					ResourceClaims: []v1.PodResourceClaim{
  1055  						{
  1056  							Name: "test-pod-claim-1",
  1057  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1058  								s := "test-pod-claim-1"
  1059  								return &s
  1060  							}()},
  1061  						},
  1062  					},
  1063  					Containers: []v1.Container{
  1064  						{
  1065  							Resources: v1.ResourceRequirements{
  1066  								Claims: []v1.ResourceClaim{
  1067  									{
  1068  										Name: "test-pod-claim-1",
  1069  									},
  1070  								},
  1071  							},
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  			claimInfo: &ClaimInfo{
  1077  				ClaimInfoState: state.ClaimInfoState{
  1078  					DriverName: driverName,
  1079  					ClaimName:  "test-pod-claim-1",
  1080  					Namespace:  "test-namespace",
  1081  					PodUIDs:    sets.Set[string]{"test-reserved": sets.Empty{}, "test-reserved-2": sets.Empty{}},
  1082  				},
  1083  			},
  1084  			wantResourceSkipped: true,
  1085  		},
  1086  		{
  1087  			description: "should timeout",
  1088  			driverName:  driverName,
  1089  			pod: &v1.Pod{
  1090  				ObjectMeta: metav1.ObjectMeta{
  1091  					Name:      "test-pod",
  1092  					Namespace: "test-namespace",
  1093  					UID:       "test-reserved",
  1094  				},
  1095  				Spec: v1.PodSpec{
  1096  					ResourceClaims: []v1.PodResourceClaim{
  1097  						{
  1098  							Name: "test-pod-claim-2",
  1099  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1100  								s := "test-pod-claim-2"
  1101  								return &s
  1102  							}()},
  1103  						},
  1104  					},
  1105  					Containers: []v1.Container{
  1106  						{
  1107  							Resources: v1.ResourceRequirements{
  1108  								Claims: []v1.ResourceClaim{
  1109  									{
  1110  										Name: "test-pod-claim-2",
  1111  									},
  1112  								},
  1113  							},
  1114  						},
  1115  					},
  1116  				},
  1117  			},
  1118  			claimInfo: &ClaimInfo{
  1119  				ClaimInfoState: state.ClaimInfoState{
  1120  					DriverName: driverName,
  1121  					ClaimName:  "test-pod-claim-2",
  1122  					Namespace:  "test-namespace",
  1123  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1124  						{
  1125  							DriverName: driverName,
  1126  							Data:       "test data",
  1127  						},
  1128  					},
  1129  				},
  1130  			},
  1131  			resp:                   &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"test-reserved": {}}},
  1132  			wantErr:                true,
  1133  			wantTimeout:            true,
  1134  			expectedUnprepareCalls: 1,
  1135  		},
  1136  		{
  1137  			description: "should unprepare resource, claim previously prepared by currently running manager",
  1138  			driverName:  driverName,
  1139  			pod: &v1.Pod{
  1140  				ObjectMeta: metav1.ObjectMeta{
  1141  					Name:      "test-pod",
  1142  					Namespace: "test-namespace",
  1143  					UID:       "test-reserved",
  1144  				},
  1145  				Spec: v1.PodSpec{
  1146  					ResourceClaims: []v1.PodResourceClaim{
  1147  						{
  1148  							Name: "test-pod-claim-3",
  1149  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1150  								s := "test-pod-claim-3"
  1151  								return &s
  1152  							}()},
  1153  						},
  1154  					},
  1155  					Containers: []v1.Container{
  1156  						{
  1157  							Resources: v1.ResourceRequirements{
  1158  								Claims: []v1.ResourceClaim{
  1159  									{
  1160  										Name: "test-pod-claim-3",
  1161  									},
  1162  								},
  1163  							},
  1164  						},
  1165  					},
  1166  				},
  1167  			},
  1168  			claimInfo: &ClaimInfo{
  1169  				ClaimInfoState: state.ClaimInfoState{
  1170  					DriverName: driverName,
  1171  					ClaimName:  "test-pod-claim-3",
  1172  					Namespace:  "test-namespace",
  1173  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1174  						{
  1175  							DriverName: driverName,
  1176  							Data:       "test data",
  1177  						},
  1178  					},
  1179  				},
  1180  				prepared: true,
  1181  			},
  1182  			resp:                   &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"": {}}},
  1183  			expectedUnprepareCalls: 1,
  1184  		},
  1185  		{
  1186  			description: "should unprepare resource, claim previously was not prepared by currently running manager",
  1187  			driverName:  driverName,
  1188  			pod: &v1.Pod{
  1189  				ObjectMeta: metav1.ObjectMeta{
  1190  					Name:      "test-pod",
  1191  					Namespace: "test-namespace",
  1192  					UID:       "test-reserved",
  1193  				},
  1194  				Spec: v1.PodSpec{
  1195  					ResourceClaims: []v1.PodResourceClaim{
  1196  						{
  1197  							Name: "test-pod-claim",
  1198  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1199  								s := "test-pod-claim"
  1200  								return &s
  1201  							}()},
  1202  						},
  1203  					},
  1204  					Containers: []v1.Container{
  1205  						{
  1206  							Resources: v1.ResourceRequirements{
  1207  								Claims: []v1.ResourceClaim{
  1208  									{
  1209  										Name: "test-pod-claim",
  1210  									},
  1211  								},
  1212  							},
  1213  						},
  1214  					},
  1215  				},
  1216  			},
  1217  			claimInfo: &ClaimInfo{
  1218  				ClaimInfoState: state.ClaimInfoState{
  1219  					DriverName: driverName,
  1220  					ClaimName:  "test-pod-claim",
  1221  					Namespace:  "test-namespace",
  1222  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1223  						{
  1224  							DriverName: driverName,
  1225  							Data:       "test data",
  1226  						},
  1227  					},
  1228  				},
  1229  				prepared: false,
  1230  			},
  1231  			resp:                   &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"": {}}},
  1232  			expectedUnprepareCalls: 1,
  1233  		},
  1234  		{
  1235  			description: "should unprepare resource, driver returns nil value",
  1236  			driverName:  driverName,
  1237  			pod: &v1.Pod{
  1238  				ObjectMeta: metav1.ObjectMeta{
  1239  					Name:      "test-pod",
  1240  					Namespace: "test-namespace",
  1241  					UID:       "test-reserved",
  1242  				},
  1243  				Spec: v1.PodSpec{
  1244  					ResourceClaims: []v1.PodResourceClaim{
  1245  						{
  1246  							Name: "test-pod-claim-nil",
  1247  							Source: v1.ClaimSource{
  1248  								ResourceClaimName: func() *string {
  1249  									s := "test-pod-claim-nil"
  1250  									return &s
  1251  								}(),
  1252  							},
  1253  						},
  1254  					},
  1255  					Containers: []v1.Container{
  1256  						{
  1257  							Resources: v1.ResourceRequirements{
  1258  								Claims: []v1.ResourceClaim{
  1259  									{
  1260  										Name: "test-pod-claim-nil",
  1261  									},
  1262  								},
  1263  							},
  1264  						},
  1265  					},
  1266  				},
  1267  			},
  1268  			claimInfo: &ClaimInfo{
  1269  				ClaimInfoState: state.ClaimInfoState{
  1270  					DriverName: driverName,
  1271  					ClaimName:  "test-pod-claim-nil",
  1272  					Namespace:  "test-namespace",
  1273  					ClaimUID:   "test-reserved",
  1274  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1275  						{
  1276  							DriverName: driverName,
  1277  							Data:       "test data",
  1278  						},
  1279  					},
  1280  				},
  1281  				prepared: true,
  1282  			},
  1283  			resp:                   &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"test-reserved": nil}},
  1284  			expectedUnprepareCalls: 1,
  1285  		},
  1286  	} {
  1287  		t.Run(test.description, func(t *testing.T) {
  1288  			cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1289  			if err != nil {
  1290  				t.Fatalf("failed to create a new instance of the claimInfoCache, err: %v", err)
  1291  			}
  1292  
  1293  			var pluginClientTimeout *time.Duration
  1294  			if test.wantTimeout {
  1295  				timeout := time.Millisecond * 20
  1296  				pluginClientTimeout = &timeout
  1297  			}
  1298  
  1299  			draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout, pluginClientTimeout, nil, test.resp)
  1300  			if err != nil {
  1301  				t.Fatal(err)
  1302  			}
  1303  			defer draServerInfo.teardownFn()
  1304  
  1305  			plg := plugin.NewRegistrationHandler(nil, getFakeNode)
  1306  			if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}, pluginClientTimeout); err != nil {
  1307  				t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err)
  1308  			}
  1309  			defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests
  1310  
  1311  			manager := &ManagerImpl{
  1312  				kubeClient: fakeKubeClient,
  1313  				cache:      cache,
  1314  			}
  1315  
  1316  			if test.claimInfo != nil {
  1317  				manager.cache.add(test.claimInfo)
  1318  			}
  1319  
  1320  			err = manager.UnprepareResources(test.pod)
  1321  
  1322  			assert.Equal(t, test.expectedUnprepareCalls, draServerInfo.server.unprepareResourceCalls.Load())
  1323  
  1324  			if test.wantErr {
  1325  				assert.Error(t, err)
  1326  				return // UnprepareResources returned an error so stopping the subtest here
  1327  			} else if test.wantResourceSkipped {
  1328  				assert.NoError(t, err)
  1329  				return // resource skipped so no need to continue
  1330  			}
  1331  
  1332  			assert.NoError(t, err)
  1333  			// Check that the cache has been updated correctly
  1334  			claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0])
  1335  			if err != nil {
  1336  				t.Fatal(err)
  1337  			}
  1338  			if manager.cache.contains(*claimName, test.pod.Namespace) {
  1339  				t.Fatalf("claimInfo still found in cache after calling UnprepareResources")
  1340  			}
  1341  		})
  1342  	}
  1343  }
  1344  
  1345  func TestPodMightNeedToUnprepareResources(t *testing.T) {
  1346  	fakeKubeClient := fake.NewSimpleClientset()
  1347  
  1348  	cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1349  	if err != nil {
  1350  		t.Fatalf("failed to newClaimInfoCache, err:%v", err)
  1351  	}
  1352  
  1353  	manager := &ManagerImpl{
  1354  		kubeClient: fakeKubeClient,
  1355  		cache:      cache,
  1356  	}
  1357  
  1358  	claimName := "test-claim"
  1359  	podUID := "test-pod-uid"
  1360  	namespace := "test-namespace"
  1361  
  1362  	claimInfo := &ClaimInfo{
  1363  		ClaimInfoState: state.ClaimInfoState{PodUIDs: sets.New(podUID), ClaimName: claimName, Namespace: namespace},
  1364  	}
  1365  	manager.cache.add(claimInfo)
  1366  	if !manager.cache.contains(claimName, namespace) {
  1367  		t.Fatalf("failed to get claimInfo from cache for claim name %s, namespace %s: err:%v", claimName, namespace, err)
  1368  	}
  1369  	claimInfo.addPodReference(types.UID(podUID))
  1370  	needsUnprepare := manager.PodMightNeedToUnprepareResources(types.UID(podUID))
  1371  	assert.True(t, needsUnprepare)
  1372  }
  1373  
  1374  func TestGetContainerClaimInfos(t *testing.T) {
  1375  	cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1376  	if err != nil {
  1377  		t.Fatalf("error occur:%v", err)
  1378  	}
  1379  	manager := &ManagerImpl{
  1380  		cache: cache,
  1381  	}
  1382  
  1383  	resourceClaimName := "test-resource-claim-1"
  1384  	resourceClaimName2 := "test-resource-claim-2"
  1385  
  1386  	for i, test := range []struct {
  1387  		expectedClaimName string
  1388  		pod               *v1.Pod
  1389  		container         *v1.Container
  1390  		claimInfo         *ClaimInfo
  1391  	}{
  1392  		{
  1393  			expectedClaimName: resourceClaimName,
  1394  			pod: &v1.Pod{
  1395  				Spec: v1.PodSpec{
  1396  					ResourceClaims: []v1.PodResourceClaim{
  1397  						{
  1398  							Name:   "claim1",
  1399  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
  1400  						},
  1401  					},
  1402  				},
  1403  			},
  1404  			container: &v1.Container{
  1405  				Resources: v1.ResourceRequirements{
  1406  					Claims: []v1.ResourceClaim{
  1407  						{
  1408  							Name: "claim1",
  1409  						},
  1410  					},
  1411  				},
  1412  			},
  1413  			claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName}},
  1414  		},
  1415  		{
  1416  			expectedClaimName: resourceClaimName2,
  1417  			pod: &v1.Pod{
  1418  				Spec: v1.PodSpec{
  1419  					ResourceClaims: []v1.PodResourceClaim{
  1420  						{
  1421  							Name:   "claim2",
  1422  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName2},
  1423  						},
  1424  					},
  1425  				},
  1426  			},
  1427  			container: &v1.Container{
  1428  				Resources: v1.ResourceRequirements{
  1429  					Claims: []v1.ResourceClaim{
  1430  						{
  1431  							Name: "claim2",
  1432  						},
  1433  					},
  1434  				},
  1435  			},
  1436  			claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName2}},
  1437  		},
  1438  	} {
  1439  		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
  1440  			manager.cache.add(test.claimInfo)
  1441  
  1442  			fakeClaimInfos, err := manager.GetContainerClaimInfos(test.pod, test.container)
  1443  			assert.NoError(t, err)
  1444  			assert.Equal(t, 1, len(fakeClaimInfos))
  1445  			assert.Equal(t, test.expectedClaimName, fakeClaimInfos[0].ClaimInfoState.ClaimName)
  1446  
  1447  			manager.cache.delete(test.pod.Spec.ResourceClaims[0].Name, "default")
  1448  			_, err = manager.GetContainerClaimInfos(test.pod, test.container)
  1449  			assert.NoError(t, err)
  1450  		})
  1451  	}
  1452  }
  1453  
  1454  // TestParallelPrepareUnprepareResources calls PrepareResources and UnprepareResources APIs in parallel
  1455  // to detect possible data races
  1456  func TestParallelPrepareUnprepareResources(t *testing.T) {
  1457  	// Setup and register fake DRA driver
  1458  	draServerInfo, err := setupFakeDRADriverGRPCServer(false, nil, nil, nil)
  1459  	if err != nil {
  1460  		t.Fatal(err)
  1461  	}
  1462  	defer draServerInfo.teardownFn()
  1463  
  1464  	plg := plugin.NewRegistrationHandler(nil, getFakeNode)
  1465  	if err := plg.RegisterPlugin(driverName, draServerInfo.socketName, []string{"1.27"}, nil); err != nil {
  1466  		t.Fatalf("failed to register plugin %s, err: %v", driverName, err)
  1467  	}
  1468  	defer plg.DeRegisterPlugin(driverName)
  1469  
  1470  	// Create ClaimInfo cache
  1471  	cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1472  	if err != nil {
  1473  		t.Errorf("failed to newClaimInfoCache, err: %+v", err)
  1474  		return
  1475  	}
  1476  
  1477  	// Create fake Kube client and DRA manager
  1478  	fakeKubeClient := fake.NewSimpleClientset()
  1479  	manager := &ManagerImpl{kubeClient: fakeKubeClient, cache: cache}
  1480  
  1481  	// Call PrepareResources in parallel
  1482  	var wgSync, wgStart sync.WaitGroup // groups to sync goroutines
  1483  	numGoroutines := 30
  1484  	wgSync.Add(numGoroutines)
  1485  	wgStart.Add(1)
  1486  	for i := 0; i < numGoroutines; i++ {
  1487  		go func(t *testing.T, goRoutineNum int) {
  1488  			defer wgSync.Done()
  1489  			wgStart.Wait() // Wait to start all goroutines at the same time
  1490  
  1491  			var err error
  1492  			nameSpace := "test-namespace-parallel"
  1493  			claimName := fmt.Sprintf("test-pod-claim-%d", goRoutineNum)
  1494  			podUID := types.UID(fmt.Sprintf("test-reserved-%d", goRoutineNum))
  1495  			pod := &v1.Pod{
  1496  				ObjectMeta: metav1.ObjectMeta{
  1497  					Name:      fmt.Sprintf("test-pod-%d", goRoutineNum),
  1498  					Namespace: nameSpace,
  1499  					UID:       podUID,
  1500  				},
  1501  				Spec: v1.PodSpec{
  1502  					ResourceClaims: []v1.PodResourceClaim{
  1503  						{
  1504  							Name: claimName,
  1505  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1506  								s := claimName
  1507  								return &s
  1508  							}()},
  1509  						},
  1510  					},
  1511  					Containers: []v1.Container{
  1512  						{
  1513  							Resources: v1.ResourceRequirements{
  1514  								Claims: []v1.ResourceClaim{
  1515  									{
  1516  										Name: claimName,
  1517  									},
  1518  								},
  1519  							},
  1520  						},
  1521  					},
  1522  				},
  1523  			}
  1524  			resourceClaim := &resourcev1alpha2.ResourceClaim{
  1525  				ObjectMeta: metav1.ObjectMeta{
  1526  					Name:      claimName,
  1527  					Namespace: nameSpace,
  1528  					UID:       types.UID(fmt.Sprintf("claim-%d", goRoutineNum)),
  1529  				},
  1530  				Spec: resourcev1alpha2.ResourceClaimSpec{
  1531  					ResourceClassName: "test-class",
  1532  				},
  1533  				Status: resourcev1alpha2.ResourceClaimStatus{
  1534  					DriverName: driverName,
  1535  					Allocation: &resourcev1alpha2.AllocationResult{
  1536  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1537  							{Data: "test-data", DriverName: driverName},
  1538  						},
  1539  					},
  1540  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
  1541  						{UID: podUID},
  1542  					},
  1543  				},
  1544  			}
  1545  
  1546  			if _, err = fakeKubeClient.ResourceV1alpha2().ResourceClaims(pod.Namespace).Create(context.Background(), resourceClaim, metav1.CreateOptions{}); err != nil {
  1547  				t.Errorf("failed to create ResourceClaim %s: %+v", resourceClaim.Name, err)
  1548  				return
  1549  			}
  1550  
  1551  			if err = manager.PrepareResources(pod); err != nil {
  1552  				t.Errorf("pod: %s: PrepareResources failed: %+v", pod.Name, err)
  1553  				return
  1554  			}
  1555  
  1556  			if err = manager.UnprepareResources(pod); err != nil {
  1557  				t.Errorf("pod: %s: UnprepareResources failed: %+v", pod.Name, err)
  1558  				return
  1559  			}
  1560  
  1561  		}(t, i)
  1562  	}
  1563  	wgStart.Done() // Start executing goroutines
  1564  	wgSync.Wait()  // Wait for all goroutines to finish
  1565  }