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