k8s.io/kubernetes@v1.29.3/pkg/controller/volume/persistentvolume/index_test.go (about)

     1  /*
     2  Copyright 2014 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 persistentvolume
    18  
    19  import (
    20  	"sort"
    21  	"testing"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/client-go/kubernetes/scheme"
    27  	ref "k8s.io/client-go/tools/reference"
    28  	"k8s.io/component-helpers/storage/volume"
    29  	"k8s.io/kubernetes/pkg/volume/util"
    30  )
    31  
    32  func makePVC(size string, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim {
    33  	fs := v1.PersistentVolumeFilesystem
    34  	pvc := v1.PersistentVolumeClaim{
    35  		ObjectMeta: metav1.ObjectMeta{
    36  			Name:      "claim01",
    37  			Namespace: "myns",
    38  		},
    39  		Spec: v1.PersistentVolumeClaimSpec{
    40  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
    41  			Resources: v1.VolumeResourceRequirements{
    42  				Requests: v1.ResourceList{
    43  					v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
    44  				},
    45  			},
    46  			VolumeMode: &fs,
    47  		},
    48  	}
    49  	if modfn != nil {
    50  		modfn(&pvc)
    51  	}
    52  	return &pvc
    53  }
    54  
    55  func makeVolumeModePVC(size string, mode *v1.PersistentVolumeMode, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim {
    56  	pvc := v1.PersistentVolumeClaim{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name:      "claim01",
    59  			Namespace: "myns",
    60  		},
    61  		Spec: v1.PersistentVolumeClaimSpec{
    62  			VolumeMode:  mode,
    63  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
    64  			Resources: v1.VolumeResourceRequirements{
    65  				Requests: v1.ResourceList{
    66  					v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
    67  				},
    68  			},
    69  		},
    70  	}
    71  	if modfn != nil {
    72  		modfn(&pvc)
    73  	}
    74  	return &pvc
    75  }
    76  
    77  func TestMatchVolume(t *testing.T) {
    78  	volList := newPersistentVolumeOrderedIndex()
    79  	for _, pv := range createTestVolumes() {
    80  		volList.store.Add(pv)
    81  	}
    82  
    83  	scenarios := map[string]struct {
    84  		expectedMatch string
    85  		claim         *v1.PersistentVolumeClaim
    86  	}{
    87  		"successful-match-gce-10": {
    88  			expectedMatch: "gce-pd-10",
    89  			claim:         makePVC("8G", nil),
    90  		},
    91  		"successful-match-nfs-5": {
    92  			expectedMatch: "nfs-5",
    93  			claim: makePVC("5G", func(pvc *v1.PersistentVolumeClaim) {
    94  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce, v1.ReadWriteMany}
    95  			}),
    96  		},
    97  		"successful-skip-1g-bound-volume": {
    98  			expectedMatch: "gce-pd-5",
    99  			claim:         makePVC("1G", nil),
   100  		},
   101  		"successful-no-match": {
   102  			expectedMatch: "",
   103  			claim:         makePVC("999G", nil),
   104  		},
   105  		"successful-no-match-due-to-label": {
   106  			expectedMatch: "",
   107  			claim: makePVC("999G", func(pvc *v1.PersistentVolumeClaim) {
   108  				pvc.Spec.Selector = &metav1.LabelSelector{
   109  					MatchLabels: map[string]string{
   110  						"should-not-exist": "true",
   111  					},
   112  				}
   113  			}),
   114  		},
   115  		"successful-no-match-due-to-size-constraint-with-label-selector": {
   116  			expectedMatch: "",
   117  			claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) {
   118  				pvc.Spec.Selector = &metav1.LabelSelector{
   119  					MatchLabels: map[string]string{
   120  						"should-exist": "true",
   121  					},
   122  				}
   123  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce}
   124  			}),
   125  		},
   126  		"successful-match-due-with-constraint-and-label-selector": {
   127  			expectedMatch: "gce-pd-2",
   128  			claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) {
   129  				pvc.Spec.Selector = &metav1.LabelSelector{
   130  					MatchLabels: map[string]string{
   131  						"should-exist": "true",
   132  					},
   133  				}
   134  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   135  			}),
   136  		},
   137  		"successful-match-with-class": {
   138  			expectedMatch: "gce-pd-silver1",
   139  			claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) {
   140  				pvc.Spec.Selector = &metav1.LabelSelector{
   141  					MatchLabels: map[string]string{
   142  						"should-exist": "true",
   143  					},
   144  				}
   145  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   146  				pvc.Spec.StorageClassName = &classSilver
   147  			}),
   148  		},
   149  		"successful-match-with-class-and-labels": {
   150  			expectedMatch: "gce-pd-silver2",
   151  			claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) {
   152  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   153  				pvc.Spec.StorageClassName = &classSilver
   154  			}),
   155  		},
   156  		"successful-match-very-large": {
   157  			expectedMatch: "local-pd-very-large",
   158  			// we keep the pvc size less than int64 so that in case the pv overflows
   159  			// the pvc does not overflow equally and give us false matching signals.
   160  			claim: makePVC("1E", func(pvc *v1.PersistentVolumeClaim) {
   161  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   162  				pvc.Spec.StorageClassName = &classLarge
   163  			}),
   164  		},
   165  		"successful-match-exact-extremely-large": {
   166  			expectedMatch: "local-pd-extremely-large",
   167  			claim: makePVC("800E", func(pvc *v1.PersistentVolumeClaim) {
   168  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   169  				pvc.Spec.StorageClassName = &classLarge
   170  			}),
   171  		},
   172  		"successful-no-match-way-too-large": {
   173  			expectedMatch: "",
   174  			claim: makePVC("950E", func(pvc *v1.PersistentVolumeClaim) {
   175  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   176  				pvc.Spec.StorageClassName = &classLarge
   177  			}),
   178  		},
   179  	}
   180  
   181  	for name, scenario := range scenarios {
   182  		volume, err := volList.findBestMatchForClaim(scenario.claim, false)
   183  		if err != nil {
   184  			t.Errorf("Unexpected error matching volume by claim: %v", err)
   185  		}
   186  		if len(scenario.expectedMatch) != 0 && volume == nil {
   187  			t.Errorf("Expected match but received nil volume for scenario: %s", name)
   188  		}
   189  		if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch {
   190  			t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name)
   191  		}
   192  		if len(scenario.expectedMatch) == 0 && volume != nil {
   193  			t.Errorf("Unexpected match for scenario: %s, matched with %s instead", name, volume.UID)
   194  		}
   195  	}
   196  }
   197  
   198  func TestMatchingWithBoundVolumes(t *testing.T) {
   199  	fs := v1.PersistentVolumeFilesystem
   200  	volumeIndex := newPersistentVolumeOrderedIndex()
   201  	// two similar volumes, one is bound
   202  	pv1 := &v1.PersistentVolume{
   203  		ObjectMeta: metav1.ObjectMeta{
   204  			UID:  "gce-pd-1",
   205  			Name: "gce001",
   206  		},
   207  		Spec: v1.PersistentVolumeSpec{
   208  			Capacity: v1.ResourceList{
   209  				v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
   210  			},
   211  			PersistentVolumeSource: v1.PersistentVolumeSource{
   212  				GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   213  			},
   214  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
   215  			// this one we're pretending is already bound
   216  			ClaimRef:   &v1.ObjectReference{UID: "abc123"},
   217  			VolumeMode: &fs,
   218  		},
   219  		Status: v1.PersistentVolumeStatus{
   220  			Phase: v1.VolumeBound,
   221  		},
   222  	}
   223  
   224  	pv2 := &v1.PersistentVolume{
   225  		ObjectMeta: metav1.ObjectMeta{
   226  			UID:  "gce-pd-2",
   227  			Name: "gce002",
   228  		},
   229  		Spec: v1.PersistentVolumeSpec{
   230  			Capacity: v1.ResourceList{
   231  				v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
   232  			},
   233  			PersistentVolumeSource: v1.PersistentVolumeSource{
   234  				GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   235  			},
   236  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
   237  			VolumeMode:  &fs,
   238  		},
   239  		Status: v1.PersistentVolumeStatus{
   240  			Phase: v1.VolumeAvailable,
   241  		},
   242  	}
   243  
   244  	volumeIndex.store.Add(pv1)
   245  	volumeIndex.store.Add(pv2)
   246  
   247  	claim := &v1.PersistentVolumeClaim{
   248  		ObjectMeta: metav1.ObjectMeta{
   249  			Name:      "claim01",
   250  			Namespace: "myns",
   251  		},
   252  		Spec: v1.PersistentVolumeClaimSpec{
   253  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
   254  			Resources: v1.VolumeResourceRequirements{
   255  				Requests: v1.ResourceList{
   256  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
   257  				},
   258  			},
   259  			VolumeMode: &fs,
   260  		},
   261  	}
   262  
   263  	volume, err := volumeIndex.findBestMatchForClaim(claim, false)
   264  	if err != nil {
   265  		t.Fatalf("Unexpected error matching volume by claim: %v", err)
   266  	}
   267  	if volume == nil {
   268  		t.Fatalf("Unexpected nil volume.  Expected %s", pv2.Name)
   269  	}
   270  	if pv2.Name != volume.Name {
   271  		t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name)
   272  	}
   273  }
   274  
   275  func TestListByAccessModes(t *testing.T) {
   276  	volList := newPersistentVolumeOrderedIndex()
   277  	for _, pv := range createTestVolumes() {
   278  		volList.store.Add(pv)
   279  	}
   280  
   281  	volumes, err := volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany})
   282  	if err != nil {
   283  		t.Error("Unexpected error retrieving volumes by access modes:", err)
   284  	}
   285  	sort.Sort(byCapacity{volumes})
   286  
   287  	for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} {
   288  		if string(volumes[i].UID) != expected {
   289  			t.Errorf("Incorrect ordering of persistent volumes.  Expected %s but got %s", expected, volumes[i].UID)
   290  		}
   291  	}
   292  
   293  	volumes, err = volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany})
   294  	if err != nil {
   295  		t.Error("Unexpected error retrieving volumes by access modes:", err)
   296  	}
   297  	sort.Sort(byCapacity{volumes})
   298  
   299  	for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10", "local-pd-very-large", "local-pd-extremely-large"} {
   300  		if string(volumes[i].UID) != expected {
   301  			t.Errorf("Incorrect ordering of persistent volumes.  Expected %s but got %s", expected, volumes[i].UID)
   302  		}
   303  	}
   304  }
   305  
   306  func TestAllPossibleAccessModes(t *testing.T) {
   307  	index := newPersistentVolumeOrderedIndex()
   308  	for _, pv := range createTestVolumes() {
   309  		index.store.Add(pv)
   310  	}
   311  
   312  	// the mock PVs creates contain 2 types of accessmodes:   RWO+ROX and RWO+ROW+RWX
   313  	possibleModes := index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
   314  	if len(possibleModes) != 3 {
   315  		t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
   316  	}
   317  	for _, m := range possibleModes {
   318  		if !util.ContainsAccessMode(m, v1.ReadWriteOnce) {
   319  			t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
   320  		}
   321  	}
   322  
   323  	possibleModes = index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteMany})
   324  	if len(possibleModes) != 1 {
   325  		t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
   326  	}
   327  	if !util.ContainsAccessMode(possibleModes[0], v1.ReadWriteMany) {
   328  		t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
   329  	}
   330  
   331  }
   332  
   333  func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
   334  	fs := v1.PersistentVolumeFilesystem
   335  	gce := &v1.PersistentVolume{
   336  		ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "gce"},
   337  		Spec: v1.PersistentVolumeSpec{
   338  			Capacity:               v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
   339  			PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}},
   340  			AccessModes: []v1.PersistentVolumeAccessMode{
   341  				v1.ReadWriteOnce,
   342  				v1.ReadOnlyMany,
   343  			},
   344  			VolumeMode: &fs,
   345  		},
   346  		Status: v1.PersistentVolumeStatus{
   347  			Phase: v1.VolumeAvailable,
   348  		},
   349  	}
   350  
   351  	ebs := &v1.PersistentVolume{
   352  		ObjectMeta: metav1.ObjectMeta{UID: "002", Name: "ebs"},
   353  		Spec: v1.PersistentVolumeSpec{
   354  			Capacity:               v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
   355  			PersistentVolumeSource: v1.PersistentVolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}},
   356  			AccessModes: []v1.PersistentVolumeAccessMode{
   357  				v1.ReadWriteOnce,
   358  			},
   359  			VolumeMode: &fs,
   360  		},
   361  		Status: v1.PersistentVolumeStatus{
   362  			Phase: v1.VolumeAvailable,
   363  		},
   364  	}
   365  
   366  	nfs := &v1.PersistentVolume{
   367  		ObjectMeta: metav1.ObjectMeta{UID: "003", Name: "nfs"},
   368  		Spec: v1.PersistentVolumeSpec{
   369  			Capacity:               v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
   370  			PersistentVolumeSource: v1.PersistentVolumeSource{NFS: &v1.NFSVolumeSource{}},
   371  			AccessModes: []v1.PersistentVolumeAccessMode{
   372  				v1.ReadWriteOnce,
   373  				v1.ReadOnlyMany,
   374  				v1.ReadWriteMany,
   375  			},
   376  			VolumeMode: &fs,
   377  		},
   378  		Status: v1.PersistentVolumeStatus{
   379  			Phase: v1.VolumeAvailable,
   380  		},
   381  	}
   382  
   383  	claim := &v1.PersistentVolumeClaim{
   384  		ObjectMeta: metav1.ObjectMeta{
   385  			Name:      "claim01",
   386  			Namespace: "myns",
   387  		},
   388  		Spec: v1.PersistentVolumeClaimSpec{
   389  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   390  			Resources:   v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}},
   391  			VolumeMode:  &fs,
   392  		},
   393  	}
   394  
   395  	index := newPersistentVolumeOrderedIndex()
   396  	index.store.Add(gce)
   397  	index.store.Add(ebs)
   398  	index.store.Add(nfs)
   399  
   400  	volume, _ := index.findBestMatchForClaim(claim, false)
   401  	if volume.Name != ebs.Name {
   402  		t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name)
   403  	}
   404  
   405  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}
   406  	volume, _ = index.findBestMatchForClaim(claim, false)
   407  	if volume.Name != gce.Name {
   408  		t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
   409  	}
   410  
   411  	// order of the requested modes should not matter
   412  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce, v1.ReadOnlyMany}
   413  	volume, _ = index.findBestMatchForClaim(claim, false)
   414  	if volume.Name != nfs.Name {
   415  		t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
   416  	}
   417  
   418  	// fewer modes requested should still match
   419  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
   420  	volume, _ = index.findBestMatchForClaim(claim, false)
   421  	if volume.Name != nfs.Name {
   422  		t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
   423  	}
   424  
   425  	// pretend the exact match is bound.  should get the next level up of modes.
   426  	ebs.Spec.ClaimRef = &v1.ObjectReference{}
   427  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   428  	volume, _ = index.findBestMatchForClaim(claim, false)
   429  	if volume.Name != gce.Name {
   430  		t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
   431  	}
   432  
   433  	// continue up the levels of modes.
   434  	gce.Spec.ClaimRef = &v1.ObjectReference{}
   435  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
   436  	volume, _ = index.findBestMatchForClaim(claim, false)
   437  	if volume.Name != nfs.Name {
   438  		t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
   439  	}
   440  
   441  	// partial mode request
   442  	gce.Spec.ClaimRef = nil
   443  	claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
   444  	volume, _ = index.findBestMatchForClaim(claim, false)
   445  	if volume.Name != gce.Name {
   446  		t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
   447  	}
   448  }
   449  
   450  // createVolumeNodeAffinity returns a VolumeNodeAffinity for given key and value.
   451  func createNodeAffinity(key string, value string) *v1.VolumeNodeAffinity {
   452  	return &v1.VolumeNodeAffinity{
   453  		Required: &v1.NodeSelector{
   454  			NodeSelectorTerms: []v1.NodeSelectorTerm{
   455  				{
   456  					MatchExpressions: []v1.NodeSelectorRequirement{
   457  						{
   458  							Key:      key,
   459  							Operator: v1.NodeSelectorOpIn,
   460  							Values:   []string{value},
   461  						},
   462  					},
   463  				},
   464  			},
   465  		},
   466  	}
   467  }
   468  
   469  func createTestVolumes() []*v1.PersistentVolume {
   470  	fs := v1.PersistentVolumeFilesystem
   471  	// these volumes are deliberately out-of-order to test indexing and sorting
   472  	return []*v1.PersistentVolume{
   473  		{
   474  			ObjectMeta: metav1.ObjectMeta{
   475  				UID:  "gce-pd-10",
   476  				Name: "gce003",
   477  			},
   478  			Spec: v1.PersistentVolumeSpec{
   479  				Capacity: v1.ResourceList{
   480  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
   481  				},
   482  				PersistentVolumeSource: v1.PersistentVolumeSource{
   483  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   484  				},
   485  				AccessModes: []v1.PersistentVolumeAccessMode{
   486  					v1.ReadWriteOnce,
   487  					v1.ReadOnlyMany,
   488  				},
   489  				VolumeMode: &fs,
   490  			},
   491  			Status: v1.PersistentVolumeStatus{
   492  				Phase: v1.VolumeAvailable,
   493  			},
   494  		},
   495  		{
   496  			ObjectMeta: metav1.ObjectMeta{
   497  				UID:  "gce-pd-20",
   498  				Name: "gce004",
   499  			},
   500  			Spec: v1.PersistentVolumeSpec{
   501  				Capacity: v1.ResourceList{
   502  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("20G"),
   503  				},
   504  				PersistentVolumeSource: v1.PersistentVolumeSource{
   505  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   506  				},
   507  				AccessModes: []v1.PersistentVolumeAccessMode{
   508  					v1.ReadWriteOnce,
   509  					v1.ReadOnlyMany,
   510  				},
   511  				// this one we're pretending is already bound
   512  				ClaimRef:   &v1.ObjectReference{UID: "def456"},
   513  				VolumeMode: &fs,
   514  			},
   515  			Status: v1.PersistentVolumeStatus{
   516  				Phase: v1.VolumeBound,
   517  			},
   518  		},
   519  		{
   520  			ObjectMeta: metav1.ObjectMeta{
   521  				UID:  "nfs-5",
   522  				Name: "nfs002",
   523  			},
   524  			Spec: v1.PersistentVolumeSpec{
   525  				Capacity: v1.ResourceList{
   526  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
   527  				},
   528  				PersistentVolumeSource: v1.PersistentVolumeSource{
   529  					Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
   530  				},
   531  				AccessModes: []v1.PersistentVolumeAccessMode{
   532  					v1.ReadWriteOnce,
   533  					v1.ReadOnlyMany,
   534  					v1.ReadWriteMany,
   535  				},
   536  				VolumeMode: &fs,
   537  			},
   538  			Status: v1.PersistentVolumeStatus{
   539  				Phase: v1.VolumeAvailable,
   540  			},
   541  		},
   542  		{
   543  			ObjectMeta: metav1.ObjectMeta{
   544  				UID:  "gce-pd-1",
   545  				Name: "gce001",
   546  			},
   547  			Spec: v1.PersistentVolumeSpec{
   548  				Capacity: v1.ResourceList{
   549  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
   550  				},
   551  				PersistentVolumeSource: v1.PersistentVolumeSource{
   552  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   553  				},
   554  				AccessModes: []v1.PersistentVolumeAccessMode{
   555  					v1.ReadWriteOnce,
   556  					v1.ReadOnlyMany,
   557  				},
   558  				// this one we're pretending is already bound
   559  				ClaimRef:   &v1.ObjectReference{UID: "abc123"},
   560  				VolumeMode: &fs,
   561  			},
   562  			Status: v1.PersistentVolumeStatus{
   563  				Phase: v1.VolumeBound,
   564  			},
   565  		},
   566  		{
   567  			ObjectMeta: metav1.ObjectMeta{
   568  				UID:  "nfs-10",
   569  				Name: "nfs003",
   570  			},
   571  			Spec: v1.PersistentVolumeSpec{
   572  				Capacity: v1.ResourceList{
   573  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
   574  				},
   575  				PersistentVolumeSource: v1.PersistentVolumeSource{
   576  					Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
   577  				},
   578  				AccessModes: []v1.PersistentVolumeAccessMode{
   579  					v1.ReadWriteOnce,
   580  					v1.ReadOnlyMany,
   581  					v1.ReadWriteMany,
   582  				},
   583  				VolumeMode: &fs,
   584  			},
   585  			Status: v1.PersistentVolumeStatus{
   586  				Phase: v1.VolumeAvailable,
   587  			},
   588  		},
   589  		{
   590  			ObjectMeta: metav1.ObjectMeta{
   591  				UID:  "gce-pd-5",
   592  				Name: "gce002",
   593  			},
   594  			Spec: v1.PersistentVolumeSpec{
   595  				Capacity: v1.ResourceList{
   596  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
   597  				},
   598  				PersistentVolumeSource: v1.PersistentVolumeSource{
   599  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   600  				},
   601  				AccessModes: []v1.PersistentVolumeAccessMode{
   602  					v1.ReadWriteOnce,
   603  					v1.ReadOnlyMany,
   604  				},
   605  				VolumeMode: &fs,
   606  			},
   607  			Status: v1.PersistentVolumeStatus{
   608  				Phase: v1.VolumeAvailable,
   609  			},
   610  		},
   611  		{
   612  			ObjectMeta: metav1.ObjectMeta{
   613  				UID:  "nfs-1",
   614  				Name: "nfs001",
   615  			},
   616  			Spec: v1.PersistentVolumeSpec{
   617  				Capacity: v1.ResourceList{
   618  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
   619  				},
   620  				PersistentVolumeSource: v1.PersistentVolumeSource{
   621  					Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
   622  				},
   623  				AccessModes: []v1.PersistentVolumeAccessMode{
   624  					v1.ReadWriteOnce,
   625  					v1.ReadOnlyMany,
   626  					v1.ReadWriteMany,
   627  				},
   628  				VolumeMode: &fs,
   629  			},
   630  			Status: v1.PersistentVolumeStatus{
   631  				Phase: v1.VolumeAvailable,
   632  			},
   633  		},
   634  		{
   635  			ObjectMeta: metav1.ObjectMeta{
   636  				UID:  "gce-pd-2",
   637  				Name: "gce0022",
   638  				Labels: map[string]string{
   639  					"should-exist": "true",
   640  				},
   641  			},
   642  			Spec: v1.PersistentVolumeSpec{
   643  				Capacity: v1.ResourceList{
   644  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"),
   645  				},
   646  				PersistentVolumeSource: v1.PersistentVolumeSource{
   647  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   648  				},
   649  				AccessModes: []v1.PersistentVolumeAccessMode{
   650  					v1.ReadWriteOnce,
   651  				},
   652  				VolumeMode: &fs,
   653  			},
   654  			Status: v1.PersistentVolumeStatus{
   655  				Phase: v1.VolumeAvailable,
   656  			},
   657  		},
   658  		{
   659  			ObjectMeta: metav1.ObjectMeta{
   660  				UID:  "gce-pd-silver1",
   661  				Name: "gce0023",
   662  				Labels: map[string]string{
   663  					"should-exist": "true",
   664  				},
   665  			},
   666  			Spec: v1.PersistentVolumeSpec{
   667  				Capacity: v1.ResourceList{
   668  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("10000G"),
   669  				},
   670  				PersistentVolumeSource: v1.PersistentVolumeSource{
   671  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   672  				},
   673  				AccessModes: []v1.PersistentVolumeAccessMode{
   674  					v1.ReadWriteOnce,
   675  				},
   676  				StorageClassName: classSilver,
   677  				VolumeMode:       &fs,
   678  			},
   679  			Status: v1.PersistentVolumeStatus{
   680  				Phase: v1.VolumeAvailable,
   681  			},
   682  		},
   683  		{
   684  			ObjectMeta: metav1.ObjectMeta{
   685  				UID:  "gce-pd-silver2",
   686  				Name: "gce0024",
   687  			},
   688  			Spec: v1.PersistentVolumeSpec{
   689  				Capacity: v1.ResourceList{
   690  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
   691  				},
   692  				PersistentVolumeSource: v1.PersistentVolumeSource{
   693  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   694  				},
   695  				AccessModes: []v1.PersistentVolumeAccessMode{
   696  					v1.ReadWriteOnce,
   697  				},
   698  				StorageClassName: classSilver,
   699  				VolumeMode:       &fs,
   700  			},
   701  			Status: v1.PersistentVolumeStatus{
   702  				Phase: v1.VolumeAvailable,
   703  			},
   704  		},
   705  		{
   706  			ObjectMeta: metav1.ObjectMeta{
   707  				UID:  "gce-pd-gold",
   708  				Name: "gce0025",
   709  			},
   710  			Spec: v1.PersistentVolumeSpec{
   711  				Capacity: v1.ResourceList{
   712  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("50G"),
   713  				},
   714  				PersistentVolumeSource: v1.PersistentVolumeSource{
   715  					GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
   716  				},
   717  				AccessModes: []v1.PersistentVolumeAccessMode{
   718  					v1.ReadWriteOnce,
   719  				},
   720  				StorageClassName: classGold,
   721  				VolumeMode:       &fs,
   722  			},
   723  			Status: v1.PersistentVolumeStatus{
   724  				Phase: v1.VolumeAvailable,
   725  			},
   726  		},
   727  		{
   728  			ObjectMeta: metav1.ObjectMeta{
   729  				UID:  "local-pd-very-large",
   730  				Name: "local001",
   731  			},
   732  			Spec: v1.PersistentVolumeSpec{
   733  				Capacity: v1.ResourceList{
   734  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200E"),
   735  				},
   736  				PersistentVolumeSource: v1.PersistentVolumeSource{
   737  					Local: &v1.LocalVolumeSource{},
   738  				},
   739  				AccessModes: []v1.PersistentVolumeAccessMode{
   740  					v1.ReadWriteOnce,
   741  					v1.ReadOnlyMany,
   742  					v1.ReadWriteMany,
   743  				},
   744  				StorageClassName: classLarge,
   745  				VolumeMode:       &fs,
   746  			},
   747  			Status: v1.PersistentVolumeStatus{
   748  				Phase: v1.VolumeAvailable,
   749  			},
   750  		},
   751  		{
   752  			ObjectMeta: metav1.ObjectMeta{
   753  				UID:  "local-pd-extremely-large",
   754  				Name: "local002",
   755  			},
   756  			Spec: v1.PersistentVolumeSpec{
   757  				Capacity: v1.ResourceList{
   758  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("800E"),
   759  				},
   760  				PersistentVolumeSource: v1.PersistentVolumeSource{
   761  					Local: &v1.LocalVolumeSource{},
   762  				},
   763  				AccessModes: []v1.PersistentVolumeAccessMode{
   764  					v1.ReadWriteOnce,
   765  					v1.ReadOnlyMany,
   766  					v1.ReadWriteMany,
   767  				},
   768  				StorageClassName: classLarge,
   769  				VolumeMode:       &fs,
   770  			},
   771  			Status: v1.PersistentVolumeStatus{
   772  				Phase: v1.VolumeAvailable,
   773  			},
   774  		},
   775  		{
   776  			ObjectMeta: metav1.ObjectMeta{
   777  				UID:  "affinity-pv",
   778  				Name: "affinity001",
   779  			},
   780  			Spec: v1.PersistentVolumeSpec{
   781  				Capacity: v1.ResourceList{
   782  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
   783  				},
   784  				PersistentVolumeSource: v1.PersistentVolumeSource{
   785  					Local: &v1.LocalVolumeSource{},
   786  				},
   787  				AccessModes: []v1.PersistentVolumeAccessMode{
   788  					v1.ReadWriteOnce,
   789  					v1.ReadOnlyMany,
   790  				},
   791  				StorageClassName: classWait,
   792  				NodeAffinity:     createNodeAffinity("key1", "value1"),
   793  				VolumeMode:       &fs,
   794  			},
   795  			Status: v1.PersistentVolumeStatus{
   796  				Phase: v1.VolumeAvailable,
   797  			},
   798  		},
   799  		{
   800  			ObjectMeta: metav1.ObjectMeta{
   801  				UID:  "affinity-pv2",
   802  				Name: "affinity002",
   803  			},
   804  			Spec: v1.PersistentVolumeSpec{
   805  				Capacity: v1.ResourceList{
   806  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("150G"),
   807  				},
   808  				PersistentVolumeSource: v1.PersistentVolumeSource{
   809  					Local: &v1.LocalVolumeSource{},
   810  				},
   811  				AccessModes: []v1.PersistentVolumeAccessMode{
   812  					v1.ReadWriteOnce,
   813  					v1.ReadOnlyMany,
   814  				},
   815  				StorageClassName: classWait,
   816  				NodeAffinity:     createNodeAffinity("key1", "value1"),
   817  				VolumeMode:       &fs,
   818  			},
   819  			Status: v1.PersistentVolumeStatus{
   820  				Phase: v1.VolumeAvailable,
   821  			},
   822  		},
   823  		{
   824  			ObjectMeta: metav1.ObjectMeta{
   825  				UID:  "affinity-prebound",
   826  				Name: "affinity003",
   827  			},
   828  			Spec: v1.PersistentVolumeSpec{
   829  				Capacity: v1.ResourceList{
   830  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
   831  				},
   832  				PersistentVolumeSource: v1.PersistentVolumeSource{
   833  					Local: &v1.LocalVolumeSource{},
   834  				},
   835  				AccessModes: []v1.PersistentVolumeAccessMode{
   836  					v1.ReadWriteOnce,
   837  					v1.ReadOnlyMany,
   838  				},
   839  				StorageClassName: classWait,
   840  				ClaimRef:         &v1.ObjectReference{Name: "claim02", Namespace: "myns"},
   841  				NodeAffinity:     createNodeAffinity("key1", "value1"),
   842  				VolumeMode:       &fs,
   843  			},
   844  			Status: v1.PersistentVolumeStatus{
   845  				Phase: v1.VolumeAvailable,
   846  			},
   847  		},
   848  		{
   849  			ObjectMeta: metav1.ObjectMeta{
   850  				UID:  "affinity-pv3",
   851  				Name: "affinity003",
   852  			},
   853  			Spec: v1.PersistentVolumeSpec{
   854  				Capacity: v1.ResourceList{
   855  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
   856  				},
   857  				PersistentVolumeSource: v1.PersistentVolumeSource{
   858  					Local: &v1.LocalVolumeSource{},
   859  				},
   860  				AccessModes: []v1.PersistentVolumeAccessMode{
   861  					v1.ReadWriteOnce,
   862  					v1.ReadOnlyMany,
   863  				},
   864  				StorageClassName: classWait,
   865  				NodeAffinity:     createNodeAffinity("key1", "value3"),
   866  				VolumeMode:       &fs,
   867  			},
   868  			Status: v1.PersistentVolumeStatus{
   869  				Phase: v1.VolumeAvailable,
   870  			},
   871  		},
   872  		{
   873  			ObjectMeta: metav1.ObjectMeta{
   874  				UID:  "affinity-pv4-pending",
   875  				Name: "affinity004-pending",
   876  			},
   877  			Spec: v1.PersistentVolumeSpec{
   878  				Capacity: v1.ResourceList{
   879  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
   880  				},
   881  				PersistentVolumeSource: v1.PersistentVolumeSource{
   882  					Local: &v1.LocalVolumeSource{},
   883  				},
   884  				AccessModes: []v1.PersistentVolumeAccessMode{
   885  					v1.ReadWriteOnce,
   886  					v1.ReadOnlyMany,
   887  				},
   888  				StorageClassName: classWait,
   889  				NodeAffinity:     createNodeAffinity("key1", "value4"),
   890  				VolumeMode:       &fs,
   891  			},
   892  			Status: v1.PersistentVolumeStatus{
   893  				Phase: v1.VolumePending,
   894  			},
   895  		},
   896  		{
   897  			ObjectMeta: metav1.ObjectMeta{
   898  				UID:  "affinity-pv4-failed",
   899  				Name: "affinity004-failed",
   900  			},
   901  			Spec: v1.PersistentVolumeSpec{
   902  				Capacity: v1.ResourceList{
   903  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
   904  				},
   905  				PersistentVolumeSource: v1.PersistentVolumeSource{
   906  					Local: &v1.LocalVolumeSource{},
   907  				},
   908  				AccessModes: []v1.PersistentVolumeAccessMode{
   909  					v1.ReadWriteOnce,
   910  					v1.ReadOnlyMany,
   911  				},
   912  				StorageClassName: classWait,
   913  				NodeAffinity:     createNodeAffinity("key1", "value4"),
   914  				VolumeMode:       &fs,
   915  			},
   916  			Status: v1.PersistentVolumeStatus{
   917  				Phase: v1.VolumeFailed,
   918  			},
   919  		},
   920  		{
   921  			ObjectMeta: metav1.ObjectMeta{
   922  				UID:  "affinity-pv4-released",
   923  				Name: "affinity004-released",
   924  			},
   925  			Spec: v1.PersistentVolumeSpec{
   926  				Capacity: v1.ResourceList{
   927  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
   928  				},
   929  				PersistentVolumeSource: v1.PersistentVolumeSource{
   930  					Local: &v1.LocalVolumeSource{},
   931  				},
   932  				AccessModes: []v1.PersistentVolumeAccessMode{
   933  					v1.ReadWriteOnce,
   934  					v1.ReadOnlyMany,
   935  				},
   936  				StorageClassName: classWait,
   937  				NodeAffinity:     createNodeAffinity("key1", "value4"),
   938  				VolumeMode:       &fs,
   939  			},
   940  			Status: v1.PersistentVolumeStatus{
   941  				Phase: v1.VolumeReleased,
   942  			},
   943  		},
   944  		{
   945  			ObjectMeta: metav1.ObjectMeta{
   946  				UID:  "affinity-pv4-empty",
   947  				Name: "affinity004-empty",
   948  			},
   949  			Spec: v1.PersistentVolumeSpec{
   950  				Capacity: v1.ResourceList{
   951  					v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
   952  				},
   953  				PersistentVolumeSource: v1.PersistentVolumeSource{
   954  					Local: &v1.LocalVolumeSource{},
   955  				},
   956  				AccessModes: []v1.PersistentVolumeAccessMode{
   957  					v1.ReadWriteOnce,
   958  					v1.ReadOnlyMany,
   959  				},
   960  				StorageClassName: classWait,
   961  				NodeAffinity:     createNodeAffinity("key1", "value4"),
   962  				VolumeMode:       &fs,
   963  			},
   964  		},
   965  	}
   966  }
   967  
   968  func testVolume(name, size string) *v1.PersistentVolume {
   969  	fs := v1.PersistentVolumeFilesystem
   970  	return &v1.PersistentVolume{
   971  		ObjectMeta: metav1.ObjectMeta{
   972  			Name:        name,
   973  			Annotations: map[string]string{},
   974  		},
   975  		Spec: v1.PersistentVolumeSpec{
   976  			Capacity:               v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse(size)},
   977  			PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}},
   978  			AccessModes:            []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
   979  			VolumeMode:             &fs,
   980  		},
   981  		Status: v1.PersistentVolumeStatus{
   982  			Phase: v1.VolumeAvailable,
   983  		},
   984  	}
   985  }
   986  
   987  func createVolumeModeBlockTestVolume() *v1.PersistentVolume {
   988  	blockMode := v1.PersistentVolumeBlock
   989  
   990  	return &v1.PersistentVolume{
   991  		ObjectMeta: metav1.ObjectMeta{
   992  			UID:  "local-1",
   993  			Name: "block",
   994  		},
   995  		Spec: v1.PersistentVolumeSpec{
   996  			Capacity: v1.ResourceList{
   997  				v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
   998  			},
   999  			PersistentVolumeSource: v1.PersistentVolumeSource{
  1000  				Local: &v1.LocalVolumeSource{},
  1001  			},
  1002  			AccessModes: []v1.PersistentVolumeAccessMode{
  1003  				v1.ReadWriteOnce,
  1004  			},
  1005  			VolumeMode: &blockMode,
  1006  		},
  1007  		Status: v1.PersistentVolumeStatus{
  1008  			Phase: v1.VolumeAvailable,
  1009  		},
  1010  	}
  1011  }
  1012  
  1013  func createVolumeModeFilesystemTestVolume() *v1.PersistentVolume {
  1014  	filesystemMode := v1.PersistentVolumeFilesystem
  1015  
  1016  	return &v1.PersistentVolume{
  1017  		ObjectMeta: metav1.ObjectMeta{
  1018  			UID:  "local-1",
  1019  			Name: "block",
  1020  		},
  1021  		Spec: v1.PersistentVolumeSpec{
  1022  			Capacity: v1.ResourceList{
  1023  				v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  1024  			},
  1025  			PersistentVolumeSource: v1.PersistentVolumeSource{
  1026  				Local: &v1.LocalVolumeSource{},
  1027  			},
  1028  			AccessModes: []v1.PersistentVolumeAccessMode{
  1029  				v1.ReadWriteOnce,
  1030  			},
  1031  			VolumeMode: &filesystemMode,
  1032  		},
  1033  		Status: v1.PersistentVolumeStatus{
  1034  			Phase: v1.VolumeAvailable,
  1035  		},
  1036  	}
  1037  }
  1038  
  1039  func createVolumeModeNilTestVolume() *v1.PersistentVolume {
  1040  	return &v1.PersistentVolume{
  1041  		ObjectMeta: metav1.ObjectMeta{
  1042  			UID:  "local-1",
  1043  			Name: "nil-mode",
  1044  		},
  1045  		Spec: v1.PersistentVolumeSpec{
  1046  			Capacity: v1.ResourceList{
  1047  				v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  1048  			},
  1049  			PersistentVolumeSource: v1.PersistentVolumeSource{
  1050  				Local: &v1.LocalVolumeSource{},
  1051  			},
  1052  			AccessModes: []v1.PersistentVolumeAccessMode{
  1053  				v1.ReadWriteOnce,
  1054  			},
  1055  		},
  1056  		Status: v1.PersistentVolumeStatus{
  1057  			Phase: v1.VolumeAvailable,
  1058  		},
  1059  	}
  1060  }
  1061  
  1062  func createTestVolOrderedIndex(pv *v1.PersistentVolume) persistentVolumeOrderedIndex {
  1063  	volFile := newPersistentVolumeOrderedIndex()
  1064  	volFile.store.Add(pv)
  1065  	return volFile
  1066  }
  1067  
  1068  func TestVolumeModeCheck(t *testing.T) {
  1069  
  1070  	blockMode := v1.PersistentVolumeBlock
  1071  	filesystemMode := v1.PersistentVolumeFilesystem
  1072  
  1073  	// If feature gate is enabled, VolumeMode will always be defaulted
  1074  	// If feature gate is disabled, VolumeMode is dropped by API and ignored
  1075  	scenarios := map[string]struct {
  1076  		isExpectedMismatch bool
  1077  		vol                *v1.PersistentVolume
  1078  		pvc                *v1.PersistentVolumeClaim
  1079  	}{
  1080  		"pvc block and pv filesystem": {
  1081  			isExpectedMismatch: true,
  1082  			vol:                createVolumeModeFilesystemTestVolume(),
  1083  			pvc:                makeVolumeModePVC("8G", &blockMode, nil),
  1084  		},
  1085  		"pvc filesystem and pv block": {
  1086  			isExpectedMismatch: true,
  1087  			vol:                createVolumeModeBlockTestVolume(),
  1088  			pvc:                makeVolumeModePVC("8G", &filesystemMode, nil),
  1089  		},
  1090  		"pvc block and pv block": {
  1091  			isExpectedMismatch: false,
  1092  			vol:                createVolumeModeBlockTestVolume(),
  1093  			pvc:                makeVolumeModePVC("8G", &blockMode, nil),
  1094  		},
  1095  		"pvc filesystem and pv filesystem": {
  1096  			isExpectedMismatch: false,
  1097  			vol:                createVolumeModeFilesystemTestVolume(),
  1098  			pvc:                makeVolumeModePVC("8G", &filesystemMode, nil),
  1099  		},
  1100  		"pvc filesystem and pv nil": {
  1101  			isExpectedMismatch: false,
  1102  			vol:                createVolumeModeNilTestVolume(),
  1103  			pvc:                makeVolumeModePVC("8G", &filesystemMode, nil),
  1104  		},
  1105  		"pvc nil and pv filesystem": {
  1106  			isExpectedMismatch: false,
  1107  			vol:                createVolumeModeFilesystemTestVolume(),
  1108  			pvc:                makeVolumeModePVC("8G", nil, nil),
  1109  		},
  1110  		"pvc nil and pv nil": {
  1111  			isExpectedMismatch: false,
  1112  			vol:                createVolumeModeNilTestVolume(),
  1113  			pvc:                makeVolumeModePVC("8G", nil, nil),
  1114  		},
  1115  		"pvc nil and pv block": {
  1116  			isExpectedMismatch: true,
  1117  			vol:                createVolumeModeBlockTestVolume(),
  1118  			pvc:                makeVolumeModePVC("8G", nil, nil),
  1119  		},
  1120  		"pvc block and pv nil": {
  1121  			isExpectedMismatch: true,
  1122  			vol:                createVolumeModeNilTestVolume(),
  1123  			pvc:                makeVolumeModePVC("8G", &blockMode, nil),
  1124  		},
  1125  	}
  1126  
  1127  	for name, scenario := range scenarios {
  1128  		t.Run(name, func(t *testing.T) {
  1129  			expectedMismatch := volume.CheckVolumeModeMismatches(&scenario.pvc.Spec, &scenario.vol.Spec)
  1130  			// expected to match but either got an error or no returned pvmatch
  1131  			if expectedMismatch && !scenario.isExpectedMismatch {
  1132  				t.Errorf("Unexpected failure for scenario, expected not to mismatch on modes but did: %s", name)
  1133  			}
  1134  			if !expectedMismatch && scenario.isExpectedMismatch {
  1135  				t.Errorf("Unexpected failure for scenario, did not mismatch on mode when expected to mismatch: %s", name)
  1136  			}
  1137  		})
  1138  	}
  1139  }
  1140  
  1141  func TestFilteringVolumeModes(t *testing.T) {
  1142  	blockMode := v1.PersistentVolumeBlock
  1143  	filesystemMode := v1.PersistentVolumeFilesystem
  1144  
  1145  	// If feature gate is enabled, VolumeMode will always be defaulted
  1146  	// If feature gate is disabled, VolumeMode is dropped by API and ignored
  1147  	scenarios := map[string]struct {
  1148  		isExpectedMatch bool
  1149  		vol             persistentVolumeOrderedIndex
  1150  		pvc             *v1.PersistentVolumeClaim
  1151  	}{
  1152  		"pvc block and pv filesystem": {
  1153  			isExpectedMatch: false,
  1154  			vol:             createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1155  			pvc:             makeVolumeModePVC("8G", &blockMode, nil),
  1156  		},
  1157  		"pvc filesystem and pv block": {
  1158  			isExpectedMatch: false,
  1159  			vol:             createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1160  			pvc:             makeVolumeModePVC("8G", &filesystemMode, nil),
  1161  		},
  1162  		"pvc block and pv no mode with default filesystem": {
  1163  			isExpectedMatch: false,
  1164  			vol:             createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1165  			pvc:             makeVolumeModePVC("8G", &blockMode, nil),
  1166  		},
  1167  		"pvc no mode defaulted to filesystem and pv block": {
  1168  			isExpectedMatch: false,
  1169  			vol:             createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1170  			pvc:             makeVolumeModePVC("8G", &filesystemMode, nil),
  1171  		},
  1172  		"pvc block and pv block": {
  1173  			isExpectedMatch: true,
  1174  			vol:             createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1175  			pvc:             makeVolumeModePVC("8G", &blockMode, nil),
  1176  		},
  1177  		"pvc filesystem and pv filesystem": {
  1178  			isExpectedMatch: true,
  1179  			vol:             createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1180  			pvc:             makeVolumeModePVC("8G", &filesystemMode, nil),
  1181  		},
  1182  		"pvc mode is nil and defaulted and pv mode is nil and defaulted": {
  1183  			isExpectedMatch: true,
  1184  			vol:             createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1185  			pvc:             makeVolumeModePVC("8G", &filesystemMode, nil),
  1186  		},
  1187  	}
  1188  
  1189  	for name, scenario := range scenarios {
  1190  		t.Run(name, func(t *testing.T) {
  1191  			pvmatch, err := scenario.vol.findBestMatchForClaim(scenario.pvc, false)
  1192  			// expected to match but either got an error or no returned pvmatch
  1193  			if pvmatch == nil && scenario.isExpectedMatch {
  1194  				t.Errorf("Unexpected failure for scenario, no matching volume: %s", name)
  1195  			}
  1196  			if err != nil && scenario.isExpectedMatch {
  1197  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, err)
  1198  			}
  1199  			// expected to not match but either got an error or a returned pvmatch
  1200  			if pvmatch != nil && !scenario.isExpectedMatch {
  1201  				t.Errorf("Unexpected failure for scenario, expected no matching volume: %s", name)
  1202  			}
  1203  			if err != nil && !scenario.isExpectedMatch {
  1204  				t.Errorf("Unexpected failure for scenario: %s - %+v", name, err)
  1205  			}
  1206  		})
  1207  	}
  1208  }
  1209  
  1210  func TestStorageObjectInUseProtectionFiltering(t *testing.T) {
  1211  	fs := v1.PersistentVolumeFilesystem
  1212  	pv := &v1.PersistentVolume{
  1213  		ObjectMeta: metav1.ObjectMeta{
  1214  			Name:        "pv1",
  1215  			Annotations: map[string]string{},
  1216  		},
  1217  		Spec: v1.PersistentVolumeSpec{
  1218  			Capacity:               v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")},
  1219  			PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}},
  1220  			AccessModes:            []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1221  			VolumeMode:             &fs,
  1222  		},
  1223  		Status: v1.PersistentVolumeStatus{
  1224  			Phase: v1.VolumeAvailable,
  1225  		},
  1226  	}
  1227  
  1228  	pvToDelete := pv.DeepCopy()
  1229  	now := metav1.Now()
  1230  	pvToDelete.ObjectMeta.DeletionTimestamp = &now
  1231  
  1232  	pvc := &v1.PersistentVolumeClaim{
  1233  		ObjectMeta: metav1.ObjectMeta{
  1234  			Name:      "pvc1",
  1235  			Namespace: "myns",
  1236  		},
  1237  		Spec: v1.PersistentVolumeClaimSpec{
  1238  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1239  			Resources:   v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}},
  1240  			VolumeMode:  &fs,
  1241  		},
  1242  	}
  1243  
  1244  	satisfyingTestCases := map[string]struct {
  1245  		isExpectedMatch bool
  1246  		vol             *v1.PersistentVolume
  1247  		pvc             *v1.PersistentVolumeClaim
  1248  	}{
  1249  		"pv deletionTimeStamp not set": {
  1250  			isExpectedMatch: true,
  1251  			vol:             pv,
  1252  			pvc:             pvc,
  1253  		},
  1254  		"pv deletionTimeStamp set": {
  1255  			isExpectedMatch: false,
  1256  			vol:             pvToDelete,
  1257  			pvc:             pvc,
  1258  		},
  1259  	}
  1260  
  1261  	for name, testCase := range satisfyingTestCases {
  1262  		t.Run(name, func(t *testing.T) {
  1263  			err := checkVolumeSatisfyClaim(testCase.vol, testCase.pvc)
  1264  			// expected to match but got an error
  1265  			if err != nil && testCase.isExpectedMatch {
  1266  				t.Errorf("%s: expected to match but got an error: %v", name, err)
  1267  			}
  1268  			// not expected to match but did
  1269  			if err == nil && !testCase.isExpectedMatch {
  1270  				t.Errorf("%s: not expected to match but did", name)
  1271  			}
  1272  		})
  1273  	}
  1274  
  1275  	filteringTestCases := map[string]struct {
  1276  		isExpectedMatch bool
  1277  		vol             persistentVolumeOrderedIndex
  1278  		pvc             *v1.PersistentVolumeClaim
  1279  	}{
  1280  		"pv deletionTimeStamp not set": {
  1281  			isExpectedMatch: true,
  1282  			vol:             createTestVolOrderedIndex(pv),
  1283  			pvc:             pvc,
  1284  		},
  1285  		"pv deletionTimeStamp set": {
  1286  			isExpectedMatch: false,
  1287  			vol:             createTestVolOrderedIndex(pvToDelete),
  1288  			pvc:             pvc,
  1289  		},
  1290  	}
  1291  	for name, testCase := range filteringTestCases {
  1292  		t.Run(name, func(t *testing.T) {
  1293  			pvmatch, err := testCase.vol.findBestMatchForClaim(testCase.pvc, false)
  1294  			// expected to match but either got an error or no returned pvmatch
  1295  			if pvmatch == nil && testCase.isExpectedMatch {
  1296  				t.Errorf("Unexpected failure for testcase, no matching volume: %s", name)
  1297  			}
  1298  			if err != nil && testCase.isExpectedMatch {
  1299  				t.Errorf("Unexpected failure for testcase: %s - %+v", name, err)
  1300  			}
  1301  			// expected to not match but either got an error or a returned pvmatch
  1302  			if pvmatch != nil && !testCase.isExpectedMatch {
  1303  				t.Errorf("Unexpected failure for testcase, expected no matching volume: %s", name)
  1304  			}
  1305  			if err != nil && !testCase.isExpectedMatch {
  1306  				t.Errorf("Unexpected failure for testcase: %s - %+v", name, err)
  1307  			}
  1308  		})
  1309  	}
  1310  }
  1311  
  1312  func TestFindingPreboundVolumes(t *testing.T) {
  1313  	fs := v1.PersistentVolumeFilesystem
  1314  	claim := &v1.PersistentVolumeClaim{
  1315  		ObjectMeta: metav1.ObjectMeta{
  1316  			Name:      "claim01",
  1317  			Namespace: "myns",
  1318  		},
  1319  		Spec: v1.PersistentVolumeClaimSpec{
  1320  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1321  			Resources:   v1.VolumeResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi")}},
  1322  			VolumeMode:  &fs,
  1323  		},
  1324  	}
  1325  	claimRef, err := ref.GetReference(scheme.Scheme, claim)
  1326  	if err != nil {
  1327  		t.Errorf("error getting claimRef: %v", err)
  1328  	}
  1329  
  1330  	pv1 := testVolume("pv1", "1Gi")
  1331  	pv5 := testVolume("pv5", "5Gi")
  1332  	pv8 := testVolume("pv8", "8Gi")
  1333  	pvBadSize := testVolume("pvBadSize", "1Mi")
  1334  	pvBadMode := testVolume("pvBadMode", "1Gi")
  1335  	pvBadMode.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
  1336  
  1337  	index := newPersistentVolumeOrderedIndex()
  1338  	index.store.Add(pv1)
  1339  	index.store.Add(pv5)
  1340  	index.store.Add(pv8)
  1341  	index.store.Add(pvBadSize)
  1342  	index.store.Add(pvBadMode)
  1343  
  1344  	// expected exact match on size
  1345  	volume, _ := index.findBestMatchForClaim(claim, false)
  1346  	if volume.Name != pv1.Name {
  1347  		t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1348  	}
  1349  
  1350  	// pretend the exact match is pre-bound.  should get the next size up.
  1351  	pv1.Spec.ClaimRef = &v1.ObjectReference{Name: "foo", Namespace: "bar"}
  1352  	volume, _ = index.findBestMatchForClaim(claim, false)
  1353  	if volume.Name != pv5.Name {
  1354  		t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name)
  1355  	}
  1356  
  1357  	// pretend the exact match is available but the largest volume is pre-bound to the claim.
  1358  	pv1.Spec.ClaimRef = nil
  1359  	pv8.Spec.ClaimRef = claimRef
  1360  	volume, _ = index.findBestMatchForClaim(claim, false)
  1361  	if volume.Name != pv8.Name {
  1362  		t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
  1363  	}
  1364  
  1365  	// pretend the volume with too small a size is pre-bound to the claim. should get the exact match.
  1366  	pv8.Spec.ClaimRef = nil
  1367  	pvBadSize.Spec.ClaimRef = claimRef
  1368  	volume, _ = index.findBestMatchForClaim(claim, false)
  1369  	if volume.Name != pv1.Name {
  1370  		t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1371  	}
  1372  
  1373  	// pretend the volume without the right access mode is pre-bound to the claim. should get the exact match.
  1374  	pvBadSize.Spec.ClaimRef = nil
  1375  	pvBadMode.Spec.ClaimRef = claimRef
  1376  	volume, _ = index.findBestMatchForClaim(claim, false)
  1377  	if volume.Name != pv1.Name {
  1378  		t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1379  	}
  1380  }
  1381  
  1382  func TestBestMatchDelayed(t *testing.T) {
  1383  	volList := newPersistentVolumeOrderedIndex()
  1384  	for _, pv := range createTestVolumes() {
  1385  		volList.store.Add(pv)
  1386  	}
  1387  
  1388  	// binding through PV controller should be delayed
  1389  	claim := makePVC("8G", nil)
  1390  	volume, err := volList.findBestMatchForClaim(claim, true)
  1391  	if err != nil {
  1392  		t.Errorf("Unexpected error matching volume by claim: %v", err)
  1393  	}
  1394  	if volume != nil {
  1395  		t.Errorf("Unexpected match with %q", volume.UID)
  1396  	}
  1397  }
  1398  
  1399  func TestCheckAccessModes(t *testing.T) {
  1400  	pv := &v1.PersistentVolume{
  1401  		Spec: v1.PersistentVolumeSpec{
  1402  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteMany},
  1403  		},
  1404  	}
  1405  
  1406  	scenarios := map[string]struct {
  1407  		shouldSucceed bool
  1408  		claim         *v1.PersistentVolumeClaim
  1409  	}{
  1410  		"success-single-mode": {
  1411  			shouldSucceed: true,
  1412  			claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1413  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
  1414  			}),
  1415  		},
  1416  		"success-many-modes": {
  1417  			shouldSucceed: true,
  1418  			claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1419  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce}
  1420  			}),
  1421  		},
  1422  		"fail-single-mode": {
  1423  			shouldSucceed: false,
  1424  			claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1425  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
  1426  			}),
  1427  		},
  1428  		"fail-many-modes": {
  1429  			shouldSucceed: false,
  1430  			claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1431  				pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadOnlyMany}
  1432  			}),
  1433  		},
  1434  	}
  1435  
  1436  	for name, scenario := range scenarios {
  1437  		result := volume.CheckAccessModes(scenario.claim, pv)
  1438  		if result != scenario.shouldSucceed {
  1439  			t.Errorf("Test %q failed: Expected %v, got %v", name, scenario.shouldSucceed, result)
  1440  		}
  1441  	}
  1442  }
  1443  
  1444  // byCapacity is used to order volumes by ascending storage size
  1445  type byCapacity struct {
  1446  	volumes []*v1.PersistentVolume
  1447  }
  1448  
  1449  func (c byCapacity) Less(i, j int) bool {
  1450  	return matchStorageCapacity(c.volumes[i], c.volumes[j])
  1451  }
  1452  
  1453  func (c byCapacity) Swap(i, j int) {
  1454  	c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
  1455  }
  1456  
  1457  func (c byCapacity) Len() int {
  1458  	return len(c.volumes)
  1459  }
  1460  
  1461  // matchStorageCapacity is a matchPredicate used to sort and find volumes
  1462  func matchStorageCapacity(pvA, pvB *v1.PersistentVolume) bool {
  1463  	aQty := pvA.Spec.Capacity[v1.ResourceStorage]
  1464  	bQty := pvB.Spec.Capacity[v1.ResourceStorage]
  1465  	return aQty.Cmp(bQty) <= 0
  1466  }