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

     1  /*
     2  Copyright 2016 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  	"errors"
    21  
    22  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    23  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    24  	"k8s.io/klog/v2/ktesting"
    25  	"k8s.io/kubernetes/pkg/features"
    26  	"testing"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	storage "k8s.io/api/storage/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	corelisters "k8s.io/client-go/listers/core/v1"
    33  	"k8s.io/client-go/tools/cache"
    34  	"k8s.io/component-helpers/storage/volume"
    35  	api "k8s.io/kubernetes/pkg/apis/core"
    36  	pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
    37  )
    38  
    39  var class1Parameters = map[string]string{
    40  	"param1": "value1",
    41  }
    42  var class2Parameters = map[string]string{
    43  	"param2": "value2",
    44  }
    45  var deleteReclaimPolicy = v1.PersistentVolumeReclaimDelete
    46  var modeImmediate = storage.VolumeBindingImmediate
    47  var storageClasses = []*storage.StorageClass{
    48  	{
    49  		TypeMeta: metav1.TypeMeta{
    50  			Kind: "StorageClass",
    51  		},
    52  
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name: "gold",
    55  		},
    56  
    57  		Provisioner:       mockPluginName,
    58  		Parameters:        class1Parameters,
    59  		ReclaimPolicy:     &deleteReclaimPolicy,
    60  		VolumeBindingMode: &modeImmediate,
    61  	},
    62  	{
    63  		TypeMeta: metav1.TypeMeta{
    64  			Kind: "StorageClass",
    65  		},
    66  		ObjectMeta: metav1.ObjectMeta{
    67  			Name: "silver",
    68  		},
    69  		Provisioner:       mockPluginName,
    70  		Parameters:        class2Parameters,
    71  		ReclaimPolicy:     &deleteReclaimPolicy,
    72  		VolumeBindingMode: &modeImmediate,
    73  	},
    74  	{
    75  		TypeMeta: metav1.TypeMeta{
    76  			Kind: "StorageClass",
    77  		},
    78  		ObjectMeta: metav1.ObjectMeta{
    79  			Name: "copper",
    80  		},
    81  		Provisioner:       mockPluginName,
    82  		Parameters:        class1Parameters,
    83  		ReclaimPolicy:     &deleteReclaimPolicy,
    84  		VolumeBindingMode: &modeWait,
    85  	},
    86  	{
    87  		TypeMeta: metav1.TypeMeta{
    88  			Kind: "StorageClass",
    89  		},
    90  		ObjectMeta: metav1.ObjectMeta{
    91  			Name: "external",
    92  		},
    93  		Provisioner:       "vendor.com/my-volume",
    94  		Parameters:        class1Parameters,
    95  		ReclaimPolicy:     &deleteReclaimPolicy,
    96  		VolumeBindingMode: &modeImmediate,
    97  	},
    98  	{
    99  		TypeMeta: metav1.TypeMeta{
   100  			Kind: "StorageClass",
   101  		},
   102  		ObjectMeta: metav1.ObjectMeta{
   103  			Name: "external-wait",
   104  		},
   105  		Provisioner:       "vendor.com/my-volume-wait",
   106  		Parameters:        class1Parameters,
   107  		ReclaimPolicy:     &deleteReclaimPolicy,
   108  		VolumeBindingMode: &modeWait,
   109  	},
   110  	{
   111  		TypeMeta: metav1.TypeMeta{
   112  			Kind: "StorageClass",
   113  		},
   114  		ObjectMeta: metav1.ObjectMeta{
   115  			Name: "unknown-internal",
   116  		},
   117  		Provisioner:       "kubernetes.io/unknown",
   118  		Parameters:        class1Parameters,
   119  		ReclaimPolicy:     &deleteReclaimPolicy,
   120  		VolumeBindingMode: &modeImmediate,
   121  	},
   122  	{
   123  		TypeMeta: metav1.TypeMeta{
   124  			Kind: "StorageClass",
   125  		},
   126  		ObjectMeta: metav1.ObjectMeta{
   127  			Name: "unsupported-mountoptions",
   128  		},
   129  		Provisioner:       mockPluginName,
   130  		Parameters:        class1Parameters,
   131  		ReclaimPolicy:     &deleteReclaimPolicy,
   132  		MountOptions:      []string{"foo"},
   133  		VolumeBindingMode: &modeImmediate,
   134  	},
   135  	{
   136  		TypeMeta: metav1.TypeMeta{
   137  			Kind: "StorageClass",
   138  		},
   139  
   140  		ObjectMeta: metav1.ObjectMeta{
   141  			Name: "csi",
   142  		},
   143  
   144  		Provisioner:       "mydriver.csi.k8s.io",
   145  		Parameters:        class1Parameters,
   146  		ReclaimPolicy:     &deleteReclaimPolicy,
   147  		VolumeBindingMode: &modeImmediate,
   148  	},
   149  }
   150  
   151  // call to storageClass 1, returning an error
   152  var provision1Error = provisionCall{
   153  	ret:                errors.New("Mock provisioner error"),
   154  	expectedParameters: class1Parameters,
   155  }
   156  
   157  // call to storageClass 1, returning a valid PV
   158  var provision1Success = provisionCall{
   159  	ret:                nil,
   160  	expectedParameters: class1Parameters,
   161  }
   162  
   163  // call to storageClass 2, returning a valid PV
   164  var provision2Success = provisionCall{
   165  	ret:                nil,
   166  	expectedParameters: class2Parameters,
   167  }
   168  
   169  // Test single call to syncVolume, expecting provisioning to happen.
   170  // 1. Fill in the controller with initial data
   171  // 2. Call the syncVolume *once*.
   172  // 3. Compare resulting volumes with expected volumes.
   173  func TestProvisionSync(t *testing.T) {
   174  	// Default enable the HonorPVReclaimPolicy feature gate.
   175  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.HonorPVReclaimPolicy, true)()
   176  	_, ctx := ktesting.NewTestContext(t)
   177  	tests := []controllerTest{
   178  		{
   179  			// Provision a volume (with a default class)
   180  			name:            "11-1 - successful provision with storage class 1",
   181  			initialVolumes:  novolumes,
   182  			expectedVolumes: volumesWithFinalizers(newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned), []string{volume.PVDeletionInTreeProtectionFinalizer}),
   183  			// Binding will be completed in the next syncClaim
   184  			initialClaims:  newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, &classGold),
   185  			expectedClaims: newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   186  			expectedEvents: []string{"Normal ProvisioningSucceeded"},
   187  			errors:         noerrors,
   188  			test:           wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   189  		},
   190  		{
   191  			// Provision failure - plugin not found
   192  			name:            "11-2 - plugin not found",
   193  			initialVolumes:  novolumes,
   194  			expectedVolumes: novolumes,
   195  			initialClaims:   newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, &classGold),
   196  			expectedClaims:  newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, &classGold),
   197  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   198  			errors:          noerrors,
   199  			test:            testSyncClaim,
   200  		},
   201  		{
   202  			// Provision failure - newProvisioner returns error
   203  			name:            "11-3 - newProvisioner failure",
   204  			initialVolumes:  novolumes,
   205  			expectedVolumes: novolumes,
   206  			initialClaims:   newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, &classGold),
   207  			expectedClaims:  newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   208  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   209  			errors:          noerrors,
   210  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   211  		},
   212  		{
   213  			// Provision failure - Provision returns error
   214  			name:            "11-4 - provision failure",
   215  			initialVolumes:  novolumes,
   216  			expectedVolumes: novolumes,
   217  			initialClaims:   newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, &classGold),
   218  			expectedClaims:  newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   219  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   220  			errors:          noerrors,
   221  			test:            wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim),
   222  		},
   223  		{
   224  			// No provisioning if there is a matching volume available
   225  			name:            "11-6 - provisioning when there is a volume available",
   226  			initialVolumes:  newVolumeArray("volume11-6", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classGold),
   227  			expectedVolumes: newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classGold, volume.AnnBoundByController),
   228  			initialClaims:   newClaimArray("claim11-6", "uid11-6", "1Gi", "", v1.ClaimPending, &classGold),
   229  			expectedClaims:  newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", v1.ClaimBound, &classGold, volume.AnnBoundByController, volume.AnnBindCompleted),
   230  			expectedEvents:  noevents,
   231  			errors:          noerrors,
   232  			// No provisioning plugin confingure - makes the test fail when
   233  			// the controller erroneously tries to provision something
   234  			test: wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   235  		},
   236  		{
   237  			// Provision success? - claim is bound before provisioner creates
   238  			// a volume.
   239  			name:            "11-7 - claim is bound before provisioning",
   240  			initialVolumes:  novolumes,
   241  			expectedVolumes: newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned),
   242  			initialClaims:   newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, &classGold),
   243  			// The claim would be bound in next syncClaim
   244  			expectedClaims: newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   245  			expectedEvents: noevents,
   246  			errors:         noerrors,
   247  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   248  				// Create a volume before provisionClaimOperation starts.
   249  				// This similates a parallel controller provisioning the volume.
   250  				volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned)
   251  				reactor.AddVolume(volume)
   252  			}),
   253  		},
   254  		{
   255  			// Provision success - cannot save provisioned PV once,
   256  			// second retry succeeds
   257  			name:            "11-8 - cannot save provisioned volume",
   258  			initialVolumes:  novolumes,
   259  			expectedVolumes: volumesWithFinalizers(newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned), []string{volume.PVDeletionInTreeProtectionFinalizer}),
   260  			initialClaims:   newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, &classGold),
   261  			// Binding will be completed in the next syncClaim
   262  			expectedClaims: newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   263  			expectedEvents: []string{"Normal ProvisioningSucceeded"},
   264  			errors: []pvtesting.ReactorError{
   265  				// Inject error to the first
   266  				// kubeclient.PersistentVolumes.Create() call. All other calls
   267  				// will succeed.
   268  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error")},
   269  			},
   270  			test: wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   271  		},
   272  		{
   273  			// Provision success? - cannot save provisioned PV five times,
   274  			// volume is deleted and delete succeeds
   275  			name:            "11-9 - cannot save provisioned volume, delete succeeds",
   276  			initialVolumes:  novolumes,
   277  			expectedVolumes: novolumes,
   278  			initialClaims:   newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, &classGold),
   279  			expectedClaims:  newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   280  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   281  			errors: []pvtesting.ReactorError{
   282  				// Inject error to five kubeclient.PersistentVolumes.Create()
   283  				// calls
   284  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
   285  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
   286  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
   287  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
   288  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
   289  			},
   290  			test: wrapTestWithPluginCalls(
   291  				nil,                                // recycle calls
   292  				[]error{nil},                       // delete calls
   293  				[]provisionCall{provision1Success}, // provision calls
   294  				testSyncClaim,
   295  			),
   296  		},
   297  		{
   298  			// Provision failure - cannot save provisioned PV five times,
   299  			// volume delete failed - no plugin found
   300  			name:            "11-10 - cannot save provisioned volume, no delete plugin found",
   301  			initialVolumes:  novolumes,
   302  			expectedVolumes: novolumes,
   303  			initialClaims:   newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, &classGold),
   304  			expectedClaims:  newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   305  			expectedEvents:  []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
   306  			errors: []pvtesting.ReactorError{
   307  				// Inject error to five kubeclient.PersistentVolumes.Create()
   308  				// calls
   309  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
   310  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
   311  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
   312  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
   313  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
   314  			},
   315  			// No deleteCalls are configured, which results into no deleter plugin available for the volume
   316  			test: wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   317  		},
   318  		{
   319  			// Provision failure - cannot save provisioned PV five times,
   320  			// volume delete failed - deleter returns error five times
   321  			name:            "11-11 - cannot save provisioned volume, deleter fails",
   322  			initialVolumes:  novolumes,
   323  			expectedVolumes: novolumes,
   324  			initialClaims:   newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, &classGold),
   325  			expectedClaims:  newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   326  			expectedEvents:  []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
   327  			errors: []pvtesting.ReactorError{
   328  				// Inject error to five kubeclient.PersistentVolumes.Create()
   329  				// calls
   330  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
   331  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
   332  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
   333  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
   334  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
   335  			},
   336  			test: wrapTestWithPluginCalls(
   337  				nil, // recycle calls
   338  				[]error{ // delete calls
   339  					errors.New("Mock deletion error1"),
   340  					errors.New("Mock deletion error2"),
   341  					errors.New("Mock deletion error3"),
   342  					errors.New("Mock deletion error4"),
   343  					errors.New("Mock deletion error5"),
   344  				},
   345  				[]provisionCall{provision1Success}, // provision calls
   346  				testSyncClaim),
   347  		},
   348  		{
   349  			// Provision failure - cannot save provisioned PV five times,
   350  			// volume delete succeeds 2nd time
   351  			name:            "11-12 - cannot save provisioned volume, delete succeeds 2nd time",
   352  			initialVolumes:  novolumes,
   353  			expectedVolumes: novolumes,
   354  			initialClaims:   newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, &classGold),
   355  			expectedClaims:  newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   356  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   357  			errors: []pvtesting.ReactorError{
   358  				// Inject error to five kubeclient.PersistentVolumes.Create()
   359  				// calls
   360  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
   361  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
   362  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
   363  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
   364  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
   365  			},
   366  			test: wrapTestWithPluginCalls(
   367  				nil, // recycle calls
   368  				[]error{ // delete calls
   369  					errors.New("Mock deletion error1"),
   370  					nil,
   371  				}, //  provison calls
   372  				[]provisionCall{provision1Success},
   373  				testSyncClaim,
   374  			),
   375  		},
   376  		{
   377  			// Provision a volume (with non-default class)
   378  			name:            "11-13 - successful provision with storage class 2",
   379  			initialVolumes:  novolumes,
   380  			expectedVolumes: volumesWithFinalizers(newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classSilver, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned), []string{volume.PVDeletionInTreeProtectionFinalizer}),
   381  			initialClaims:   newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending, &classSilver),
   382  			// Binding will be completed in the next syncClaim
   383  			expectedClaims: newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending, &classSilver, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   384  			expectedEvents: []string{"Normal ProvisioningSucceeded"},
   385  			errors:         noerrors,
   386  			test:           wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim),
   387  		},
   388  		{
   389  			// Provision error - non existing class
   390  			name:            "11-14 - fail due to non-existing class",
   391  			initialVolumes:  novolumes,
   392  			expectedVolumes: novolumes,
   393  			initialClaims:   newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending, &classNonExisting),
   394  			expectedClaims:  newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending, &classNonExisting),
   395  			expectedEvents:  noevents,
   396  			errors:          noerrors,
   397  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   398  		},
   399  		{
   400  			// No provisioning with class=""
   401  			name:            "11-15 - no provisioning with class=''",
   402  			initialVolumes:  novolumes,
   403  			expectedVolumes: novolumes,
   404  			initialClaims:   newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, &classEmpty),
   405  			expectedClaims:  newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, &classEmpty),
   406  			expectedEvents:  noevents,
   407  			errors:          noerrors,
   408  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   409  		},
   410  		{
   411  			// No provisioning with class=nil
   412  			name:            "11-16 - no provisioning with class=nil",
   413  			initialVolumes:  novolumes,
   414  			expectedVolumes: novolumes,
   415  			initialClaims:   newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, nil),
   416  			expectedClaims:  newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, nil),
   417  			expectedEvents:  noevents,
   418  			errors:          noerrors,
   419  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   420  		},
   421  		{
   422  			// No provisioning + normal event with external provisioner
   423  			name:            "11-17 - external provisioner",
   424  			initialVolumes:  novolumes,
   425  			expectedVolumes: novolumes,
   426  			initialClaims:   newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending, &classExternal),
   427  			expectedClaims: claimWithAnnotation(volume.AnnBetaStorageProvisioner, "vendor.com/my-volume",
   428  				claimWithAnnotation(volume.AnnStorageProvisioner, "vendor.com/my-volume",
   429  					newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending, &classExternal))),
   430  			expectedEvents: []string{"Normal ExternalProvisioning"},
   431  			errors:         noerrors,
   432  			test:           wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   433  		},
   434  		{
   435  			// No provisioning + warning event with unknown internal provisioner
   436  			name:            "11-18 - unknown internal provisioner",
   437  			initialVolumes:  novolumes,
   438  			expectedVolumes: novolumes,
   439  			initialClaims:   newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending, &classUnknownInternal),
   440  			expectedClaims:  newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending, &classUnknownInternal),
   441  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   442  			errors:          noerrors,
   443  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   444  		},
   445  		{
   446  			// Provision success - first save of a PV to API server fails (API
   447  			// server has written the object to etcd, but crashed before sending
   448  			// 200 OK response to the controller). Controller retries and the
   449  			// second save of the PV returns "AlreadyExists" because the PV
   450  			// object already is in the API server.
   451  			//
   452  			"11-19 - provisioned volume saved but API server crashed",
   453  			novolumes,
   454  			// We don't actually simulate API server saving the object and
   455  			// crashing afterwards, Create() just returns error without saving
   456  			// the volume in this test. So the set of expected volumes at the
   457  			// end of the test is empty.
   458  			novolumes,
   459  			newClaimArray("claim11-19", "uid11-19", "1Gi", "", v1.ClaimPending, &classGold),
   460  			newClaimArray("claim11-19", "uid11-19", "1Gi", "", v1.ClaimPending, &classGold, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   461  			noevents,
   462  			[]pvtesting.ReactorError{
   463  				// Inject errors to simulate crashed API server during
   464  				// kubeclient.PersistentVolumes.Create()
   465  				{Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
   466  				{Verb: "create", Resource: "persistentvolumes", Error: apierrors.NewAlreadyExists(api.Resource("persistentvolumes"), "")},
   467  			},
   468  			wrapTestWithPluginCalls(
   469  				nil, // recycle calls
   470  				nil, // delete calls - if Delete was called the test would fail
   471  				[]provisionCall{provision1Success},
   472  				testSyncClaim,
   473  			),
   474  		},
   475  		{
   476  			// No provisioning + warning event with unsupported storageClass.mountOptions
   477  			name:            "11-20 - unsupported storageClass.mountOptions",
   478  			initialVolumes:  novolumes,
   479  			expectedVolumes: novolumes,
   480  			initialClaims:   newClaimArray("claim11-20", "uid11-20", "1Gi", "", v1.ClaimPending, &classUnsupportedMountOptions),
   481  			expectedClaims:  newClaimArray("claim11-20", "uid11-20", "1Gi", "", v1.ClaimPending, &classUnsupportedMountOptions, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   482  			// Expect event to be prefixed with "Mount options" because saving PV will fail anyway
   483  			expectedEvents: []string{"Warning ProvisioningFailed Mount options"},
   484  			errors:         noerrors,
   485  			test:           wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   486  		},
   487  		{
   488  			// No provisioning due to CSI migration + normal event with external provisioner
   489  			name:            "11-21 - external provisioner for CSI migration",
   490  			initialVolumes:  novolumes,
   491  			expectedVolumes: novolumes,
   492  			initialClaims:   newClaimArray("claim11-21", "uid11-21", "1Gi", "", v1.ClaimPending, &classGold),
   493  			expectedClaims: []*v1.PersistentVolumeClaim{
   494  				annotateClaim(
   495  					newClaim("claim11-21", "uid11-21", "1Gi", "", v1.ClaimPending, &classGold),
   496  					map[string]string{
   497  						volume.AnnStorageProvisioner:     "vendor.com/MockCSIDriver",
   498  						volume.AnnBetaStorageProvisioner: "vendor.com/MockCSIDriver",
   499  						volume.AnnMigratedTo:             "vendor.com/MockCSIDriver",
   500  					}),
   501  			},
   502  			expectedEvents: []string{"Normal ExternalProvisioning"},
   503  			errors:         noerrors,
   504  			test:           wrapTestWithCSIMigrationProvisionCalls(testSyncClaim),
   505  		},
   506  		{
   507  			// volume provisioned and available
   508  			// in this case, NO normal event with external provisioner should be issued
   509  			name:            "11-22 - external provisioner with volume available",
   510  			initialVolumes:  newVolumeArray("volume11-22", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classExternal),
   511  			expectedVolumes: newVolumeArray("volume11-22", "1Gi", "uid11-22", "claim11-22", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, volume.AnnBoundByController),
   512  			initialClaims:   newClaimArray("claim11-22", "uid11-22", "1Gi", "", v1.ClaimPending, &classExternal),
   513  			expectedClaims:  newClaimArray("claim11-22", "uid11-22", "1Gi", "volume11-22", v1.ClaimBound, &classExternal, volume.AnnBoundByController, volume.AnnBindCompleted),
   514  			expectedEvents:  noevents,
   515  			errors:          noerrors,
   516  			test:            wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   517  		},
   518  		{
   519  			// volume provision for PVC scheduled
   520  			"11-23 - skip finding PV and provision for PVC annotated with AnnSelectedNode",
   521  			newVolumeArray("volume11-23", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classCopper),
   522  			[]*v1.PersistentVolume{
   523  				newVolumeWithFinalizers("volume11-23", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classCopper, nil /*No Finalizer is added here since the test doesn't trigger syncVolume, instead just syncClaim*/),
   524  				newVolumeWithFinalizers("pvc-uid11-23", "1Gi", "uid11-23", "claim11-23", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classCopper, []string{volume.PVDeletionInTreeProtectionFinalizer}, volume.AnnDynamicallyProvisioned, volume.AnnBoundByController),
   525  			},
   526  			claimWithAnnotation(volume.AnnSelectedNode, "node1",
   527  				newClaimArray("claim11-23", "uid11-23", "1Gi", "", v1.ClaimPending, &classCopper)),
   528  			claimWithAnnotation(volume.AnnSelectedNode, "node1",
   529  				newClaimArray("claim11-23", "uid11-23", "1Gi", "", v1.ClaimPending, &classCopper, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner)),
   530  			[]string{"Normal ProvisioningSucceeded"},
   531  			noerrors,
   532  			wrapTestWithInjectedOperation(ctx, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   533  				func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   534  					nodesIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
   535  					node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}}
   536  					nodesIndexer.Add(node)
   537  					ctrl.NodeLister = corelisters.NewNodeLister(nodesIndexer)
   538  				}),
   539  		},
   540  		{
   541  			// volume provision for PVC that scheduled
   542  			name:            "11-24 - skip finding PV and wait external provisioner for PVC annotated with AnnSelectedNode",
   543  			initialVolumes:  newVolumeArray("volume11-24", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classExternalWait),
   544  			expectedVolumes: newVolumeArray("volume11-24", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimDelete, classExternalWait),
   545  			initialClaims: claimWithAnnotation(volume.AnnSelectedNode, "node1",
   546  				newClaimArray("claim11-24", "uid11-24", "1Gi", "", v1.ClaimPending, &classExternalWait)),
   547  			expectedClaims: claimWithAnnotation(volume.AnnBetaStorageProvisioner, "vendor.com/my-volume-wait",
   548  				claimWithAnnotation(volume.AnnStorageProvisioner, "vendor.com/my-volume-wait",
   549  					claimWithAnnotation(volume.AnnSelectedNode, "node1",
   550  						newClaimArray("claim11-24", "uid11-24", "1Gi", "", v1.ClaimPending, &classExternalWait)))),
   551  			expectedEvents: []string{"Normal ExternalProvisioning"},
   552  			errors:         noerrors,
   553  			test:           testSyncClaim,
   554  		},
   555  		{
   556  			// Provision a volume with a data source will fail
   557  			// for in-tree plugins
   558  			name:            "11-25 - failed in-tree provision with data source",
   559  			initialVolumes:  novolumes,
   560  			expectedVolumes: novolumes,
   561  			initialClaims:   claimWithDataSource("test-snap", "VolumeSnapshot", "snapshot.storage.k8s.io", newClaimArray("claim11-25", "uid11-25", "1Gi", "", v1.ClaimPending, &classGold)),
   562  			expectedClaims:  claimWithDataSource("test-snap", "VolumeSnapshot", "snapshot.storage.k8s.io", newClaimArray("claim11-25", "uid11-25", "1Gi", "", v1.ClaimPending, &classGold)),
   563  			expectedEvents:  []string{"Warning ProvisioningFailed"},
   564  			errors:          noerrors,
   565  			test:            testSyncClaim,
   566  		},
   567  		{
   568  			// Provision a volume with a data source will proceed
   569  			// for CSI plugins
   570  			"11-26 - csi with data source",
   571  			novolumes,
   572  			novolumes,
   573  			claimWithAnnotation(volume.AnnStorageProvisioner, "mydriver.csi.k8s.io",
   574  				claimWithDataSource("test-snap", "VolumeSnapshot", "snapshot.storage.k8s.io", newClaimArray("claim11-26", "uid11-26", "1Gi", "", v1.ClaimPending, &classCSI))),
   575  			claimWithAnnotation(volume.AnnStorageProvisioner, "mydriver.csi.k8s.io",
   576  				claimWithDataSource("test-snap", "VolumeSnapshot", "snapshot.storage.k8s.io", newClaimArray("claim11-26", "uid11-26", "1Gi", "", v1.ClaimPending, &classCSI))),
   577  			[]string{"Normal ExternalProvisioning"},
   578  			noerrors,
   579  			wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
   580  		},
   581  	}
   582  	runSyncTests(t, ctx, tests, storageClasses, []*v1.Pod{})
   583  }
   584  
   585  // Test multiple calls to syncClaim/syncVolume and periodic sync of all
   586  // volume/claims. The test follows this pattern:
   587  //  0. Load the controller with initial data.
   588  //  1. Call controllerTest.testCall() once as in TestSync()
   589  //  2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
   590  //     call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
   591  //     events). Go to 2. if these calls change anything.
   592  //  3. When all changes are processed and no new changes were made, call
   593  //     syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
   594  //  4. If some changes were done by step 3., go to 2. (simulation of
   595  //     "volume/claim updated" events, eventually performing step 3. again)
   596  //  5. When 3. does not do any changes, finish the tests and compare final set
   597  //     of volumes/claims with expected claims/volumes and report differences.
   598  //
   599  // Some limit of calls in enforced to prevent endless loops.
   600  func TestProvisionMultiSync(t *testing.T) {
   601  	_, ctx := ktesting.NewTestContext(t)
   602  	tests := []controllerTest{
   603  		{
   604  			// Provision a volume with binding
   605  			name:            "12-1 - successful provision",
   606  			initialVolumes:  novolumes,
   607  			expectedVolumes: newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, volume.AnnBoundByController, volume.AnnDynamicallyProvisioned),
   608  			initialClaims:   newClaimArray("claim12-1", "uid12-1", "1Gi", "", v1.ClaimPending, &classGold),
   609  			expectedClaims:  newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", v1.ClaimBound, &classGold, volume.AnnBoundByController, volume.AnnBindCompleted, volume.AnnStorageProvisioner, volume.AnnBetaStorageProvisioner),
   610  			expectedEvents:  noevents,
   611  			errors:          noerrors,
   612  			test:            wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   613  		},
   614  		{
   615  			// provision a volume (external provisioner) and binding + normal event with external provisioner
   616  			name:            "12-2 - external provisioner with volume provisioned success",
   617  			initialVolumes:  novolumes,
   618  			expectedVolumes: newVolumeArray("pvc-uid12-2", "1Gi", "uid12-2", "claim12-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, volume.AnnBoundByController),
   619  			initialClaims:   newClaimArray("claim12-2", "uid12-2", "1Gi", "", v1.ClaimPending, &classExternal),
   620  			expectedClaims: claimWithAnnotation(volume.AnnBetaStorageProvisioner, "vendor.com/my-volume",
   621  				claimWithAnnotation(volume.AnnStorageProvisioner, "vendor.com/my-volume",
   622  					newClaimArray("claim12-2", "uid12-2", "1Gi", "pvc-uid12-2", v1.ClaimBound, &classExternal, volume.AnnBoundByController, volume.AnnBindCompleted))),
   623  			expectedEvents: []string{"Normal ExternalProvisioning"},
   624  			errors:         noerrors,
   625  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   626  				// Create a volume before syncClaim tries to bind a PV to PVC
   627  				// This simulates external provisioner creating a volume while the controller
   628  				// is waiting for a volume to bind to the existed claim
   629  				// the external provisioner workflow implemented in "provisionClaimOperationCSI"
   630  				// should issue an ExternalProvisioning event to signal that some external provisioner
   631  				// is working on provisioning the PV, also add the operation start timestamp into local cache
   632  				// operationTimestamps. Rely on the existences of the start time stamp to create a PV for binding
   633  				if ctrl.operationTimestamps.Has("default/claim12-2") {
   634  					volume := newVolume("pvc-uid12-2", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classExternal)
   635  					ctrl.volumes.store.Add(volume) // add the volume to controller
   636  					reactor.AddVolume(volume)
   637  				}
   638  			}),
   639  		},
   640  		{
   641  			// provision a volume (external provisioner) but binding will not happen + normal event with external provisioner
   642  			name:            "12-3 - external provisioner with volume to be provisioned",
   643  			initialVolumes:  novolumes,
   644  			expectedVolumes: novolumes,
   645  			initialClaims:   newClaimArray("claim12-3", "uid12-3", "1Gi", "", v1.ClaimPending, &classExternal),
   646  			expectedClaims: claimWithAnnotation(volume.AnnBetaStorageProvisioner, "vendor.com/my-volume",
   647  				claimWithAnnotation(volume.AnnStorageProvisioner, "vendor.com/my-volume",
   648  					newClaimArray("claim12-3", "uid12-3", "1Gi", "", v1.ClaimPending, &classExternal))),
   649  			expectedEvents: []string{"Normal ExternalProvisioning"},
   650  			errors:         noerrors,
   651  			test:           wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
   652  		},
   653  		{
   654  			// provision a volume (external provisioner) and binding + normal event with external provisioner
   655  			name:            "12-4 - external provisioner with volume provisioned/bound success",
   656  			initialVolumes:  novolumes,
   657  			expectedVolumes: newVolumeArray("pvc-uid12-4", "1Gi", "uid12-4", "claim12-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, volume.AnnBoundByController),
   658  			initialClaims:   newClaimArray("claim12-4", "uid12-4", "1Gi", "", v1.ClaimPending, &classExternal),
   659  			expectedClaims: claimWithAnnotation(volume.AnnBetaStorageProvisioner, "vendor.com/my-volume",
   660  				claimWithAnnotation(volume.AnnStorageProvisioner, "vendor.com/my-volume",
   661  					newClaimArray("claim12-4", "uid12-4", "1Gi", "pvc-uid12-4", v1.ClaimBound, &classExternal, volume.AnnBoundByController, volume.AnnBindCompleted))),
   662  			expectedEvents: []string{"Normal ExternalProvisioning"},
   663  			errors:         noerrors,
   664  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   665  				// Create a volume before syncClaim tries to bind a PV to PVC
   666  				// This simulates external provisioner creating a volume while the controller
   667  				// is waiting for a volume to bind to the existed claim
   668  				// the external provisioner workflow implemented in "provisionClaimOperationCSI"
   669  				// should issue an ExternalProvisioning event to signal that some external provisioner
   670  				// is working on provisioning the PV, also add the operation start timestamp into local cache
   671  				// operationTimestamps. Rely on the existences of the start time stamp to create a PV for binding
   672  				if ctrl.operationTimestamps.Has("default/claim12-4") {
   673  					volume := newVolume("pvc-uid12-4", "1Gi", "uid12-4", "claim12-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, volume.AnnBoundByController)
   674  					ctrl.volumes.store.Add(volume) // add the volume to controller
   675  					reactor.AddVolume(volume)
   676  				}
   677  			}),
   678  		},
   679  	}
   680  
   681  	runMultisyncTests(t, ctx, tests, storageClasses, storageClasses[0].Name)
   682  }
   683  
   684  // When provisioning is disabled, provisioning a claim should instantly return nil
   685  func TestDisablingDynamicProvisioner(t *testing.T) {
   686  	_, ctx := ktesting.NewTestContext(t)
   687  	ctrl, err := newTestController(ctx, nil, nil, false)
   688  	if err != nil {
   689  		t.Fatalf("Construct PersistentVolume controller failed: %v", err)
   690  	}
   691  	retVal := ctrl.provisionClaim(ctx, nil)
   692  	if retVal != nil {
   693  		t.Errorf("Expected nil return but got %v", retVal)
   694  	}
   695  }