1
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
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
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 }
233
View as plain text