k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/batch/job/strategy_test.go (about)

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