...

Source file src/k8s.io/kubernetes/pkg/registry/batch/job/strategy_test.go

Documentation: k8s.io/kubernetes/pkg/registry/batch/job

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

View as plain text