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

     1  /*
     2  Copyright 2024 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  	"errors"
    21  	"fmt"
    22  	"path"
    23  	"reflect"
    24  	"sort"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	resourcev1alpha2 "k8s.io/api/resource/v1alpha2"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/kubernetes/pkg/kubelet/cm/dra/state"
    33  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    34  )
    35  
    36  // ClaimInfo test cases
    37  
    38  func TestNewClaimInfoFromClaim(t *testing.T) {
    39  	namespace := "test-namespace"
    40  	className := "test-class"
    41  	driverName := "test-plugin"
    42  	claimUID := types.UID("claim-uid")
    43  	claimName := "test-claim"
    44  
    45  	for _, test := range []struct {
    46  		description    string
    47  		claim          *resourcev1alpha2.ResourceClaim
    48  		expectedResult *ClaimInfo
    49  	}{
    50  		{
    51  			description: "successfully created object",
    52  			claim: &resourcev1alpha2.ResourceClaim{
    53  				ObjectMeta: metav1.ObjectMeta{
    54  					UID:       claimUID,
    55  					Name:      claimName,
    56  					Namespace: namespace,
    57  				},
    58  				Status: resourcev1alpha2.ResourceClaimStatus{
    59  					DriverName: driverName,
    60  					Allocation: &resourcev1alpha2.AllocationResult{
    61  						ResourceHandles: []resourcev1alpha2.ResourceHandle{},
    62  					},
    63  				},
    64  				Spec: resourcev1alpha2.ResourceClaimSpec{
    65  					ResourceClassName: className,
    66  				},
    67  			},
    68  			expectedResult: &ClaimInfo{
    69  				ClaimInfoState: state.ClaimInfoState{
    70  					DriverName: driverName,
    71  					ClassName:  className,
    72  					ClaimUID:   claimUID,
    73  					ClaimName:  claimName,
    74  					Namespace:  claimName,
    75  					PodUIDs:    sets.New[string](),
    76  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
    77  						{},
    78  					},
    79  					CDIDevices: make(map[string][]string),
    80  				},
    81  			},
    82  		},
    83  		{
    84  			description: "successfully created object with empty allocation",
    85  			claim: &resourcev1alpha2.ResourceClaim{
    86  				ObjectMeta: metav1.ObjectMeta{
    87  					UID:       claimUID,
    88  					Name:      claimName,
    89  					Namespace: namespace,
    90  				},
    91  				Status: resourcev1alpha2.ResourceClaimStatus{
    92  					DriverName: driverName,
    93  					Allocation: &resourcev1alpha2.AllocationResult{},
    94  				},
    95  				Spec: resourcev1alpha2.ResourceClaimSpec{
    96  					ResourceClassName: className,
    97  				},
    98  			},
    99  			expectedResult: &ClaimInfo{
   100  				ClaimInfoState: state.ClaimInfoState{
   101  					DriverName: driverName,
   102  					ClassName:  className,
   103  					ClaimUID:   claimUID,
   104  					ClaimName:  claimName,
   105  					Namespace:  claimName,
   106  					PodUIDs:    sets.New[string](),
   107  					ResourceHandles: []resourcev1alpha2.ResourceHandle{
   108  						{},
   109  					},
   110  					CDIDevices: make(map[string][]string),
   111  				},
   112  			},
   113  		},
   114  	} {
   115  		t.Run(test.description, func(t *testing.T) {
   116  			result := newClaimInfoFromClaim(test.claim)
   117  			if reflect.DeepEqual(result, test.expectedResult) {
   118  				t.Errorf("Expected %v, but got %v", test.expectedResult, result)
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func TestNewClaimInfoFromState(t *testing.T) {
   125  	for _, test := range []struct {
   126  		description    string
   127  		state          *state.ClaimInfoState
   128  		expectedResult *ClaimInfo
   129  	}{
   130  		{
   131  			description: "successfully created object",
   132  			state: &state.ClaimInfoState{
   133  				DriverName:      "test-driver",
   134  				ClassName:       "test-class",
   135  				ClaimUID:        "test-uid",
   136  				ClaimName:       "test-claim",
   137  				Namespace:       "test-namespace",
   138  				PodUIDs:         sets.New[string]("test-pod-uid"),
   139  				ResourceHandles: []resourcev1alpha2.ResourceHandle{},
   140  				CDIDevices:      map[string][]string{},
   141  			},
   142  		},
   143  	} {
   144  		t.Run(test.description, func(t *testing.T) {
   145  			result := newClaimInfoFromState(test.state)
   146  			if reflect.DeepEqual(result, test.expectedResult) {
   147  				t.Errorf("Expected %v, but got %v", test.expectedResult, result)
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestClaimInfoSetCDIDevices(t *testing.T) {
   154  	claimUID := types.UID("claim-uid")
   155  	pluginName := "test-plugin"
   156  	device := "vendor.com/device=device1"
   157  	annotationName := fmt.Sprintf("cdi.k8s.io/%s_%s", pluginName, claimUID)
   158  	for _, test := range []struct {
   159  		description         string
   160  		claimInfo           *ClaimInfo
   161  		devices             []string
   162  		expectedCDIDevices  map[string][]string
   163  		expectedAnnotations map[string][]kubecontainer.Annotation
   164  		wantErr             bool
   165  	}{
   166  		{
   167  			description: "successfully add one device",
   168  			claimInfo: &ClaimInfo{
   169  				ClaimInfoState: state.ClaimInfoState{
   170  					DriverName: pluginName,
   171  					ClaimUID:   claimUID,
   172  				},
   173  			},
   174  			devices: []string{device},
   175  			expectedCDIDevices: map[string][]string{
   176  				pluginName: {device},
   177  			},
   178  			expectedAnnotations: map[string][]kubecontainer.Annotation{
   179  				pluginName: {
   180  					{
   181  						Name:  annotationName,
   182  						Value: device,
   183  					},
   184  				},
   185  			},
   186  		},
   187  		{
   188  			description: "empty list of devices",
   189  			claimInfo: &ClaimInfo{
   190  				ClaimInfoState: state.ClaimInfoState{
   191  					DriverName: pluginName,
   192  					ClaimUID:   claimUID,
   193  				},
   194  			},
   195  			devices:             []string{},
   196  			expectedCDIDevices:  map[string][]string{pluginName: {}},
   197  			expectedAnnotations: map[string][]kubecontainer.Annotation{pluginName: nil},
   198  		},
   199  		{
   200  			description: "incorrect device format",
   201  			claimInfo: &ClaimInfo{
   202  				ClaimInfoState: state.ClaimInfoState{
   203  					DriverName: pluginName,
   204  					ClaimUID:   claimUID,
   205  				},
   206  			},
   207  			devices: []string{"incorrect"},
   208  			wantErr: true,
   209  		},
   210  	} {
   211  		t.Run(test.description, func(t *testing.T) {
   212  			err := test.claimInfo.setCDIDevices(pluginName, test.devices)
   213  			if test.wantErr {
   214  				assert.Error(t, err)
   215  				return
   216  			}
   217  			assert.NoError(t, err)
   218  			assert.Equal(t, test.expectedCDIDevices, test.claimInfo.CDIDevices)
   219  			assert.Equal(t, test.expectedAnnotations, test.claimInfo.annotations)
   220  		})
   221  	}
   222  }
   223  
   224  func TestClaimInfoAnnotationsAsList(t *testing.T) {
   225  	for _, test := range []struct {
   226  		description    string
   227  		claimInfo      *ClaimInfo
   228  		expectedResult []kubecontainer.Annotation
   229  	}{
   230  		{
   231  			description: "empty annotations",
   232  			claimInfo: &ClaimInfo{
   233  				annotations: map[string][]kubecontainer.Annotation{},
   234  			},
   235  		},
   236  		{
   237  			description: "nil annotations",
   238  			claimInfo:   &ClaimInfo{},
   239  		},
   240  		{
   241  			description: "valid annotations",
   242  			claimInfo: &ClaimInfo{
   243  				annotations: map[string][]kubecontainer.Annotation{
   244  					"test-plugin1": {
   245  						{
   246  							Name:  "cdi.k8s.io/test-plugin1_claim-uid1",
   247  							Value: "vendor.com/device=device1",
   248  						},
   249  						{
   250  							Name:  "cdi.k8s.io/test-plugin1_claim-uid2",
   251  							Value: "vendor.com/device=device2",
   252  						},
   253  					},
   254  					"test-plugin2": {
   255  						{
   256  							Name:  "cdi.k8s.io/test-plugin2_claim-uid1",
   257  							Value: "vendor.com/device=device1",
   258  						},
   259  						{
   260  							Name:  "cdi.k8s.io/test-plugin2_claim-uid2",
   261  							Value: "vendor.com/device=device2",
   262  						},
   263  					},
   264  				},
   265  			},
   266  			expectedResult: []kubecontainer.Annotation{
   267  				{
   268  					Name:  "cdi.k8s.io/test-plugin1_claim-uid1",
   269  					Value: "vendor.com/device=device1",
   270  				},
   271  				{
   272  					Name:  "cdi.k8s.io/test-plugin1_claim-uid2",
   273  					Value: "vendor.com/device=device2",
   274  				},
   275  				{
   276  					Name:  "cdi.k8s.io/test-plugin2_claim-uid1",
   277  					Value: "vendor.com/device=device1",
   278  				},
   279  				{
   280  					Name:  "cdi.k8s.io/test-plugin2_claim-uid2",
   281  					Value: "vendor.com/device=device2",
   282  				},
   283  			},
   284  		},
   285  	} {
   286  		t.Run(test.description, func(t *testing.T) {
   287  			result := test.claimInfo.annotationsAsList()
   288  			sort.Slice(result, func(i, j int) bool {
   289  				return result[i].Name < result[j].Name
   290  			})
   291  			assert.Equal(t, test.expectedResult, result)
   292  		})
   293  	}
   294  }
   295  
   296  func TestClaimInfoCDIdevicesAsList(t *testing.T) {
   297  	for _, test := range []struct {
   298  		description    string
   299  		claimInfo      *ClaimInfo
   300  		expectedResult []kubecontainer.CDIDevice
   301  	}{
   302  		{
   303  			description: "empty CDI devices",
   304  			claimInfo: &ClaimInfo{
   305  				ClaimInfoState: state.ClaimInfoState{
   306  					CDIDevices: map[string][]string{},
   307  				},
   308  			},
   309  		},
   310  		{
   311  			description: "nil CDI devices",
   312  			claimInfo:   &ClaimInfo{},
   313  		},
   314  		{
   315  			description: "valid CDI devices",
   316  			claimInfo: &ClaimInfo{
   317  				ClaimInfoState: state.ClaimInfoState{
   318  					CDIDevices: map[string][]string{
   319  						"test-plugin1": {
   320  							"vendor.com/device=device1",
   321  							"vendor.com/device=device2",
   322  						},
   323  						"test-plugin2": {
   324  							"vendor.com/device=device1",
   325  							"vendor.com/device=device2",
   326  						},
   327  					},
   328  				},
   329  			},
   330  			expectedResult: []kubecontainer.CDIDevice{
   331  				{
   332  					Name: "vendor.com/device=device1",
   333  				},
   334  				{
   335  					Name: "vendor.com/device=device1",
   336  				},
   337  				{
   338  					Name: "vendor.com/device=device2",
   339  				},
   340  				{
   341  					Name: "vendor.com/device=device2",
   342  				},
   343  			},
   344  		},
   345  	} {
   346  		t.Run(test.description, func(t *testing.T) {
   347  			result := test.claimInfo.cdiDevicesAsList()
   348  			sort.Slice(result, func(i, j int) bool {
   349  				return result[i].Name < result[j].Name
   350  			})
   351  			assert.Equal(t, test.expectedResult, result)
   352  		})
   353  	}
   354  }
   355  func TestClaimInfoAddPodReference(t *testing.T) {
   356  	podUID := types.UID("pod-uid")
   357  	for _, test := range []struct {
   358  		description string
   359  		claimInfo   *ClaimInfo
   360  		expectedLen int
   361  	}{
   362  		{
   363  			description: "successfully add pod reference",
   364  			claimInfo: &ClaimInfo{
   365  				ClaimInfoState: state.ClaimInfoState{
   366  					PodUIDs: sets.New[string](),
   367  				},
   368  			},
   369  			expectedLen: 1,
   370  		},
   371  		{
   372  			description: "duplicate pod reference",
   373  			claimInfo: &ClaimInfo{
   374  				ClaimInfoState: state.ClaimInfoState{
   375  					PodUIDs: sets.New[string](string(podUID)),
   376  				},
   377  			},
   378  			expectedLen: 1,
   379  		},
   380  		{
   381  			description: "duplicate pod reference",
   382  			claimInfo: &ClaimInfo{
   383  				ClaimInfoState: state.ClaimInfoState{
   384  					PodUIDs: sets.New[string]("pod-uid1"),
   385  				},
   386  			},
   387  			expectedLen: 2,
   388  		},
   389  	} {
   390  		t.Run(test.description, func(t *testing.T) {
   391  			test.claimInfo.addPodReference(podUID)
   392  			assert.True(t, test.claimInfo.hasPodReference(podUID))
   393  			assert.Len(t, test.claimInfo.PodUIDs, test.expectedLen)
   394  		})
   395  	}
   396  }
   397  
   398  func TestClaimInfoHasPodReference(t *testing.T) {
   399  	podUID := types.UID("pod-uid")
   400  	for _, test := range []struct {
   401  		description    string
   402  		claimInfo      *ClaimInfo
   403  		expectedResult bool
   404  	}{
   405  		{
   406  			description: "claim doesn't reference pod",
   407  			claimInfo: &ClaimInfo{
   408  				ClaimInfoState: state.ClaimInfoState{
   409  					PodUIDs: sets.New[string](),
   410  				},
   411  			},
   412  		},
   413  		{
   414  			description: "claim references pod",
   415  			claimInfo: &ClaimInfo{
   416  				ClaimInfoState: state.ClaimInfoState{
   417  					PodUIDs: sets.New[string](string(podUID)),
   418  				},
   419  			},
   420  			expectedResult: true,
   421  		},
   422  		{
   423  			description: "empty claim info",
   424  			claimInfo:   &ClaimInfo{},
   425  		},
   426  	} {
   427  		t.Run(test.description, func(t *testing.T) {
   428  			assert.Equal(t, test.claimInfo.hasPodReference(podUID), test.expectedResult)
   429  		})
   430  	}
   431  }
   432  
   433  func TestClaimInfoDeletePodReference(t *testing.T) {
   434  	podUID := types.UID("pod-uid")
   435  	for _, test := range []struct {
   436  		description string
   437  		claimInfo   *ClaimInfo
   438  	}{
   439  		{
   440  			description: "claim doesn't reference pod",
   441  			claimInfo: &ClaimInfo{
   442  				ClaimInfoState: state.ClaimInfoState{
   443  					PodUIDs: sets.New[string](),
   444  				},
   445  			},
   446  		},
   447  		{
   448  			description: "claim references pod",
   449  			claimInfo: &ClaimInfo{
   450  				ClaimInfoState: state.ClaimInfoState{
   451  					PodUIDs: sets.New[string](string(podUID)),
   452  				},
   453  			},
   454  		},
   455  		{
   456  			description: "empty claim info",
   457  			claimInfo:   &ClaimInfo{},
   458  		},
   459  	} {
   460  		t.Run(test.description, func(t *testing.T) {
   461  			test.claimInfo.deletePodReference(podUID)
   462  			assert.False(t, test.claimInfo.hasPodReference(podUID))
   463  		})
   464  	}
   465  }
   466  
   467  func TestClaimInfoSetPrepared(t *testing.T) {
   468  	for _, test := range []struct {
   469  		description string
   470  		claimInfo   *ClaimInfo
   471  	}{
   472  		{
   473  			description: "claim info is not prepared",
   474  			claimInfo: &ClaimInfo{
   475  				prepared: false,
   476  			},
   477  		},
   478  		{
   479  			description: "claim info is prepared",
   480  			claimInfo: &ClaimInfo{
   481  				prepared: true,
   482  			},
   483  		},
   484  		{
   485  			description: "empty claim info",
   486  			claimInfo:   &ClaimInfo{},
   487  		},
   488  	} {
   489  		t.Run(test.description, func(t *testing.T) {
   490  			test.claimInfo.setPrepared()
   491  			assert.Equal(t, test.claimInfo.isPrepared(), true)
   492  		})
   493  	}
   494  }
   495  
   496  func TestClaimInfoIsPrepared(t *testing.T) {
   497  	for _, test := range []struct {
   498  		description    string
   499  		claimInfo      *ClaimInfo
   500  		expectedResult bool
   501  	}{
   502  		{
   503  			description: "claim info is not prepared",
   504  			claimInfo: &ClaimInfo{
   505  				prepared: false,
   506  			},
   507  			expectedResult: false,
   508  		},
   509  		{
   510  			description: "claim info is prepared",
   511  			claimInfo: &ClaimInfo{
   512  				prepared: true,
   513  			},
   514  			expectedResult: true,
   515  		},
   516  		{
   517  			description:    "empty claim info",
   518  			claimInfo:      &ClaimInfo{},
   519  			expectedResult: false,
   520  		},
   521  	} {
   522  		t.Run(test.description, func(t *testing.T) {
   523  			assert.Equal(t, test.claimInfo.isPrepared(), test.expectedResult)
   524  		})
   525  	}
   526  }
   527  
   528  // claimInfoCache test cases
   529  func TestNewClaimInfoCache(t *testing.T) {
   530  	for _, test := range []struct {
   531  		description    string
   532  		stateDir       string
   533  		checkpointName string
   534  		wantErr        bool
   535  	}{
   536  		{
   537  			description:    "successfully created cache",
   538  			stateDir:       t.TempDir(),
   539  			checkpointName: "test-checkpoint",
   540  		},
   541  		{
   542  			description: "empty parameters",
   543  			wantErr:     true,
   544  		},
   545  		{
   546  			description: "empty checkpoint name",
   547  			stateDir:    t.TempDir(),
   548  			wantErr:     true,
   549  		},
   550  		{
   551  			description: "incorrect checkpoint name",
   552  			stateDir:    path.Join(t.TempDir(), "incorrect checkpoint"),
   553  			wantErr:     true,
   554  		},
   555  	} {
   556  		t.Run(test.description, func(t *testing.T) {
   557  			result, err := newClaimInfoCache(test.stateDir, test.checkpointName)
   558  			if test.wantErr {
   559  				assert.Error(t, err)
   560  				return
   561  			}
   562  			assert.NoError(t, err)
   563  			assert.NotNil(t, result)
   564  		})
   565  	}
   566  }
   567  
   568  func TestClaimInfoCacheWithLock(t *testing.T) {
   569  	for _, test := range []struct {
   570  		description string
   571  		funcGen     func(cache *claimInfoCache) func() error
   572  		wantErr     bool
   573  	}{
   574  		{
   575  			description: "cache is locked inside a function",
   576  			funcGen: func(cache *claimInfoCache) func() error {
   577  				return func() error {
   578  					if cache.RWMutex.TryLock() {
   579  						return errors.New("Lock succeeded")
   580  					}
   581  					return nil
   582  				}
   583  			},
   584  		},
   585  		{
   586  			description: "cache is Rlocked inside a function",
   587  			funcGen: func(cache *claimInfoCache) func() error {
   588  				return func() error {
   589  					if cache.RWMutex.TryRLock() {
   590  						return errors.New("RLock succeeded")
   591  					}
   592  					return nil
   593  				}
   594  			},
   595  		},
   596  		{
   597  			description: "successfully called function",
   598  			funcGen: func(cache *claimInfoCache) func() error {
   599  				return func() error {
   600  					return nil
   601  				}
   602  			},
   603  		},
   604  		{
   605  			description: "erroring function",
   606  			funcGen: func(cache *claimInfoCache) func() error {
   607  				return func() error {
   608  					return errors.New("test error")
   609  				}
   610  			},
   611  			wantErr: true,
   612  		},
   613  	} {
   614  		t.Run(test.description, func(t *testing.T) {
   615  			cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint")
   616  			assert.NoError(t, err)
   617  			assert.NotNil(t, cache)
   618  			err = cache.withLock(test.funcGen(cache))
   619  			if test.wantErr {
   620  				assert.Error(t, err)
   621  				return
   622  			}
   623  			assert.NoError(t, err)
   624  		})
   625  	}
   626  }
   627  
   628  func TestClaimInfoCacheWithRLock(t *testing.T) {
   629  	for _, test := range []struct {
   630  		description string
   631  		funcGen     func(cache *claimInfoCache) func() error
   632  		wantErr     bool
   633  	}{
   634  		{
   635  			description: "RLock-ed cache allows another RLock",
   636  			funcGen: func(cache *claimInfoCache) func() error {
   637  				return func() error {
   638  					if !cache.RWMutex.TryRLock() {
   639  						return errors.New("RLock failed")
   640  					}
   641  					return nil
   642  				}
   643  			},
   644  		},
   645  		{
   646  			description: "cache is locked inside a function",
   647  			funcGen: func(cache *claimInfoCache) func() error {
   648  				return func() error {
   649  					if cache.RWMutex.TryLock() {
   650  						return errors.New("Lock succeeded")
   651  					}
   652  					return nil
   653  				}
   654  			},
   655  		},
   656  		{
   657  			description: "successfully called function",
   658  			funcGen: func(cache *claimInfoCache) func() error {
   659  				return func() error {
   660  					return nil
   661  				}
   662  			},
   663  		},
   664  		{
   665  			description: "erroring function",
   666  			funcGen: func(cache *claimInfoCache) func() error {
   667  				return func() error {
   668  					return errors.New("test error")
   669  				}
   670  			},
   671  			wantErr: true,
   672  		},
   673  	} {
   674  		t.Run(test.description, func(t *testing.T) {
   675  			cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint")
   676  			assert.NoError(t, err)
   677  			assert.NotNil(t, cache)
   678  			err = cache.withRLock(test.funcGen(cache))
   679  			if test.wantErr {
   680  				assert.Error(t, err)
   681  				return
   682  			}
   683  			assert.NoError(t, err)
   684  		})
   685  	}
   686  }
   687  
   688  func TestClaimInfoCacheAdd(t *testing.T) {
   689  	for _, test := range []struct {
   690  		description string
   691  		claimInfo   *ClaimInfo
   692  	}{
   693  		{
   694  			description: "claimInfo successfully added",
   695  			claimInfo: &ClaimInfo{
   696  				ClaimInfoState: state.ClaimInfoState{
   697  					ClaimName: "test-claim",
   698  					Namespace: "test-namespace",
   699  				},
   700  			},
   701  		},
   702  	} {
   703  		t.Run(test.description, func(t *testing.T) {
   704  			cache, err := newClaimInfoCache(t.TempDir(), "test-checkpoint")
   705  			assert.NoError(t, err)
   706  			assert.NotNil(t, cache)
   707  			cache.add(test.claimInfo)
   708  			assert.True(t, cache.contains(test.claimInfo.ClaimName, test.claimInfo.Namespace))
   709  		})
   710  	}
   711  }
   712  
   713  func TestClaimInfoCacheContains(t *testing.T) {
   714  	claimName := "test-claim"
   715  	namespace := "test-namespace"
   716  	for _, test := range []struct {
   717  		description    string
   718  		claimInfo      *ClaimInfo
   719  		claimInfoCache *claimInfoCache
   720  		expectedResult bool
   721  	}{
   722  		{
   723  			description: "cache hit",
   724  			claimInfoCache: &claimInfoCache{
   725  				claimInfo: map[string]*ClaimInfo{
   726  					namespace + "/" + claimName: {
   727  						ClaimInfoState: state.ClaimInfoState{
   728  							ClaimName: claimName,
   729  							Namespace: namespace,
   730  						},
   731  					},
   732  				},
   733  			},
   734  			claimInfo: &ClaimInfo{
   735  				ClaimInfoState: state.ClaimInfoState{
   736  					ClaimName: claimName,
   737  					Namespace: namespace,
   738  				},
   739  			},
   740  			expectedResult: true,
   741  		},
   742  		{
   743  			description:    "cache miss",
   744  			claimInfoCache: &claimInfoCache{},
   745  			claimInfo: &ClaimInfo{
   746  				ClaimInfoState: state.ClaimInfoState{
   747  					ClaimName: claimName,
   748  					Namespace: namespace,
   749  				},
   750  			},
   751  		},
   752  		{
   753  			description:    "cache miss: empty cache and empty claim info",
   754  			claimInfoCache: &claimInfoCache{},
   755  			claimInfo: &ClaimInfo{
   756  				ClaimInfoState: state.ClaimInfoState{},
   757  			},
   758  		},
   759  	} {
   760  		t.Run(test.description, func(t *testing.T) {
   761  			assert.Equal(t, test.expectedResult, test.claimInfoCache.contains(test.claimInfo.ClaimName, test.claimInfo.Namespace))
   762  		})
   763  	}
   764  }
   765  
   766  func TestClaimInfoCacheGet(t *testing.T) {
   767  	claimName := "test-claim"
   768  	namespace := "test-namespace"
   769  	for _, test := range []struct {
   770  		description    string
   771  		claimInfoCache *claimInfoCache
   772  		expectedNil    bool
   773  		expectedExists bool
   774  	}{
   775  		{
   776  			description: "cache hit",
   777  			claimInfoCache: &claimInfoCache{
   778  				claimInfo: map[string]*ClaimInfo{
   779  					namespace + "/" + claimName: {
   780  						ClaimInfoState: state.ClaimInfoState{
   781  							ClaimName: claimName,
   782  							Namespace: namespace,
   783  						},
   784  					},
   785  				},
   786  			},
   787  			expectedExists: true,
   788  		},
   789  		{
   790  			description:    "cache miss",
   791  			claimInfoCache: &claimInfoCache{},
   792  			expectedNil:    true,
   793  		},
   794  	} {
   795  		t.Run(test.description, func(t *testing.T) {
   796  			result, exists := test.claimInfoCache.get(claimName, namespace)
   797  			assert.Equal(t, test.expectedExists, exists)
   798  			assert.Equal(t, test.expectedNil, result == nil)
   799  		})
   800  	}
   801  }
   802  
   803  func TestClaimInfoCacheDelete(t *testing.T) {
   804  	claimName := "test-claim"
   805  	namespace := "test-namespace"
   806  	for _, test := range []struct {
   807  		description    string
   808  		claimInfoCache *claimInfoCache
   809  	}{
   810  		{
   811  			description: "item in cache",
   812  			claimInfoCache: &claimInfoCache{
   813  				claimInfo: map[string]*ClaimInfo{
   814  					claimName + namespace: {
   815  						ClaimInfoState: state.ClaimInfoState{
   816  							ClaimName: claimName,
   817  							Namespace: namespace,
   818  						},
   819  					},
   820  				},
   821  			},
   822  		},
   823  		{
   824  			description:    "item not in cache",
   825  			claimInfoCache: &claimInfoCache{},
   826  		},
   827  	} {
   828  		t.Run(test.description, func(t *testing.T) {
   829  			test.claimInfoCache.delete(claimName, namespace)
   830  			assert.False(t, test.claimInfoCache.contains(claimName, namespace))
   831  		})
   832  	}
   833  }
   834  
   835  func TestClaimInfoCacheHasPodReference(t *testing.T) {
   836  	claimName := "test-claim"
   837  	namespace := "test-namespace"
   838  	uid := types.UID("test-uid")
   839  	for _, test := range []struct {
   840  		description    string
   841  		claimInfoCache *claimInfoCache
   842  		expectedResult bool
   843  	}{
   844  		{
   845  			description: "uid is referenced",
   846  			claimInfoCache: &claimInfoCache{
   847  				claimInfo: map[string]*ClaimInfo{
   848  					claimName + namespace: {
   849  						ClaimInfoState: state.ClaimInfoState{
   850  							ClaimName: claimName,
   851  							Namespace: namespace,
   852  							PodUIDs:   sets.New[string](string(uid)),
   853  						},
   854  					},
   855  				},
   856  			},
   857  			expectedResult: true,
   858  		},
   859  		{
   860  			description:    "uid is not referenced",
   861  			claimInfoCache: &claimInfoCache{},
   862  		},
   863  	} {
   864  		t.Run(test.description, func(t *testing.T) {
   865  			assert.Equal(t, test.expectedResult, test.claimInfoCache.hasPodReference(uid))
   866  		})
   867  	}
   868  }
   869  
   870  func TestSyncToCheckpoint(t *testing.T) {
   871  	for _, test := range []struct {
   872  		description    string
   873  		stateDir       string
   874  		checkpointName string
   875  		wantErr        bool
   876  	}{
   877  		{
   878  			description:    "successfully checkpointed cache",
   879  			stateDir:       t.TempDir(),
   880  			checkpointName: "test-checkpoint",
   881  		},
   882  	} {
   883  		t.Run(test.description, func(t *testing.T) {
   884  			cache, err := newClaimInfoCache(test.stateDir, test.checkpointName)
   885  			assert.NoError(t, err)
   886  			err = cache.syncToCheckpoint()
   887  			if test.wantErr {
   888  				assert.Error(t, err)
   889  				return
   890  			}
   891  			assert.NoError(t, err)
   892  		})
   893  	}
   894  }