1
16
17 package job
18
19 import (
20 "testing"
21 "time"
22
23 "github.com/google/go-cmp/cmp"
24 "github.com/google/go-cmp/cmp/cmpopts"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/labels"
27 "k8s.io/apimachinery/pkg/types"
28 "k8s.io/apimachinery/pkg/util/validation/field"
29 genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
30 "k8s.io/apiserver/pkg/registry/rest"
31 utilfeature "k8s.io/apiserver/pkg/util/feature"
32 featuregatetesting "k8s.io/component-base/featuregate/testing"
33 apitesting "k8s.io/kubernetes/pkg/api/testing"
34 "k8s.io/kubernetes/pkg/apis/batch"
35 _ "k8s.io/kubernetes/pkg/apis/batch/install"
36 api "k8s.io/kubernetes/pkg/apis/core"
37 "k8s.io/kubernetes/pkg/features"
38 "k8s.io/utils/pointer"
39 "k8s.io/utils/ptr"
40 )
41
42 var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
43
44
45 func TestJobStrategy_PrepareForUpdate(t *testing.T) {
46 validSelector := getValidLabelSelector()
47 validPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelector)
48
49 podFailurePolicy := &batch.PodFailurePolicy{
50 Rules: []batch.PodFailurePolicyRule{
51 {
52 Action: batch.PodFailurePolicyActionFailJob,
53 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
54 ContainerName: pointer.String("container-name"),
55 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
56 Values: []int32{1},
57 },
58 },
59 },
60 }
61 updatedPodFailurePolicy := &batch.PodFailurePolicy{
62 Rules: []batch.PodFailurePolicyRule{
63 {
64 Action: batch.PodFailurePolicyActionIgnore,
65 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
66 ContainerName: pointer.String("updated-container-name"),
67 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
68 Values: []int32{2},
69 },
70 },
71 },
72 }
73 successPolicy := &batch.SuccessPolicy{
74 Rules: []batch.SuccessPolicyRule{
75 {
76 SucceededIndexes: ptr.To("1,3-7"),
77 SucceededCount: ptr.To[int32](4),
78 },
79 },
80 }
81 updatedSuccessPolicy := &batch.SuccessPolicy{
82 Rules: []batch.SuccessPolicyRule{
83 {
84 SucceededIndexes: ptr.To("1,3-7"),
85 SucceededCount: ptr.To[int32](5),
86 },
87 },
88 }
89
90 cases := map[string]struct {
91 enableJobPodFailurePolicy bool
92 enableJobBackoffLimitPerIndex bool
93 enableJobPodReplacementPolicy bool
94 enableJobSuccessPolicy bool
95 job batch.Job
96 updatedJob batch.Job
97 wantJob batch.Job
98 }{
99 "update job with a new field; updated when JobSuccessPolicy enabled": {
100 enableJobSuccessPolicy: true,
101 job: batch.Job{
102 ObjectMeta: getValidObjectMeta(0),
103 Spec: batch.JobSpec{
104 Selector: validSelector,
105 Template: validPodTemplateSpec,
106 SuccessPolicy: nil,
107 },
108 },
109 updatedJob: batch.Job{
110 ObjectMeta: getValidObjectMeta(0),
111 Spec: batch.JobSpec{
112 Selector: validSelector,
113 Template: validPodTemplateSpec,
114 SuccessPolicy: updatedSuccessPolicy,
115 },
116 },
117 wantJob: batch.Job{
118 ObjectMeta: getValidObjectMeta(1),
119 Spec: batch.JobSpec{
120 Selector: validSelector,
121 Template: validPodTemplateSpec,
122 SuccessPolicy: updatedSuccessPolicy,
123 },
124 },
125 },
126 "update pre-existing field; updated when JobSuccessPolicy enabled": {
127 enableJobSuccessPolicy: true,
128 job: batch.Job{
129 ObjectMeta: getValidObjectMeta(0),
130 Spec: batch.JobSpec{
131 Selector: validSelector,
132 Template: validPodTemplateSpec,
133 SuccessPolicy: successPolicy,
134 },
135 },
136 updatedJob: batch.Job{
137 ObjectMeta: getValidObjectMeta(0),
138 Spec: batch.JobSpec{
139 Selector: validSelector,
140 Template: validPodTemplateSpec,
141 SuccessPolicy: updatedSuccessPolicy,
142 },
143 },
144 wantJob: batch.Job{
145 ObjectMeta: getValidObjectMeta(1),
146 Spec: batch.JobSpec{
147 Selector: validSelector,
148 Template: validPodTemplateSpec,
149 SuccessPolicy: updatedSuccessPolicy,
150 },
151 },
152 },
153 "update job with a new field: not update when JobSuccessPolicy disabled": {
154 enableJobSuccessPolicy: false,
155 job: batch.Job{
156 ObjectMeta: getValidObjectMeta(0),
157 Spec: batch.JobSpec{
158 Selector: validSelector,
159 Template: validPodTemplateSpec,
160 SuccessPolicy: nil,
161 },
162 },
163 updatedJob: batch.Job{
164 ObjectMeta: getValidObjectMeta(0),
165 Spec: batch.JobSpec{
166 Selector: validSelector,
167 Template: validPodTemplateSpec,
168 SuccessPolicy: updatedSuccessPolicy,
169 },
170 },
171 wantJob: batch.Job{
172 ObjectMeta: getValidObjectMeta(0),
173 Spec: batch.JobSpec{
174 Selector: validSelector,
175 Template: validPodTemplateSpec,
176 SuccessPolicy: nil,
177 },
178 },
179 },
180 "update pre-existing field; updated when JobSuccessPolicy disabled": {
181 enableJobSuccessPolicy: false,
182 job: batch.Job{
183 ObjectMeta: getValidObjectMeta(0),
184 Spec: batch.JobSpec{
185 Selector: validSelector,
186 Template: validPodTemplateSpec,
187 SuccessPolicy: successPolicy,
188 },
189 },
190 updatedJob: batch.Job{
191 ObjectMeta: getValidObjectMeta(0),
192 Spec: batch.JobSpec{
193 Selector: validSelector,
194 Template: validPodTemplateSpec,
195 SuccessPolicy: updatedSuccessPolicy,
196 },
197 },
198 wantJob: batch.Job{
199 ObjectMeta: getValidObjectMeta(1),
200 Spec: batch.JobSpec{
201 Selector: validSelector,
202 Template: validPodTemplateSpec,
203 SuccessPolicy: updatedSuccessPolicy,
204 },
205 },
206 },
207 "update job with a new field; updated when JobBackoffLimitPerIndex enabled": {
208 enableJobBackoffLimitPerIndex: true,
209 job: batch.Job{
210 ObjectMeta: getValidObjectMeta(0),
211 Spec: batch.JobSpec{
212 Selector: validSelector,
213 Template: validPodTemplateSpec,
214 BackoffLimitPerIndex: nil,
215 MaxFailedIndexes: nil,
216 },
217 },
218 updatedJob: batch.Job{
219 ObjectMeta: getValidObjectMeta(0),
220 Spec: batch.JobSpec{
221 Selector: validSelector,
222 Template: validPodTemplateSpec,
223 BackoffLimitPerIndex: pointer.Int32(1),
224 MaxFailedIndexes: pointer.Int32(1),
225 },
226 },
227 wantJob: batch.Job{
228 ObjectMeta: getValidObjectMeta(1),
229 Spec: batch.JobSpec{
230 Selector: validSelector,
231 Template: validPodTemplateSpec,
232 BackoffLimitPerIndex: pointer.Int32(1),
233 MaxFailedIndexes: pointer.Int32(1),
234 },
235 },
236 },
237 "update job with a new field; not updated when JobBackoffLimitPerIndex disabled": {
238 enableJobBackoffLimitPerIndex: false,
239 job: batch.Job{
240 ObjectMeta: getValidObjectMeta(0),
241 Spec: batch.JobSpec{
242 Selector: validSelector,
243 Template: validPodTemplateSpec,
244 BackoffLimitPerIndex: nil,
245 MaxFailedIndexes: nil,
246 },
247 },
248 updatedJob: batch.Job{
249 ObjectMeta: getValidObjectMeta(0),
250 Spec: batch.JobSpec{
251 Selector: validSelector,
252 Template: validPodTemplateSpec,
253 BackoffLimitPerIndex: pointer.Int32(1),
254 MaxFailedIndexes: pointer.Int32(1),
255 },
256 },
257 wantJob: batch.Job{
258 ObjectMeta: getValidObjectMeta(0),
259 Spec: batch.JobSpec{
260 Selector: validSelector,
261 Template: validPodTemplateSpec,
262 BackoffLimitPerIndex: nil,
263 MaxFailedIndexes: nil,
264 },
265 },
266 },
267 "update job with a new field; updated when JobPodFailurePolicy enabled": {
268 enableJobPodFailurePolicy: true,
269 job: batch.Job{
270 ObjectMeta: getValidObjectMeta(0),
271 Spec: batch.JobSpec{
272 Selector: validSelector,
273 Template: validPodTemplateSpec,
274 PodFailurePolicy: nil,
275 },
276 },
277 updatedJob: batch.Job{
278 ObjectMeta: getValidObjectMeta(0),
279 Spec: batch.JobSpec{
280 Selector: validSelector,
281 Template: validPodTemplateSpec,
282 PodFailurePolicy: updatedPodFailurePolicy,
283 },
284 },
285 wantJob: batch.Job{
286 ObjectMeta: getValidObjectMeta(1),
287 Spec: batch.JobSpec{
288 Selector: validSelector,
289 Template: validPodTemplateSpec,
290 PodFailurePolicy: updatedPodFailurePolicy,
291 },
292 },
293 },
294 "update job with a new field; updated when JobPodReplacementPolicy enabled": {
295 enableJobPodReplacementPolicy: true,
296 job: batch.Job{
297 ObjectMeta: getValidObjectMeta(0),
298 Spec: batch.JobSpec{
299 Selector: validSelector,
300 Template: validPodTemplateSpec,
301 PodReplacementPolicy: nil,
302 },
303 },
304 updatedJob: batch.Job{
305 ObjectMeta: getValidObjectMeta(0),
306 Spec: batch.JobSpec{
307 Selector: validSelector,
308 Template: validPodTemplateSpec,
309 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
310 },
311 },
312 wantJob: batch.Job{
313 ObjectMeta: getValidObjectMeta(1),
314 Spec: batch.JobSpec{
315 Selector: validSelector,
316 Template: validPodTemplateSpec,
317 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
318 },
319 },
320 },
321 "update job with a new field; not updated when JobPodReplacementPolicy disabled": {
322 enableJobPodReplacementPolicy: false,
323 job: batch.Job{
324 ObjectMeta: getValidObjectMeta(0),
325 Spec: batch.JobSpec{
326 Selector: validSelector,
327 Template: validPodTemplateSpec,
328 PodReplacementPolicy: nil,
329 },
330 },
331 updatedJob: batch.Job{
332 ObjectMeta: getValidObjectMeta(0),
333 Spec: batch.JobSpec{
334 Selector: validSelector,
335 Template: validPodTemplateSpec,
336 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
337 },
338 },
339 wantJob: batch.Job{
340 ObjectMeta: getValidObjectMeta(0),
341 Spec: batch.JobSpec{
342 Selector: validSelector,
343 Template: validPodTemplateSpec,
344 PodReplacementPolicy: nil,
345 },
346 },
347 },
348 "update job with a new field; not updated when JobPodFailurePolicy disabled": {
349 enableJobPodFailurePolicy: false,
350 job: batch.Job{
351 ObjectMeta: getValidObjectMeta(0),
352 Spec: batch.JobSpec{
353 Selector: validSelector,
354 Template: validPodTemplateSpec,
355 PodFailurePolicy: nil,
356 },
357 },
358 updatedJob: batch.Job{
359 ObjectMeta: getValidObjectMeta(0),
360 Spec: batch.JobSpec{
361 Selector: validSelector,
362 Template: validPodTemplateSpec,
363 PodFailurePolicy: updatedPodFailurePolicy,
364 },
365 },
366 wantJob: batch.Job{
367 ObjectMeta: getValidObjectMeta(0),
368 Spec: batch.JobSpec{
369 Selector: validSelector,
370 Template: validPodTemplateSpec,
371 PodFailurePolicy: nil,
372 },
373 },
374 },
375 "update pre-existing field; updated when JobPodFailurePolicy enabled": {
376 enableJobPodFailurePolicy: true,
377 job: batch.Job{
378 ObjectMeta: getValidObjectMeta(0),
379 Spec: batch.JobSpec{
380 Selector: validSelector,
381 Template: validPodTemplateSpec,
382 PodFailurePolicy: podFailurePolicy,
383 },
384 },
385 updatedJob: batch.Job{
386 ObjectMeta: getValidObjectMeta(0),
387 Spec: batch.JobSpec{
388 Selector: validSelector,
389 Template: validPodTemplateSpec,
390 PodFailurePolicy: updatedPodFailurePolicy,
391 },
392 },
393 wantJob: batch.Job{
394 ObjectMeta: getValidObjectMeta(1),
395 Spec: batch.JobSpec{
396 Selector: validSelector,
397 Template: validPodTemplateSpec,
398 PodFailurePolicy: updatedPodFailurePolicy,
399 },
400 },
401 },
402 "update pre-existing field; updated when JobPodFailurePolicy disabled": {
403 enableJobPodFailurePolicy: false,
404 job: batch.Job{
405 ObjectMeta: getValidObjectMeta(0),
406 Spec: batch.JobSpec{
407 Selector: validSelector,
408 Template: validPodTemplateSpec,
409 PodFailurePolicy: podFailurePolicy,
410 },
411 },
412 updatedJob: batch.Job{
413 ObjectMeta: getValidObjectMeta(0),
414 Spec: batch.JobSpec{
415 Selector: validSelector,
416 Template: validPodTemplateSpec,
417 PodFailurePolicy: updatedPodFailurePolicy,
418 },
419 },
420 wantJob: batch.Job{
421 ObjectMeta: getValidObjectMeta(1),
422 Spec: batch.JobSpec{
423 Selector: validSelector,
424 Template: validPodTemplateSpec,
425 PodFailurePolicy: updatedPodFailurePolicy,
426 },
427 },
428 },
429 "add tracking annotation back": {
430 job: batch.Job{
431 ObjectMeta: getValidObjectMeta(0),
432 Spec: batch.JobSpec{
433 Selector: validSelector,
434 Template: validPodTemplateSpec,
435 PodFailurePolicy: podFailurePolicy,
436 },
437 },
438 updatedJob: batch.Job{
439 ObjectMeta: getValidObjectMeta(0),
440 Spec: batch.JobSpec{
441 Selector: validSelector,
442 Template: validPodTemplateSpec,
443 },
444 },
445 wantJob: batch.Job{
446 ObjectMeta: getValidObjectMeta(1),
447 Spec: batch.JobSpec{
448 Selector: validSelector,
449 Template: validPodTemplateSpec,
450 },
451 },
452 },
453 "attempt status update and verify it doesn't change": {
454 job: batch.Job{
455 ObjectMeta: getValidObjectMeta(0),
456 Spec: batch.JobSpec{
457 Selector: validSelector,
458 Template: validPodTemplateSpec,
459 },
460 Status: batch.JobStatus{
461 Active: 1,
462 },
463 },
464 updatedJob: batch.Job{
465 ObjectMeta: getValidObjectMeta(0),
466 Spec: batch.JobSpec{
467 Selector: validSelector,
468 Template: validPodTemplateSpec,
469 },
470 Status: batch.JobStatus{
471 Active: 2,
472 },
473 },
474 wantJob: batch.Job{
475 ObjectMeta: getValidObjectMeta(0),
476 Spec: batch.JobSpec{
477 Selector: validSelector,
478 Template: validPodTemplateSpec,
479 },
480 Status: batch.JobStatus{
481 Active: 1,
482 },
483 },
484 },
485 "ensure generation doesn't change over non spec updates": {
486 job: batch.Job{
487 ObjectMeta: getValidObjectMeta(0),
488 Spec: batch.JobSpec{
489 Selector: validSelector,
490 Template: validPodTemplateSpec,
491 },
492 Status: batch.JobStatus{
493 Active: 1,
494 },
495 },
496 updatedJob: batch.Job{
497 ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
498 Spec: batch.JobSpec{
499 Selector: validSelector,
500 Template: validPodTemplateSpec,
501 },
502 Status: batch.JobStatus{
503 Active: 2,
504 },
505 },
506 wantJob: batch.Job{
507 ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
508 Spec: batch.JobSpec{
509 Selector: validSelector,
510 Template: validPodTemplateSpec,
511 },
512 Status: batch.JobStatus{
513 Active: 1,
514 },
515 },
516 },
517 "test updating suspend false->true": {
518 job: batch.Job{
519 ObjectMeta: getValidObjectMeta(0),
520 Spec: batch.JobSpec{
521 Selector: validSelector,
522 Template: validPodTemplateSpec,
523 Suspend: pointer.Bool(false),
524 },
525 },
526 updatedJob: batch.Job{
527 ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
528 Spec: batch.JobSpec{
529 Selector: validSelector,
530 Template: validPodTemplateSpec,
531 Suspend: pointer.Bool(true),
532 },
533 },
534 wantJob: batch.Job{
535 ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}),
536 Spec: batch.JobSpec{
537 Selector: validSelector,
538 Template: validPodTemplateSpec,
539 Suspend: pointer.Bool(true),
540 },
541 },
542 },
543 "test updating suspend nil -> true": {
544 job: batch.Job{
545 ObjectMeta: getValidObjectMeta(0),
546 Spec: batch.JobSpec{
547 Selector: validSelector,
548 Template: validPodTemplateSpec,
549 },
550 },
551 updatedJob: batch.Job{
552 ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
553 Spec: batch.JobSpec{
554 Selector: validSelector,
555 Template: validPodTemplateSpec,
556 Suspend: pointer.Bool(true),
557 },
558 },
559 wantJob: batch.Job{
560 ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}),
561 Spec: batch.JobSpec{
562 Selector: validSelector,
563 Template: validPodTemplateSpec,
564 Suspend: pointer.Bool(true),
565 },
566 },
567 },
568 }
569
570 for name, tc := range cases {
571 t.Run(name, func(t *testing.T) {
572 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
573 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
574 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodReplacementPolicy, tc.enableJobPodReplacementPolicy)()
575 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobSuccessPolicy, tc.enableJobSuccessPolicy)()
576 ctx := genericapirequest.NewDefaultContext()
577
578 Strategy.PrepareForUpdate(ctx, &tc.updatedJob, &tc.job)
579
580 if diff := cmp.Diff(tc.wantJob, tc.updatedJob); diff != "" {
581 t.Errorf("Job update differences (-want,+got):\n%s", diff)
582 }
583 })
584 }
585 }
586
587
588 func TestJobStrategy_PrepareForCreate(t *testing.T) {
589 validSelector := getValidLabelSelector()
590 validPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelector)
591 validSelectorWithBatchLabels := &metav1.LabelSelector{MatchLabels: getValidBatchLabelsWithNonBatch()}
592 expectedPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelectorWithBatchLabels)
593
594 podFailurePolicy := &batch.PodFailurePolicy{
595 Rules: []batch.PodFailurePolicyRule{
596 {
597 Action: batch.PodFailurePolicyActionFailJob,
598 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
599 ContainerName: pointer.String("container-name"),
600 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
601 Values: []int32{1},
602 },
603 },
604 },
605 }
606 successPolicy := &batch.SuccessPolicy{
607 Rules: []batch.SuccessPolicyRule{
608 {
609 SucceededIndexes: ptr.To("1,3-7"),
610 SucceededCount: ptr.To[int32](4),
611 },
612 },
613 }
614
615 cases := map[string]struct {
616 enableJobPodFailurePolicy bool
617 enableJobBackoffLimitPerIndex bool
618 enableJobPodReplacementPolicy bool
619 enableJobManageBy bool
620 enableJobSuccessPolicy bool
621 job batch.Job
622 wantJob batch.Job
623 }{
624 "generate selectors": {
625 job: batch.Job{
626 ObjectMeta: getValidObjectMeta(0),
627 Spec: batch.JobSpec{
628 Selector: validSelector,
629 ManualSelector: pointer.Bool(false),
630 Template: validPodTemplateSpec,
631 },
632 },
633 wantJob: batch.Job{
634 ObjectMeta: getValidObjectMeta(1),
635 Spec: batch.JobSpec{
636 Selector: validSelector,
637 ManualSelector: pointer.Bool(false),
638 Template: expectedPodTemplateSpec,
639 },
640 },
641 },
642 "create job with a new field; JobSuccessPolicy enabled": {
643 enableJobSuccessPolicy: true,
644 job: batch.Job{
645 ObjectMeta: getValidObjectMeta(0),
646 Spec: batch.JobSpec{
647 Selector: validSelector,
648 ManualSelector: ptr.To(false),
649 Template: validPodTemplateSpec,
650 SuccessPolicy: successPolicy,
651 },
652 },
653 wantJob: batch.Job{
654 ObjectMeta: getValidObjectMeta(1),
655 Spec: batch.JobSpec{
656 Selector: validSelector,
657 ManualSelector: ptr.To(false),
658 Template: expectedPodTemplateSpec,
659 SuccessPolicy: successPolicy,
660 },
661 },
662 },
663 "create job with a new field; JobSuccessPolicy disabled": {
664 enableJobSuccessPolicy: false,
665 job: batch.Job{
666 ObjectMeta: getValidObjectMeta(0),
667 Spec: batch.JobSpec{
668 Selector: validSelector,
669 ManualSelector: ptr.To(false),
670 Template: validPodTemplateSpec,
671 SuccessPolicy: successPolicy,
672 },
673 },
674 wantJob: batch.Job{
675 ObjectMeta: getValidObjectMeta(1),
676 Spec: batch.JobSpec{
677 Selector: validSelector,
678 ManualSelector: ptr.To(false),
679 Template: validPodTemplateSpec,
680 SuccessPolicy: nil,
681 },
682 },
683 },
684 "create job with a new fields; JobBackoffLimitPerIndex enabled": {
685 enableJobBackoffLimitPerIndex: true,
686 job: batch.Job{
687 ObjectMeta: getValidObjectMeta(0),
688 Spec: batch.JobSpec{
689 Selector: validSelector,
690 ManualSelector: pointer.Bool(false),
691 Template: validPodTemplateSpec,
692 BackoffLimitPerIndex: pointer.Int32(1),
693 MaxFailedIndexes: pointer.Int32(1),
694 },
695 },
696 wantJob: batch.Job{
697 ObjectMeta: getValidObjectMeta(1),
698 Spec: batch.JobSpec{
699 Selector: validSelector,
700 ManualSelector: pointer.Bool(false),
701 Template: expectedPodTemplateSpec,
702 BackoffLimitPerIndex: pointer.Int32(1),
703 MaxFailedIndexes: pointer.Int32(1),
704 },
705 },
706 },
707 "create job with a new fields; JobBackoffLimitPerIndex disabled": {
708 enableJobBackoffLimitPerIndex: false,
709 job: batch.Job{
710 ObjectMeta: getValidObjectMeta(0),
711 Spec: batch.JobSpec{
712 Selector: validSelector,
713 ManualSelector: pointer.Bool(false),
714 Template: validPodTemplateSpec,
715 BackoffLimitPerIndex: pointer.Int32(1),
716 MaxFailedIndexes: pointer.Int32(1),
717 },
718 },
719 wantJob: batch.Job{
720 ObjectMeta: getValidObjectMeta(1),
721 Spec: batch.JobSpec{
722 Selector: validSelector,
723 ManualSelector: pointer.Bool(false),
724 Template: expectedPodTemplateSpec,
725 BackoffLimitPerIndex: nil,
726 MaxFailedIndexes: nil,
727 },
728 },
729 },
730 "create job with a new field; JobPodFailurePolicy enabled": {
731 enableJobPodFailurePolicy: true,
732 job: batch.Job{
733 ObjectMeta: getValidObjectMeta(0),
734 Spec: batch.JobSpec{
735 Selector: validSelector,
736 ManualSelector: pointer.Bool(false),
737 Template: validPodTemplateSpec,
738 PodFailurePolicy: podFailurePolicy,
739 },
740 },
741 wantJob: batch.Job{
742 ObjectMeta: getValidObjectMeta(1),
743 Spec: batch.JobSpec{
744 Selector: validSelector,
745 ManualSelector: pointer.Bool(false),
746 Template: expectedPodTemplateSpec,
747 PodFailurePolicy: podFailurePolicy,
748 },
749 },
750 },
751 "create job with a new field; JobPodReplacementPolicy enabled": {
752 enableJobPodReplacementPolicy: true,
753 job: batch.Job{
754 ObjectMeta: getValidObjectMeta(0),
755 Spec: batch.JobSpec{
756 Selector: validSelector,
757 ManualSelector: pointer.Bool(false),
758 Template: validPodTemplateSpec,
759 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
760 },
761 },
762 wantJob: batch.Job{
763 ObjectMeta: getValidObjectMeta(1),
764 Spec: batch.JobSpec{
765 Selector: validSelector,
766 ManualSelector: pointer.Bool(false),
767 Template: expectedPodTemplateSpec,
768 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
769 },
770 },
771 },
772 "create job with a new field; JobPodReplacementPolicy disabled": {
773 enableJobPodReplacementPolicy: false,
774 job: batch.Job{
775 ObjectMeta: getValidObjectMeta(0),
776 Spec: batch.JobSpec{
777 Selector: validSelector,
778 ManualSelector: pointer.Bool(false),
779 Template: validPodTemplateSpec,
780 PodReplacementPolicy: podReplacementPolicy(batch.Failed),
781 },
782 },
783 wantJob: batch.Job{
784 ObjectMeta: getValidObjectMeta(1),
785 Spec: batch.JobSpec{
786 Selector: validSelector,
787 ManualSelector: pointer.Bool(false),
788 Template: expectedPodTemplateSpec,
789 PodReplacementPolicy: nil,
790 },
791 },
792 },
793 "create job with a new field; JobPodFailurePolicy disabled": {
794 enableJobPodFailurePolicy: false,
795 job: batch.Job{
796 ObjectMeta: getValidObjectMeta(0),
797 Spec: batch.JobSpec{
798 Selector: validSelector,
799 ManualSelector: pointer.Bool(false),
800 Template: validPodTemplateSpec,
801 PodFailurePolicy: podFailurePolicy,
802 },
803 },
804 wantJob: batch.Job{
805 ObjectMeta: getValidObjectMeta(1),
806 Spec: batch.JobSpec{
807 Selector: validSelector,
808 ManualSelector: pointer.Bool(false),
809 Template: expectedPodTemplateSpec,
810 PodFailurePolicy: nil,
811 },
812 },
813 },
814 "job does not allow setting status on create": {
815 job: batch.Job{
816 ObjectMeta: getValidObjectMeta(0),
817 Spec: batch.JobSpec{
818 Selector: validSelector,
819 ManualSelector: pointer.Bool(false),
820 Template: validPodTemplateSpec,
821 },
822 Status: batch.JobStatus{
823 Active: 1,
824 },
825 },
826 wantJob: batch.Job{
827 ObjectMeta: getValidObjectMeta(1),
828 Spec: batch.JobSpec{
829 Selector: validSelector,
830 ManualSelector: pointer.Bool(false),
831 Template: expectedPodTemplateSpec,
832 },
833 },
834 },
835 "create job with pod failure policy using FailIndex action; JobPodFailurePolicy enabled, JobBackoffLimitPerIndex disabled": {
836 enableJobBackoffLimitPerIndex: false,
837 enableJobPodFailurePolicy: true,
838 job: batch.Job{
839 ObjectMeta: getValidObjectMeta(0),
840 Spec: batch.JobSpec{
841 Selector: validSelector,
842 ManualSelector: pointer.Bool(false),
843 Template: validPodTemplateSpec,
844 BackoffLimitPerIndex: pointer.Int32(1),
845 PodFailurePolicy: &batch.PodFailurePolicy{
846 Rules: []batch.PodFailurePolicyRule{
847 {
848 Action: batch.PodFailurePolicyActionFailIndex,
849 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
850 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
851 Values: []int32{1},
852 },
853 },
854 },
855 },
856 },
857 },
858 wantJob: batch.Job{
859 ObjectMeta: getValidObjectMeta(1),
860 Spec: batch.JobSpec{
861 Selector: validSelector,
862 ManualSelector: pointer.Bool(false),
863 Template: expectedPodTemplateSpec,
864 PodFailurePolicy: &batch.PodFailurePolicy{
865 Rules: []batch.PodFailurePolicyRule{},
866 },
867 },
868 },
869 },
870 "create job with multiple pod failure policy rules, some using FailIndex action; JobPodFailurePolicy enabled, JobBackoffLimitPerIndex disabled": {
871 enableJobBackoffLimitPerIndex: false,
872 enableJobPodFailurePolicy: true,
873 job: batch.Job{
874 ObjectMeta: getValidObjectMeta(0),
875 Spec: batch.JobSpec{
876 Selector: validSelector,
877 ManualSelector: pointer.Bool(false),
878 Template: validPodTemplateSpec,
879 BackoffLimitPerIndex: pointer.Int32(1),
880 PodFailurePolicy: &batch.PodFailurePolicy{
881 Rules: []batch.PodFailurePolicyRule{
882 {
883 Action: batch.PodFailurePolicyActionFailJob,
884 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
885 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
886 Values: []int32{2},
887 },
888 },
889 {
890 Action: batch.PodFailurePolicyActionFailIndex,
891 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
892 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
893 Values: []int32{1},
894 },
895 },
896 {
897 Action: batch.PodFailurePolicyActionIgnore,
898 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
899 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
900 Values: []int32{13},
901 },
902 },
903 },
904 },
905 },
906 },
907 wantJob: batch.Job{
908 ObjectMeta: getValidObjectMeta(1),
909 Spec: batch.JobSpec{
910 Selector: validSelector,
911 ManualSelector: pointer.Bool(false),
912 Template: expectedPodTemplateSpec,
913 PodFailurePolicy: &batch.PodFailurePolicy{
914 Rules: []batch.PodFailurePolicyRule{
915 {
916 Action: batch.PodFailurePolicyActionFailJob,
917 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
918 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
919 Values: []int32{2},
920 },
921 },
922 {
923 Action: batch.PodFailurePolicyActionIgnore,
924 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
925 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
926 Values: []int32{13},
927 },
928 },
929 },
930 },
931 },
932 },
933 },
934 "managedBy field is dropped when the feature gate is disabled": {
935 enableJobManageBy: false,
936 job: batch.Job{
937 ObjectMeta: getValidObjectMeta(0),
938 Spec: batch.JobSpec{
939 Selector: validSelector,
940 ManualSelector: pointer.Bool(false),
941 Template: validPodTemplateSpec,
942 ManagedBy: ptr.To("custom-controller-name"),
943 },
944 },
945 wantJob: batch.Job{
946 ObjectMeta: getValidObjectMeta(1),
947 Spec: batch.JobSpec{
948 Selector: validSelector,
949 ManualSelector: pointer.Bool(false),
950 Template: expectedPodTemplateSpec,
951 },
952 },
953 },
954 "managedBy field is set when the feature gate is enabled": {
955 enableJobManageBy: true,
956 job: batch.Job{
957 ObjectMeta: getValidObjectMeta(0),
958 Spec: batch.JobSpec{
959 Selector: validSelector,
960 ManualSelector: pointer.Bool(false),
961 Template: validPodTemplateSpec,
962 ManagedBy: ptr.To("custom-controller-name"),
963 },
964 },
965 wantJob: batch.Job{
966 ObjectMeta: getValidObjectMeta(1),
967 Spec: batch.JobSpec{
968 Selector: validSelector,
969 ManualSelector: pointer.Bool(false),
970 Template: expectedPodTemplateSpec,
971 ManagedBy: ptr.To("custom-controller-name"),
972 },
973 },
974 },
975 }
976
977 for name, tc := range cases {
978 t.Run(name, func(t *testing.T) {
979 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
980 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
981 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodReplacementPolicy, tc.enableJobPodReplacementPolicy)()
982 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobManagedBy, tc.enableJobManageBy)()
983 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobSuccessPolicy, tc.enableJobSuccessPolicy)()
984 ctx := genericapirequest.NewDefaultContext()
985
986 Strategy.PrepareForCreate(ctx, &tc.job)
987
988 if diff := cmp.Diff(tc.wantJob, tc.job); diff != "" {
989 t.Errorf("Job pod failure policy (-want,+got):\n%s", diff)
990 }
991 })
992 }
993 }
994
995 func TestJobStrategy_GarbageCollectionPolicy(t *testing.T) {
996
997
998 var gcds rest.GarbageCollectionDeleteStrategy = Strategy
999 if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.DeleteDependents; got != want {
1000 t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
1001 }
1002
1003 var (
1004 v1Ctx = genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1", Resource: "jobs"})
1005 otherVersionCtx = genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v100", Resource: "jobs"})
1006 )
1007 if got, want := gcds.DefaultGarbageCollectionPolicy(v1Ctx), rest.OrphanDependents; got != want {
1008 t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
1009 }
1010 if got, want := gcds.DefaultGarbageCollectionPolicy(otherVersionCtx), rest.DeleteDependents; got != want {
1011 t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
1012 }
1013 }
1014
1015 func TestJobStrategy_ValidateUpdate(t *testing.T) {
1016 ctx := genericapirequest.NewDefaultContext()
1017 validSelector := &metav1.LabelSelector{
1018 MatchLabels: map[string]string{"a": "b"},
1019 }
1020 validPodTemplateSpec := api.PodTemplateSpec{
1021 ObjectMeta: metav1.ObjectMeta{
1022 Labels: validSelector.MatchLabels,
1023 },
1024 Spec: api.PodSpec{
1025 RestartPolicy: api.RestartPolicyOnFailure,
1026 DNSPolicy: api.DNSClusterFirst,
1027 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1028 },
1029 }
1030 validPodTemplateSpecNever := *validPodTemplateSpec.DeepCopy()
1031 validPodTemplateSpecNever.Spec.RestartPolicy = api.RestartPolicyNever
1032 now := metav1.Now()
1033 cases := map[string]struct {
1034 enableJobPodFailurePolicy bool
1035 enableJobBackoffLimitPerIndex bool
1036 job *batch.Job
1037 update func(*batch.Job)
1038 wantErrs field.ErrorList
1039 }{
1040 "update parallelism": {
1041 job: &batch.Job{
1042 ObjectMeta: metav1.ObjectMeta{
1043 Name: "myjob",
1044 Namespace: metav1.NamespaceDefault,
1045 ResourceVersion: "0",
1046 },
1047 Spec: batch.JobSpec{
1048 Selector: validSelector,
1049 Template: validPodTemplateSpec,
1050 ManualSelector: pointer.BoolPtr(true),
1051 Parallelism: pointer.Int32Ptr(1),
1052 },
1053 },
1054 update: func(job *batch.Job) {
1055 job.Spec.Parallelism = pointer.Int32Ptr(2)
1056 },
1057 },
1058 "update completions disallowed": {
1059 job: &batch.Job{
1060 ObjectMeta: metav1.ObjectMeta{
1061 Name: "myjob",
1062 Namespace: metav1.NamespaceDefault,
1063 ResourceVersion: "0",
1064 },
1065 Spec: batch.JobSpec{
1066 Selector: validSelector,
1067 Template: validPodTemplateSpec,
1068 ManualSelector: pointer.BoolPtr(true),
1069 Parallelism: pointer.Int32Ptr(1),
1070 Completions: pointer.Int32Ptr(1),
1071 },
1072 },
1073 update: func(job *batch.Job) {
1074 job.Spec.Completions = pointer.Int32Ptr(2)
1075 },
1076 wantErrs: field.ErrorList{
1077 {Type: field.ErrorTypeInvalid, Field: "spec.completions"},
1078 },
1079 },
1080 "preserving tracking annotation": {
1081 job: &batch.Job{
1082 ObjectMeta: metav1.ObjectMeta{
1083 Name: "myjob",
1084 Namespace: metav1.NamespaceDefault,
1085 ResourceVersion: "0",
1086 Annotations: map[string]string{
1087 batch.JobTrackingFinalizer: "",
1088 },
1089 },
1090 Spec: batch.JobSpec{
1091 Selector: validSelector,
1092 Template: validPodTemplateSpec,
1093 ManualSelector: pointer.BoolPtr(true),
1094 Parallelism: pointer.Int32Ptr(1),
1095 },
1096 },
1097 update: func(job *batch.Job) {
1098 job.Annotations["foo"] = "bar"
1099 },
1100 },
1101 "deleting user annotation": {
1102 job: &batch.Job{
1103 ObjectMeta: metav1.ObjectMeta{
1104 Name: "myjob",
1105 Namespace: metav1.NamespaceDefault,
1106 ResourceVersion: "0",
1107 Annotations: map[string]string{
1108 batch.JobTrackingFinalizer: "",
1109 "foo": "bar",
1110 },
1111 },
1112 Spec: batch.JobSpec{
1113 Selector: validSelector,
1114 Template: validPodTemplateSpec,
1115 ManualSelector: pointer.BoolPtr(true),
1116 Parallelism: pointer.Int32Ptr(1),
1117 },
1118 },
1119 update: func(job *batch.Job) {
1120 delete(job.Annotations, "foo")
1121 },
1122 },
1123 "updating node selector for unsuspended job disallowed": {
1124 job: &batch.Job{
1125 ObjectMeta: metav1.ObjectMeta{
1126 Name: "myjob",
1127 Namespace: metav1.NamespaceDefault,
1128 ResourceVersion: "0",
1129 Annotations: map[string]string{"foo": "bar"},
1130 },
1131 Spec: batch.JobSpec{
1132 Selector: validSelector,
1133 Template: validPodTemplateSpec,
1134 ManualSelector: pointer.BoolPtr(true),
1135 Parallelism: pointer.Int32Ptr(1),
1136 },
1137 },
1138 update: func(job *batch.Job) {
1139 job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
1140 },
1141 wantErrs: field.ErrorList{
1142 {Type: field.ErrorTypeInvalid, Field: "spec.template"},
1143 },
1144 },
1145 "updating node selector for suspended but previously started job disallowed": {
1146 job: &batch.Job{
1147 ObjectMeta: metav1.ObjectMeta{
1148 Name: "myjob",
1149 Namespace: metav1.NamespaceDefault,
1150 ResourceVersion: "0",
1151 Annotations: map[string]string{"foo": "bar"},
1152 },
1153 Spec: batch.JobSpec{
1154 Selector: validSelector,
1155 Template: validPodTemplateSpec,
1156 ManualSelector: pointer.BoolPtr(true),
1157 Parallelism: pointer.Int32Ptr(1),
1158 Suspend: pointer.BoolPtr(true),
1159 },
1160 Status: batch.JobStatus{
1161 StartTime: &now,
1162 },
1163 },
1164 update: func(job *batch.Job) {
1165 job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
1166 },
1167 wantErrs: field.ErrorList{
1168 {Type: field.ErrorTypeInvalid, Field: "spec.template"},
1169 },
1170 },
1171 "updating node selector for suspended and not previously started job allowed": {
1172 job: &batch.Job{
1173 ObjectMeta: metav1.ObjectMeta{
1174 Name: "myjob",
1175 Namespace: metav1.NamespaceDefault,
1176 ResourceVersion: "0",
1177 Annotations: map[string]string{"foo": "bar"},
1178 },
1179 Spec: batch.JobSpec{
1180 Selector: validSelector,
1181 Template: validPodTemplateSpec,
1182 ManualSelector: pointer.BoolPtr(true),
1183 Parallelism: pointer.Int32Ptr(1),
1184 Suspend: pointer.BoolPtr(true),
1185 },
1186 },
1187 update: func(job *batch.Job) {
1188 job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
1189 },
1190 },
1191 "invalid label selector": {
1192 job: &batch.Job{
1193 ObjectMeta: metav1.ObjectMeta{
1194 Name: "myjob",
1195 Namespace: metav1.NamespaceDefault,
1196 ResourceVersion: "0",
1197 Annotations: map[string]string{"foo": "bar"},
1198 },
1199 Spec: batch.JobSpec{
1200 Selector: &metav1.LabelSelector{
1201 MatchLabels: map[string]string{"a": "b"},
1202 MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}},
1203 },
1204 ManualSelector: pointer.BoolPtr(true),
1205 Template: validPodTemplateSpec,
1206 },
1207 },
1208 update: func(job *batch.Job) {
1209 job.Annotations["hello"] = "world"
1210 },
1211 },
1212 "old job has no batch.kubernetes.io labels": {
1213 job: &batch.Job{
1214 ObjectMeta: metav1.ObjectMeta{
1215 Name: "myjob",
1216 UID: "test",
1217 Namespace: metav1.NamespaceDefault,
1218 ResourceVersion: "10",
1219 Annotations: map[string]string{"hello": "world"},
1220 },
1221 Spec: batch.JobSpec{
1222 Selector: &metav1.LabelSelector{
1223 MatchLabels: map[string]string{batch.LegacyControllerUidLabel: "test"},
1224 },
1225 Parallelism: pointer.Int32(4),
1226 Template: api.PodTemplateSpec{
1227 ObjectMeta: metav1.ObjectMeta{
1228 Labels: map[string]string{batch.LegacyJobNameLabel: "myjob", batch.LegacyControllerUidLabel: "test"},
1229 },
1230 Spec: api.PodSpec{
1231 RestartPolicy: api.RestartPolicyOnFailure,
1232 DNSPolicy: api.DNSClusterFirst,
1233 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1234 },
1235 },
1236 },
1237 },
1238 update: func(job *batch.Job) {
1239 job.Annotations["hello"] = "world"
1240 },
1241 },
1242 "old job has all labels": {
1243 job: &batch.Job{
1244 ObjectMeta: metav1.ObjectMeta{
1245 Name: "myjob",
1246 UID: "test",
1247 Namespace: metav1.NamespaceDefault,
1248 ResourceVersion: "10",
1249 Annotations: map[string]string{"foo": "bar"},
1250 },
1251 Spec: batch.JobSpec{
1252 Selector: &metav1.LabelSelector{
1253 MatchLabels: map[string]string{batch.ControllerUidLabel: "test"},
1254 },
1255 Parallelism: pointer.Int32(4),
1256 Template: api.PodTemplateSpec{
1257 ObjectMeta: metav1.ObjectMeta{
1258 Labels: map[string]string{batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: "test", batch.ControllerUidLabel: "test"},
1259 },
1260 Spec: api.PodSpec{
1261 RestartPolicy: api.RestartPolicyOnFailure,
1262 DNSPolicy: api.DNSClusterFirst,
1263 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1264 },
1265 },
1266 },
1267 },
1268 update: func(job *batch.Job) {
1269 job.Annotations["hello"] = "world"
1270 },
1271 },
1272 "old job is using FailIndex JobBackoffLimitPerIndex is disabled, but FailIndex was already used": {
1273 enableJobPodFailurePolicy: true,
1274 enableJobBackoffLimitPerIndex: false,
1275 job: &batch.Job{
1276 ObjectMeta: metav1.ObjectMeta{
1277 Name: "myjob",
1278 Namespace: metav1.NamespaceDefault,
1279 ResourceVersion: "0",
1280 Annotations: map[string]string{"foo": "bar"},
1281 },
1282 Spec: batch.JobSpec{
1283 CompletionMode: completionModePtr(batch.IndexedCompletion),
1284 Completions: pointer.Int32(2),
1285 BackoffLimitPerIndex: pointer.Int32(1),
1286 Selector: validSelector,
1287 ManualSelector: pointer.Bool(true),
1288 Template: validPodTemplateSpecNever,
1289 PodFailurePolicy: &batch.PodFailurePolicy{
1290 Rules: []batch.PodFailurePolicyRule{
1291 {
1292 Action: batch.PodFailurePolicyActionFailIndex,
1293 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1294 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1295 Values: []int32{1},
1296 },
1297 },
1298 },
1299 },
1300 },
1301 },
1302 update: func(job *batch.Job) {
1303 job.Annotations["hello"] = "world"
1304 },
1305 },
1306 }
1307 for name, tc := range cases {
1308 t.Run(name, func(t *testing.T) {
1309 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
1310 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
1311 newJob := tc.job.DeepCopy()
1312 tc.update(newJob)
1313 errs := Strategy.ValidateUpdate(ctx, newJob, tc.job)
1314 if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" {
1315 t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
1316 }
1317 })
1318 }
1319 }
1320
1321 func TestJobStrategy_WarningsOnUpdate(t *testing.T) {
1322 ctx := genericapirequest.NewDefaultContext()
1323 validSelector := &metav1.LabelSelector{
1324 MatchLabels: map[string]string{"a": "b"},
1325 }
1326 validPodTemplateSpec := api.PodTemplateSpec{
1327 ObjectMeta: metav1.ObjectMeta{
1328 Labels: validSelector.MatchLabels,
1329 },
1330 Spec: api.PodSpec{
1331 RestartPolicy: api.RestartPolicyOnFailure,
1332 DNSPolicy: api.DNSClusterFirst,
1333 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1334 },
1335 }
1336 cases := map[string]struct {
1337 oldJob *batch.Job
1338 job *batch.Job
1339 wantWarningsCount int32
1340 }{
1341 "generation 0 for both": {
1342 job: &batch.Job{
1343 ObjectMeta: metav1.ObjectMeta{
1344 Name: "myjob",
1345 Namespace: metav1.NamespaceDefault,
1346 ResourceVersion: "0",
1347 Generation: 0,
1348 },
1349 Spec: batch.JobSpec{
1350 Selector: validSelector,
1351 Template: validPodTemplateSpec,
1352 ManualSelector: pointer.BoolPtr(true),
1353 Parallelism: pointer.Int32Ptr(1),
1354 },
1355 },
1356
1357 oldJob: &batch.Job{
1358 ObjectMeta: metav1.ObjectMeta{
1359 Name: "myjob",
1360 Namespace: metav1.NamespaceDefault,
1361 ResourceVersion: "0",
1362 Generation: 0,
1363 },
1364 Spec: batch.JobSpec{
1365 Selector: validSelector,
1366 Template: validPodTemplateSpec,
1367 ManualSelector: pointer.BoolPtr(true),
1368 Parallelism: pointer.Int32Ptr(1),
1369 },
1370 },
1371 },
1372 "generation 1 for new; force WarningsOnUpdate to check PodTemplate for updates": {
1373 job: &batch.Job{
1374 ObjectMeta: metav1.ObjectMeta{
1375 Name: "myjob",
1376 Namespace: metav1.NamespaceDefault,
1377 ResourceVersion: "0",
1378 Generation: 1,
1379 },
1380 Spec: batch.JobSpec{
1381 Selector: validSelector,
1382 Template: validPodTemplateSpec,
1383 ManualSelector: pointer.BoolPtr(true),
1384 Parallelism: pointer.Int32Ptr(1),
1385 },
1386 },
1387
1388 oldJob: &batch.Job{
1389 ObjectMeta: metav1.ObjectMeta{
1390 Name: "myjob",
1391 Namespace: metav1.NamespaceDefault,
1392 ResourceVersion: "0",
1393 Generation: 0,
1394 },
1395 Spec: batch.JobSpec{
1396 Selector: validSelector,
1397 Template: validPodTemplateSpec,
1398 ManualSelector: pointer.BoolPtr(true),
1399 Parallelism: pointer.Int32Ptr(1),
1400 },
1401 },
1402 },
1403 "force validation failure in pod template": {
1404 job: &batch.Job{
1405 ObjectMeta: metav1.ObjectMeta{
1406 Name: "myjob",
1407 Namespace: metav1.NamespaceDefault,
1408 ResourceVersion: "0",
1409 Generation: 1,
1410 },
1411 Spec: batch.JobSpec{
1412 Selector: validSelector,
1413 Template: api.PodTemplateSpec{
1414 Spec: api.PodSpec{ImagePullSecrets: []api.LocalObjectReference{{Name: ""}}},
1415 },
1416 ManualSelector: pointer.BoolPtr(true),
1417 Parallelism: pointer.Int32Ptr(1),
1418 },
1419 },
1420
1421 oldJob: &batch.Job{
1422 ObjectMeta: metav1.ObjectMeta{
1423 Name: "myjob",
1424 Namespace: metav1.NamespaceDefault,
1425 ResourceVersion: "0",
1426 Generation: 0,
1427 },
1428 Spec: batch.JobSpec{
1429 Selector: validSelector,
1430 Template: validPodTemplateSpec,
1431 ManualSelector: pointer.BoolPtr(true),
1432 Parallelism: pointer.Int32Ptr(1),
1433 },
1434 },
1435 wantWarningsCount: 1,
1436 },
1437 "Invalid transition to high parallelism": {
1438 wantWarningsCount: 1,
1439 job: &batch.Job{
1440 ObjectMeta: metav1.ObjectMeta{
1441 Name: "myjob2",
1442 Namespace: metav1.NamespaceDefault,
1443 Generation: 1,
1444 ResourceVersion: "0",
1445 },
1446 Spec: batch.JobSpec{
1447 CompletionMode: completionModePtr(batch.IndexedCompletion),
1448 Completions: pointer.Int32(100_001),
1449 Parallelism: pointer.Int32(10_001),
1450 Template: validPodTemplateSpec,
1451 },
1452 },
1453 oldJob: &batch.Job{
1454 ObjectMeta: metav1.ObjectMeta{
1455 Name: "myjob2",
1456 Namespace: metav1.NamespaceDefault,
1457 Generation: 0,
1458 ResourceVersion: "0",
1459 },
1460 Spec: batch.JobSpec{
1461 CompletionMode: completionModePtr(batch.IndexedCompletion),
1462 Completions: pointer.Int32(100_001),
1463 Parallelism: pointer.Int32(10_000),
1464 Template: validPodTemplateSpec,
1465 },
1466 },
1467 },
1468 }
1469 for val, tc := range cases {
1470 t.Run(val, func(t *testing.T) {
1471 gotWarnings := Strategy.WarningsOnUpdate(ctx, tc.job, tc.oldJob)
1472 if len(gotWarnings) != int(tc.wantWarningsCount) {
1473 t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount)
1474 }
1475 })
1476 }
1477 }
1478 func TestJobStrategy_WarningsOnCreate(t *testing.T) {
1479 ctx := genericapirequest.NewDefaultContext()
1480
1481 theUID := types.UID("1a2b3c4d5e6f7g8h9i0k")
1482 validSelector := &metav1.LabelSelector{
1483 MatchLabels: map[string]string{"a": "b"},
1484 }
1485 validPodTemplate := api.PodTemplateSpec{
1486 ObjectMeta: metav1.ObjectMeta{
1487 Labels: validSelector.MatchLabels,
1488 },
1489 Spec: api.PodSpec{
1490 RestartPolicy: api.RestartPolicyOnFailure,
1491 DNSPolicy: api.DNSClusterFirst,
1492 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1493 },
1494 }
1495 validSpec := batch.JobSpec{
1496 CompletionMode: completionModePtr(batch.NonIndexedCompletion),
1497 Selector: nil,
1498 Template: validPodTemplate,
1499 }
1500
1501 testcases := map[string]struct {
1502 job *batch.Job
1503 wantWarningsCount int32
1504 }{
1505 "happy path job": {
1506 job: &batch.Job{
1507 ObjectMeta: metav1.ObjectMeta{
1508 Name: "myjob2",
1509 Namespace: metav1.NamespaceDefault,
1510 UID: theUID,
1511 },
1512 Spec: validSpec,
1513 },
1514 },
1515 "dns invalid name": {
1516 wantWarningsCount: 1,
1517 job: &batch.Job{
1518 ObjectMeta: metav1.ObjectMeta{
1519 Name: "my job2",
1520 Namespace: metav1.NamespaceDefault,
1521 UID: theUID,
1522 },
1523 Spec: validSpec,
1524 },
1525 },
1526 "high completions and parallelism": {
1527 wantWarningsCount: 1,
1528 job: &batch.Job{
1529 ObjectMeta: metav1.ObjectMeta{
1530 Name: "myjob2",
1531 Namespace: metav1.NamespaceDefault,
1532 UID: theUID,
1533 },
1534 Spec: batch.JobSpec{
1535 CompletionMode: completionModePtr(batch.IndexedCompletion),
1536 Parallelism: pointer.Int32(100_001),
1537 Completions: pointer.Int32(100_001),
1538 Template: validPodTemplate,
1539 },
1540 },
1541 },
1542 }
1543 for name, tc := range testcases {
1544 t.Run(name, func(t *testing.T) {
1545 gotWarnings := Strategy.WarningsOnCreate(ctx, tc.job)
1546 if len(gotWarnings) != int(tc.wantWarningsCount) {
1547 t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount)
1548 }
1549 })
1550 }
1551 }
1552 func TestJobStrategy_Validate(t *testing.T) {
1553 ctx := genericapirequest.NewDefaultContext()
1554
1555 theUID := getValidUID()
1556 validSelector := getValidLabelSelector()
1557 batchLabels := getValidBatchLabels()
1558 labelsWithNonBatch := getValidBatchLabelsWithNonBatch()
1559 defaultSelector := &metav1.LabelSelector{MatchLabels: map[string]string{batch.ControllerUidLabel: string(theUID)}}
1560 validPodSpec := api.PodSpec{
1561 RestartPolicy: api.RestartPolicyOnFailure,
1562 DNSPolicy: api.DNSClusterFirst,
1563 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1564 }
1565 validPodSpecNever := *validPodSpec.DeepCopy()
1566 validPodSpecNever.RestartPolicy = api.RestartPolicyNever
1567 validObjectMeta := getValidObjectMeta(0)
1568 testcases := map[string]struct {
1569 enableJobPodFailurePolicy bool
1570 enableJobBackoffLimitPerIndex bool
1571 job *batch.Job
1572 wantJob *batch.Job
1573 wantWarningCount int32
1574 }{
1575 "valid job with batch labels in pod template": {
1576 job: &batch.Job{
1577 ObjectMeta: validObjectMeta,
1578 Spec: batch.JobSpec{
1579 Selector: defaultSelector,
1580 ManualSelector: pointer.Bool(false),
1581 Template: api.PodTemplateSpec{
1582 ObjectMeta: metav1.ObjectMeta{
1583 Labels: batchLabels,
1584 },
1585 Spec: validPodSpec,
1586 }},
1587 },
1588 wantJob: &batch.Job{
1589 ObjectMeta: validObjectMeta,
1590 Spec: batch.JobSpec{
1591 Selector: defaultSelector,
1592 ManualSelector: pointer.Bool(false),
1593 Template: api.PodTemplateSpec{
1594 ObjectMeta: metav1.ObjectMeta{
1595 Labels: batchLabels,
1596 },
1597 Spec: validPodSpec,
1598 }},
1599 },
1600 },
1601 "valid job with batch and non-batch labels in pod template": {
1602 job: &batch.Job{
1603 ObjectMeta: validObjectMeta,
1604 Spec: batch.JobSpec{
1605 Selector: defaultSelector,
1606 ManualSelector: pointer.Bool(false),
1607 Template: api.PodTemplateSpec{
1608 ObjectMeta: metav1.ObjectMeta{
1609 Labels: labelsWithNonBatch,
1610 },
1611 Spec: validPodSpec,
1612 }},
1613 },
1614 wantJob: &batch.Job{
1615 ObjectMeta: validObjectMeta,
1616 Spec: batch.JobSpec{
1617 Selector: defaultSelector,
1618 ManualSelector: pointer.Bool(false),
1619 Template: api.PodTemplateSpec{
1620 ObjectMeta: metav1.ObjectMeta{
1621 Labels: labelsWithNonBatch,
1622 },
1623 Spec: validPodSpec,
1624 }},
1625 },
1626 },
1627 "job with non-batch labels and without batch labels in pod template": {
1628 job: &batch.Job{
1629 ObjectMeta: validObjectMeta,
1630 Spec: batch.JobSpec{
1631 Selector: defaultSelector,
1632 ManualSelector: pointer.Bool(false),
1633 Template: api.PodTemplateSpec{
1634 ObjectMeta: metav1.ObjectMeta{
1635 Labels: map[string]string{},
1636 },
1637 Spec: validPodSpec,
1638 }},
1639 },
1640 wantJob: &batch.Job{
1641 ObjectMeta: validObjectMeta,
1642 Spec: batch.JobSpec{
1643 Selector: defaultSelector,
1644 ManualSelector: pointer.Bool(false),
1645 Template: api.PodTemplateSpec{
1646 ObjectMeta: metav1.ObjectMeta{
1647 Labels: map[string]string{},
1648 },
1649 Spec: validPodSpec,
1650 }},
1651 },
1652 wantWarningCount: 5,
1653 },
1654 "no labels in job": {
1655 job: &batch.Job{
1656 ObjectMeta: validObjectMeta,
1657 Spec: batch.JobSpec{
1658 Selector: defaultSelector,
1659 Template: api.PodTemplateSpec{
1660 Spec: validPodSpec,
1661 }},
1662 },
1663 wantJob: &batch.Job{
1664 ObjectMeta: validObjectMeta,
1665 Spec: batch.JobSpec{
1666 Selector: defaultSelector,
1667 Template: api.PodTemplateSpec{
1668 Spec: validPodSpec,
1669 }},
1670 },
1671 wantWarningCount: 5,
1672 },
1673 "manual selector; do not generate labels": {
1674 job: &batch.Job{
1675 ObjectMeta: validObjectMeta,
1676 Spec: batch.JobSpec{
1677 Selector: validSelector,
1678 Template: api.PodTemplateSpec{
1679 ObjectMeta: metav1.ObjectMeta{
1680 Labels: validSelector.MatchLabels,
1681 },
1682 Spec: validPodSpec,
1683 },
1684 Completions: pointer.Int32Ptr(2),
1685 ManualSelector: pointer.BoolPtr(true),
1686 },
1687 },
1688 wantJob: &batch.Job{
1689 ObjectMeta: validObjectMeta,
1690 Spec: batch.JobSpec{
1691 Selector: validSelector,
1692 Template: api.PodTemplateSpec{
1693 ObjectMeta: metav1.ObjectMeta{
1694 Labels: validSelector.MatchLabels,
1695 },
1696 Spec: validPodSpec,
1697 },
1698 Completions: pointer.Int32Ptr(2),
1699 ManualSelector: pointer.BoolPtr(true),
1700 },
1701 },
1702 },
1703 "valid job with extended configuration": {
1704 job: &batch.Job{
1705 ObjectMeta: validObjectMeta,
1706 Spec: batch.JobSpec{
1707 Selector: defaultSelector,
1708 ManualSelector: pointer.Bool(false),
1709 Template: api.PodTemplateSpec{
1710 ObjectMeta: metav1.ObjectMeta{
1711 Labels: labelsWithNonBatch,
1712 },
1713 Spec: validPodSpec,
1714 },
1715 Completions: pointer.Int32Ptr(2),
1716 Suspend: pointer.BoolPtr(true),
1717 TTLSecondsAfterFinished: pointer.Int32Ptr(0),
1718 CompletionMode: completionModePtr(batch.IndexedCompletion),
1719 },
1720 },
1721 wantJob: &batch.Job{
1722 ObjectMeta: validObjectMeta,
1723 Spec: batch.JobSpec{
1724 Selector: defaultSelector,
1725 ManualSelector: pointer.Bool(false),
1726 Template: api.PodTemplateSpec{
1727 ObjectMeta: metav1.ObjectMeta{
1728 Labels: labelsWithNonBatch,
1729 },
1730 Spec: validPodSpec,
1731 },
1732 Completions: pointer.Int32Ptr(2),
1733 Suspend: pointer.BoolPtr(true),
1734 TTLSecondsAfterFinished: pointer.Int32Ptr(0),
1735 CompletionMode: completionModePtr(batch.IndexedCompletion),
1736 },
1737 },
1738 },
1739 "fail validation due to invalid volume spec": {
1740 job: &batch.Job{
1741 ObjectMeta: validObjectMeta,
1742 Spec: batch.JobSpec{
1743 Selector: defaultSelector,
1744 ManualSelector: pointer.Bool(false),
1745 Template: api.PodTemplateSpec{
1746 ObjectMeta: metav1.ObjectMeta{
1747 Labels: labelsWithNonBatch,
1748 },
1749 Spec: api.PodSpec{
1750 RestartPolicy: api.RestartPolicyOnFailure,
1751 DNSPolicy: api.DNSClusterFirst,
1752 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1753 Volumes: []api.Volume{{Name: "volume-name"}},
1754 },
1755 },
1756 },
1757 },
1758 wantJob: &batch.Job{
1759 ObjectMeta: validObjectMeta,
1760 Spec: batch.JobSpec{
1761 Selector: defaultSelector,
1762 ManualSelector: pointer.Bool(false),
1763 Template: api.PodTemplateSpec{
1764 ObjectMeta: metav1.ObjectMeta{
1765 Labels: labelsWithNonBatch,
1766 },
1767 Spec: api.PodSpec{
1768 RestartPolicy: api.RestartPolicyOnFailure,
1769 DNSPolicy: api.DNSClusterFirst,
1770 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1771 Volumes: []api.Volume{{Name: "volume-name"}},
1772 },
1773 },
1774 },
1775 },
1776 wantWarningCount: 1,
1777 },
1778 "FailIndex action; when JobBackoffLimitPerIndex is disabled - validation error": {
1779 enableJobPodFailurePolicy: true,
1780 enableJobBackoffLimitPerIndex: false,
1781 job: &batch.Job{
1782 ObjectMeta: validObjectMeta,
1783 Spec: batch.JobSpec{
1784 Selector: validSelector,
1785 ManualSelector: pointer.Bool(true),
1786 Template: api.PodTemplateSpec{
1787 ObjectMeta: metav1.ObjectMeta{
1788 Labels: validSelector.MatchLabels,
1789 },
1790 Spec: validPodSpecNever,
1791 },
1792 PodFailurePolicy: &batch.PodFailurePolicy{
1793 Rules: []batch.PodFailurePolicyRule{
1794 {
1795 Action: batch.PodFailurePolicyActionFailIndex,
1796 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1797 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1798 Values: []int32{1},
1799 },
1800 },
1801 },
1802 },
1803 },
1804 },
1805 wantJob: &batch.Job{
1806 ObjectMeta: validObjectMeta,
1807 Spec: batch.JobSpec{
1808 Selector: validSelector,
1809 ManualSelector: pointer.Bool(true),
1810 Template: api.PodTemplateSpec{
1811 ObjectMeta: metav1.ObjectMeta{
1812 Labels: validSelector.MatchLabels,
1813 },
1814 Spec: validPodSpecNever,
1815 },
1816 PodFailurePolicy: &batch.PodFailurePolicy{
1817 Rules: []batch.PodFailurePolicyRule{
1818 {
1819 Action: batch.PodFailurePolicyActionFailIndex,
1820 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1821 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1822 Values: []int32{1},
1823 },
1824 },
1825 },
1826 },
1827 },
1828 },
1829 wantWarningCount: 1,
1830 },
1831 "FailIndex action; when JobBackoffLimitPerIndex is enabled, but not used - validation error": {
1832 enableJobPodFailurePolicy: true,
1833 enableJobBackoffLimitPerIndex: true,
1834 job: &batch.Job{
1835 ObjectMeta: validObjectMeta,
1836 Spec: batch.JobSpec{
1837 Selector: validSelector,
1838 ManualSelector: pointer.Bool(true),
1839 Template: api.PodTemplateSpec{
1840 ObjectMeta: metav1.ObjectMeta{
1841 Labels: validSelector.MatchLabels,
1842 },
1843 Spec: validPodSpecNever,
1844 },
1845 PodFailurePolicy: &batch.PodFailurePolicy{
1846 Rules: []batch.PodFailurePolicyRule{
1847 {
1848 Action: batch.PodFailurePolicyActionFailIndex,
1849 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1850 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1851 Values: []int32{1},
1852 },
1853 },
1854 },
1855 },
1856 },
1857 },
1858 wantJob: &batch.Job{
1859 ObjectMeta: validObjectMeta,
1860 Spec: batch.JobSpec{
1861 Selector: validSelector,
1862 ManualSelector: pointer.Bool(true),
1863 Template: api.PodTemplateSpec{
1864 ObjectMeta: metav1.ObjectMeta{
1865 Labels: validSelector.MatchLabels,
1866 },
1867 Spec: validPodSpecNever,
1868 },
1869 PodFailurePolicy: &batch.PodFailurePolicy{
1870 Rules: []batch.PodFailurePolicyRule{
1871 {
1872 Action: batch.PodFailurePolicyActionFailIndex,
1873 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1874 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1875 Values: []int32{1},
1876 },
1877 },
1878 },
1879 },
1880 },
1881 },
1882 wantWarningCount: 1,
1883 },
1884 "FailIndex action; when JobBackoffLimitPerIndex is enabled and used - no error": {
1885 enableJobPodFailurePolicy: true,
1886 enableJobBackoffLimitPerIndex: true,
1887 job: &batch.Job{
1888 ObjectMeta: validObjectMeta,
1889 Spec: batch.JobSpec{
1890 CompletionMode: completionModePtr(batch.IndexedCompletion),
1891 Completions: pointer.Int32(2),
1892 BackoffLimitPerIndex: pointer.Int32(1),
1893 Selector: validSelector,
1894 ManualSelector: pointer.Bool(true),
1895 Template: api.PodTemplateSpec{
1896 ObjectMeta: metav1.ObjectMeta{
1897 Labels: validSelector.MatchLabels,
1898 },
1899 Spec: validPodSpecNever,
1900 },
1901 PodFailurePolicy: &batch.PodFailurePolicy{
1902 Rules: []batch.PodFailurePolicyRule{
1903 {
1904 Action: batch.PodFailurePolicyActionFailIndex,
1905 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1906 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1907 Values: []int32{1},
1908 },
1909 },
1910 },
1911 },
1912 },
1913 },
1914 wantJob: &batch.Job{
1915 ObjectMeta: validObjectMeta,
1916 Spec: batch.JobSpec{
1917 CompletionMode: completionModePtr(batch.IndexedCompletion),
1918 Completions: pointer.Int32(2),
1919 BackoffLimitPerIndex: pointer.Int32(1),
1920 Selector: validSelector,
1921 ManualSelector: pointer.Bool(true),
1922 Template: api.PodTemplateSpec{
1923 ObjectMeta: metav1.ObjectMeta{
1924 Labels: validSelector.MatchLabels,
1925 },
1926 Spec: validPodSpecNever,
1927 },
1928 PodFailurePolicy: &batch.PodFailurePolicy{
1929 Rules: []batch.PodFailurePolicyRule{
1930 {
1931 Action: batch.PodFailurePolicyActionFailIndex,
1932 OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
1933 Operator: batch.PodFailurePolicyOnExitCodesOpIn,
1934 Values: []int32{1},
1935 },
1936 },
1937 },
1938 },
1939 },
1940 },
1941 },
1942 }
1943 for name, tc := range testcases {
1944 t.Run(name, func(t *testing.T) {
1945 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
1946 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
1947 errs := Strategy.Validate(ctx, tc.job)
1948 if len(errs) != int(tc.wantWarningCount) {
1949 t.Errorf("want warnings %d but got %d, errors: %v", tc.wantWarningCount, len(errs), errs)
1950 }
1951 if diff := cmp.Diff(tc.wantJob, tc.job); diff != "" {
1952 t.Errorf("Unexpected job (-want,+got):\n%s", diff)
1953 }
1954 })
1955 }
1956 }
1957
1958 func TestStrategy_ResetFields(t *testing.T) {
1959 resetFields := Strategy.GetResetFields()
1960 if len(resetFields) != 1 {
1961 t.Errorf("ResetFields should have 1 element, but have %d", len(resetFields))
1962 }
1963 }
1964
1965 func TestJobStatusStrategy_ResetFields(t *testing.T) {
1966 resetFields := StatusStrategy.GetResetFields()
1967 if len(resetFields) != 1 {
1968 t.Errorf("ResetFields should have 1 element, but have %d", len(resetFields))
1969 }
1970 }
1971
1972 func TestStatusStrategy_PrepareForUpdate(t *testing.T) {
1973 ctx := genericapirequest.NewDefaultContext()
1974 validSelector := &metav1.LabelSelector{
1975 MatchLabels: map[string]string{"a": "b"},
1976 }
1977 validPodTemplateSpec := api.PodTemplateSpec{
1978 ObjectMeta: metav1.ObjectMeta{
1979 Labels: validSelector.MatchLabels,
1980 },
1981 Spec: api.PodSpec{
1982 RestartPolicy: api.RestartPolicyOnFailure,
1983 DNSPolicy: api.DNSClusterFirst,
1984 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1985 },
1986 }
1987 validObjectMeta := metav1.ObjectMeta{
1988 Name: "myjob",
1989 Namespace: metav1.NamespaceDefault,
1990 ResourceVersion: "10",
1991 }
1992
1993 cases := map[string]struct {
1994 job *batch.Job
1995 newJob *batch.Job
1996 wantJob *batch.Job
1997 }{
1998 "job must allow status updates": {
1999 job: &batch.Job{
2000 ObjectMeta: validObjectMeta,
2001 Spec: batch.JobSpec{
2002 Selector: validSelector,
2003 Template: validPodTemplateSpec,
2004 Parallelism: pointer.Int32(4),
2005 },
2006 Status: batch.JobStatus{
2007 Active: 11,
2008 },
2009 },
2010 newJob: &batch.Job{
2011 ObjectMeta: validObjectMeta,
2012 Spec: batch.JobSpec{
2013 Selector: validSelector,
2014 Template: validPodTemplateSpec,
2015 Parallelism: pointer.Int32(4),
2016 },
2017 Status: batch.JobStatus{
2018 Active: 12,
2019 },
2020 },
2021 wantJob: &batch.Job{
2022 ObjectMeta: validObjectMeta,
2023 Spec: batch.JobSpec{
2024 Selector: validSelector,
2025 Template: validPodTemplateSpec,
2026 Parallelism: pointer.Int32(4),
2027 },
2028 Status: batch.JobStatus{
2029 Active: 12,
2030 },
2031 },
2032 },
2033 "parallelism changes not allowed": {
2034 job: &batch.Job{
2035 ObjectMeta: validObjectMeta,
2036 Spec: batch.JobSpec{
2037 Selector: validSelector,
2038 Template: validPodTemplateSpec,
2039 Parallelism: pointer.Int32(3),
2040 },
2041 },
2042 newJob: &batch.Job{
2043 ObjectMeta: validObjectMeta,
2044 Spec: batch.JobSpec{
2045 Selector: validSelector,
2046 Template: validPodTemplateSpec,
2047 Parallelism: pointer.Int32(4),
2048 },
2049 },
2050 wantJob: &batch.Job{
2051 ObjectMeta: validObjectMeta,
2052 Spec: batch.JobSpec{
2053 Selector: validSelector,
2054 Template: validPodTemplateSpec,
2055 Parallelism: pointer.Int32(3),
2056 },
2057 },
2058 },
2059 }
2060 for name, tc := range cases {
2061 t.Run(name, func(t *testing.T) {
2062 StatusStrategy.PrepareForUpdate(ctx, tc.newJob, tc.job)
2063 if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" {
2064 t.Errorf("Unexpected job (-want,+got):\n%s", diff)
2065 }
2066 })
2067 }
2068 }
2069
2070 func TestStatusStrategy_ValidateUpdate(t *testing.T) {
2071 ctx := genericapirequest.NewDefaultContext()
2072 validSelector := &metav1.LabelSelector{
2073 MatchLabels: map[string]string{"a": "b"},
2074 }
2075 validPodTemplateSpec := api.PodTemplateSpec{
2076 ObjectMeta: metav1.ObjectMeta{
2077 Labels: validSelector.MatchLabels,
2078 },
2079 Spec: api.PodSpec{
2080 RestartPolicy: api.RestartPolicyOnFailure,
2081 DNSPolicy: api.DNSClusterFirst,
2082 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2083 },
2084 }
2085 validObjectMeta := metav1.ObjectMeta{
2086 Name: "myjob",
2087 Namespace: metav1.NamespaceDefault,
2088 ResourceVersion: "10",
2089 }
2090 validSuccessPolicy := &batch.SuccessPolicy{
2091 Rules: []batch.SuccessPolicyRule{{
2092 SucceededIndexes: ptr.To("0-2"),
2093 }},
2094 }
2095 now := metav1.Now()
2096 nowPlusMinute := metav1.Time{Time: now.Add(time.Minute)}
2097
2098 cases := map[string]struct {
2099 enableJobManagedBy bool
2100 enableJobSuccessPolicy bool
2101
2102 job *batch.Job
2103 newJob *batch.Job
2104 wantJob *batch.Job
2105 wantErrs field.ErrorList
2106 }{
2107 "incoming resource version on update should not be mutated": {
2108 job: &batch.Job{
2109 ObjectMeta: metav1.ObjectMeta{
2110 Name: "myjob",
2111 Namespace: metav1.NamespaceDefault,
2112 ResourceVersion: "10",
2113 },
2114 Spec: batch.JobSpec{
2115 Selector: validSelector,
2116 Template: validPodTemplateSpec,
2117 Parallelism: pointer.Int32(4),
2118 },
2119 },
2120 newJob: &batch.Job{
2121 ObjectMeta: metav1.ObjectMeta{
2122 Name: "myjob",
2123 Namespace: metav1.NamespaceDefault,
2124 ResourceVersion: "9",
2125 },
2126 Spec: batch.JobSpec{
2127 Selector: validSelector,
2128 Template: validPodTemplateSpec,
2129 Parallelism: pointer.Int32(4),
2130 },
2131 },
2132 wantJob: &batch.Job{
2133 ObjectMeta: metav1.ObjectMeta{
2134 Name: "myjob",
2135 Namespace: metav1.NamespaceDefault,
2136 ResourceVersion: "9",
2137 },
2138 Spec: batch.JobSpec{
2139 Selector: validSelector,
2140 Template: validPodTemplateSpec,
2141 Parallelism: pointer.Int32(4),
2142 },
2143 },
2144 },
2145 "invalid addition of both Failed=True and Complete=True; allowed because feature gate disabled": {
2146 enableJobManagedBy: false,
2147 job: &batch.Job{
2148 ObjectMeta: validObjectMeta,
2149 },
2150 newJob: &batch.Job{
2151 ObjectMeta: validObjectMeta,
2152 Status: batch.JobStatus{
2153 StartTime: &now,
2154 CompletionTime: &now,
2155 Conditions: []batch.JobCondition{
2156 {
2157 Type: batch.JobComplete,
2158 Status: api.ConditionTrue,
2159 },
2160 {
2161 Type: batch.JobFailed,
2162 Status: api.ConditionTrue,
2163 },
2164 },
2165 },
2166 },
2167 },
2168 "invalid addition of both Failed=True and Complete=True": {
2169 enableJobManagedBy: true,
2170 job: &batch.Job{
2171 ObjectMeta: validObjectMeta,
2172 },
2173 newJob: &batch.Job{
2174 ObjectMeta: validObjectMeta,
2175 Status: batch.JobStatus{
2176 StartTime: &now,
2177 CompletionTime: &now,
2178 Conditions: []batch.JobCondition{
2179 {
2180 Type: batch.JobComplete,
2181 Status: api.ConditionTrue,
2182 },
2183 {
2184 Type: batch.JobFailed,
2185 Status: api.ConditionTrue,
2186 },
2187 },
2188 },
2189 },
2190 wantErrs: field.ErrorList{
2191 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2192 },
2193 },
2194 "completionTime can be removed to fix still running job": {
2195 enableJobManagedBy: true,
2196 job: &batch.Job{
2197 ObjectMeta: validObjectMeta,
2198 Status: batch.JobStatus{
2199 StartTime: &now,
2200 CompletionTime: &now,
2201 },
2202 },
2203 newJob: &batch.Job{
2204 ObjectMeta: validObjectMeta,
2205 Status: batch.JobStatus{
2206 StartTime: &now,
2207 },
2208 },
2209 },
2210 "invalid attempt to transition to Failed=True without startTime": {
2211 enableJobManagedBy: true,
2212 job: &batch.Job{
2213 ObjectMeta: validObjectMeta,
2214 },
2215 newJob: &batch.Job{
2216 ObjectMeta: validObjectMeta,
2217 Status: batch.JobStatus{
2218 Conditions: []batch.JobCondition{
2219 {
2220 Type: batch.JobFailed,
2221 Status: api.ConditionTrue,
2222 },
2223 },
2224 },
2225 },
2226 wantErrs: field.ErrorList{
2227 {Type: field.ErrorTypeRequired, Field: "status.startTime"},
2228 },
2229 },
2230 "invalid attempt to transition to Complete=True without startTime": {
2231 enableJobManagedBy: true,
2232 job: &batch.Job{
2233 ObjectMeta: validObjectMeta,
2234 },
2235 newJob: &batch.Job{
2236 ObjectMeta: validObjectMeta,
2237 Status: batch.JobStatus{
2238 CompletionTime: &now,
2239 Conditions: []batch.JobCondition{
2240 {
2241 Type: batch.JobComplete,
2242 Status: api.ConditionTrue,
2243 },
2244 },
2245 },
2246 },
2247 wantErrs: field.ErrorList{
2248 {Type: field.ErrorTypeRequired, Field: "status.startTime"},
2249 },
2250 },
2251 "invalid attempt to transition to Complete=True with active > 0": {
2252 enableJobManagedBy: true,
2253 job: &batch.Job{
2254 ObjectMeta: validObjectMeta,
2255 },
2256 newJob: &batch.Job{
2257 ObjectMeta: validObjectMeta,
2258 Status: batch.JobStatus{
2259 StartTime: &now,
2260 CompletionTime: &now,
2261 Active: 1,
2262 Conditions: []batch.JobCondition{
2263 {
2264 Type: batch.JobComplete,
2265 Status: api.ConditionTrue,
2266 },
2267 },
2268 },
2269 },
2270 wantErrs: field.ErrorList{
2271 {Type: field.ErrorTypeInvalid, Field: "status.active"},
2272 },
2273 },
2274 "transition to Failed condition with terminating>0 and ready>0": {
2275 enableJobManagedBy: true,
2276 job: &batch.Job{
2277 ObjectMeta: validObjectMeta,
2278 },
2279 newJob: &batch.Job{
2280 ObjectMeta: validObjectMeta,
2281 Status: batch.JobStatus{
2282 StartTime: &now,
2283 Conditions: []batch.JobCondition{
2284 {
2285 Type: batch.JobFailed,
2286 Status: api.ConditionTrue,
2287 },
2288 },
2289 Terminating: ptr.To[int32](1),
2290 Ready: ptr.To[int32](1),
2291 },
2292 },
2293 },
2294 "invalid attempt to transition to Failed=True with uncountedTerminatedPods.Failed>0": {
2295 enableJobManagedBy: true,
2296 job: &batch.Job{
2297 ObjectMeta: validObjectMeta,
2298 },
2299 newJob: &batch.Job{
2300 ObjectMeta: validObjectMeta,
2301 Status: batch.JobStatus{
2302 StartTime: &now,
2303 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2304 Failed: []types.UID{"a"},
2305 },
2306 Conditions: []batch.JobCondition{
2307 {
2308 Type: batch.JobFailed,
2309 Status: api.ConditionTrue,
2310 },
2311 },
2312 },
2313 },
2314 wantErrs: field.ErrorList{
2315 {Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods"},
2316 },
2317 },
2318 "invalid attempt to update uncountedTerminatedPods.Succeeded for Complete job": {
2319 enableJobManagedBy: true,
2320 job: &batch.Job{
2321 ObjectMeta: validObjectMeta,
2322 Status: batch.JobStatus{
2323 StartTime: &now,
2324 CompletionTime: &now,
2325 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2326 Failed: []types.UID{"a"},
2327 },
2328 Conditions: []batch.JobCondition{
2329 {
2330 Type: batch.JobComplete,
2331 Status: api.ConditionTrue,
2332 },
2333 },
2334 },
2335 },
2336 newJob: &batch.Job{
2337 ObjectMeta: validObjectMeta,
2338 Status: batch.JobStatus{
2339 StartTime: &now,
2340 CompletionTime: &now,
2341 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2342 Failed: []types.UID{"b"},
2343 },
2344 Conditions: []batch.JobCondition{
2345 {
2346 Type: batch.JobComplete,
2347 Status: api.ConditionTrue,
2348 },
2349 },
2350 },
2351 },
2352 wantErrs: field.ErrorList{
2353 {Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods"},
2354 },
2355 },
2356 "non-empty uncountedTerminatedPods for complete job, unrelated update": {
2357 enableJobManagedBy: true,
2358 job: &batch.Job{
2359 ObjectMeta: validObjectMeta,
2360 Status: batch.JobStatus{
2361 StartTime: &now,
2362 CompletionTime: &now,
2363 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2364 Failed: []types.UID{"a"},
2365 },
2366 Conditions: []batch.JobCondition{
2367 {
2368 Type: batch.JobComplete,
2369 Status: api.ConditionTrue,
2370 },
2371 },
2372 },
2373 },
2374 newJob: &batch.Job{
2375 ObjectMeta: validObjectMeta,
2376 Status: batch.JobStatus{
2377 StartTime: &now,
2378 CompletionTime: &now,
2379 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2380 Failed: []types.UID{"a"},
2381 },
2382 Conditions: []batch.JobCondition{
2383 {
2384 Type: batch.JobComplete,
2385 Status: api.ConditionTrue,
2386 },
2387 {
2388 Type: batch.JobConditionType("CustomJobCondition"),
2389 Status: api.ConditionTrue,
2390 },
2391 },
2392 },
2393 },
2394 },
2395 "invalid attempt to transition to Complete=True with uncountedTerminatedPods.Succeeded>0": {
2396 enableJobManagedBy: true,
2397 job: &batch.Job{
2398 ObjectMeta: validObjectMeta,
2399 },
2400 newJob: &batch.Job{
2401 ObjectMeta: validObjectMeta,
2402 Status: batch.JobStatus{
2403 StartTime: &now,
2404 CompletionTime: &now,
2405 UncountedTerminatedPods: &batch.UncountedTerminatedPods{
2406 Succeeded: []types.UID{"a"},
2407 },
2408 Conditions: []batch.JobCondition{
2409 {
2410 Type: batch.JobComplete,
2411 Status: api.ConditionTrue,
2412 },
2413 },
2414 },
2415 },
2416 wantErrs: field.ErrorList{
2417 {Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods"},
2418 },
2419 },
2420 "invalid addition Complete=True without setting CompletionTime": {
2421 enableJobManagedBy: true,
2422 job: &batch.Job{
2423 ObjectMeta: validObjectMeta,
2424 },
2425 newJob: &batch.Job{
2426 ObjectMeta: validObjectMeta,
2427 Status: batch.JobStatus{
2428 StartTime: &now,
2429 Conditions: []batch.JobCondition{
2430 {
2431 Type: batch.JobComplete,
2432 Status: api.ConditionTrue,
2433 },
2434 },
2435 },
2436 },
2437 wantErrs: field.ErrorList{
2438 {Type: field.ErrorTypeRequired, Field: "status.completionTime"},
2439 },
2440 },
2441 "invalid attempt to remove completionTime": {
2442 enableJobManagedBy: true,
2443 job: &batch.Job{
2444 ObjectMeta: validObjectMeta,
2445 Status: batch.JobStatus{
2446 CompletionTime: &now,
2447 Conditions: []batch.JobCondition{
2448 {
2449 Type: batch.JobComplete,
2450 Status: api.ConditionTrue,
2451 },
2452 },
2453 },
2454 },
2455 newJob: &batch.Job{
2456 ObjectMeta: validObjectMeta,
2457 Status: batch.JobStatus{
2458 CompletionTime: nil,
2459 StartTime: &now,
2460 Conditions: []batch.JobCondition{
2461 {
2462 Type: batch.JobComplete,
2463 Status: api.ConditionTrue,
2464 },
2465 },
2466 },
2467 },
2468 wantErrs: field.ErrorList{
2469 {Type: field.ErrorTypeRequired, Field: "status.completionTime"},
2470 },
2471 },
2472 "verify startTime can be cleared for suspended job": {
2473 enableJobManagedBy: true,
2474 job: &batch.Job{
2475 ObjectMeta: validObjectMeta,
2476 Spec: batch.JobSpec{
2477 Suspend: ptr.To(true),
2478 },
2479 Status: batch.JobStatus{
2480 StartTime: &now,
2481 },
2482 },
2483 newJob: &batch.Job{
2484 ObjectMeta: validObjectMeta,
2485 Spec: batch.JobSpec{
2486 Suspend: ptr.To(true),
2487 },
2488 Status: batch.JobStatus{
2489 StartTime: nil,
2490 },
2491 },
2492 },
2493 "verify startTime cannot be removed for unsuspended job": {
2494 enableJobManagedBy: true,
2495 job: &batch.Job{
2496 ObjectMeta: validObjectMeta,
2497 Status: batch.JobStatus{
2498 StartTime: &now,
2499 },
2500 },
2501 newJob: &batch.Job{
2502 ObjectMeta: validObjectMeta,
2503 Status: batch.JobStatus{
2504 StartTime: nil,
2505 },
2506 },
2507 wantErrs: field.ErrorList{
2508 {Type: field.ErrorTypeRequired, Field: "status.startTime"},
2509 },
2510 },
2511 "verify startTime cannot be updated for unsuspended job": {
2512 enableJobManagedBy: true,
2513 job: &batch.Job{
2514 ObjectMeta: validObjectMeta,
2515 Status: batch.JobStatus{
2516 StartTime: &now,
2517 },
2518 },
2519 newJob: &batch.Job{
2520 ObjectMeta: validObjectMeta,
2521 Status: batch.JobStatus{
2522 StartTime: &nowPlusMinute,
2523 },
2524 },
2525 wantErrs: field.ErrorList{
2526 {Type: field.ErrorTypeRequired, Field: "status.startTime"},
2527 },
2528 },
2529 "invalid attempt to set completionTime before startTime": {
2530 enableJobManagedBy: true,
2531 job: &batch.Job{
2532 ObjectMeta: validObjectMeta,
2533 Status: batch.JobStatus{
2534 StartTime: &nowPlusMinute,
2535 },
2536 },
2537 newJob: &batch.Job{
2538 ObjectMeta: validObjectMeta,
2539 Status: batch.JobStatus{
2540 StartTime: &nowPlusMinute,
2541 CompletionTime: &now,
2542 Conditions: []batch.JobCondition{
2543 {
2544 Type: batch.JobComplete,
2545 Status: api.ConditionTrue,
2546 },
2547 },
2548 },
2549 },
2550 wantErrs: field.ErrorList{
2551 {Type: field.ErrorTypeInvalid, Field: "status.completionTime"},
2552 },
2553 },
2554 "invalid attempt to modify completionTime": {
2555 enableJobManagedBy: true,
2556 job: &batch.Job{
2557 ObjectMeta: validObjectMeta,
2558 Status: batch.JobStatus{
2559 CompletionTime: &now,
2560 Conditions: []batch.JobCondition{
2561 {
2562 Type: batch.JobComplete,
2563 Status: api.ConditionTrue,
2564 },
2565 },
2566 },
2567 },
2568 newJob: &batch.Job{
2569 ObjectMeta: validObjectMeta,
2570 Status: batch.JobStatus{
2571 CompletionTime: &nowPlusMinute,
2572 StartTime: &now,
2573 Conditions: []batch.JobCondition{
2574 {
2575 Type: batch.JobComplete,
2576 Status: api.ConditionTrue,
2577 },
2578 },
2579 },
2580 },
2581 wantErrs: field.ErrorList{
2582 {Type: field.ErrorTypeInvalid, Field: "status.completionTime"},
2583 },
2584 },
2585 "invalid removal of terminal condition Failed=True": {
2586 enableJobManagedBy: true,
2587 job: &batch.Job{
2588 ObjectMeta: validObjectMeta,
2589 Status: batch.JobStatus{
2590 Conditions: []batch.JobCondition{
2591 {
2592 Type: batch.JobFailed,
2593 Status: api.ConditionTrue,
2594 },
2595 },
2596 },
2597 },
2598 newJob: &batch.Job{
2599 ObjectMeta: validObjectMeta,
2600 },
2601 wantErrs: field.ErrorList{
2602 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2603 },
2604 },
2605 "invalid removal of terminal condition Complete=True": {
2606 enableJobManagedBy: true,
2607 job: &batch.Job{
2608 ObjectMeta: validObjectMeta,
2609 Status: batch.JobStatus{
2610 Conditions: []batch.JobCondition{
2611 {
2612 Type: batch.JobComplete,
2613 Status: api.ConditionTrue,
2614 },
2615 },
2616 },
2617 },
2618 newJob: &batch.Job{
2619 ObjectMeta: validObjectMeta,
2620 },
2621 wantErrs: field.ErrorList{
2622 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2623 },
2624 },
2625 "invalid removal of terminal condition FailureTarget=True": {
2626 enableJobManagedBy: true,
2627 job: &batch.Job{
2628 ObjectMeta: validObjectMeta,
2629 Status: batch.JobStatus{
2630 Conditions: []batch.JobCondition{
2631 {
2632 Type: batch.JobFailureTarget,
2633 Status: api.ConditionTrue,
2634 },
2635 },
2636 },
2637 },
2638 newJob: &batch.Job{
2639 ObjectMeta: validObjectMeta,
2640 },
2641 wantErrs: field.ErrorList{
2642 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2643 },
2644 },
2645 "invalid addition of FailureTarget=True when Complete=True": {
2646 enableJobManagedBy: true,
2647 job: &batch.Job{
2648 ObjectMeta: validObjectMeta,
2649 Status: batch.JobStatus{
2650 StartTime: &now,
2651 CompletionTime: &now,
2652 Conditions: []batch.JobCondition{
2653 {
2654 Type: batch.JobComplete,
2655 Status: api.ConditionTrue,
2656 },
2657 },
2658 },
2659 },
2660 newJob: &batch.Job{
2661 ObjectMeta: validObjectMeta,
2662 Status: batch.JobStatus{
2663 StartTime: &now,
2664 CompletionTime: &now,
2665 Conditions: []batch.JobCondition{
2666 {
2667 Type: batch.JobComplete,
2668 Status: api.ConditionTrue,
2669 },
2670 {
2671 Type: batch.JobFailureTarget,
2672 Status: api.ConditionTrue,
2673 },
2674 },
2675 },
2676 },
2677 wantErrs: field.ErrorList{
2678 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2679 },
2680 },
2681 "invalid attempt setting of CompletionTime when there is no Complete condition": {
2682 enableJobManagedBy: true,
2683 job: &batch.Job{
2684 ObjectMeta: validObjectMeta,
2685 },
2686 newJob: &batch.Job{
2687 ObjectMeta: validObjectMeta,
2688 Status: batch.JobStatus{
2689 CompletionTime: &now,
2690 },
2691 },
2692 wantErrs: field.ErrorList{
2693 {Type: field.ErrorTypeInvalid, Field: "status.completionTime"},
2694 },
2695 },
2696 "invalid CompletionTime when there is no Complete condition, but allowed": {
2697 enableJobManagedBy: true,
2698 job: &batch.Job{
2699 ObjectMeta: validObjectMeta,
2700 Status: batch.JobStatus{
2701 CompletionTime: &now,
2702 },
2703 },
2704 newJob: &batch.Job{
2705 ObjectMeta: validObjectMeta,
2706 Status: batch.JobStatus{
2707 CompletionTime: &now,
2708 Active: 1,
2709 },
2710 },
2711 },
2712 "invalid attempt setting CompletedIndexes when non-indexed completion mode is used": {
2713 enableJobManagedBy: true,
2714 job: &batch.Job{
2715 ObjectMeta: validObjectMeta,
2716 Spec: batch.JobSpec{
2717 Completions: ptr.To[int32](5),
2718 CompletionMode: completionModePtr(batch.NonIndexedCompletion),
2719 },
2720 },
2721 newJob: &batch.Job{
2722 ObjectMeta: validObjectMeta,
2723 Spec: batch.JobSpec{
2724 Completions: ptr.To[int32](5),
2725 CompletionMode: completionModePtr(batch.NonIndexedCompletion),
2726 },
2727 Status: batch.JobStatus{
2728 StartTime: &now,
2729 CompletedIndexes: "0",
2730 },
2731 },
2732 wantErrs: field.ErrorList{
2733 {Type: field.ErrorTypeInvalid, Field: "status.completedIndexes"},
2734 },
2735 },
2736 "invalid because CompletedIndexes set when non-indexed completion mode is used; but allowed": {
2737 enableJobManagedBy: true,
2738 job: &batch.Job{
2739 ObjectMeta: validObjectMeta,
2740 Spec: batch.JobSpec{
2741 Completions: ptr.To[int32](5),
2742 CompletionMode: completionModePtr(batch.NonIndexedCompletion),
2743 },
2744 Status: batch.JobStatus{
2745 CompletedIndexes: "0",
2746 },
2747 },
2748 newJob: &batch.Job{
2749 ObjectMeta: validObjectMeta,
2750 Spec: batch.JobSpec{
2751 Completions: ptr.To[int32](5),
2752 CompletionMode: completionModePtr(batch.NonIndexedCompletion),
2753 },
2754 Status: batch.JobStatus{
2755 CompletedIndexes: "0",
2756 Active: 1,
2757 },
2758 },
2759 },
2760 "invalid attempt setting FailedIndexes when not backoffLimitPerIndex": {
2761 enableJobManagedBy: true,
2762 job: &batch.Job{
2763 ObjectMeta: validObjectMeta,
2764 Spec: batch.JobSpec{
2765 Completions: ptr.To[int32](5),
2766 CompletionMode: completionModePtr(batch.IndexedCompletion),
2767 },
2768 },
2769 newJob: &batch.Job{
2770 ObjectMeta: validObjectMeta,
2771 Spec: batch.JobSpec{
2772 Completions: ptr.To[int32](5),
2773 CompletionMode: completionModePtr(batch.IndexedCompletion),
2774 },
2775 Status: batch.JobStatus{
2776 FailedIndexes: ptr.To("0"),
2777 },
2778 },
2779 wantErrs: field.ErrorList{
2780 {Type: field.ErrorTypeInvalid, Field: "status.failedIndexes"},
2781 },
2782 },
2783 "invalid attempt to decrease the failed counter": {
2784 enableJobManagedBy: true,
2785 job: &batch.Job{
2786 ObjectMeta: validObjectMeta,
2787 Spec: batch.JobSpec{
2788 Completions: ptr.To[int32](5),
2789 },
2790 Status: batch.JobStatus{
2791 Failed: 3,
2792 },
2793 },
2794 newJob: &batch.Job{
2795 ObjectMeta: validObjectMeta,
2796 Spec: batch.JobSpec{
2797 Completions: ptr.To[int32](5),
2798 },
2799 Status: batch.JobStatus{
2800 Failed: 1,
2801 },
2802 },
2803 wantErrs: field.ErrorList{
2804 {Type: field.ErrorTypeInvalid, Field: "status.failed"},
2805 },
2806 },
2807 "invalid attempt to decrease the succeeded counter": {
2808 enableJobManagedBy: true,
2809 job: &batch.Job{
2810 ObjectMeta: validObjectMeta,
2811 Spec: batch.JobSpec{
2812 Completions: ptr.To[int32](5),
2813 },
2814 Status: batch.JobStatus{
2815 Succeeded: 3,
2816 },
2817 },
2818 newJob: &batch.Job{
2819 ObjectMeta: validObjectMeta,
2820 Spec: batch.JobSpec{
2821 Completions: ptr.To[int32](5),
2822 },
2823 Status: batch.JobStatus{
2824 Succeeded: 1,
2825 },
2826 },
2827 wantErrs: field.ErrorList{
2828 {Type: field.ErrorTypeInvalid, Field: "status.succeeded"},
2829 },
2830 },
2831 "invalid attempt to set bad format for CompletedIndexes": {
2832 enableJobManagedBy: true,
2833 job: &batch.Job{
2834 ObjectMeta: validObjectMeta,
2835 Spec: batch.JobSpec{
2836 Completions: ptr.To[int32](5),
2837 CompletionMode: completionModePtr(batch.IndexedCompletion),
2838 },
2839 },
2840 newJob: &batch.Job{
2841 ObjectMeta: validObjectMeta,
2842 Spec: batch.JobSpec{
2843 Completions: ptr.To[int32](5),
2844 CompletionMode: completionModePtr(batch.IndexedCompletion),
2845 },
2846 Status: batch.JobStatus{
2847 CompletedIndexes: "invalid format",
2848 },
2849 },
2850 wantErrs: field.ErrorList{
2851 {Type: field.ErrorTypeInvalid, Field: "status.completedIndexes"},
2852 },
2853 },
2854 "invalid format for CompletedIndexes, but allowed": {
2855 enableJobManagedBy: true,
2856 job: &batch.Job{
2857 ObjectMeta: validObjectMeta,
2858 Spec: batch.JobSpec{
2859 Completions: ptr.To[int32](5),
2860 CompletionMode: completionModePtr(batch.IndexedCompletion),
2861 },
2862 Status: batch.JobStatus{
2863 CompletedIndexes: "invalid format",
2864 },
2865 },
2866 newJob: &batch.Job{
2867 ObjectMeta: validObjectMeta,
2868 Spec: batch.JobSpec{
2869 Completions: ptr.To[int32](5),
2870 CompletionMode: completionModePtr(batch.IndexedCompletion),
2871 },
2872 Status: batch.JobStatus{
2873 CompletedIndexes: "invalid format",
2874 Active: 1,
2875 },
2876 },
2877 },
2878 "invalid attempt to set bad format for FailedIndexes": {
2879 enableJobManagedBy: true,
2880 job: &batch.Job{
2881 ObjectMeta: validObjectMeta,
2882 Spec: batch.JobSpec{
2883 Completions: ptr.To[int32](5),
2884 CompletionMode: completionModePtr(batch.IndexedCompletion),
2885 BackoffLimitPerIndex: pointer.Int32(1),
2886 },
2887 },
2888 newJob: &batch.Job{
2889 ObjectMeta: validObjectMeta,
2890 Spec: batch.JobSpec{
2891 Completions: ptr.To[int32](5),
2892 CompletionMode: completionModePtr(batch.IndexedCompletion),
2893 BackoffLimitPerIndex: pointer.Int32(1),
2894 },
2895 Status: batch.JobStatus{
2896 FailedIndexes: ptr.To("invalid format"),
2897 },
2898 },
2899 wantErrs: field.ErrorList{
2900 {Type: field.ErrorTypeInvalid, Field: "status.failedIndexes"},
2901 },
2902 },
2903 "invalid format for FailedIndexes, but allowed": {
2904 enableJobManagedBy: true,
2905 job: &batch.Job{
2906 ObjectMeta: validObjectMeta,
2907 Spec: batch.JobSpec{
2908 Completions: ptr.To[int32](5),
2909 CompletionMode: completionModePtr(batch.IndexedCompletion),
2910 BackoffLimitPerIndex: pointer.Int32(1),
2911 },
2912 Status: batch.JobStatus{
2913 FailedIndexes: ptr.To("invalid format"),
2914 },
2915 },
2916 newJob: &batch.Job{
2917 ObjectMeta: validObjectMeta,
2918 Spec: batch.JobSpec{
2919 Completions: ptr.To[int32](5),
2920 CompletionMode: completionModePtr(batch.IndexedCompletion),
2921 BackoffLimitPerIndex: pointer.Int32(1),
2922 },
2923 Status: batch.JobStatus{
2924 FailedIndexes: ptr.To("invalid format"),
2925 Active: 1,
2926 },
2927 },
2928 },
2929 "more ready pods than active, but allowed": {
2930 enableJobManagedBy: true,
2931 job: &batch.Job{
2932 ObjectMeta: validObjectMeta,
2933 Spec: batch.JobSpec{
2934 Completions: ptr.To[int32](5),
2935 },
2936 Status: batch.JobStatus{
2937 Active: 1,
2938 Ready: ptr.To[int32](2),
2939 },
2940 },
2941 newJob: &batch.Job{
2942 ObjectMeta: validObjectMeta,
2943 Spec: batch.JobSpec{
2944 Completions: ptr.To[int32](5),
2945 },
2946 Status: batch.JobStatus{
2947 Active: 1,
2948 Ready: ptr.To[int32](2),
2949 Succeeded: 1,
2950 },
2951 },
2952 },
2953 "invalid addition of both FailureTarget=True and Complete=True": {
2954 enableJobManagedBy: true,
2955 job: &batch.Job{
2956 ObjectMeta: validObjectMeta,
2957 },
2958 newJob: &batch.Job{
2959 ObjectMeta: validObjectMeta,
2960 Status: batch.JobStatus{
2961 StartTime: &now,
2962 CompletionTime: &now,
2963 Conditions: []batch.JobCondition{
2964 {
2965 Type: batch.JobComplete,
2966 Status: api.ConditionTrue,
2967 },
2968 {
2969 Type: batch.JobFailureTarget,
2970 Status: api.ConditionTrue,
2971 },
2972 },
2973 },
2974 },
2975 wantErrs: field.ErrorList{
2976 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
2977 },
2978 },
2979 "invalid failedIndexes, which overlap with completedIndexes": {
2980 enableJobManagedBy: true,
2981 job: &batch.Job{
2982 ObjectMeta: validObjectMeta,
2983 Spec: batch.JobSpec{
2984 Completions: ptr.To[int32](5),
2985 CompletionMode: completionModePtr(batch.IndexedCompletion),
2986 },
2987 Status: batch.JobStatus{
2988 FailedIndexes: ptr.To("0,2"),
2989 CompletedIndexes: "3-4",
2990 },
2991 },
2992 newJob: &batch.Job{
2993 ObjectMeta: validObjectMeta,
2994 Spec: batch.JobSpec{
2995 Completions: ptr.To[int32](5),
2996 CompletionMode: completionModePtr(batch.IndexedCompletion),
2997 },
2998 Status: batch.JobStatus{
2999 FailedIndexes: ptr.To("0,2"),
3000 CompletedIndexes: "2-4",
3001 },
3002 },
3003 wantErrs: field.ErrorList{
3004 {Type: field.ErrorTypeInvalid, Field: "status.failedIndexes"},
3005 },
3006 },
3007 "failedIndexes overlap with completedIndexes, unrelated field change": {
3008 enableJobManagedBy: true,
3009 job: &batch.Job{
3010 ObjectMeta: validObjectMeta,
3011 Spec: batch.JobSpec{
3012 Completions: ptr.To[int32](5),
3013 CompletionMode: completionModePtr(batch.IndexedCompletion),
3014 },
3015 Status: batch.JobStatus{
3016 FailedIndexes: ptr.To("0,2"),
3017 CompletedIndexes: "2-4",
3018 },
3019 },
3020 newJob: &batch.Job{
3021 ObjectMeta: validObjectMeta,
3022 Spec: batch.JobSpec{
3023 Completions: ptr.To[int32](5),
3024 CompletionMode: completionModePtr(batch.IndexedCompletion),
3025 },
3026 Status: batch.JobStatus{
3027 FailedIndexes: ptr.To("0,2"),
3028 CompletedIndexes: "2-4",
3029 Active: 1,
3030 },
3031 },
3032 },
3033 "invalid addition of SuccessCriteriaMet for NonIndexed Job": {
3034 enableJobSuccessPolicy: true,
3035 job: &batch.Job{
3036 ObjectMeta: validObjectMeta,
3037 Spec: batch.JobSpec{
3038 SuccessPolicy: validSuccessPolicy,
3039 },
3040 },
3041 newJob: &batch.Job{
3042 ObjectMeta: validObjectMeta,
3043 Spec: batch.JobSpec{
3044 SuccessPolicy: validSuccessPolicy,
3045 },
3046 Status: batch.JobStatus{
3047 Conditions: []batch.JobCondition{{
3048 Type: batch.JobSuccessCriteriaMet,
3049 Status: api.ConditionTrue,
3050 }},
3051 },
3052 },
3053 wantErrs: field.ErrorList{
3054 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3055 },
3056 },
3057 "invalid addition of SuccessCriteriaMet for Job with Failed": {
3058 enableJobSuccessPolicy: true,
3059 job: &batch.Job{
3060 ObjectMeta: validObjectMeta,
3061 Spec: batch.JobSpec{
3062 CompletionMode: completionModePtr(batch.IndexedCompletion),
3063 Completions: ptr.To[int32](10),
3064 SuccessPolicy: validSuccessPolicy,
3065 },
3066 Status: batch.JobStatus{
3067 Conditions: []batch.JobCondition{{
3068 Type: batch.JobFailed,
3069 Status: api.ConditionTrue,
3070 }},
3071 },
3072 },
3073 newJob: &batch.Job{
3074 ObjectMeta: validObjectMeta,
3075 Spec: batch.JobSpec{
3076 CompletionMode: completionModePtr(batch.IndexedCompletion),
3077 Completions: ptr.To[int32](10),
3078 SuccessPolicy: validSuccessPolicy,
3079 },
3080 Status: batch.JobStatus{
3081 Conditions: []batch.JobCondition{
3082 {
3083 Type: batch.JobFailed,
3084 Status: api.ConditionTrue,
3085 },
3086 {
3087 Type: batch.JobSuccessCriteriaMet,
3088 Status: api.ConditionTrue,
3089 },
3090 },
3091 },
3092 },
3093 wantErrs: field.ErrorList{
3094 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3095 },
3096 },
3097 "invalid addition of Failed for Job with SuccessCriteriaMet": {
3098 enableJobSuccessPolicy: true,
3099 job: &batch.Job{
3100 ObjectMeta: validObjectMeta,
3101 Spec: batch.JobSpec{
3102 CompletionMode: completionModePtr(batch.IndexedCompletion),
3103 Completions: ptr.To[int32](10),
3104 SuccessPolicy: validSuccessPolicy,
3105 },
3106 Status: batch.JobStatus{
3107 Conditions: []batch.JobCondition{{
3108 Type: batch.JobSuccessCriteriaMet,
3109 Status: api.ConditionTrue,
3110 }},
3111 },
3112 },
3113 newJob: &batch.Job{
3114 ObjectMeta: validObjectMeta,
3115 Spec: batch.JobSpec{
3116 CompletionMode: completionModePtr(batch.IndexedCompletion),
3117 Completions: ptr.To[int32](10),
3118 SuccessPolicy: validSuccessPolicy,
3119 },
3120 Status: batch.JobStatus{
3121 Conditions: []batch.JobCondition{
3122 {
3123 Type: batch.JobSuccessCriteriaMet,
3124 Status: api.ConditionTrue,
3125 },
3126 {
3127 Type: batch.JobFailed,
3128 Status: api.ConditionTrue,
3129 },
3130 },
3131 },
3132 },
3133 wantErrs: field.ErrorList{
3134 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3135 },
3136 },
3137 "invalid addition of SuccessCriteriaMet for Job with FailureTarget": {
3138 enableJobSuccessPolicy: true,
3139 job: &batch.Job{
3140 ObjectMeta: validObjectMeta,
3141 Spec: batch.JobSpec{
3142 CompletionMode: completionModePtr(batch.IndexedCompletion),
3143 Completions: ptr.To[int32](10),
3144 SuccessPolicy: validSuccessPolicy,
3145 },
3146 Status: batch.JobStatus{
3147 Conditions: []batch.JobCondition{{
3148 Type: batch.JobFailureTarget,
3149 Status: api.ConditionTrue,
3150 }},
3151 },
3152 },
3153 newJob: &batch.Job{
3154 ObjectMeta: validObjectMeta,
3155 Spec: batch.JobSpec{
3156 CompletionMode: completionModePtr(batch.IndexedCompletion),
3157 Completions: ptr.To[int32](10),
3158 SuccessPolicy: validSuccessPolicy,
3159 },
3160 Status: batch.JobStatus{
3161 Conditions: []batch.JobCondition{
3162 {
3163 Type: batch.JobFailureTarget,
3164 Status: api.ConditionTrue,
3165 },
3166 {
3167 Type: batch.JobSuccessCriteriaMet,
3168 Status: api.ConditionTrue,
3169 },
3170 },
3171 },
3172 },
3173 wantErrs: field.ErrorList{
3174 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3175 },
3176 },
3177 "invalid addition of FailureTarget for Job with SuccessCriteriaMet": {
3178 enableJobSuccessPolicy: true,
3179 job: &batch.Job{
3180 ObjectMeta: validObjectMeta,
3181 Spec: batch.JobSpec{
3182 CompletionMode: completionModePtr(batch.IndexedCompletion),
3183 Completions: ptr.To[int32](10),
3184 SuccessPolicy: validSuccessPolicy,
3185 },
3186 Status: batch.JobStatus{
3187 Conditions: []batch.JobCondition{{
3188 Type: batch.JobSuccessCriteriaMet,
3189 Status: api.ConditionTrue,
3190 }},
3191 },
3192 },
3193 newJob: &batch.Job{
3194 ObjectMeta: validObjectMeta,
3195 Spec: batch.JobSpec{
3196 CompletionMode: completionModePtr(batch.IndexedCompletion),
3197 Completions: ptr.To[int32](10),
3198 SuccessPolicy: validSuccessPolicy,
3199 },
3200 Status: batch.JobStatus{
3201 Conditions: []batch.JobCondition{
3202 {
3203 Type: batch.JobSuccessCriteriaMet,
3204 Status: api.ConditionTrue,
3205 },
3206 {
3207 Type: batch.JobFailureTarget,
3208 Status: api.ConditionTrue,
3209 },
3210 },
3211 },
3212 },
3213 wantErrs: field.ErrorList{
3214 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3215 },
3216 },
3217 "invalid addition of SuccessCriteriaMet for Job with Complete": {
3218 enableJobSuccessPolicy: true,
3219 job: &batch.Job{
3220 ObjectMeta: validObjectMeta,
3221 Spec: batch.JobSpec{
3222 CompletionMode: completionModePtr(batch.IndexedCompletion),
3223 Completions: ptr.To[int32](10),
3224 SuccessPolicy: validSuccessPolicy,
3225 },
3226 Status: batch.JobStatus{
3227 Conditions: []batch.JobCondition{{
3228 Type: batch.JobComplete,
3229 Status: api.ConditionTrue,
3230 }},
3231 },
3232 },
3233 newJob: &batch.Job{
3234 ObjectMeta: validObjectMeta,
3235 Spec: batch.JobSpec{
3236 CompletionMode: completionModePtr(batch.IndexedCompletion),
3237 Completions: ptr.To[int32](10),
3238 SuccessPolicy: validSuccessPolicy,
3239 },
3240 Status: batch.JobStatus{
3241 Conditions: []batch.JobCondition{
3242 {
3243 Type: batch.JobComplete,
3244 Status: api.ConditionTrue,
3245 },
3246 {
3247 Type: batch.JobSuccessCriteriaMet,
3248 Status: api.ConditionTrue,
3249 },
3250 },
3251 },
3252 },
3253 wantErrs: field.ErrorList{
3254 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3255 },
3256 },
3257 "valid addition of Complete for Job with SuccessCriteriaMet": {
3258 enableJobSuccessPolicy: true,
3259 job: &batch.Job{
3260 ObjectMeta: validObjectMeta,
3261 Spec: batch.JobSpec{
3262 CompletionMode: completionModePtr(batch.IndexedCompletion),
3263 Completions: ptr.To[int32](10),
3264 SuccessPolicy: validSuccessPolicy,
3265 },
3266 Status: batch.JobStatus{
3267 Conditions: []batch.JobCondition{{
3268 Type: batch.JobSuccessCriteriaMet,
3269 Status: api.ConditionTrue,
3270 }},
3271 },
3272 },
3273 newJob: &batch.Job{
3274 ObjectMeta: validObjectMeta,
3275 Spec: batch.JobSpec{
3276 CompletionMode: completionModePtr(batch.IndexedCompletion),
3277 Completions: ptr.To[int32](10),
3278 SuccessPolicy: validSuccessPolicy,
3279 },
3280 Status: batch.JobStatus{
3281 Conditions: []batch.JobCondition{
3282 {
3283 Type: batch.JobSuccessCriteriaMet,
3284 Status: api.ConditionTrue,
3285 },
3286 {
3287 Type: batch.JobComplete,
3288 Status: api.ConditionTrue,
3289 },
3290 },
3291 },
3292 },
3293 },
3294 "invalid addition of SuccessCriteriaMet for Job without SuccessPolicy": {
3295 enableJobSuccessPolicy: true,
3296 job: &batch.Job{
3297 ObjectMeta: validObjectMeta,
3298 Spec: batch.JobSpec{
3299 CompletionMode: completionModePtr(batch.IndexedCompletion),
3300 Completions: ptr.To[int32](10),
3301 },
3302 },
3303 newJob: &batch.Job{
3304 ObjectMeta: validObjectMeta,
3305 Spec: batch.JobSpec{
3306 CompletionMode: completionModePtr(batch.IndexedCompletion),
3307 Completions: ptr.To[int32](10),
3308 },
3309 Status: batch.JobStatus{
3310 Conditions: []batch.JobCondition{
3311 {
3312 Type: batch.JobSuccessCriteriaMet,
3313 Status: api.ConditionTrue,
3314 },
3315 },
3316 },
3317 },
3318 wantErrs: field.ErrorList{
3319 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3320 },
3321 },
3322 "invalid addition of Complete for Job with SuccessPolicy unless SuccessCriteriaMet": {
3323 enableJobSuccessPolicy: true,
3324 job: &batch.Job{
3325 ObjectMeta: validObjectMeta,
3326 Spec: batch.JobSpec{
3327 CompletionMode: completionModePtr(batch.IndexedCompletion),
3328 Completions: ptr.To[int32](10),
3329 SuccessPolicy: validSuccessPolicy,
3330 },
3331 },
3332 newJob: &batch.Job{
3333 ObjectMeta: validObjectMeta,
3334 Spec: batch.JobSpec{
3335 CompletionMode: completionModePtr(batch.IndexedCompletion),
3336 Completions: ptr.To[int32](10),
3337 SuccessPolicy: validSuccessPolicy,
3338 },
3339 Status: batch.JobStatus{
3340 Conditions: []batch.JobCondition{
3341 {
3342 Type: batch.JobComplete,
3343 Status: api.ConditionTrue,
3344 },
3345 },
3346 },
3347 },
3348 wantErrs: field.ErrorList{
3349 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3350 },
3351 },
3352 "invalid disabling of SuccessCriteriaMet for Job": {
3353 enableJobSuccessPolicy: true,
3354 job: &batch.Job{
3355 ObjectMeta: validObjectMeta,
3356 Spec: batch.JobSpec{
3357 CompletionMode: completionModePtr(batch.IndexedCompletion),
3358 Completions: ptr.To[int32](10),
3359 SuccessPolicy: validSuccessPolicy,
3360 },
3361 Status: batch.JobStatus{
3362 Conditions: []batch.JobCondition{{
3363 Type: batch.JobSuccessCriteriaMet,
3364 Status: api.ConditionTrue,
3365 }},
3366 },
3367 },
3368 newJob: &batch.Job{
3369 ObjectMeta: validObjectMeta,
3370 Spec: batch.JobSpec{
3371 CompletionMode: completionModePtr(batch.IndexedCompletion),
3372 Completions: ptr.To[int32](10),
3373 SuccessPolicy: validSuccessPolicy,
3374 },
3375 Status: batch.JobStatus{
3376 Conditions: []batch.JobCondition{{
3377 Type: batch.JobComplete,
3378 Status: api.ConditionFalse,
3379 }},
3380 },
3381 },
3382 wantErrs: field.ErrorList{
3383 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3384 },
3385 },
3386 "invalid removing of SuccessCriteriaMet for Job": {
3387 enableJobSuccessPolicy: true,
3388 job: &batch.Job{
3389 ObjectMeta: validObjectMeta,
3390 Spec: batch.JobSpec{
3391 CompletionMode: completionModePtr(batch.IndexedCompletion),
3392 Completions: ptr.To[int32](10),
3393 SuccessPolicy: validSuccessPolicy,
3394 },
3395 Status: batch.JobStatus{
3396 Conditions: []batch.JobCondition{{
3397 Type: batch.JobSuccessCriteriaMet,
3398 Status: api.ConditionTrue,
3399 }},
3400 },
3401 },
3402 newJob: &batch.Job{
3403 ObjectMeta: validObjectMeta,
3404 Spec: batch.JobSpec{
3405 CompletionMode: completionModePtr(batch.IndexedCompletion),
3406 Completions: ptr.To[int32](10),
3407 SuccessPolicy: validSuccessPolicy,
3408 },
3409 },
3410 wantErrs: field.ErrorList{
3411 {Type: field.ErrorTypeInvalid, Field: "status.conditions"},
3412 },
3413 },
3414 }
3415 for name, tc := range cases {
3416 t.Run(name, func(t *testing.T) {
3417 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobManagedBy, tc.enableJobManagedBy)()
3418 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobSuccessPolicy, tc.enableJobSuccessPolicy)()
3419
3420 errs := StatusStrategy.ValidateUpdate(ctx, tc.newJob, tc.job)
3421 if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" {
3422 t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
3423 }
3424 if tc.wantJob != nil {
3425 if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" {
3426 t.Errorf("Unexpected job (-want,+got):\n%s", diff)
3427 }
3428 }
3429 })
3430 }
3431 }
3432
3433 func TestJobStrategy_GetAttrs(t *testing.T) {
3434 validSelector := &metav1.LabelSelector{
3435 MatchLabels: map[string]string{"a": "b"},
3436 }
3437 validPodTemplateSpec := api.PodTemplateSpec{
3438 ObjectMeta: metav1.ObjectMeta{
3439 Labels: validSelector.MatchLabels,
3440 },
3441 Spec: api.PodSpec{
3442 RestartPolicy: api.RestartPolicyOnFailure,
3443 DNSPolicy: api.DNSClusterFirst,
3444 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3445 },
3446 }
3447
3448 cases := map[string]struct {
3449 job *batch.Job
3450 wantErr string
3451 nonJobObject *api.Pod
3452 }{
3453 "valid job with no labels": {
3454 job: &batch.Job{
3455 ObjectMeta: metav1.ObjectMeta{
3456 Name: "myjob",
3457 Namespace: metav1.NamespaceDefault,
3458 ResourceVersion: "0",
3459 },
3460 Spec: batch.JobSpec{
3461 Selector: validSelector,
3462 Template: validPodTemplateSpec,
3463 ManualSelector: pointer.BoolPtr(true),
3464 Parallelism: pointer.Int32Ptr(1),
3465 },
3466 },
3467 },
3468 "valid job with a label": {
3469 job: &batch.Job{
3470 ObjectMeta: metav1.ObjectMeta{
3471 Name: "myjob",
3472 Namespace: metav1.NamespaceDefault,
3473 ResourceVersion: "0",
3474 Labels: map[string]string{"a": "b"},
3475 },
3476 Spec: batch.JobSpec{
3477 Selector: validSelector,
3478 Template: validPodTemplateSpec,
3479 ManualSelector: pointer.BoolPtr(true),
3480 Parallelism: pointer.Int32Ptr(1),
3481 },
3482 },
3483 },
3484 "pod instead": {
3485 job: nil,
3486 nonJobObject: &api.Pod{},
3487 wantErr: "given object is not a job.",
3488 },
3489 }
3490 for name, tc := range cases {
3491 t.Run(name, func(t *testing.T) {
3492 if tc.job == nil {
3493 _, _, err := GetAttrs(tc.nonJobObject)
3494 if diff := cmp.Diff(tc.wantErr, err.Error()); diff != "" {
3495 t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
3496 }
3497 } else {
3498 gotLabels, _, err := GetAttrs(tc.job)
3499 if err != nil {
3500 t.Errorf("Error %s supposed to be nil", err.Error())
3501 }
3502 if diff := cmp.Diff(labels.Set(tc.job.ObjectMeta.Labels), gotLabels); diff != "" {
3503 t.Errorf("Unexpected attrs (-want,+got):\n%s", diff)
3504 }
3505 }
3506 })
3507 }
3508 }
3509
3510 func TestJobToSelectiableFields(t *testing.T) {
3511 apitesting.TestSelectableFieldLabelConversionsOfKind(t,
3512 "batch/v1",
3513 "Job",
3514 JobToSelectableFields(&batch.Job{}),
3515 nil,
3516 )
3517 }
3518
3519 func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
3520 return &m
3521 }
3522
3523 func podReplacementPolicy(m batch.PodReplacementPolicy) *batch.PodReplacementPolicy {
3524 return &m
3525 }
3526
3527 func getValidObjectMeta(generation int64) metav1.ObjectMeta {
3528 return getValidObjectMetaWithAnnotations(generation, nil)
3529 }
3530
3531 func getValidUID() types.UID {
3532 return "1a2b3c4d5e6f7g8h9i0k"
3533 }
3534
3535 func getValidObjectMetaWithAnnotations(generation int64, annotations map[string]string) metav1.ObjectMeta {
3536 return metav1.ObjectMeta{
3537 Name: "myjob",
3538 UID: getValidUID(),
3539 Namespace: metav1.NamespaceDefault,
3540 Generation: generation,
3541 Annotations: annotations,
3542 }
3543 }
3544
3545 func getValidLabelSelector() *metav1.LabelSelector {
3546 return &metav1.LabelSelector{
3547 MatchLabels: map[string]string{"a": "b"},
3548 }
3549 }
3550
3551 func getValidBatchLabels() map[string]string {
3552 theUID := getValidUID()
3553 return map[string]string{batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: string(theUID), batch.ControllerUidLabel: string(theUID)}
3554 }
3555
3556 func getValidBatchLabelsWithNonBatch() map[string]string {
3557 theUID := getValidUID()
3558 return map[string]string{"a": "b", batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: string(theUID), batch.ControllerUidLabel: string(theUID)}
3559 }
3560
3561 func getValidPodTemplateSpecForSelector(validSelector *metav1.LabelSelector) api.PodTemplateSpec {
3562 return api.PodTemplateSpec{
3563 ObjectMeta: metav1.ObjectMeta{
3564 Labels: validSelector.MatchLabels,
3565 },
3566 Spec: api.PodSpec{
3567 RestartPolicy: api.RestartPolicyOnFailure,
3568 DNSPolicy: api.DNSClusterFirst,
3569 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3570 },
3571 }
3572 }
3573
View as plain text