k8s.io/kubernetes@v1.29.3/pkg/controller/job/tracking_utils_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 job 18 19 import ( 20 "fmt" 21 "sync" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 batch "k8s.io/api/batch/v1" 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/util/sets" 29 "k8s.io/component-base/metrics/testutil" 30 "k8s.io/klog/v2/ktesting" 31 "k8s.io/kubernetes/pkg/controller/job/metrics" 32 ) 33 34 func TestUIDTrackingExpectations(t *testing.T) { 35 logger, _ := ktesting.NewTestContext(t) 36 tracks := []struct { 37 job string 38 firstRound []string 39 secondRound []string 40 }{ 41 { 42 job: "foo", 43 firstRound: []string{"a", "b", "c", "d"}, 44 secondRound: []string{"e", "f"}, 45 }, 46 { 47 job: "bar", 48 firstRound: []string{"x", "y", "z"}, 49 secondRound: []string{"u", "v", "w"}, 50 }, 51 { 52 job: "baz", 53 firstRound: []string{"w"}, 54 secondRound: []string{"a"}, 55 }, 56 } 57 expectations := newUIDTrackingExpectations() 58 59 // Insert first round of keys in parallel. 60 61 var wg sync.WaitGroup 62 wg.Add(len(tracks)) 63 errs := make([]error, len(tracks)) 64 for i := range tracks { 65 track := tracks[i] 66 go func(errID int) { 67 errs[errID] = expectations.expectFinalizersRemoved(logger, track.job, track.firstRound) 68 wg.Done() 69 }(i) 70 } 71 wg.Wait() 72 for i, err := range errs { 73 if err != nil { 74 t.Errorf("Failed adding first round of UIDs for job %s: %v", tracks[i].job, err) 75 } 76 } 77 78 for _, track := range tracks { 79 uids := expectations.getSet(track.job) 80 if uids == nil { 81 t.Errorf("Set of UIDs is empty for job %s", track.job) 82 } else if diff := cmp.Diff(track.firstRound, sets.List(uids.set)); diff != "" { 83 t.Errorf("Unexpected keys for job %s (-want,+got):\n%s", track.job, diff) 84 } 85 } 86 87 // Delete the first round of keys and add the second round in parallel. 88 89 for i, track := range tracks { 90 wg.Add(len(track.firstRound) + 1) 91 track := track 92 for _, uid := range track.firstRound { 93 uid := uid 94 go func() { 95 expectations.finalizerRemovalObserved(logger, track.job, uid) 96 wg.Done() 97 }() 98 } 99 go func(errID int) { 100 errs[errID] = expectations.expectFinalizersRemoved(logger, track.job, track.secondRound) 101 wg.Done() 102 }(i) 103 } 104 wg.Wait() 105 106 for i, err := range errs { 107 if err != nil { 108 t.Errorf("Failed adding second round of UIDs for job %s: %v", tracks[i].job, err) 109 } 110 } 111 112 for _, track := range tracks { 113 uids := expectations.getSet(track.job) 114 if uids == nil { 115 t.Errorf("Set of UIDs is empty for job %s", track.job) 116 } else if diff := cmp.Diff(track.secondRound, sets.List(uids.set)); diff != "" { 117 t.Errorf("Unexpected keys for job %s (-want,+got):\n%s", track.job, diff) 118 } 119 } 120 for _, track := range tracks { 121 expectations.deleteExpectations(logger, track.job) 122 uids := expectations.getSet(track.job) 123 if uids != nil { 124 t.Errorf("Wanted expectations for job %s to be cleared, but they were not", track.job) 125 } 126 } 127 } 128 129 func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) { 130 metrics.Register() 131 cases := map[string]struct { 132 oldPod *v1.Pod 133 newPod *v1.Pod 134 wantAdd int 135 wantDelete int 136 }{ 137 "new non-finished Pod with finalizer": { 138 newPod: &v1.Pod{ 139 ObjectMeta: metav1.ObjectMeta{ 140 Finalizers: []string{batch.JobTrackingFinalizer}, 141 }, 142 Status: v1.PodStatus{ 143 Phase: v1.PodPending, 144 }, 145 }, 146 }, 147 "pod with finalizer fails": { 148 oldPod: &v1.Pod{ 149 ObjectMeta: metav1.ObjectMeta{ 150 Finalizers: []string{batch.JobTrackingFinalizer}, 151 }, 152 Status: v1.PodStatus{ 153 Phase: v1.PodRunning, 154 }, 155 }, 156 newPod: &v1.Pod{ 157 ObjectMeta: metav1.ObjectMeta{ 158 Finalizers: []string{batch.JobTrackingFinalizer}, 159 }, 160 Status: v1.PodStatus{ 161 Phase: v1.PodFailed, 162 }, 163 }, 164 wantAdd: 1, 165 }, 166 "pod with finalizer succeeds": { 167 oldPod: &v1.Pod{ 168 ObjectMeta: metav1.ObjectMeta{ 169 Finalizers: []string{batch.JobTrackingFinalizer}, 170 }, 171 Status: v1.PodStatus{ 172 Phase: v1.PodRunning, 173 }, 174 }, 175 newPod: &v1.Pod{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Finalizers: []string{batch.JobTrackingFinalizer}, 178 }, 179 Status: v1.PodStatus{ 180 Phase: v1.PodSucceeded, 181 }, 182 }, 183 wantAdd: 1, 184 }, 185 "succeeded pod loses finalizer": { 186 oldPod: &v1.Pod{ 187 ObjectMeta: metav1.ObjectMeta{ 188 Finalizers: []string{batch.JobTrackingFinalizer}, 189 }, 190 Status: v1.PodStatus{ 191 Phase: v1.PodSucceeded, 192 }, 193 }, 194 newPod: &v1.Pod{ 195 Status: v1.PodStatus{ 196 Phase: v1.PodSucceeded, 197 }, 198 }, 199 wantDelete: 1, 200 }, 201 "pod without finalizer removed": { 202 oldPod: &v1.Pod{ 203 Status: v1.PodStatus{ 204 Phase: v1.PodSucceeded, 205 }, 206 }, 207 }, 208 } 209 for name, tc := range cases { 210 t.Run(name, func(t *testing.T) { 211 metrics.TerminatedPodsTrackingFinalizerTotal.Reset() 212 recordFinishedPodWithTrackingFinalizer(tc.oldPod, tc.newPod) 213 if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Add, tc.wantAdd); err != nil { 214 t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(add): %v", err) 215 } 216 if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Delete, tc.wantDelete); err != nil { 217 t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(delete): %v", err) 218 } 219 }) 220 } 221 } 222 223 func validateTerminatedPodsTrackingFinalizerTotal(event string, want int) error { 224 got, err := testutil.GetCounterMetricValue(metrics.TerminatedPodsTrackingFinalizerTotal.WithLabelValues(event)) 225 if err != nil { 226 return err 227 } 228 if int(got) != want { 229 return fmt.Errorf("got value %d, want %d", int(got), want) 230 } 231 return nil 232 }