k8s.io/kubernetes@v1.29.3/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/atomic"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"google.golang.org/grpc"
    31  	v1 "k8s.io/api/core/v1"
    32  	resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	"k8s.io/client-go/kubernetes/fake"
    36  	"k8s.io/dynamic-resource-allocation/resourceclaim"
    37  	drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1alpha3"
    38  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin"
    39  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
    40  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    41  )
    42  
    43  const (
    44  	driverName      = "test-cdi-device"
    45  	driverClassName = "test"
    46  )
    47  
    48  type fakeDRADriverGRPCServer struct {
    49  	drapbv1.UnimplementedNodeServer
    50  	driverName             string
    51  	timeout                *time.Duration
    52  	prepareResourceCalls   atomic.Uint32
    53  	unprepareResourceCalls atomic.Uint32
    54  }
    55  
    56  func (s *fakeDRADriverGRPCServer) NodePrepareResources(ctx context.Context, req *drapbv1.NodePrepareResourcesRequest) (*drapbv1.NodePrepareResourcesResponse, error) {
    57  	s.prepareResourceCalls.Add(1)
    58  
    59  	if s.timeout != nil {
    60  		time.Sleep(*s.timeout)
    61  	}
    62  	deviceName := "claim-" + req.Claims[0].Uid
    63  	result := s.driverName + "/" + driverClassName + "=" + deviceName
    64  	return &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{req.Claims[0].Uid: {CDIDevices: []string{result}}}}, nil
    65  }
    66  
    67  func (s *fakeDRADriverGRPCServer) NodeUnprepareResources(ctx context.Context, req *drapbv1.NodeUnprepareResourcesRequest) (*drapbv1.NodeUnprepareResourcesResponse, error) {
    68  	s.unprepareResourceCalls.Add(1)
    69  
    70  	if s.timeout != nil {
    71  		time.Sleep(*s.timeout)
    72  	}
    73  	return &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{req.Claims[0].Uid: {}}}, nil
    74  }
    75  
    76  type tearDown func()
    77  
    78  type fakeDRAServerInfo struct {
    79  	// fake DRA server
    80  	server *fakeDRADriverGRPCServer
    81  	// fake DRA plugin socket name
    82  	socketName string
    83  	// teardownFn stops fake gRPC server
    84  	teardownFn tearDown
    85  }
    86  
    87  func setupFakeDRADriverGRPCServer(shouldTimeout bool) (fakeDRAServerInfo, error) {
    88  	socketDir, err := os.MkdirTemp("", "dra")
    89  	if err != nil {
    90  		return fakeDRAServerInfo{
    91  			server:     nil,
    92  			socketName: "",
    93  			teardownFn: nil,
    94  		}, err
    95  	}
    96  
    97  	socketName := filepath.Join(socketDir, "server.sock")
    98  	stopCh := make(chan struct{})
    99  
   100  	teardown := func() {
   101  		close(stopCh)
   102  		os.RemoveAll(socketName)
   103  	}
   104  
   105  	l, err := net.Listen("unix", socketName)
   106  	if err != nil {
   107  		teardown()
   108  		return fakeDRAServerInfo{
   109  			server:     nil,
   110  			socketName: "",
   111  			teardownFn: nil,
   112  		}, err
   113  	}
   114  
   115  	s := grpc.NewServer()
   116  	fakeDRADriverGRPCServer := &fakeDRADriverGRPCServer{
   117  		driverName: driverName,
   118  	}
   119  	if shouldTimeout {
   120  		timeout := plugin.PluginClientTimeout + time.Second
   121  		fakeDRADriverGRPCServer.timeout = &timeout
   122  	}
   123  
   124  	drapbv1.RegisterNodeServer(s, fakeDRADriverGRPCServer)
   125  
   126  	go func() {
   127  		go s.Serve(l)
   128  		<-stopCh
   129  		s.GracefulStop()
   130  	}()
   131  
   132  	return fakeDRAServerInfo{
   133  		server:     fakeDRADriverGRPCServer,
   134  		socketName: socketName,
   135  		teardownFn: teardown,
   136  	}, nil
   137  }
   138  
   139  func TestNewManagerImpl(t *testing.T) {
   140  	kubeClient := fake.NewSimpleClientset()
   141  	for _, test := range []struct {
   142  		description        string
   143  		stateFileDirectory string
   144  		wantErr            bool
   145  	}{
   146  		{
   147  			description:        "invalid directory path",
   148  			stateFileDirectory: "",
   149  			wantErr:            true,
   150  		},
   151  		{
   152  			description:        "valid directory path",
   153  			stateFileDirectory: t.TempDir(),
   154  		},
   155  	} {
   156  		t.Run(test.description, func(t *testing.T) {
   157  			manager, err := NewManagerImpl(kubeClient, test.stateFileDirectory)
   158  			if test.wantErr {
   159  				assert.Error(t, err)
   160  				return
   161  			}
   162  
   163  			assert.NoError(t, err)
   164  			assert.NotNil(t, manager.cache)
   165  			assert.NotNil(t, manager.kubeClient)
   166  		})
   167  	}
   168  }
   169  
   170  func TestGetResources(t *testing.T) {
   171  	kubeClient := fake.NewSimpleClientset()
   172  	resourceClaimName := "test-pod-claim-1"
   173  
   174  	for _, test := range []struct {
   175  		description string
   176  		container   *v1.Container
   177  		pod         *v1.Pod
   178  		claimInfo   *ClaimInfo
   179  		wantErr     bool
   180  	}{
   181  		{
   182  			description: "claim info with annotations",
   183  			container: &v1.Container{
   184  				Name: "test-container",
   185  				Resources: v1.ResourceRequirements{
   186  					Claims: []v1.ResourceClaim{
   187  						{
   188  							Name: "test-pod-claim-1",
   189  						},
   190  					},
   191  				},
   192  			},
   193  			pod: &v1.Pod{
   194  				ObjectMeta: metav1.ObjectMeta{
   195  					Name:      "test-pod",
   196  					Namespace: "test-namespace",
   197  				},
   198  				Spec: v1.PodSpec{
   199  					ResourceClaims: []v1.PodResourceClaim{
   200  						{
   201  							Name:   "test-pod-claim-1",
   202  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
   203  						},
   204  					},
   205  				},
   206  			},
   207  			claimInfo: &ClaimInfo{
   208  				annotations: map[string][]kubecontainer.Annotation{
   209  					"test-plugin": {
   210  						{
   211  							Name:  "test-annotation",
   212  							Value: "123",
   213  						},
   214  					},
   215  				},
   216  				ClaimInfoState: state.ClaimInfoState{
   217  					ClaimName: "test-pod-claim-1",
   218  					CDIDevices: map[string][]string{
   219  						driverName: {"123"},
   220  					},
   221  					Namespace: "test-namespace",
   222  				},
   223  			},
   224  		},
   225  		{
   226  			description: "claim info without annotations",
   227  			container: &v1.Container{
   228  				Name: "test-container",
   229  				Resources: v1.ResourceRequirements{
   230  					Claims: []v1.ResourceClaim{
   231  						{
   232  							Name: "test-pod-claim-1",
   233  						},
   234  					},
   235  				},
   236  			},
   237  			pod: &v1.Pod{
   238  				ObjectMeta: metav1.ObjectMeta{
   239  					Name:      "test-pod",
   240  					Namespace: "test-namespace",
   241  				},
   242  				Spec: v1.PodSpec{
   243  					ResourceClaims: []v1.PodResourceClaim{
   244  						{
   245  							Name:   "test-pod-claim-1",
   246  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
   247  						},
   248  					},
   249  				},
   250  			},
   251  			claimInfo: &ClaimInfo{
   252  				ClaimInfoState: state.ClaimInfoState{
   253  					ClaimName: "test-pod-claim-1",
   254  					CDIDevices: map[string][]string{
   255  						driverName: {"123"},
   256  					},
   257  					Namespace: "test-namespace",
   258  				},
   259  			},
   260  		},
   261  		{
   262  			description: "no claim info",
   263  			container: &v1.Container{
   264  				Name: "test-container",
   265  				Resources: v1.ResourceRequirements{
   266  					Claims: []v1.ResourceClaim{
   267  						{
   268  							Name: "test-pod-claim-1",
   269  						},
   270  					},
   271  				},
   272  			},
   273  			pod: &v1.Pod{
   274  				ObjectMeta: metav1.ObjectMeta{
   275  					Name:      "test-pod",
   276  					Namespace: "test-namespace",
   277  				},
   278  				Spec: v1.PodSpec{
   279  					ResourceClaims: []v1.PodResourceClaim{
   280  						{
   281  							Name: "test-pod-claim-1",
   282  						},
   283  					},
   284  				},
   285  			},
   286  			wantErr: true,
   287  		},
   288  	} {
   289  		t.Run(test.description, func(t *testing.T) {
   290  			manager, err := NewManagerImpl(kubeClient, t.TempDir())
   291  			assert.NoError(t, err)
   292  
   293  			if test.claimInfo != nil {
   294  				manager.cache.add(test.claimInfo)
   295  			}
   296  
   297  			containerInfo, err := manager.GetResources(test.pod, test.container)
   298  			if test.wantErr {
   299  				assert.Error(t, err)
   300  				return
   301  			}
   302  
   303  			assert.NoError(t, err)
   304  			assert.Equal(t, test.claimInfo.CDIDevices[driverName][0], containerInfo.CDIDevices[0].Name)
   305  		})
   306  	}
   307  }
   308  
   309  func TestPrepareResources(t *testing.T) {
   310  	fakeKubeClient := fake.NewSimpleClientset()
   311  
   312  	for _, test := range []struct {
   313  		description          string
   314  		driverName           string
   315  		pod                  *v1.Pod
   316  		claimInfo            *ClaimInfo
   317  		resourceClaim        *resourcev1alpha2.ResourceClaim
   318  		wantErr              bool
   319  		wantTimeout          bool
   320  		wantResourceSkipped  bool
   321  		ExpectedPrepareCalls uint32
   322  	}{
   323  		{
   324  			description: "failed to fetch ResourceClaim",
   325  			driverName:  driverName,
   326  			pod: &v1.Pod{
   327  				ObjectMeta: metav1.ObjectMeta{
   328  					Name:      "test-pod",
   329  					Namespace: "test-namespace",
   330  					UID:       "test-reserved",
   331  				},
   332  				Spec: v1.PodSpec{
   333  					ResourceClaims: []v1.PodResourceClaim{
   334  						{
   335  							Name: "test-pod-claim-0",
   336  							Source: v1.ClaimSource{
   337  								ResourceClaimName: func() *string {
   338  									s := "test-pod-claim-0"
   339  									return &s
   340  								}(),
   341  							},
   342  						},
   343  					},
   344  				},
   345  			},
   346  			wantErr: true,
   347  		},
   348  		{
   349  			description: "plugin does not exist",
   350  			driverName:  "this-plugin-does-not-exist",
   351  			pod: &v1.Pod{
   352  				ObjectMeta: metav1.ObjectMeta{
   353  					Name:      "test-pod",
   354  					Namespace: "test-namespace",
   355  					UID:       "test-reserved",
   356  				},
   357  				Spec: v1.PodSpec{
   358  					ResourceClaims: []v1.PodResourceClaim{
   359  						{
   360  							Name: "test-pod-claim-1",
   361  							Source: v1.ClaimSource{
   362  								ResourceClaimName: func() *string {
   363  									s := "test-pod-claim-1"
   364  									return &s
   365  								}(),
   366  							},
   367  						},
   368  					},
   369  					Containers: []v1.Container{
   370  						{
   371  							Resources: v1.ResourceRequirements{
   372  								Claims: []v1.ResourceClaim{
   373  									{
   374  										Name: "test-pod-claim-1",
   375  									},
   376  								},
   377  							},
   378  						},
   379  					},
   380  				},
   381  			},
   382  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   383  				ObjectMeta: metav1.ObjectMeta{
   384  					Name:      "test-pod-claim-1",
   385  					Namespace: "test-namespace",
   386  					UID:       "test-reserved",
   387  				},
   388  				Spec: resourcev1alpha2.ResourceClaimSpec{
   389  					ResourceClassName: "test-class",
   390  				},
   391  				Status: resourcev1alpha2.ResourceClaimStatus{
   392  					DriverName: driverName,
   393  					Allocation: &resourcev1alpha2.AllocationResult{
   394  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   395  							{Data: "test-data", DriverName: driverName},
   396  						},
   397  					},
   398  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   399  						{UID: "test-reserved"},
   400  					},
   401  				},
   402  			},
   403  			wantErr: true,
   404  		},
   405  		{
   406  			description: "pod is not allowed to use resource claim",
   407  			driverName:  driverName,
   408  			pod: &v1.Pod{
   409  				ObjectMeta: metav1.ObjectMeta{
   410  					Name:      "test-pod",
   411  					Namespace: "test-namespace",
   412  					UID:       "test-reserved",
   413  				},
   414  				Spec: v1.PodSpec{
   415  					ResourceClaims: []v1.PodResourceClaim{
   416  						{
   417  							Name: "test-pod-claim-2",
   418  							Source: v1.ClaimSource{
   419  								ResourceClaimName: func() *string {
   420  									s := "test-pod-claim-2"
   421  									return &s
   422  								}(),
   423  							},
   424  						},
   425  					},
   426  				},
   427  			},
   428  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   429  				ObjectMeta: metav1.ObjectMeta{
   430  					Name:      "test-pod-claim-2",
   431  					Namespace: "test-namespace",
   432  					UID:       "test-reserved",
   433  				},
   434  				Spec: resourcev1alpha2.ResourceClaimSpec{
   435  					ResourceClassName: "test-class",
   436  				},
   437  				Status: resourcev1alpha2.ResourceClaimStatus{
   438  					DriverName: driverName,
   439  					Allocation: &resourcev1alpha2.AllocationResult{
   440  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   441  							{Data: "test-data", DriverName: driverName},
   442  						},
   443  					},
   444  				},
   445  			},
   446  			wantErr: true,
   447  		},
   448  		{
   449  			description: "no container actually uses the claim",
   450  			driverName:  driverName,
   451  			pod: &v1.Pod{
   452  				ObjectMeta: metav1.ObjectMeta{
   453  					Name:      "test-pod",
   454  					Namespace: "test-namespace",
   455  					UID:       "test-reserved",
   456  				},
   457  				Spec: v1.PodSpec{
   458  					ResourceClaims: []v1.PodResourceClaim{
   459  						{
   460  							Name: "test-pod-claim-3",
   461  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   462  								s := "test-pod-claim-3"
   463  								return &s
   464  							}()},
   465  						},
   466  					},
   467  				},
   468  			},
   469  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   470  				ObjectMeta: metav1.ObjectMeta{
   471  					Name:      "test-pod-claim-3",
   472  					Namespace: "test-namespace",
   473  					UID:       "test-reserved",
   474  				},
   475  				Spec: resourcev1alpha2.ResourceClaimSpec{
   476  					ResourceClassName: "test-class",
   477  				},
   478  				Status: resourcev1alpha2.ResourceClaimStatus{
   479  					DriverName: driverName,
   480  					Allocation: &resourcev1alpha2.AllocationResult{
   481  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   482  							{Data: "test-data", DriverName: driverName},
   483  						},
   484  					},
   485  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   486  						{UID: "test-reserved"},
   487  					},
   488  				},
   489  			},
   490  			wantResourceSkipped: true,
   491  		},
   492  		{
   493  			description: "resource already prepared",
   494  			driverName:  driverName,
   495  			pod: &v1.Pod{
   496  				ObjectMeta: metav1.ObjectMeta{
   497  					Name:      "test-pod",
   498  					Namespace: "test-namespace",
   499  					UID:       "test-reserved",
   500  				},
   501  				Spec: v1.PodSpec{
   502  					ResourceClaims: []v1.PodResourceClaim{
   503  						{
   504  							Name: "test-pod-claim-4",
   505  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   506  								s := "test-pod-claim-4"
   507  								return &s
   508  							}()},
   509  						},
   510  					},
   511  					Containers: []v1.Container{
   512  						{
   513  							Resources: v1.ResourceRequirements{
   514  								Claims: []v1.ResourceClaim{
   515  									{
   516  										Name: "test-pod-claim-4",
   517  									},
   518  								},
   519  							},
   520  						},
   521  					},
   522  				},
   523  			},
   524  			claimInfo: &ClaimInfo{
   525  				ClaimInfoState: state.ClaimInfoState{
   526  					DriverName: driverName,
   527  					ClaimName:  "test-pod-claim-4",
   528  					Namespace:  "test-namespace",
   529  					PodUIDs:    sets.Set[string]{"test-another-pod-reserved": sets.Empty{}},
   530  				},
   531  				prepared: true,
   532  			},
   533  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   534  				ObjectMeta: metav1.ObjectMeta{
   535  					Name:      "test-pod-claim-4",
   536  					Namespace: "test-namespace",
   537  					UID:       "test-reserved",
   538  				},
   539  				Spec: resourcev1alpha2.ResourceClaimSpec{
   540  					ResourceClassName: "test-class",
   541  				},
   542  				Status: resourcev1alpha2.ResourceClaimStatus{
   543  					DriverName: driverName,
   544  					Allocation: &resourcev1alpha2.AllocationResult{
   545  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   546  							{Data: "test-data", DriverName: driverName},
   547  						},
   548  					},
   549  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   550  						{UID: "test-reserved"},
   551  					},
   552  				},
   553  			},
   554  			wantResourceSkipped: true,
   555  		},
   556  		{
   557  			description: "should timeout",
   558  			driverName:  driverName,
   559  			pod: &v1.Pod{
   560  				ObjectMeta: metav1.ObjectMeta{
   561  					Name:      "test-pod",
   562  					Namespace: "test-namespace",
   563  					UID:       "test-reserved",
   564  				},
   565  				Spec: v1.PodSpec{
   566  					ResourceClaims: []v1.PodResourceClaim{
   567  						{
   568  							Name: "test-pod-claim-5",
   569  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   570  								s := "test-pod-claim-5"
   571  								return &s
   572  							}()},
   573  						},
   574  					},
   575  					Containers: []v1.Container{
   576  						{
   577  							Resources: v1.ResourceRequirements{
   578  								Claims: []v1.ResourceClaim{
   579  									{
   580  										Name: "test-pod-claim-5",
   581  									},
   582  								},
   583  							},
   584  						},
   585  					},
   586  				},
   587  			},
   588  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   589  				ObjectMeta: metav1.ObjectMeta{
   590  					Name:      "test-pod-claim-5",
   591  					Namespace: "test-namespace",
   592  					UID:       "test-reserved",
   593  				},
   594  				Spec: resourcev1alpha2.ResourceClaimSpec{
   595  					ResourceClassName: "test-class",
   596  				},
   597  				Status: resourcev1alpha2.ResourceClaimStatus{
   598  					DriverName: driverName,
   599  					Allocation: &resourcev1alpha2.AllocationResult{
   600  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   601  							{Data: "test-data", DriverName: driverName},
   602  						},
   603  					},
   604  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   605  						{UID: "test-reserved"},
   606  					},
   607  				},
   608  			},
   609  			wantErr:              true,
   610  			wantTimeout:          true,
   611  			ExpectedPrepareCalls: 1,
   612  		},
   613  		{
   614  			description: "should prepare resource, claim not in cache",
   615  			driverName:  driverName,
   616  			pod: &v1.Pod{
   617  				ObjectMeta: metav1.ObjectMeta{
   618  					Name:      "test-pod",
   619  					Namespace: "test-namespace",
   620  					UID:       "test-reserved",
   621  				},
   622  				Spec: v1.PodSpec{
   623  					ResourceClaims: []v1.PodResourceClaim{
   624  						{
   625  							Name: "test-pod-claim-6",
   626  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   627  								s := "test-pod-claim-6"
   628  								return &s
   629  							}()},
   630  						},
   631  					},
   632  					Containers: []v1.Container{
   633  						{
   634  							Resources: v1.ResourceRequirements{
   635  								Claims: []v1.ResourceClaim{
   636  									{
   637  										Name: "test-pod-claim-6",
   638  									},
   639  								},
   640  							},
   641  						},
   642  					},
   643  				},
   644  			},
   645  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   646  				ObjectMeta: metav1.ObjectMeta{
   647  					Name:      "test-pod-claim-6",
   648  					Namespace: "test-namespace",
   649  					UID:       "test-reserved",
   650  				},
   651  				Spec: resourcev1alpha2.ResourceClaimSpec{
   652  					ResourceClassName: "test-class",
   653  				},
   654  				Status: resourcev1alpha2.ResourceClaimStatus{
   655  					DriverName: driverName,
   656  					Allocation: &resourcev1alpha2.AllocationResult{
   657  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   658  							{Data: "test-data"},
   659  						},
   660  					},
   661  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   662  						{UID: "test-reserved"},
   663  					},
   664  				},
   665  			},
   666  			ExpectedPrepareCalls: 1,
   667  		},
   668  		{
   669  			description: "should prepare resource. claim in cache, manager did not prepare resource",
   670  			driverName:  driverName,
   671  			pod: &v1.Pod{
   672  				ObjectMeta: metav1.ObjectMeta{
   673  					Name:      "test-pod",
   674  					Namespace: "test-namespace",
   675  					UID:       "test-reserved",
   676  				},
   677  				Spec: v1.PodSpec{
   678  					ResourceClaims: []v1.PodResourceClaim{
   679  						{
   680  							Name: "test-pod-claim",
   681  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   682  								s := "test-pod-claim"
   683  								return &s
   684  							}()},
   685  						},
   686  					},
   687  					Containers: []v1.Container{
   688  						{
   689  							Resources: v1.ResourceRequirements{
   690  								Claims: []v1.ResourceClaim{
   691  									{
   692  										Name: "test-pod-claim",
   693  									},
   694  								},
   695  							},
   696  						},
   697  					},
   698  				},
   699  			},
   700  			claimInfo: &ClaimInfo{
   701  				ClaimInfoState: state.ClaimInfoState{
   702  					DriverName: driverName,
   703  					ClassName:  "test-class",
   704  					ClaimName:  "test-pod-claim",
   705  					ClaimUID:   "test-reserved",
   706  					Namespace:  "test-namespace",
   707  					PodUIDs:    sets.Set[string]{"test-reserved": sets.Empty{}},
   708  					CDIDevices: map[string][]string{
   709  						driverName: {fmt.Sprintf("%s/%s=some-device", driverName, driverClassName)},
   710  					},
   711  					ResourceHandles: []resourcev1alpha2.ResourceHandle{{Data: "test-data"}},
   712  				},
   713  				annotations: make(map[string][]kubecontainer.Annotation),
   714  				prepared:    false,
   715  			},
   716  			resourceClaim: &resourcev1alpha2.ResourceClaim{
   717  				ObjectMeta: metav1.ObjectMeta{
   718  					Name:      "test-pod-claim",
   719  					Namespace: "test-namespace",
   720  					UID:       "test-reserved",
   721  				},
   722  				Spec: resourcev1alpha2.ResourceClaimSpec{
   723  					ResourceClassName: "test-class",
   724  				},
   725  				Status: resourcev1alpha2.ResourceClaimStatus{
   726  					DriverName: driverName,
   727  					Allocation: &resourcev1alpha2.AllocationResult{
   728  						ResourceHandles: []resourcev1alpha2.ResourceHandle{
   729  							{Data: "test-data"},
   730  						},
   731  					},
   732  					ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{
   733  						{UID: "test-reserved"},
   734  					},
   735  				},
   736  			},
   737  			ExpectedPrepareCalls: 1,
   738  		},
   739  	} {
   740  		t.Run(test.description, func(t *testing.T) {
   741  			cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
   742  			if err != nil {
   743  				t.Fatalf("failed to newClaimInfoCache, err:%v", err)
   744  			}
   745  
   746  			manager := &ManagerImpl{
   747  				kubeClient: fakeKubeClient,
   748  				cache:      cache,
   749  			}
   750  
   751  			if test.resourceClaim != nil {
   752  				if _, err := fakeKubeClient.ResourceV1alpha2().ResourceClaims(test.pod.Namespace).Create(context.Background(), test.resourceClaim, metav1.CreateOptions{}); err != nil {
   753  					t.Fatalf("failed to create ResourceClaim %s: %+v", test.resourceClaim.Name, err)
   754  				}
   755  			}
   756  
   757  			draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout)
   758  			if err != nil {
   759  				t.Fatal(err)
   760  			}
   761  			defer draServerInfo.teardownFn()
   762  
   763  			plg := plugin.NewRegistrationHandler()
   764  			if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}); err != nil {
   765  				t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err)
   766  			}
   767  			defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests
   768  
   769  			if test.claimInfo != nil {
   770  				manager.cache.add(test.claimInfo)
   771  			}
   772  
   773  			err = manager.PrepareResources(test.pod)
   774  
   775  			assert.Equal(t, test.ExpectedPrepareCalls, draServerInfo.server.prepareResourceCalls.Load())
   776  
   777  			if test.wantErr {
   778  				assert.Error(t, err)
   779  				return // PrepareResources returned an error so stopping the subtest here
   780  			} else if test.wantResourceSkipped {
   781  				assert.NoError(t, err)
   782  				return // resource skipped so no need to continue
   783  			}
   784  
   785  			assert.NoError(t, err)
   786  			// check the cache contains the expected claim info
   787  			claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0])
   788  			if err != nil {
   789  				t.Fatal(err)
   790  			}
   791  			claimInfo := manager.cache.get(*claimName, test.pod.Namespace)
   792  			if claimInfo == nil {
   793  				t.Fatalf("claimInfo not found in cache for claim %s", *claimName)
   794  			}
   795  			if claimInfo.DriverName != test.resourceClaim.Status.DriverName {
   796  				t.Fatalf("driverName mismatch: expected %s, got %s", test.resourceClaim.Status.DriverName, claimInfo.DriverName)
   797  			}
   798  			if claimInfo.ClassName != test.resourceClaim.Spec.ResourceClassName {
   799  				t.Fatalf("resourceClassName mismatch: expected %s, got %s", test.resourceClaim.Spec.ResourceClassName, claimInfo.ClassName)
   800  			}
   801  			if len(claimInfo.PodUIDs) != 1 || !claimInfo.PodUIDs.Has(string(test.pod.UID)) {
   802  				t.Fatalf("podUIDs mismatch: expected [%s], got %v", test.pod.UID, claimInfo.PodUIDs)
   803  			}
   804  			expectedResourceClaimDriverName := fmt.Sprintf("%s/%s=claim-%s", driverName, driverClassName, string(test.resourceClaim.Status.ReservedFor[0].UID))
   805  			if len(claimInfo.CDIDevices[test.resourceClaim.Status.DriverName]) != 1 || claimInfo.CDIDevices[test.resourceClaim.Status.DriverName][0] != expectedResourceClaimDriverName {
   806  				t.Fatalf("cdiDevices mismatch: expected [%s], got %v", []string{expectedResourceClaimDriverName}, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName])
   807  			}
   808  		})
   809  	}
   810  }
   811  
   812  func TestUnprepareResources(t *testing.T) {
   813  	fakeKubeClient := fake.NewSimpleClientset()
   814  
   815  	for _, test := range []struct {
   816  		description            string
   817  		driverName             string
   818  		pod                    *v1.Pod
   819  		claimInfo              *ClaimInfo
   820  		wantErr                bool
   821  		wantTimeout            bool
   822  		wantResourceSkipped    bool
   823  		expectedUnprepareCalls uint32
   824  	}{
   825  		{
   826  			description: "plugin does not exist",
   827  			driverName:  "this-plugin-does-not-exist",
   828  			pod: &v1.Pod{
   829  				ObjectMeta: metav1.ObjectMeta{
   830  					Name:      "test-pod",
   831  					Namespace: "test-namespace",
   832  					UID:       "test-reserved",
   833  				},
   834  				Spec: v1.PodSpec{
   835  					ResourceClaims: []v1.PodResourceClaim{
   836  						{
   837  							Name: "another-claim-test",
   838  							Source: v1.ClaimSource{
   839  								ResourceClaimName: func() *string {
   840  									s := "another-claim-test"
   841  									return &s
   842  								}(),
   843  							},
   844  						},
   845  					},
   846  				},
   847  			},
   848  			claimInfo: &ClaimInfo{
   849  				ClaimInfoState: state.ClaimInfoState{
   850  					DriverName: driverName,
   851  					ClaimName:  "another-claim-test",
   852  					Namespace:  "test-namespace",
   853  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
   854  						{
   855  							DriverName: driverName,
   856  							Data:       "test data",
   857  						},
   858  					},
   859  				},
   860  			},
   861  			wantErr: true,
   862  		},
   863  		{
   864  			description: "resource claim referenced by other pod(s)",
   865  			driverName:  driverName,
   866  			pod: &v1.Pod{
   867  				ObjectMeta: metav1.ObjectMeta{
   868  					Name:      "test-pod",
   869  					Namespace: "test-namespace",
   870  					UID:       "test-reserved",
   871  				},
   872  				Spec: v1.PodSpec{
   873  					ResourceClaims: []v1.PodResourceClaim{
   874  						{
   875  							Name: "test-pod-claim-1",
   876  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   877  								s := "test-pod-claim-1"
   878  								return &s
   879  							}()},
   880  						},
   881  					},
   882  					Containers: []v1.Container{
   883  						{
   884  							Resources: v1.ResourceRequirements{
   885  								Claims: []v1.ResourceClaim{
   886  									{
   887  										Name: "test-pod-claim-1",
   888  									},
   889  								},
   890  							},
   891  						},
   892  					},
   893  				},
   894  			},
   895  			claimInfo: &ClaimInfo{
   896  				ClaimInfoState: state.ClaimInfoState{
   897  					DriverName: driverName,
   898  					ClaimName:  "test-pod-claim-1",
   899  					Namespace:  "test-namespace",
   900  					PodUIDs:    sets.Set[string]{"test-reserved": sets.Empty{}, "test-reserved-2": sets.Empty{}},
   901  				},
   902  			},
   903  			wantResourceSkipped: true,
   904  		},
   905  		{
   906  			description: "should timeout",
   907  			driverName:  driverName,
   908  			pod: &v1.Pod{
   909  				ObjectMeta: metav1.ObjectMeta{
   910  					Name:      "test-pod",
   911  					Namespace: "test-namespace",
   912  					UID:       "test-reserved",
   913  				},
   914  				Spec: v1.PodSpec{
   915  					ResourceClaims: []v1.PodResourceClaim{
   916  						{
   917  							Name: "test-pod-claim-2",
   918  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   919  								s := "test-pod-claim-2"
   920  								return &s
   921  							}()},
   922  						},
   923  					},
   924  					Containers: []v1.Container{
   925  						{
   926  							Resources: v1.ResourceRequirements{
   927  								Claims: []v1.ResourceClaim{
   928  									{
   929  										Name: "test-pod-claim-2",
   930  									},
   931  								},
   932  							},
   933  						},
   934  					},
   935  				},
   936  			},
   937  			claimInfo: &ClaimInfo{
   938  				ClaimInfoState: state.ClaimInfoState{
   939  					DriverName: driverName,
   940  					ClaimName:  "test-pod-claim-2",
   941  					Namespace:  "test-namespace",
   942  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
   943  						{
   944  							DriverName: driverName,
   945  							Data:       "test data",
   946  						},
   947  					},
   948  				},
   949  			},
   950  			wantErr:                true,
   951  			wantTimeout:            true,
   952  			expectedUnprepareCalls: 1,
   953  		},
   954  		{
   955  			description: "should unprepare resource, claim previously prepared by currently running manager",
   956  			driverName:  driverName,
   957  			pod: &v1.Pod{
   958  				ObjectMeta: metav1.ObjectMeta{
   959  					Name:      "test-pod",
   960  					Namespace: "test-namespace",
   961  					UID:       "test-reserved",
   962  				},
   963  				Spec: v1.PodSpec{
   964  					ResourceClaims: []v1.PodResourceClaim{
   965  						{
   966  							Name: "test-pod-claim-3",
   967  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
   968  								s := "test-pod-claim-3"
   969  								return &s
   970  							}()},
   971  						},
   972  					},
   973  					Containers: []v1.Container{
   974  						{
   975  							Resources: v1.ResourceRequirements{
   976  								Claims: []v1.ResourceClaim{
   977  									{
   978  										Name: "test-pod-claim-3",
   979  									},
   980  								},
   981  							},
   982  						},
   983  					},
   984  				},
   985  			},
   986  			claimInfo: &ClaimInfo{
   987  				ClaimInfoState: state.ClaimInfoState{
   988  					DriverName: driverName,
   989  					ClaimName:  "test-pod-claim-3",
   990  					Namespace:  "test-namespace",
   991  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
   992  						{
   993  							DriverName: driverName,
   994  							Data:       "test data",
   995  						},
   996  					},
   997  				},
   998  				prepared: true,
   999  			},
  1000  			expectedUnprepareCalls: 1,
  1001  		},
  1002  		{
  1003  			description: "should unprepare resource, claim previously was not prepared by currently running manager",
  1004  			driverName:  driverName,
  1005  			pod: &v1.Pod{
  1006  				ObjectMeta: metav1.ObjectMeta{
  1007  					Name:      "test-pod",
  1008  					Namespace: "test-namespace",
  1009  					UID:       "test-reserved",
  1010  				},
  1011  				Spec: v1.PodSpec{
  1012  					ResourceClaims: []v1.PodResourceClaim{
  1013  						{
  1014  							Name: "test-pod-claim",
  1015  							Source: v1.ClaimSource{ResourceClaimName: func() *string {
  1016  								s := "test-pod-claim"
  1017  								return &s
  1018  							}()},
  1019  						},
  1020  					},
  1021  					Containers: []v1.Container{
  1022  						{
  1023  							Resources: v1.ResourceRequirements{
  1024  								Claims: []v1.ResourceClaim{
  1025  									{
  1026  										Name: "test-pod-claim",
  1027  									},
  1028  								},
  1029  							},
  1030  						},
  1031  					},
  1032  				},
  1033  			},
  1034  			claimInfo: &ClaimInfo{
  1035  				ClaimInfoState: state.ClaimInfoState{
  1036  					DriverName: driverName,
  1037  					ClaimName:  "test-pod-claim",
  1038  					Namespace:  "test-namespace",
  1039  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
  1040  						{
  1041  							DriverName: driverName,
  1042  							Data:       "test data",
  1043  						},
  1044  					},
  1045  				},
  1046  				prepared: false,
  1047  			},
  1048  			expectedUnprepareCalls: 1,
  1049  		},
  1050  	} {
  1051  		t.Run(test.description, func(t *testing.T) {
  1052  			cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1053  			if err != nil {
  1054  				t.Fatalf("failed to create a new instance of the claimInfoCache, err: %v", err)
  1055  			}
  1056  
  1057  			draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout)
  1058  			if err != nil {
  1059  				t.Fatal(err)
  1060  			}
  1061  			defer draServerInfo.teardownFn()
  1062  
  1063  			plg := plugin.NewRegistrationHandler()
  1064  			if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}); err != nil {
  1065  				t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err)
  1066  			}
  1067  			defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests
  1068  
  1069  			manager := &ManagerImpl{
  1070  				kubeClient: fakeKubeClient,
  1071  				cache:      cache,
  1072  			}
  1073  
  1074  			if test.claimInfo != nil {
  1075  				manager.cache.add(test.claimInfo)
  1076  			}
  1077  
  1078  			err = manager.UnprepareResources(test.pod)
  1079  
  1080  			assert.Equal(t, test.expectedUnprepareCalls, draServerInfo.server.unprepareResourceCalls.Load())
  1081  
  1082  			if test.wantErr {
  1083  				assert.Error(t, err)
  1084  				return // UnprepareResources returned an error so stopping the subtest here
  1085  			} else if test.wantResourceSkipped {
  1086  				assert.NoError(t, err)
  1087  				return // resource skipped so no need to continue
  1088  			}
  1089  
  1090  			assert.NoError(t, err)
  1091  			// Check that the cache has been updated correctly
  1092  			claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0])
  1093  			if err != nil {
  1094  				t.Fatal(err)
  1095  			}
  1096  			claimInfo := manager.cache.get(*claimName, test.pod.Namespace)
  1097  			if claimInfo != nil {
  1098  				t.Fatalf("claimInfo still found in cache after calling UnprepareResources")
  1099  			}
  1100  		})
  1101  	}
  1102  }
  1103  
  1104  func TestPodMightNeedToUnprepareResources(t *testing.T) {
  1105  	fakeKubeClient := fake.NewSimpleClientset()
  1106  
  1107  	cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1108  	if err != nil {
  1109  		t.Fatalf("failed to newClaimInfoCache, err:%v", err)
  1110  	}
  1111  
  1112  	manager := &ManagerImpl{
  1113  		kubeClient: fakeKubeClient,
  1114  		cache:      cache,
  1115  	}
  1116  
  1117  	podUID := sets.Set[string]{}
  1118  	podUID.Insert("test-pod-uid")
  1119  	manager.cache.add(&ClaimInfo{
  1120  		ClaimInfoState: state.ClaimInfoState{PodUIDs: podUID, ClaimName: "test-claim", Namespace: "test-namespace"},
  1121  	})
  1122  
  1123  	testClaimInfo := manager.cache.get("test-claim", "test-namespace")
  1124  	testClaimInfo.addPodReference("test-pod-uid")
  1125  
  1126  	manager.PodMightNeedToUnprepareResources("test-pod-uid")
  1127  }
  1128  
  1129  func TestGetContainerClaimInfos(t *testing.T) {
  1130  	cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName)
  1131  	if err != nil {
  1132  		t.Fatalf("error occur:%v", err)
  1133  	}
  1134  	manager := &ManagerImpl{
  1135  		cache: cache,
  1136  	}
  1137  
  1138  	resourceClaimName := "test-resource-claim-1"
  1139  	resourceClaimName2 := "test-resource-claim-2"
  1140  
  1141  	for i, test := range []struct {
  1142  		expectedClaimName string
  1143  		pod               *v1.Pod
  1144  		container         *v1.Container
  1145  		claimInfo         *ClaimInfo
  1146  	}{
  1147  		{
  1148  			expectedClaimName: resourceClaimName,
  1149  			pod: &v1.Pod{
  1150  				Spec: v1.PodSpec{
  1151  					ResourceClaims: []v1.PodResourceClaim{
  1152  						{
  1153  							Name:   "claim1",
  1154  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName},
  1155  						},
  1156  					},
  1157  				},
  1158  			},
  1159  			container: &v1.Container{
  1160  				Resources: v1.ResourceRequirements{
  1161  					Claims: []v1.ResourceClaim{
  1162  						{
  1163  							Name: "claim1",
  1164  						},
  1165  					},
  1166  				},
  1167  			},
  1168  			claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName}},
  1169  		},
  1170  		{
  1171  			expectedClaimName: resourceClaimName2,
  1172  			pod: &v1.Pod{
  1173  				Spec: v1.PodSpec{
  1174  					ResourceClaims: []v1.PodResourceClaim{
  1175  						{
  1176  							Name:   "claim2",
  1177  							Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName2},
  1178  						},
  1179  					},
  1180  				},
  1181  			},
  1182  			container: &v1.Container{
  1183  				Resources: v1.ResourceRequirements{
  1184  					Claims: []v1.ResourceClaim{
  1185  						{
  1186  							Name: "claim2",
  1187  						},
  1188  					},
  1189  				},
  1190  			},
  1191  			claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName2}},
  1192  		},
  1193  	} {
  1194  		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
  1195  			manager.cache.add(test.claimInfo)
  1196  
  1197  			fakeClaimInfos, err := manager.GetContainerClaimInfos(test.pod, test.container)
  1198  			assert.NoError(t, err)
  1199  			assert.Equal(t, 1, len(fakeClaimInfos))
  1200  			assert.Equal(t, test.expectedClaimName, fakeClaimInfos[0].ClaimInfoState.ClaimName)
  1201  
  1202  			manager.cache.delete(test.pod.Spec.ResourceClaims[0].Name, "default")
  1203  			_, err = manager.GetContainerClaimInfos(test.pod, test.container)
  1204  			assert.NoError(t, err)
  1205  		})
  1206  	}
  1207  }