k8s.io/kubernetes@v1.29.3/pkg/controller/volume/persistentvolume/recycle_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  	"testing"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	storage "k8s.io/api/storage/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/component-helpers/storage/volume"
    27  	"k8s.io/klog/v2/ktesting"
    28  	pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
    29  )
    30  
    31  // Test single call to syncVolume, expecting recycling to happen.
    32  // 1. Fill in the controller with initial data
    33  // 2. Call the syncVolume *once*.
    34  // 3. Compare resulting volumes with expected volumes.
    35  func TestRecycleSync(t *testing.T) {
    36  	_, ctx := ktesting.NewTestContext(t)
    37  	runningPod := &v1.Pod{
    38  		ObjectMeta: metav1.ObjectMeta{
    39  			Name:      "runningPod",
    40  			Namespace: testNamespace,
    41  		},
    42  		Spec: v1.PodSpec{
    43  			Volumes: []v1.Volume{
    44  				{
    45  					Name: "vol1",
    46  					VolumeSource: v1.VolumeSource{
    47  						PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
    48  							ClaimName: "runningClaim",
    49  						},
    50  					},
    51  				},
    52  			},
    53  		},
    54  		Status: v1.PodStatus{
    55  			Phase: v1.PodRunning,
    56  		},
    57  	}
    58  
    59  	pendingPod := runningPod.DeepCopy()
    60  	pendingPod.Name = "pendingPod"
    61  	pendingPod.Status.Phase = v1.PodPending
    62  	pendingPod.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = "pendingClaim"
    63  
    64  	completedPod := runningPod.DeepCopy()
    65  	completedPod.Name = "completedPod"
    66  	completedPod.Status.Phase = v1.PodSucceeded
    67  	completedPod.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = "completedClaim"
    68  
    69  	pods := []*v1.Pod{
    70  		runningPod,
    71  		pendingPod,
    72  		completedPod,
    73  	}
    74  
    75  	tests := []controllerTest{
    76  		{
    77  			// recycle volume bound by controller
    78  			name:            "6-1 - successful recycle",
    79  			initialVolumes:  newVolumeArray("volume6-1", "1Gi", "uid6-1", "claim6-1", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
    80  			expectedVolumes: newVolumeArray("volume6-1", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
    81  			initialClaims:   noclaims,
    82  			expectedClaims:  noclaims,
    83  			expectedEvents:  noevents,
    84  			errors:          noerrors,
    85  			// Inject recycler into the controller and call syncVolume. The
    86  			// recycler simulates one recycle() call that succeeds.
    87  			test: wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
    88  		},
    89  		{
    90  			// recycle volume bound by user
    91  			name:            "6-2 - successful recycle with prebound volume",
    92  			initialVolumes:  newVolumeArray("volume6-2", "1Gi", "uid6-2", "claim6-2", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
    93  			expectedVolumes: newVolumeArray("volume6-2", "1Gi", "", "claim6-2", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
    94  			initialClaims:   noclaims,
    95  			expectedClaims:  noclaims,
    96  			expectedEvents:  noevents,
    97  			errors:          noerrors,
    98  			// Inject recycler into the controller and call syncVolume. The
    99  			// recycler simulates one recycle() call that succeeds.
   100  			test: wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
   101  		},
   102  		{
   103  			// recycle failure - plugin not found
   104  			name:            "6-3 - plugin not found",
   105  			initialVolumes:  newVolumeArray("volume6-3", "1Gi", "uid6-3", "claim6-3", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   106  			expectedVolumes: withMessage("No recycler plugin found for the volume!", newVolumeArray("volume6-3", "1Gi", "uid6-3", "claim6-3", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle, classEmpty)),
   107  			initialClaims:   noclaims,
   108  			expectedClaims:  noclaims,
   109  			expectedEvents:  []string{"Warning VolumeFailedRecycle"},
   110  			errors:          noerrors,
   111  			test:            testSyncVolume,
   112  		},
   113  		{
   114  			// recycle failure - Recycle returns error
   115  			name:            "6-4 - newRecycler returns error",
   116  			initialVolumes:  newVolumeArray("volume6-4", "1Gi", "uid6-4", "claim6-4", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   117  			expectedVolumes: withMessage("Recycle failed: Mock plugin error: no recycleCalls configured", newVolumeArray("volume6-4", "1Gi", "uid6-4", "claim6-4", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle, classEmpty)),
   118  			initialClaims:   noclaims,
   119  			expectedClaims:  noclaims,
   120  			expectedEvents:  []string{"Warning VolumeFailedRecycle"},
   121  			errors:          noerrors,
   122  			test:            wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume),
   123  		},
   124  		{
   125  			// recycle failure - recycle returns error
   126  			name:            "6-5 - recycle returns error",
   127  			initialVolumes:  newVolumeArray("volume6-5", "1Gi", "uid6-5", "claim6-5", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   128  			expectedVolumes: withMessage("Recycle failed: Mock recycle error", newVolumeArray("volume6-5", "1Gi", "uid6-5", "claim6-5", v1.VolumeFailed, v1.PersistentVolumeReclaimRecycle, classEmpty)),
   129  			initialClaims:   noclaims,
   130  			expectedClaims:  noclaims,
   131  			expectedEvents:  []string{"Warning VolumeFailedRecycle"},
   132  			errors:          noerrors,
   133  			test:            wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume),
   134  		},
   135  		{
   136  			// recycle success(?) - volume is deleted before doRecycle() starts
   137  			name:            "6-6 - volume is deleted before recycling",
   138  			initialVolumes:  newVolumeArray("volume6-6", "1Gi", "uid6-6", "claim6-6", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   139  			expectedVolumes: novolumes,
   140  			initialClaims:   noclaims,
   141  			expectedClaims:  noclaims,
   142  			expectedEvents:  noevents,
   143  			errors:          noerrors,
   144  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   145  				// Delete the volume before recycle operation starts
   146  				reactor.DeleteVolume("volume6-6")
   147  			}),
   148  		},
   149  		{
   150  			// recycle success(?) - volume is recycled by previous recycler just
   151  			// at the time new doRecycle() starts. This simulates "volume no
   152  			// longer needs recycling, skipping".
   153  			name:            "6-7 - volume is deleted before recycling",
   154  			initialVolumes:  newVolumeArray("volume6-7", "1Gi", "uid6-7", "claim6-7", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   155  			expectedVolumes: newVolumeArray("volume6-7", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   156  			initialClaims:   noclaims,
   157  			expectedClaims:  noclaims,
   158  			expectedEvents:  noevents,
   159  			errors:          noerrors,
   160  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   161  				// Mark the volume as Available before the recycler starts
   162  				reactor.MarkVolumeAvailable("volume6-7")
   163  			}),
   164  		},
   165  		{
   166  			// recycle success(?) - volume bound by user is recycled by previous
   167  			// recycler just at the time new doRecycle() starts. This simulates
   168  			// "volume no longer needs recycling, skipping" with volume bound by
   169  			// user.
   170  			name:            "6-8 - prebound volume is deleted before recycling",
   171  			initialVolumes:  newVolumeArray("volume6-8", "1Gi", "uid6-8", "claim6-8", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   172  			expectedVolumes: newVolumeArray("volume6-8", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   173  			initialClaims:   noclaims,
   174  			expectedClaims:  noclaims,
   175  			expectedEvents:  noevents,
   176  			errors:          noerrors,
   177  			test: wrapTestWithInjectedOperation(ctx, wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
   178  				// Mark the volume as Available before the recycler starts
   179  				reactor.MarkVolumeAvailable("volume6-8")
   180  			}),
   181  		},
   182  		{
   183  			// recycle success - volume bound by user is recycled, while a new
   184  			// claim is created with another UID.
   185  			name:            "6-9 - prebound volume is recycled while the claim exists",
   186  			initialVolumes:  newVolumeArray("volume6-9", "1Gi", "uid6-9", "claim6-9", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   187  			expectedVolumes: newVolumeArray("volume6-9", "1Gi", "", "claim6-9", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   188  			initialClaims:   newClaimArray("claim6-9", "uid6-9-x", "10Gi", "", v1.ClaimPending, nil),
   189  			expectedClaims:  newClaimArray("claim6-9", "uid6-9-x", "10Gi", "", v1.ClaimPending, nil),
   190  			expectedEvents:  noevents,
   191  			errors:          noerrors,
   192  			// Inject recycler into the controller and call syncVolume. The
   193  			// recycler simulates one recycle() call that succeeds.
   194  			test: wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
   195  		},
   196  		{
   197  			// volume has unknown reclaim policy - failure expected
   198  			name:            "6-10 - unknown reclaim policy",
   199  			initialVolumes:  newVolumeArray("volume6-10", "1Gi", "uid6-10", "claim6-10", v1.VolumeBound, "Unknown", classEmpty),
   200  			expectedVolumes: withMessage("Volume has unrecognized PersistentVolumeReclaimPolicy", newVolumeArray("volume6-10", "1Gi", "uid6-10", "claim6-10", v1.VolumeFailed, "Unknown", classEmpty)),
   201  			initialClaims:   noclaims,
   202  			expectedClaims:  noclaims,
   203  			expectedEvents:  []string{"Warning VolumeUnknownReclaimPolicy"},
   204  			errors:          noerrors,
   205  			test:            testSyncVolume,
   206  		},
   207  		{
   208  			// volume is used by a running pod - failure expected
   209  			name:            "6-11 - used by running pod",
   210  			initialVolumes:  newVolumeArray("volume6-11", "1Gi", "uid6-11", "runningClaim", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   211  			expectedVolumes: newVolumeArray("volume6-11", "1Gi", "uid6-11", "runningClaim", v1.VolumeReleased, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   212  			initialClaims:   noclaims,
   213  			expectedClaims:  noclaims,
   214  			expectedEvents:  []string{"Normal VolumeFailedRecycle"},
   215  			errors:          noerrors,
   216  			test:            testSyncVolume,
   217  		},
   218  		{
   219  			// volume is used by a pending pod - failure expected
   220  			name:            "6-12 - used by pending pod",
   221  			initialVolumes:  newVolumeArray("volume6-12", "1Gi", "uid6-12", "pendingClaim", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   222  			expectedVolumes: newVolumeArray("volume6-12", "1Gi", "uid6-12", "pendingClaim", v1.VolumeReleased, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   223  			initialClaims:   noclaims,
   224  			expectedClaims:  noclaims,
   225  			expectedEvents:  []string{"Normal VolumeFailedRecycle"},
   226  			errors:          noerrors,
   227  			test:            testSyncVolume,
   228  		},
   229  		{
   230  			// volume is used by a completed pod - recycle succeeds
   231  			name:            "6-13 - used by completed pod",
   232  			initialVolumes:  newVolumeArray("volume6-13", "1Gi", "uid6-13", "completedClaim", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   233  			expectedVolumes: newVolumeArray("volume6-13", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   234  			initialClaims:   noclaims,
   235  			expectedClaims:  noclaims,
   236  			expectedEvents:  noevents,
   237  			errors:          noerrors,
   238  			// Inject recycler into the controller and call syncVolume. The
   239  			// recycler simulates one recycle() call that succeeds.
   240  			test: wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
   241  		},
   242  		{
   243  			// volume is used by a completed pod, pod using claim with the same name bound to different pv is running, should recycle
   244  			name:            "6-14 - seemingly used by running pod",
   245  			initialVolumes:  newVolumeArray("volume6-14", "1Gi", "uid6-14", "completedClaim", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty, volume.AnnBoundByController),
   246  			expectedVolumes: newVolumeArray("volume6-14", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   247  			initialClaims:   newClaimArray("completedClaim", "uid6-14-x", "10Gi", "", v1.ClaimBound, nil),
   248  			expectedClaims:  newClaimArray("completedClaim", "uid6-14-x", "10Gi", "", v1.ClaimBound, nil),
   249  			expectedEvents:  noevents,
   250  			errors:          noerrors,
   251  			test:            wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume),
   252  		},
   253  	}
   254  	runSyncTests(t, ctx, tests, []*storage.StorageClass{}, pods)
   255  }
   256  
   257  // Test multiple calls to syncClaim/syncVolume and periodic sync of all
   258  // volume/claims. The test follows this pattern:
   259  //  0. Load the controller with initial data.
   260  //  1. Call controllerTest.testCall() once as in TestSync()
   261  //  2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
   262  //     call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
   263  //     events). Go to 2. if these calls change anything.
   264  //  3. When all changes are processed and no new changes were made, call
   265  //     syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
   266  //  4. If some changes were done by step 3., go to 2. (simulation of
   267  //     "volume/claim updated" events, eventually performing step 3. again)
   268  //  5. When 3. does not do any changes, finish the tests and compare final set
   269  //     of volumes/claims with expected claims/volumes and report differences.
   270  //
   271  // Some limit of calls in enforced to prevent endless loops.
   272  func TestRecycleMultiSync(t *testing.T) {
   273  	_, ctx := ktesting.NewTestContext(t)
   274  	tests := []controllerTest{
   275  		{
   276  			// recycle failure - recycle returns error. The controller should
   277  			// try again.
   278  			"7-1 - recycle returns error",
   279  			newVolumeArray("volume7-1", "1Gi", "uid7-1", "claim7-1", v1.VolumeBound, v1.PersistentVolumeReclaimRecycle, classEmpty),
   280  			newVolumeArray("volume7-1", "1Gi", "", "claim7-1", v1.VolumeAvailable, v1.PersistentVolumeReclaimRecycle, classEmpty),
   281  			noclaims,
   282  			noclaims,
   283  			[]string{"Warning VolumeFailedRecycle"}, noerrors,
   284  			wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume),
   285  		},
   286  	}
   287  
   288  	runMultisyncTests(t, ctx, tests, []*storage.StorageClass{}, "")
   289  }