k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/volume/ephemeral/controller_test.go (about) 1 /* 2 Copyright 2020 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 ephemeral 18 19 import ( 20 "context" 21 "errors" 22 "sort" 23 "testing" 24 25 "github.com/stretchr/testify/assert" 26 v1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/client-go/informers" 32 "k8s.io/client-go/kubernetes/fake" 33 k8stesting "k8s.io/client-go/testing" 34 "k8s.io/client-go/tools/cache" 35 "k8s.io/component-base/metrics/testutil" 36 "k8s.io/klog/v2" 37 "k8s.io/kubernetes/pkg/controller" 38 ephemeralvolumemetrics "k8s.io/kubernetes/pkg/controller/volume/ephemeral/metrics" 39 ) 40 41 var ( 42 testPodName = "test-pod" 43 testNamespace = "my-namespace" 44 testPodUID = types.UID("uidpod1") 45 otherNamespace = "not-my-namespace" 46 ephemeralVolumeName = "ephemeral-volume" 47 48 testPod = makePod(testPodName, testNamespace, testPodUID) 49 testPodWithEphemeral = makePod(testPodName, testNamespace, testPodUID, *makeEphemeralVolume(ephemeralVolumeName)) 50 testPodEphemeralClaim = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, makeOwnerReference(testPodWithEphemeral, true)) 51 conflictingClaim = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, nil) 52 otherNamespaceClaim = makePVC(testPodName+"-"+ephemeralVolumeName, otherNamespace, nil) 53 ) 54 55 func init() { 56 klog.InitFlags(nil) 57 } 58 59 func TestSyncHandler(t *testing.T) { 60 tests := []struct { 61 name string 62 podKey string 63 pvcs []*v1.PersistentVolumeClaim 64 pods []*v1.Pod 65 expectedPVCs []v1.PersistentVolumeClaim 66 expectedError bool 67 expectedMetrics expectedMetrics 68 }{ 69 { 70 name: "create", 71 pods: []*v1.Pod{testPodWithEphemeral}, 72 podKey: podKey(testPodWithEphemeral), 73 expectedPVCs: []v1.PersistentVolumeClaim{*testPodEphemeralClaim}, 74 expectedMetrics: expectedMetrics{1, 0}, 75 }, 76 { 77 name: "no-such-pod", 78 podKey: podKey(testPodWithEphemeral), 79 }, 80 { 81 name: "pod-deleted", 82 pods: func() []*v1.Pod { 83 deleted := metav1.Now() 84 pods := []*v1.Pod{testPodWithEphemeral.DeepCopy()} 85 pods[0].DeletionTimestamp = &deleted 86 return pods 87 }(), 88 podKey: podKey(testPodWithEphemeral), 89 }, 90 { 91 name: "no-volumes", 92 pods: []*v1.Pod{testPod}, 93 podKey: podKey(testPod), 94 }, 95 { 96 name: "create-with-other-PVC", 97 pods: []*v1.Pod{testPodWithEphemeral}, 98 podKey: podKey(testPodWithEphemeral), 99 pvcs: []*v1.PersistentVolumeClaim{otherNamespaceClaim}, 100 expectedPVCs: []v1.PersistentVolumeClaim{*otherNamespaceClaim, *testPodEphemeralClaim}, 101 expectedMetrics: expectedMetrics{1, 0}, 102 }, 103 { 104 name: "wrong-PVC-owner", 105 pods: []*v1.Pod{testPodWithEphemeral}, 106 podKey: podKey(testPodWithEphemeral), 107 pvcs: []*v1.PersistentVolumeClaim{conflictingClaim}, 108 expectedPVCs: []v1.PersistentVolumeClaim{*conflictingClaim}, 109 expectedError: true, 110 }, 111 { 112 name: "create-conflict", 113 pods: []*v1.Pod{testPodWithEphemeral}, 114 podKey: podKey(testPodWithEphemeral), 115 expectedMetrics: expectedMetrics{1, 1}, 116 expectedError: true, 117 }, 118 } 119 120 for _, tc := range tests { 121 // Run sequentially because of global logging and global metrics. 122 t.Run(tc.name, func(t *testing.T) { 123 // There is no good way to shut down the informers. They spawn 124 // various goroutines and some of them (in particular shared informer) 125 // become very unhappy ("close on closed channel") when using a context 126 // that gets cancelled. Therefore we just keep everything running. 127 ctx := context.Background() 128 129 var objects []runtime.Object 130 for _, pod := range tc.pods { 131 objects = append(objects, pod) 132 } 133 for _, pvc := range tc.pvcs { 134 objects = append(objects, pvc) 135 } 136 137 fakeKubeClient := createTestClient(objects...) 138 if tc.expectedMetrics.numFailures > 0 { 139 fakeKubeClient.PrependReactor("create", "persistentvolumeclaims", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { 140 return true, nil, apierrors.NewConflict(action.GetResource().GroupResource(), "fake name", errors.New("fake conflict")) 141 }) 142 } 143 setupMetrics() 144 informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) 145 podInformer := informerFactory.Core().V1().Pods() 146 pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() 147 148 c, err := NewController(ctx, fakeKubeClient, podInformer, pvcInformer) 149 if err != nil { 150 t.Fatalf("error creating ephemeral controller : %v", err) 151 } 152 ec, _ := c.(*ephemeralController) 153 154 // Ensure informers are up-to-date. 155 go informerFactory.Start(ctx.Done()) 156 informerFactory.WaitForCacheSync(ctx.Done()) 157 cache.WaitForCacheSync(ctx.Done(), podInformer.Informer().HasSynced, pvcInformer.Informer().HasSynced) 158 159 err = ec.syncHandler(context.TODO(), tc.podKey) 160 if err != nil && !tc.expectedError { 161 t.Fatalf("unexpected error while running handler: %v", err) 162 } 163 if err == nil && tc.expectedError { 164 t.Fatalf("unexpected success") 165 } 166 167 pvcs, err := fakeKubeClient.CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{}) 168 if err != nil { 169 t.Fatalf("unexpected error while listing PVCs: %v", err) 170 } 171 assert.Equal(t, sortPVCs(tc.expectedPVCs), sortPVCs(pvcs.Items)) 172 expectMetrics(t, tc.expectedMetrics) 173 }) 174 } 175 } 176 177 func makePVC(name, namespace string, owner *metav1.OwnerReference) *v1.PersistentVolumeClaim { 178 pvc := &v1.PersistentVolumeClaim{ 179 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 180 Spec: v1.PersistentVolumeClaimSpec{}, 181 } 182 if owner != nil { 183 pvc.OwnerReferences = []metav1.OwnerReference{*owner} 184 } 185 186 return pvc 187 } 188 189 func makeEphemeralVolume(name string) *v1.Volume { 190 return &v1.Volume{ 191 Name: name, 192 VolumeSource: v1.VolumeSource{ 193 Ephemeral: &v1.EphemeralVolumeSource{ 194 VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{}, 195 }, 196 }, 197 } 198 } 199 200 func makePod(name, namespace string, uid types.UID, volumes ...v1.Volume) *v1.Pod { 201 pvc := &v1.Pod{ 202 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, UID: uid}, 203 Spec: v1.PodSpec{ 204 Volumes: volumes, 205 }, 206 } 207 208 return pvc 209 } 210 211 func podKey(pod *v1.Pod) string { 212 key, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(testPodWithEphemeral) 213 return key 214 } 215 216 func makeOwnerReference(pod *v1.Pod, isController bool) *metav1.OwnerReference { 217 isTrue := true 218 return &metav1.OwnerReference{ 219 APIVersion: "v1", 220 Kind: "Pod", 221 Name: pod.Name, 222 UID: pod.UID, 223 Controller: &isController, 224 BlockOwnerDeletion: &isTrue, 225 } 226 } 227 228 func sortPVCs(pvcs []v1.PersistentVolumeClaim) []v1.PersistentVolumeClaim { 229 sort.Slice(pvcs, func(i, j int) bool { 230 return pvcs[i].Namespace < pvcs[j].Namespace || 231 pvcs[i].Name < pvcs[j].Name 232 }) 233 return pvcs 234 } 235 236 func createTestClient(objects ...runtime.Object) *fake.Clientset { 237 fakeClient := fake.NewSimpleClientset(objects...) 238 return fakeClient 239 } 240 241 // Metrics helpers 242 243 type expectedMetrics struct { 244 numCreated int 245 numFailures int 246 } 247 248 func expectMetrics(t *testing.T, em expectedMetrics) { 249 t.Helper() 250 251 actualCreated, err := testutil.GetCounterMetricValue(ephemeralvolumemetrics.EphemeralVolumeCreateAttempts) 252 handleErr(t, err, "ephemeralVolumeCreate") 253 if actualCreated != float64(em.numCreated) { 254 t.Errorf("Expected PVCs to be created %d, got %v", em.numCreated, actualCreated) 255 } 256 actualConflicts, err := testutil.GetCounterMetricValue(ephemeralvolumemetrics.EphemeralVolumeCreateFailures) 257 handleErr(t, err, "ephemeralVolumeCreate/Conflict") 258 if actualConflicts != float64(em.numFailures) { 259 t.Errorf("Expected PVCs to have conflicts %d, got %v", em.numFailures, actualConflicts) 260 } 261 } 262 263 func handleErr(t *testing.T, err error, metricName string) { 264 if err != nil { 265 t.Errorf("Failed to get %s value, err: %v", metricName, err) 266 } 267 } 268 269 func setupMetrics() { 270 ephemeralvolumemetrics.RegisterMetrics() 271 ephemeralvolumemetrics.EphemeralVolumeCreateAttempts.Reset() 272 ephemeralvolumemetrics.EphemeralVolumeCreateFailures.Reset() 273 }