k8s.io/kubernetes@v1.29.3/pkg/registry/batch/job/strategy_test.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package job
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    29  	"k8s.io/apiserver/pkg/registry/rest"
    30  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    31  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    32  	apitesting "k8s.io/kubernetes/pkg/api/testing"
    33  	"k8s.io/kubernetes/pkg/apis/batch"
    34  	_ "k8s.io/kubernetes/pkg/apis/batch/install"
    35  	api "k8s.io/kubernetes/pkg/apis/core"
    36  	"k8s.io/kubernetes/pkg/features"
    37  	"k8s.io/utils/pointer"
    38  )
    39  
    40  var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
    41  
    42  // TestJobStrategy_PrepareForUpdate tests various scenearios for PrepareForUpdate
    43  func TestJobStrategy_PrepareForUpdate(t *testing.T) {
    44  	validSelector := getValidLabelSelector()
    45  	validPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelector)
    46  
    47  	podFailurePolicy := &batch.PodFailurePolicy{
    48  		Rules: []batch.PodFailurePolicyRule{
    49  			{
    50  				Action: batch.PodFailurePolicyActionFailJob,
    51  				OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
    52  					ContainerName: pointer.String("container-name"),
    53  					Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
    54  					Values:        []int32{1},
    55  				},
    56  			},
    57  		},
    58  	}
    59  	updatedPodFailurePolicy := &batch.PodFailurePolicy{
    60  		Rules: []batch.PodFailurePolicyRule{
    61  			{
    62  				Action: batch.PodFailurePolicyActionIgnore,
    63  				OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
    64  					ContainerName: pointer.String("updated-container-name"),
    65  					Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
    66  					Values:        []int32{2},
    67  				},
    68  			},
    69  		},
    70  	}
    71  
    72  	cases := map[string]struct {
    73  		enableJobPodFailurePolicy     bool
    74  		enableJobBackoffLimitPerIndex bool
    75  		enableJobPodReplacementPolicy bool
    76  		job                           batch.Job
    77  		updatedJob                    batch.Job
    78  		wantJob                       batch.Job
    79  	}{
    80  		"update job with a new field; updated when JobBackoffLimitPerIndex enabled": {
    81  			enableJobBackoffLimitPerIndex: true,
    82  			job: batch.Job{
    83  				ObjectMeta: getValidObjectMeta(0),
    84  				Spec: batch.JobSpec{
    85  					Selector:             validSelector,
    86  					Template:             validPodTemplateSpec,
    87  					BackoffLimitPerIndex: nil,
    88  					MaxFailedIndexes:     nil,
    89  				},
    90  			},
    91  			updatedJob: batch.Job{
    92  				ObjectMeta: getValidObjectMeta(0),
    93  				Spec: batch.JobSpec{
    94  					Selector:             validSelector,
    95  					Template:             validPodTemplateSpec,
    96  					BackoffLimitPerIndex: pointer.Int32(1),
    97  					MaxFailedIndexes:     pointer.Int32(1),
    98  				},
    99  			},
   100  			wantJob: batch.Job{
   101  				ObjectMeta: getValidObjectMeta(1),
   102  				Spec: batch.JobSpec{
   103  					Selector:             validSelector,
   104  					Template:             validPodTemplateSpec,
   105  					BackoffLimitPerIndex: pointer.Int32(1),
   106  					MaxFailedIndexes:     pointer.Int32(1),
   107  				},
   108  			},
   109  		},
   110  		"update job with a new field; not updated when JobBackoffLimitPerIndex disabled": {
   111  			enableJobBackoffLimitPerIndex: false,
   112  			job: batch.Job{
   113  				ObjectMeta: getValidObjectMeta(0),
   114  				Spec: batch.JobSpec{
   115  					Selector:             validSelector,
   116  					Template:             validPodTemplateSpec,
   117  					BackoffLimitPerIndex: nil,
   118  					MaxFailedIndexes:     nil,
   119  				},
   120  			},
   121  			updatedJob: batch.Job{
   122  				ObjectMeta: getValidObjectMeta(0),
   123  				Spec: batch.JobSpec{
   124  					Selector:             validSelector,
   125  					Template:             validPodTemplateSpec,
   126  					BackoffLimitPerIndex: pointer.Int32(1),
   127  					MaxFailedIndexes:     pointer.Int32(1),
   128  				},
   129  			},
   130  			wantJob: batch.Job{
   131  				ObjectMeta: getValidObjectMeta(0),
   132  				Spec: batch.JobSpec{
   133  					Selector:             validSelector,
   134  					Template:             validPodTemplateSpec,
   135  					BackoffLimitPerIndex: nil,
   136  					MaxFailedIndexes:     nil,
   137  				},
   138  			},
   139  		},
   140  		"update job with a new field; updated when JobPodFailurePolicy enabled": {
   141  			enableJobPodFailurePolicy: true,
   142  			job: batch.Job{
   143  				ObjectMeta: getValidObjectMeta(0),
   144  				Spec: batch.JobSpec{
   145  					Selector:         validSelector,
   146  					Template:         validPodTemplateSpec,
   147  					PodFailurePolicy: nil,
   148  				},
   149  			},
   150  			updatedJob: batch.Job{
   151  				ObjectMeta: getValidObjectMeta(0),
   152  				Spec: batch.JobSpec{
   153  					Selector:         validSelector,
   154  					Template:         validPodTemplateSpec,
   155  					PodFailurePolicy: updatedPodFailurePolicy,
   156  				},
   157  			},
   158  			wantJob: batch.Job{
   159  				ObjectMeta: getValidObjectMeta(1),
   160  				Spec: batch.JobSpec{
   161  					Selector:         validSelector,
   162  					Template:         validPodTemplateSpec,
   163  					PodFailurePolicy: updatedPodFailurePolicy,
   164  				},
   165  			},
   166  		},
   167  		"update job with a new field; updated when JobPodReplacementPolicy enabled": {
   168  			enableJobPodReplacementPolicy: true,
   169  			job: batch.Job{
   170  				ObjectMeta: getValidObjectMeta(0),
   171  				Spec: batch.JobSpec{
   172  					Selector:             validSelector,
   173  					Template:             validPodTemplateSpec,
   174  					PodReplacementPolicy: nil,
   175  				},
   176  			},
   177  			updatedJob: batch.Job{
   178  				ObjectMeta: getValidObjectMeta(0),
   179  				Spec: batch.JobSpec{
   180  					Selector:             validSelector,
   181  					Template:             validPodTemplateSpec,
   182  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   183  				},
   184  			},
   185  			wantJob: batch.Job{
   186  				ObjectMeta: getValidObjectMeta(1),
   187  				Spec: batch.JobSpec{
   188  					Selector:             validSelector,
   189  					Template:             validPodTemplateSpec,
   190  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   191  				},
   192  			},
   193  		},
   194  		"update job with a new field; not updated when JobPodReplacementPolicy disabled": {
   195  			enableJobPodReplacementPolicy: false,
   196  			job: batch.Job{
   197  				ObjectMeta: getValidObjectMeta(0),
   198  				Spec: batch.JobSpec{
   199  					Selector:             validSelector,
   200  					Template:             validPodTemplateSpec,
   201  					PodReplacementPolicy: nil,
   202  				},
   203  			},
   204  			updatedJob: batch.Job{
   205  				ObjectMeta: getValidObjectMeta(0),
   206  				Spec: batch.JobSpec{
   207  					Selector:             validSelector,
   208  					Template:             validPodTemplateSpec,
   209  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   210  				},
   211  			},
   212  			wantJob: batch.Job{
   213  				ObjectMeta: getValidObjectMeta(0),
   214  				Spec: batch.JobSpec{
   215  					Selector:             validSelector,
   216  					Template:             validPodTemplateSpec,
   217  					PodReplacementPolicy: nil,
   218  				},
   219  			},
   220  		},
   221  		"update job with a new field; not updated when JobPodFailurePolicy disabled": {
   222  			enableJobPodFailurePolicy: false,
   223  			job: batch.Job{
   224  				ObjectMeta: getValidObjectMeta(0),
   225  				Spec: batch.JobSpec{
   226  					Selector:         validSelector,
   227  					Template:         validPodTemplateSpec,
   228  					PodFailurePolicy: nil,
   229  				},
   230  			},
   231  			updatedJob: batch.Job{
   232  				ObjectMeta: getValidObjectMeta(0),
   233  				Spec: batch.JobSpec{
   234  					Selector:         validSelector,
   235  					Template:         validPodTemplateSpec,
   236  					PodFailurePolicy: updatedPodFailurePolicy,
   237  				},
   238  			},
   239  			wantJob: batch.Job{
   240  				ObjectMeta: getValidObjectMeta(0),
   241  				Spec: batch.JobSpec{
   242  					Selector:         validSelector,
   243  					Template:         validPodTemplateSpec,
   244  					PodFailurePolicy: nil,
   245  				},
   246  			},
   247  		},
   248  		"update pre-existing field; updated when JobPodFailurePolicy enabled": {
   249  			enableJobPodFailurePolicy: true,
   250  			job: batch.Job{
   251  				ObjectMeta: getValidObjectMeta(0),
   252  				Spec: batch.JobSpec{
   253  					Selector:         validSelector,
   254  					Template:         validPodTemplateSpec,
   255  					PodFailurePolicy: podFailurePolicy,
   256  				},
   257  			},
   258  			updatedJob: batch.Job{
   259  				ObjectMeta: getValidObjectMeta(0),
   260  				Spec: batch.JobSpec{
   261  					Selector:         validSelector,
   262  					Template:         validPodTemplateSpec,
   263  					PodFailurePolicy: updatedPodFailurePolicy,
   264  				},
   265  			},
   266  			wantJob: batch.Job{
   267  				ObjectMeta: getValidObjectMeta(1),
   268  				Spec: batch.JobSpec{
   269  					Selector:         validSelector,
   270  					Template:         validPodTemplateSpec,
   271  					PodFailurePolicy: updatedPodFailurePolicy,
   272  				},
   273  			},
   274  		},
   275  		"update pre-existing field; updated when JobPodFailurePolicy disabled": {
   276  			enableJobPodFailurePolicy: false,
   277  			job: batch.Job{
   278  				ObjectMeta: getValidObjectMeta(0),
   279  				Spec: batch.JobSpec{
   280  					Selector:         validSelector,
   281  					Template:         validPodTemplateSpec,
   282  					PodFailurePolicy: podFailurePolicy,
   283  				},
   284  			},
   285  			updatedJob: batch.Job{
   286  				ObjectMeta: getValidObjectMeta(0),
   287  				Spec: batch.JobSpec{
   288  					Selector:         validSelector,
   289  					Template:         validPodTemplateSpec,
   290  					PodFailurePolicy: updatedPodFailurePolicy,
   291  				},
   292  			},
   293  			wantJob: batch.Job{
   294  				ObjectMeta: getValidObjectMeta(1),
   295  				Spec: batch.JobSpec{
   296  					Selector:         validSelector,
   297  					Template:         validPodTemplateSpec,
   298  					PodFailurePolicy: updatedPodFailurePolicy,
   299  				},
   300  			},
   301  		},
   302  		"add tracking annotation back": {
   303  			job: batch.Job{
   304  				ObjectMeta: getValidObjectMeta(0),
   305  				Spec: batch.JobSpec{
   306  					Selector:         validSelector,
   307  					Template:         validPodTemplateSpec,
   308  					PodFailurePolicy: podFailurePolicy,
   309  				},
   310  			},
   311  			updatedJob: batch.Job{
   312  				ObjectMeta: getValidObjectMeta(0),
   313  				Spec: batch.JobSpec{
   314  					Selector: validSelector,
   315  					Template: validPodTemplateSpec,
   316  				},
   317  			},
   318  			wantJob: batch.Job{
   319  				ObjectMeta: getValidObjectMeta(1),
   320  				Spec: batch.JobSpec{
   321  					Selector: validSelector,
   322  					Template: validPodTemplateSpec,
   323  				},
   324  			},
   325  		},
   326  		"attempt status update and verify it doesn't change": {
   327  			job: batch.Job{
   328  				ObjectMeta: getValidObjectMeta(0),
   329  				Spec: batch.JobSpec{
   330  					Selector: validSelector,
   331  					Template: validPodTemplateSpec,
   332  				},
   333  				Status: batch.JobStatus{
   334  					Active: 1,
   335  				},
   336  			},
   337  			updatedJob: batch.Job{
   338  				ObjectMeta: getValidObjectMeta(0),
   339  				Spec: batch.JobSpec{
   340  					Selector: validSelector,
   341  					Template: validPodTemplateSpec,
   342  				},
   343  				Status: batch.JobStatus{
   344  					Active: 2,
   345  				},
   346  			},
   347  			wantJob: batch.Job{
   348  				ObjectMeta: getValidObjectMeta(0),
   349  				Spec: batch.JobSpec{
   350  					Selector: validSelector,
   351  					Template: validPodTemplateSpec,
   352  				},
   353  				Status: batch.JobStatus{
   354  					Active: 1,
   355  				},
   356  			},
   357  		},
   358  		"ensure generation doesn't change over non spec updates": {
   359  			job: batch.Job{
   360  				ObjectMeta: getValidObjectMeta(0),
   361  				Spec: batch.JobSpec{
   362  					Selector: validSelector,
   363  					Template: validPodTemplateSpec,
   364  				},
   365  				Status: batch.JobStatus{
   366  					Active: 1,
   367  				},
   368  			},
   369  			updatedJob: batch.Job{
   370  				ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
   371  				Spec: batch.JobSpec{
   372  					Selector: validSelector,
   373  					Template: validPodTemplateSpec,
   374  				},
   375  				Status: batch.JobStatus{
   376  					Active: 2,
   377  				},
   378  			},
   379  			wantJob: batch.Job{
   380  				ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
   381  				Spec: batch.JobSpec{
   382  					Selector: validSelector,
   383  					Template: validPodTemplateSpec,
   384  				},
   385  				Status: batch.JobStatus{
   386  					Active: 1,
   387  				},
   388  			},
   389  		},
   390  		"test updating suspend false->true": {
   391  			job: batch.Job{
   392  				ObjectMeta: getValidObjectMeta(0),
   393  				Spec: batch.JobSpec{
   394  					Selector: validSelector,
   395  					Template: validPodTemplateSpec,
   396  					Suspend:  pointer.Bool(false),
   397  				},
   398  			},
   399  			updatedJob: batch.Job{
   400  				ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
   401  				Spec: batch.JobSpec{
   402  					Selector: validSelector,
   403  					Template: validPodTemplateSpec,
   404  					Suspend:  pointer.Bool(true),
   405  				},
   406  			},
   407  			wantJob: batch.Job{
   408  				ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}),
   409  				Spec: batch.JobSpec{
   410  					Selector: validSelector,
   411  					Template: validPodTemplateSpec,
   412  					Suspend:  pointer.Bool(true),
   413  				},
   414  			},
   415  		},
   416  		"test updating suspend nil -> true": {
   417  			job: batch.Job{
   418  				ObjectMeta: getValidObjectMeta(0),
   419  				Spec: batch.JobSpec{
   420  					Selector: validSelector,
   421  					Template: validPodTemplateSpec,
   422  				},
   423  			},
   424  			updatedJob: batch.Job{
   425  				ObjectMeta: getValidObjectMetaWithAnnotations(0, map[string]string{"hello": "world"}),
   426  				Spec: batch.JobSpec{
   427  					Selector: validSelector,
   428  					Template: validPodTemplateSpec,
   429  					Suspend:  pointer.Bool(true),
   430  				},
   431  			},
   432  			wantJob: batch.Job{
   433  				ObjectMeta: getValidObjectMetaWithAnnotations(1, map[string]string{"hello": "world"}),
   434  				Spec: batch.JobSpec{
   435  					Selector: validSelector,
   436  					Template: validPodTemplateSpec,
   437  					Suspend:  pointer.Bool(true),
   438  				},
   439  			},
   440  		},
   441  	}
   442  
   443  	for name, tc := range cases {
   444  		t.Run(name, func(t *testing.T) {
   445  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
   446  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
   447  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodReplacementPolicy, tc.enableJobPodReplacementPolicy)()
   448  			ctx := genericapirequest.NewDefaultContext()
   449  
   450  			Strategy.PrepareForUpdate(ctx, &tc.updatedJob, &tc.job)
   451  
   452  			if diff := cmp.Diff(tc.wantJob, tc.updatedJob); diff != "" {
   453  				t.Errorf("Job update differences (-want,+got):\n%s", diff)
   454  			}
   455  		})
   456  	}
   457  }
   458  
   459  // TestJobStrategy_PrepareForCreate tests various scenarios for PrepareForCreate
   460  func TestJobStrategy_PrepareForCreate(t *testing.T) {
   461  	validSelector := getValidLabelSelector()
   462  	validPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelector)
   463  	validSelectorWithBatchLabels := &metav1.LabelSelector{MatchLabels: getValidBatchLabelsWithNonBatch()}
   464  	expectedPodTemplateSpec := getValidPodTemplateSpecForSelector(validSelectorWithBatchLabels)
   465  
   466  	podFailurePolicy := &batch.PodFailurePolicy{
   467  		Rules: []batch.PodFailurePolicyRule{
   468  			{
   469  				Action: batch.PodFailurePolicyActionFailJob,
   470  				OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   471  					ContainerName: pointer.String("container-name"),
   472  					Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   473  					Values:        []int32{1},
   474  				},
   475  			},
   476  		},
   477  	}
   478  
   479  	cases := map[string]struct {
   480  		enableJobPodFailurePolicy     bool
   481  		enableJobBackoffLimitPerIndex bool
   482  		enableJobPodReplacementPolicy bool
   483  		job                           batch.Job
   484  		wantJob                       batch.Job
   485  	}{
   486  		"generate selectors": {
   487  			job: batch.Job{
   488  				ObjectMeta: getValidObjectMeta(0),
   489  				Spec: batch.JobSpec{
   490  					Selector:       validSelector,
   491  					ManualSelector: pointer.Bool(false),
   492  					Template:       validPodTemplateSpec,
   493  				},
   494  			},
   495  			wantJob: batch.Job{
   496  				ObjectMeta: getValidObjectMeta(1),
   497  				Spec: batch.JobSpec{
   498  					Selector:       validSelector,
   499  					ManualSelector: pointer.Bool(false),
   500  					Template:       expectedPodTemplateSpec,
   501  				},
   502  			},
   503  		},
   504  		"create job with a new fields; JobBackoffLimitPerIndex enabled": {
   505  			enableJobBackoffLimitPerIndex: true,
   506  			job: batch.Job{
   507  				ObjectMeta: getValidObjectMeta(0),
   508  				Spec: batch.JobSpec{
   509  					Selector:             validSelector,
   510  					ManualSelector:       pointer.Bool(false),
   511  					Template:             validPodTemplateSpec,
   512  					BackoffLimitPerIndex: pointer.Int32(1),
   513  					MaxFailedIndexes:     pointer.Int32(1),
   514  				},
   515  			},
   516  			wantJob: batch.Job{
   517  				ObjectMeta: getValidObjectMeta(1),
   518  				Spec: batch.JobSpec{
   519  					Selector:             validSelector,
   520  					ManualSelector:       pointer.Bool(false),
   521  					Template:             expectedPodTemplateSpec,
   522  					BackoffLimitPerIndex: pointer.Int32(1),
   523  					MaxFailedIndexes:     pointer.Int32(1),
   524  				},
   525  			},
   526  		},
   527  		"create job with a new fields; JobBackoffLimitPerIndex disabled": {
   528  			enableJobBackoffLimitPerIndex: false,
   529  			job: batch.Job{
   530  				ObjectMeta: getValidObjectMeta(0),
   531  				Spec: batch.JobSpec{
   532  					Selector:             validSelector,
   533  					ManualSelector:       pointer.Bool(false),
   534  					Template:             validPodTemplateSpec,
   535  					BackoffLimitPerIndex: pointer.Int32(1),
   536  					MaxFailedIndexes:     pointer.Int32(1),
   537  				},
   538  			},
   539  			wantJob: batch.Job{
   540  				ObjectMeta: getValidObjectMeta(1),
   541  				Spec: batch.JobSpec{
   542  					Selector:             validSelector,
   543  					ManualSelector:       pointer.Bool(false),
   544  					Template:             expectedPodTemplateSpec,
   545  					BackoffLimitPerIndex: nil,
   546  					MaxFailedIndexes:     nil,
   547  				},
   548  			},
   549  		},
   550  		"create job with a new field; JobPodFailurePolicy enabled": {
   551  			enableJobPodFailurePolicy: true,
   552  			job: batch.Job{
   553  				ObjectMeta: getValidObjectMeta(0),
   554  				Spec: batch.JobSpec{
   555  					Selector:         validSelector,
   556  					ManualSelector:   pointer.Bool(false),
   557  					Template:         validPodTemplateSpec,
   558  					PodFailurePolicy: podFailurePolicy,
   559  				},
   560  			},
   561  			wantJob: batch.Job{
   562  				ObjectMeta: getValidObjectMeta(1),
   563  				Spec: batch.JobSpec{
   564  					Selector:         validSelector,
   565  					ManualSelector:   pointer.Bool(false),
   566  					Template:         expectedPodTemplateSpec,
   567  					PodFailurePolicy: podFailurePolicy,
   568  				},
   569  			},
   570  		},
   571  		"create job with a new field; JobPodReplacementPolicy enabled": {
   572  			enableJobPodReplacementPolicy: true,
   573  			job: batch.Job{
   574  				ObjectMeta: getValidObjectMeta(0),
   575  				Spec: batch.JobSpec{
   576  					Selector:             validSelector,
   577  					ManualSelector:       pointer.Bool(false),
   578  					Template:             validPodTemplateSpec,
   579  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   580  				},
   581  			},
   582  			wantJob: batch.Job{
   583  				ObjectMeta: getValidObjectMeta(1),
   584  				Spec: batch.JobSpec{
   585  					Selector:             validSelector,
   586  					ManualSelector:       pointer.Bool(false),
   587  					Template:             expectedPodTemplateSpec,
   588  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   589  				},
   590  			},
   591  		},
   592  		"create job with a new field; JobPodReplacementPolicy disabled": {
   593  			enableJobPodReplacementPolicy: false,
   594  			job: batch.Job{
   595  				ObjectMeta: getValidObjectMeta(0),
   596  				Spec: batch.JobSpec{
   597  					Selector:             validSelector,
   598  					ManualSelector:       pointer.Bool(false),
   599  					Template:             validPodTemplateSpec,
   600  					PodReplacementPolicy: podReplacementPolicy(batch.Failed),
   601  				},
   602  			},
   603  			wantJob: batch.Job{
   604  				ObjectMeta: getValidObjectMeta(1),
   605  				Spec: batch.JobSpec{
   606  					Selector:             validSelector,
   607  					ManualSelector:       pointer.Bool(false),
   608  					Template:             expectedPodTemplateSpec,
   609  					PodReplacementPolicy: nil,
   610  				},
   611  			},
   612  		},
   613  		"create job with a new field; JobPodFailurePolicy disabled": {
   614  			enableJobPodFailurePolicy: false,
   615  			job: batch.Job{
   616  				ObjectMeta: getValidObjectMeta(0),
   617  				Spec: batch.JobSpec{
   618  					Selector:         validSelector,
   619  					ManualSelector:   pointer.Bool(false),
   620  					Template:         validPodTemplateSpec,
   621  					PodFailurePolicy: podFailurePolicy,
   622  				},
   623  			},
   624  			wantJob: batch.Job{
   625  				ObjectMeta: getValidObjectMeta(1),
   626  				Spec: batch.JobSpec{
   627  					Selector:         validSelector,
   628  					ManualSelector:   pointer.Bool(false),
   629  					Template:         expectedPodTemplateSpec,
   630  					PodFailurePolicy: nil,
   631  				},
   632  			},
   633  		},
   634  		"job does not allow setting status on create": {
   635  			job: batch.Job{
   636  				ObjectMeta: getValidObjectMeta(0),
   637  				Spec: batch.JobSpec{
   638  					Selector:       validSelector,
   639  					ManualSelector: pointer.Bool(false),
   640  					Template:       validPodTemplateSpec,
   641  				},
   642  				Status: batch.JobStatus{
   643  					Active: 1,
   644  				},
   645  			},
   646  			wantJob: batch.Job{
   647  				ObjectMeta: getValidObjectMeta(1),
   648  				Spec: batch.JobSpec{
   649  					Selector:       validSelector,
   650  					ManualSelector: pointer.Bool(false),
   651  					Template:       expectedPodTemplateSpec,
   652  				},
   653  			},
   654  		},
   655  		"create job with pod failure policy using FailIndex action; JobPodFailurePolicy enabled, JobBackoffLimitPerIndex disabled": {
   656  			enableJobBackoffLimitPerIndex: false,
   657  			enableJobPodFailurePolicy:     true,
   658  			job: batch.Job{
   659  				ObjectMeta: getValidObjectMeta(0),
   660  				Spec: batch.JobSpec{
   661  					Selector:             validSelector,
   662  					ManualSelector:       pointer.Bool(false),
   663  					Template:             validPodTemplateSpec,
   664  					BackoffLimitPerIndex: pointer.Int32(1),
   665  					PodFailurePolicy: &batch.PodFailurePolicy{
   666  						Rules: []batch.PodFailurePolicyRule{
   667  							{
   668  								Action: batch.PodFailurePolicyActionFailIndex,
   669  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   670  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   671  									Values:   []int32{1},
   672  								},
   673  							},
   674  						},
   675  					},
   676  				},
   677  			},
   678  			wantJob: batch.Job{
   679  				ObjectMeta: getValidObjectMeta(1),
   680  				Spec: batch.JobSpec{
   681  					Selector:       validSelector,
   682  					ManualSelector: pointer.Bool(false),
   683  					Template:       expectedPodTemplateSpec,
   684  					PodFailurePolicy: &batch.PodFailurePolicy{
   685  						Rules: []batch.PodFailurePolicyRule{},
   686  					},
   687  				},
   688  			},
   689  		},
   690  		"create job with multiple pod failure policy rules, some using FailIndex action; JobPodFailurePolicy enabled, JobBackoffLimitPerIndex disabled": {
   691  			enableJobBackoffLimitPerIndex: false,
   692  			enableJobPodFailurePolicy:     true,
   693  			job: batch.Job{
   694  				ObjectMeta: getValidObjectMeta(0),
   695  				Spec: batch.JobSpec{
   696  					Selector:             validSelector,
   697  					ManualSelector:       pointer.Bool(false),
   698  					Template:             validPodTemplateSpec,
   699  					BackoffLimitPerIndex: pointer.Int32(1),
   700  					PodFailurePolicy: &batch.PodFailurePolicy{
   701  						Rules: []batch.PodFailurePolicyRule{
   702  							{
   703  								Action: batch.PodFailurePolicyActionFailJob,
   704  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   705  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   706  									Values:   []int32{2},
   707  								},
   708  							},
   709  							{
   710  								Action: batch.PodFailurePolicyActionFailIndex,
   711  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   712  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   713  									Values:   []int32{1},
   714  								},
   715  							},
   716  							{
   717  								Action: batch.PodFailurePolicyActionIgnore,
   718  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   719  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   720  									Values:   []int32{13},
   721  								},
   722  							},
   723  						},
   724  					},
   725  				},
   726  			},
   727  			wantJob: batch.Job{
   728  				ObjectMeta: getValidObjectMeta(1),
   729  				Spec: batch.JobSpec{
   730  					Selector:       validSelector,
   731  					ManualSelector: pointer.Bool(false),
   732  					Template:       expectedPodTemplateSpec,
   733  					PodFailurePolicy: &batch.PodFailurePolicy{
   734  						Rules: []batch.PodFailurePolicyRule{
   735  							{
   736  								Action: batch.PodFailurePolicyActionFailJob,
   737  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   738  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   739  									Values:   []int32{2},
   740  								},
   741  							},
   742  							{
   743  								Action: batch.PodFailurePolicyActionIgnore,
   744  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   745  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   746  									Values:   []int32{13},
   747  								},
   748  							},
   749  						},
   750  					},
   751  				},
   752  			},
   753  		},
   754  	}
   755  
   756  	for name, tc := range cases {
   757  		t.Run(name, func(t *testing.T) {
   758  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
   759  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
   760  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodReplacementPolicy, tc.enableJobPodReplacementPolicy)()
   761  			ctx := genericapirequest.NewDefaultContext()
   762  
   763  			Strategy.PrepareForCreate(ctx, &tc.job)
   764  
   765  			if diff := cmp.Diff(tc.wantJob, tc.job); diff != "" {
   766  				t.Errorf("Job pod failure policy (-want,+got):\n%s", diff)
   767  			}
   768  		})
   769  	}
   770  }
   771  
   772  func TestJobStrategy_GarbageCollectionPolicy(t *testing.T) {
   773  	// Make sure we correctly implement the interface.
   774  	// Otherwise a typo could silently change the default.
   775  	var gcds rest.GarbageCollectionDeleteStrategy = Strategy
   776  	if got, want := gcds.DefaultGarbageCollectionPolicy(genericapirequest.NewContext()), rest.DeleteDependents; got != want {
   777  		t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
   778  	}
   779  
   780  	var (
   781  		v1Ctx           = genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v1", Resource: "jobs"})
   782  		otherVersionCtx = genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{APIGroup: "batch", APIVersion: "v100", Resource: "jobs"})
   783  	)
   784  	if got, want := gcds.DefaultGarbageCollectionPolicy(v1Ctx), rest.OrphanDependents; got != want {
   785  		t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
   786  	}
   787  	if got, want := gcds.DefaultGarbageCollectionPolicy(otherVersionCtx), rest.DeleteDependents; got != want {
   788  		t.Errorf("DefaultGarbageCollectionPolicy() = %#v, want %#v", got, want)
   789  	}
   790  }
   791  
   792  func TestJobStrategy_ValidateUpdate(t *testing.T) {
   793  	ctx := genericapirequest.NewDefaultContext()
   794  	validSelector := &metav1.LabelSelector{
   795  		MatchLabels: map[string]string{"a": "b"},
   796  	}
   797  	validPodTemplateSpec := api.PodTemplateSpec{
   798  		ObjectMeta: metav1.ObjectMeta{
   799  			Labels: validSelector.MatchLabels,
   800  		},
   801  		Spec: api.PodSpec{
   802  			RestartPolicy: api.RestartPolicyOnFailure,
   803  			DNSPolicy:     api.DNSClusterFirst,
   804  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
   805  		},
   806  	}
   807  	validPodTemplateSpecNever := *validPodTemplateSpec.DeepCopy()
   808  	validPodTemplateSpecNever.Spec.RestartPolicy = api.RestartPolicyNever
   809  	now := metav1.Now()
   810  	cases := map[string]struct {
   811  		enableJobPodFailurePolicy     bool
   812  		enableJobBackoffLimitPerIndex bool
   813  		job                           *batch.Job
   814  		update                        func(*batch.Job)
   815  		wantErrs                      field.ErrorList
   816  	}{
   817  		"update parallelism": {
   818  			job: &batch.Job{
   819  				ObjectMeta: metav1.ObjectMeta{
   820  					Name:            "myjob",
   821  					Namespace:       metav1.NamespaceDefault,
   822  					ResourceVersion: "0",
   823  				},
   824  				Spec: batch.JobSpec{
   825  					Selector:       validSelector,
   826  					Template:       validPodTemplateSpec,
   827  					ManualSelector: pointer.BoolPtr(true),
   828  					Parallelism:    pointer.Int32Ptr(1),
   829  				},
   830  			},
   831  			update: func(job *batch.Job) {
   832  				job.Spec.Parallelism = pointer.Int32Ptr(2)
   833  			},
   834  		},
   835  		"update completions disallowed": {
   836  			job: &batch.Job{
   837  				ObjectMeta: metav1.ObjectMeta{
   838  					Name:            "myjob",
   839  					Namespace:       metav1.NamespaceDefault,
   840  					ResourceVersion: "0",
   841  				},
   842  				Spec: batch.JobSpec{
   843  					Selector:       validSelector,
   844  					Template:       validPodTemplateSpec,
   845  					ManualSelector: pointer.BoolPtr(true),
   846  					Parallelism:    pointer.Int32Ptr(1),
   847  					Completions:    pointer.Int32Ptr(1),
   848  				},
   849  			},
   850  			update: func(job *batch.Job) {
   851  				job.Spec.Completions = pointer.Int32Ptr(2)
   852  			},
   853  			wantErrs: field.ErrorList{
   854  				{Type: field.ErrorTypeInvalid, Field: "spec.completions"},
   855  			},
   856  		},
   857  		"preserving tracking annotation": {
   858  			job: &batch.Job{
   859  				ObjectMeta: metav1.ObjectMeta{
   860  					Name:            "myjob",
   861  					Namespace:       metav1.NamespaceDefault,
   862  					ResourceVersion: "0",
   863  					Annotations: map[string]string{
   864  						batch.JobTrackingFinalizer: "",
   865  					},
   866  				},
   867  				Spec: batch.JobSpec{
   868  					Selector:       validSelector,
   869  					Template:       validPodTemplateSpec,
   870  					ManualSelector: pointer.BoolPtr(true),
   871  					Parallelism:    pointer.Int32Ptr(1),
   872  				},
   873  			},
   874  			update: func(job *batch.Job) {
   875  				job.Annotations["foo"] = "bar"
   876  			},
   877  		},
   878  		"deleting user annotation": {
   879  			job: &batch.Job{
   880  				ObjectMeta: metav1.ObjectMeta{
   881  					Name:            "myjob",
   882  					Namespace:       metav1.NamespaceDefault,
   883  					ResourceVersion: "0",
   884  					Annotations: map[string]string{
   885  						batch.JobTrackingFinalizer: "",
   886  						"foo":                      "bar",
   887  					},
   888  				},
   889  				Spec: batch.JobSpec{
   890  					Selector:       validSelector,
   891  					Template:       validPodTemplateSpec,
   892  					ManualSelector: pointer.BoolPtr(true),
   893  					Parallelism:    pointer.Int32Ptr(1),
   894  				},
   895  			},
   896  			update: func(job *batch.Job) {
   897  				delete(job.Annotations, "foo")
   898  			},
   899  		},
   900  		"updating node selector for unsuspended job disallowed": {
   901  			job: &batch.Job{
   902  				ObjectMeta: metav1.ObjectMeta{
   903  					Name:            "myjob",
   904  					Namespace:       metav1.NamespaceDefault,
   905  					ResourceVersion: "0",
   906  					Annotations:     map[string]string{"foo": "bar"},
   907  				},
   908  				Spec: batch.JobSpec{
   909  					Selector:       validSelector,
   910  					Template:       validPodTemplateSpec,
   911  					ManualSelector: pointer.BoolPtr(true),
   912  					Parallelism:    pointer.Int32Ptr(1),
   913  				},
   914  			},
   915  			update: func(job *batch.Job) {
   916  				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
   917  			},
   918  			wantErrs: field.ErrorList{
   919  				{Type: field.ErrorTypeInvalid, Field: "spec.template"},
   920  			},
   921  		},
   922  		"updating node selector for suspended but previously started job disallowed": {
   923  			job: &batch.Job{
   924  				ObjectMeta: metav1.ObjectMeta{
   925  					Name:            "myjob",
   926  					Namespace:       metav1.NamespaceDefault,
   927  					ResourceVersion: "0",
   928  					Annotations:     map[string]string{"foo": "bar"},
   929  				},
   930  				Spec: batch.JobSpec{
   931  					Selector:       validSelector,
   932  					Template:       validPodTemplateSpec,
   933  					ManualSelector: pointer.BoolPtr(true),
   934  					Parallelism:    pointer.Int32Ptr(1),
   935  					Suspend:        pointer.BoolPtr(true),
   936  				},
   937  				Status: batch.JobStatus{
   938  					StartTime: &now,
   939  				},
   940  			},
   941  			update: func(job *batch.Job) {
   942  				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
   943  			},
   944  			wantErrs: field.ErrorList{
   945  				{Type: field.ErrorTypeInvalid, Field: "spec.template"},
   946  			},
   947  		},
   948  		"updating node selector for suspended and not previously started job allowed": {
   949  			job: &batch.Job{
   950  				ObjectMeta: metav1.ObjectMeta{
   951  					Name:            "myjob",
   952  					Namespace:       metav1.NamespaceDefault,
   953  					ResourceVersion: "0",
   954  					Annotations:     map[string]string{"foo": "bar"},
   955  				},
   956  				Spec: batch.JobSpec{
   957  					Selector:       validSelector,
   958  					Template:       validPodTemplateSpec,
   959  					ManualSelector: pointer.BoolPtr(true),
   960  					Parallelism:    pointer.Int32Ptr(1),
   961  					Suspend:        pointer.BoolPtr(true),
   962  				},
   963  			},
   964  			update: func(job *batch.Job) {
   965  				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
   966  			},
   967  		},
   968  		"invalid label selector": {
   969  			job: &batch.Job{
   970  				ObjectMeta: metav1.ObjectMeta{
   971  					Name:            "myjob",
   972  					Namespace:       metav1.NamespaceDefault,
   973  					ResourceVersion: "0",
   974  					Annotations:     map[string]string{"foo": "bar"},
   975  				},
   976  				Spec: batch.JobSpec{
   977  					Selector: &metav1.LabelSelector{
   978  						MatchLabels:      map[string]string{"a": "b"},
   979  						MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}},
   980  					},
   981  					ManualSelector: pointer.BoolPtr(true),
   982  					Template:       validPodTemplateSpec,
   983  				},
   984  			},
   985  			update: func(job *batch.Job) {
   986  				job.Annotations["hello"] = "world"
   987  			},
   988  		},
   989  		"old job has no batch.kubernetes.io labels": {
   990  			job: &batch.Job{
   991  				ObjectMeta: metav1.ObjectMeta{
   992  					Name:            "myjob",
   993  					UID:             "test",
   994  					Namespace:       metav1.NamespaceDefault,
   995  					ResourceVersion: "10",
   996  					Annotations:     map[string]string{"hello": "world"},
   997  				},
   998  				Spec: batch.JobSpec{
   999  					Selector: &metav1.LabelSelector{
  1000  						MatchLabels: map[string]string{batch.LegacyControllerUidLabel: "test"},
  1001  					},
  1002  					Parallelism: pointer.Int32(4),
  1003  					Template: api.PodTemplateSpec{
  1004  						ObjectMeta: metav1.ObjectMeta{
  1005  							Labels: map[string]string{batch.LegacyJobNameLabel: "myjob", batch.LegacyControllerUidLabel: "test"},
  1006  						},
  1007  						Spec: api.PodSpec{
  1008  							RestartPolicy: api.RestartPolicyOnFailure,
  1009  							DNSPolicy:     api.DNSClusterFirst,
  1010  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1011  						},
  1012  					},
  1013  				},
  1014  			},
  1015  			update: func(job *batch.Job) {
  1016  				job.Annotations["hello"] = "world"
  1017  			},
  1018  		},
  1019  		"old job has all labels": {
  1020  			job: &batch.Job{
  1021  				ObjectMeta: metav1.ObjectMeta{
  1022  					Name:            "myjob",
  1023  					UID:             "test",
  1024  					Namespace:       metav1.NamespaceDefault,
  1025  					ResourceVersion: "10",
  1026  					Annotations:     map[string]string{"foo": "bar"},
  1027  				},
  1028  				Spec: batch.JobSpec{
  1029  					Selector: &metav1.LabelSelector{
  1030  						MatchLabels: map[string]string{batch.ControllerUidLabel: "test"},
  1031  					},
  1032  					Parallelism: pointer.Int32(4),
  1033  					Template: api.PodTemplateSpec{
  1034  						ObjectMeta: metav1.ObjectMeta{
  1035  							Labels: map[string]string{batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: "test", batch.ControllerUidLabel: "test"},
  1036  						},
  1037  						Spec: api.PodSpec{
  1038  							RestartPolicy: api.RestartPolicyOnFailure,
  1039  							DNSPolicy:     api.DNSClusterFirst,
  1040  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1041  						},
  1042  					},
  1043  				},
  1044  			},
  1045  			update: func(job *batch.Job) {
  1046  				job.Annotations["hello"] = "world"
  1047  			},
  1048  		},
  1049  		"old job is using FailIndex JobBackoffLimitPerIndex is disabled, but FailIndex was already used": {
  1050  			enableJobPodFailurePolicy:     true,
  1051  			enableJobBackoffLimitPerIndex: false,
  1052  			job: &batch.Job{
  1053  				ObjectMeta: metav1.ObjectMeta{
  1054  					Name:            "myjob",
  1055  					Namespace:       metav1.NamespaceDefault,
  1056  					ResourceVersion: "0",
  1057  					Annotations:     map[string]string{"foo": "bar"},
  1058  				},
  1059  				Spec: batch.JobSpec{
  1060  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1061  					Completions:          pointer.Int32(2),
  1062  					BackoffLimitPerIndex: pointer.Int32(1),
  1063  					Selector:             validSelector,
  1064  					ManualSelector:       pointer.Bool(true),
  1065  					Template:             validPodTemplateSpecNever,
  1066  					PodFailurePolicy: &batch.PodFailurePolicy{
  1067  						Rules: []batch.PodFailurePolicyRule{
  1068  							{
  1069  								Action: batch.PodFailurePolicyActionFailIndex,
  1070  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1071  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1072  									Values:   []int32{1},
  1073  								},
  1074  							},
  1075  						},
  1076  					},
  1077  				},
  1078  			},
  1079  			update: func(job *batch.Job) {
  1080  				job.Annotations["hello"] = "world"
  1081  			},
  1082  		},
  1083  	}
  1084  	for name, tc := range cases {
  1085  		t.Run(name, func(t *testing.T) {
  1086  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
  1087  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
  1088  			newJob := tc.job.DeepCopy()
  1089  			tc.update(newJob)
  1090  			errs := Strategy.ValidateUpdate(ctx, newJob, tc.job)
  1091  			if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" {
  1092  				t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
  1093  			}
  1094  		})
  1095  	}
  1096  }
  1097  
  1098  func TestJobStrategy_WarningsOnUpdate(t *testing.T) {
  1099  	ctx := genericapirequest.NewDefaultContext()
  1100  	validSelector := &metav1.LabelSelector{
  1101  		MatchLabels: map[string]string{"a": "b"},
  1102  	}
  1103  	validPodTemplateSpec := api.PodTemplateSpec{
  1104  		ObjectMeta: metav1.ObjectMeta{
  1105  			Labels: validSelector.MatchLabels,
  1106  		},
  1107  		Spec: api.PodSpec{
  1108  			RestartPolicy: api.RestartPolicyOnFailure,
  1109  			DNSPolicy:     api.DNSClusterFirst,
  1110  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1111  		},
  1112  	}
  1113  	cases := map[string]struct {
  1114  		oldJob            *batch.Job
  1115  		job               *batch.Job
  1116  		wantWarningsCount int32
  1117  	}{
  1118  		"generation 0 for both": {
  1119  			job: &batch.Job{
  1120  				ObjectMeta: metav1.ObjectMeta{
  1121  					Name:            "myjob",
  1122  					Namespace:       metav1.NamespaceDefault,
  1123  					ResourceVersion: "0",
  1124  					Generation:      0,
  1125  				},
  1126  				Spec: batch.JobSpec{
  1127  					Selector:       validSelector,
  1128  					Template:       validPodTemplateSpec,
  1129  					ManualSelector: pointer.BoolPtr(true),
  1130  					Parallelism:    pointer.Int32Ptr(1),
  1131  				},
  1132  			},
  1133  
  1134  			oldJob: &batch.Job{
  1135  				ObjectMeta: metav1.ObjectMeta{
  1136  					Name:            "myjob",
  1137  					Namespace:       metav1.NamespaceDefault,
  1138  					ResourceVersion: "0",
  1139  					Generation:      0,
  1140  				},
  1141  				Spec: batch.JobSpec{
  1142  					Selector:       validSelector,
  1143  					Template:       validPodTemplateSpec,
  1144  					ManualSelector: pointer.BoolPtr(true),
  1145  					Parallelism:    pointer.Int32Ptr(1),
  1146  				},
  1147  			},
  1148  		},
  1149  		"generation 1 for new; force WarningsOnUpdate to check PodTemplate for updates": {
  1150  			job: &batch.Job{
  1151  				ObjectMeta: metav1.ObjectMeta{
  1152  					Name:            "myjob",
  1153  					Namespace:       metav1.NamespaceDefault,
  1154  					ResourceVersion: "0",
  1155  					Generation:      1,
  1156  				},
  1157  				Spec: batch.JobSpec{
  1158  					Selector:       validSelector,
  1159  					Template:       validPodTemplateSpec,
  1160  					ManualSelector: pointer.BoolPtr(true),
  1161  					Parallelism:    pointer.Int32Ptr(1),
  1162  				},
  1163  			},
  1164  
  1165  			oldJob: &batch.Job{
  1166  				ObjectMeta: metav1.ObjectMeta{
  1167  					Name:            "myjob",
  1168  					Namespace:       metav1.NamespaceDefault,
  1169  					ResourceVersion: "0",
  1170  					Generation:      0,
  1171  				},
  1172  				Spec: batch.JobSpec{
  1173  					Selector:       validSelector,
  1174  					Template:       validPodTemplateSpec,
  1175  					ManualSelector: pointer.BoolPtr(true),
  1176  					Parallelism:    pointer.Int32Ptr(1),
  1177  				},
  1178  			},
  1179  		},
  1180  		"force validation failure in pod template": {
  1181  			job: &batch.Job{
  1182  				ObjectMeta: metav1.ObjectMeta{
  1183  					Name:            "myjob",
  1184  					Namespace:       metav1.NamespaceDefault,
  1185  					ResourceVersion: "0",
  1186  					Generation:      1,
  1187  				},
  1188  				Spec: batch.JobSpec{
  1189  					Selector: validSelector,
  1190  					Template: api.PodTemplateSpec{
  1191  						Spec: api.PodSpec{ImagePullSecrets: []api.LocalObjectReference{{Name: ""}}},
  1192  					},
  1193  					ManualSelector: pointer.BoolPtr(true),
  1194  					Parallelism:    pointer.Int32Ptr(1),
  1195  				},
  1196  			},
  1197  
  1198  			oldJob: &batch.Job{
  1199  				ObjectMeta: metav1.ObjectMeta{
  1200  					Name:            "myjob",
  1201  					Namespace:       metav1.NamespaceDefault,
  1202  					ResourceVersion: "0",
  1203  					Generation:      0,
  1204  				},
  1205  				Spec: batch.JobSpec{
  1206  					Selector:       validSelector,
  1207  					Template:       validPodTemplateSpec,
  1208  					ManualSelector: pointer.BoolPtr(true),
  1209  					Parallelism:    pointer.Int32Ptr(1),
  1210  				},
  1211  			},
  1212  			wantWarningsCount: 1,
  1213  		},
  1214  		"Invalid transition to high parallelism": {
  1215  			wantWarningsCount: 1,
  1216  			job: &batch.Job{
  1217  				ObjectMeta: metav1.ObjectMeta{
  1218  					Name:            "myjob2",
  1219  					Namespace:       metav1.NamespaceDefault,
  1220  					Generation:      1,
  1221  					ResourceVersion: "0",
  1222  				},
  1223  				Spec: batch.JobSpec{
  1224  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1225  					Completions:    pointer.Int32(100_001),
  1226  					Parallelism:    pointer.Int32(10_001),
  1227  					Template:       validPodTemplateSpec,
  1228  				},
  1229  			},
  1230  			oldJob: &batch.Job{
  1231  				ObjectMeta: metav1.ObjectMeta{
  1232  					Name:            "myjob2",
  1233  					Namespace:       metav1.NamespaceDefault,
  1234  					Generation:      0,
  1235  					ResourceVersion: "0",
  1236  				},
  1237  				Spec: batch.JobSpec{
  1238  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1239  					Completions:    pointer.Int32(100_001),
  1240  					Parallelism:    pointer.Int32(10_000),
  1241  					Template:       validPodTemplateSpec,
  1242  				},
  1243  			},
  1244  		},
  1245  	}
  1246  	for val, tc := range cases {
  1247  		t.Run(val, func(t *testing.T) {
  1248  			gotWarnings := Strategy.WarningsOnUpdate(ctx, tc.job, tc.oldJob)
  1249  			if len(gotWarnings) != int(tc.wantWarningsCount) {
  1250  				t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount)
  1251  			}
  1252  		})
  1253  	}
  1254  }
  1255  func TestJobStrategy_WarningsOnCreate(t *testing.T) {
  1256  	ctx := genericapirequest.NewDefaultContext()
  1257  
  1258  	theUID := types.UID("1a2b3c4d5e6f7g8h9i0k")
  1259  	validSelector := &metav1.LabelSelector{
  1260  		MatchLabels: map[string]string{"a": "b"},
  1261  	}
  1262  	validPodTemplate := api.PodTemplateSpec{
  1263  		ObjectMeta: metav1.ObjectMeta{
  1264  			Labels: validSelector.MatchLabels,
  1265  		},
  1266  		Spec: api.PodSpec{
  1267  			RestartPolicy: api.RestartPolicyOnFailure,
  1268  			DNSPolicy:     api.DNSClusterFirst,
  1269  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1270  		},
  1271  	}
  1272  	validSpec := batch.JobSpec{
  1273  		CompletionMode: completionModePtr(batch.NonIndexedCompletion),
  1274  		Selector:       nil,
  1275  		Template:       validPodTemplate,
  1276  	}
  1277  
  1278  	testcases := map[string]struct {
  1279  		job               *batch.Job
  1280  		wantWarningsCount int32
  1281  	}{
  1282  		"happy path job": {
  1283  			job: &batch.Job{
  1284  				ObjectMeta: metav1.ObjectMeta{
  1285  					Name:      "myjob2",
  1286  					Namespace: metav1.NamespaceDefault,
  1287  					UID:       theUID,
  1288  				},
  1289  				Spec: validSpec,
  1290  			},
  1291  		},
  1292  		"dns invalid name": {
  1293  			wantWarningsCount: 1,
  1294  			job: &batch.Job{
  1295  				ObjectMeta: metav1.ObjectMeta{
  1296  					Name:      "my job2",
  1297  					Namespace: metav1.NamespaceDefault,
  1298  					UID:       theUID,
  1299  				},
  1300  				Spec: validSpec,
  1301  			},
  1302  		},
  1303  		"high completions and parallelism": {
  1304  			wantWarningsCount: 1,
  1305  			job: &batch.Job{
  1306  				ObjectMeta: metav1.ObjectMeta{
  1307  					Name:      "myjob2",
  1308  					Namespace: metav1.NamespaceDefault,
  1309  					UID:       theUID,
  1310  				},
  1311  				Spec: batch.JobSpec{
  1312  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1313  					Parallelism:    pointer.Int32(100_001),
  1314  					Completions:    pointer.Int32(100_001),
  1315  					Template:       validPodTemplate,
  1316  				},
  1317  			},
  1318  		},
  1319  	}
  1320  	for name, tc := range testcases {
  1321  		t.Run(name, func(t *testing.T) {
  1322  			gotWarnings := Strategy.WarningsOnCreate(ctx, tc.job)
  1323  			if len(gotWarnings) != int(tc.wantWarningsCount) {
  1324  				t.Errorf("got warning length of %d but expected %d", len(gotWarnings), tc.wantWarningsCount)
  1325  			}
  1326  		})
  1327  	}
  1328  }
  1329  func TestJobStrategy_Validate(t *testing.T) {
  1330  	ctx := genericapirequest.NewDefaultContext()
  1331  
  1332  	theUID := getValidUID()
  1333  	validSelector := getValidLabelSelector()
  1334  	batchLabels := getValidBatchLabels()
  1335  	labelsWithNonBatch := getValidBatchLabelsWithNonBatch()
  1336  	defaultSelector := &metav1.LabelSelector{MatchLabels: map[string]string{batch.ControllerUidLabel: string(theUID)}}
  1337  	validPodSpec := api.PodSpec{
  1338  		RestartPolicy: api.RestartPolicyOnFailure,
  1339  		DNSPolicy:     api.DNSClusterFirst,
  1340  		Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1341  	}
  1342  	validPodSpecNever := *validPodSpec.DeepCopy()
  1343  	validPodSpecNever.RestartPolicy = api.RestartPolicyNever
  1344  	validObjectMeta := getValidObjectMeta(0)
  1345  	testcases := map[string]struct {
  1346  		enableJobPodFailurePolicy     bool
  1347  		enableJobBackoffLimitPerIndex bool
  1348  		job                           *batch.Job
  1349  		wantJob                       *batch.Job
  1350  		wantWarningCount              int32
  1351  	}{
  1352  		"valid job with batch labels in pod template": {
  1353  			job: &batch.Job{
  1354  				ObjectMeta: validObjectMeta,
  1355  				Spec: batch.JobSpec{
  1356  					Selector:       defaultSelector,
  1357  					ManualSelector: pointer.Bool(false),
  1358  					Template: api.PodTemplateSpec{
  1359  						ObjectMeta: metav1.ObjectMeta{
  1360  							Labels: batchLabels,
  1361  						},
  1362  						Spec: validPodSpec,
  1363  					}},
  1364  			},
  1365  			wantJob: &batch.Job{
  1366  				ObjectMeta: validObjectMeta,
  1367  				Spec: batch.JobSpec{
  1368  					Selector:       defaultSelector,
  1369  					ManualSelector: pointer.Bool(false),
  1370  					Template: api.PodTemplateSpec{
  1371  						ObjectMeta: metav1.ObjectMeta{
  1372  							Labels: batchLabels,
  1373  						},
  1374  						Spec: validPodSpec,
  1375  					}},
  1376  			},
  1377  		},
  1378  		"valid job with batch and non-batch labels in pod template": {
  1379  			job: &batch.Job{
  1380  				ObjectMeta: validObjectMeta,
  1381  				Spec: batch.JobSpec{
  1382  					Selector:       defaultSelector,
  1383  					ManualSelector: pointer.Bool(false),
  1384  					Template: api.PodTemplateSpec{
  1385  						ObjectMeta: metav1.ObjectMeta{
  1386  							Labels: labelsWithNonBatch,
  1387  						},
  1388  						Spec: validPodSpec,
  1389  					}},
  1390  			},
  1391  			wantJob: &batch.Job{
  1392  				ObjectMeta: validObjectMeta,
  1393  				Spec: batch.JobSpec{
  1394  					Selector:       defaultSelector,
  1395  					ManualSelector: pointer.Bool(false),
  1396  					Template: api.PodTemplateSpec{
  1397  						ObjectMeta: metav1.ObjectMeta{
  1398  							Labels: labelsWithNonBatch,
  1399  						},
  1400  						Spec: validPodSpec,
  1401  					}},
  1402  			},
  1403  		},
  1404  		"job with non-batch labels and without batch labels in pod template": {
  1405  			job: &batch.Job{
  1406  				ObjectMeta: validObjectMeta,
  1407  				Spec: batch.JobSpec{
  1408  					Selector:       defaultSelector,
  1409  					ManualSelector: pointer.Bool(false),
  1410  					Template: api.PodTemplateSpec{
  1411  						ObjectMeta: metav1.ObjectMeta{
  1412  							Labels: map[string]string{},
  1413  						},
  1414  						Spec: validPodSpec,
  1415  					}},
  1416  			},
  1417  			wantJob: &batch.Job{
  1418  				ObjectMeta: validObjectMeta,
  1419  				Spec: batch.JobSpec{
  1420  					Selector:       defaultSelector,
  1421  					ManualSelector: pointer.Bool(false),
  1422  					Template: api.PodTemplateSpec{
  1423  						ObjectMeta: metav1.ObjectMeta{
  1424  							Labels: map[string]string{},
  1425  						},
  1426  						Spec: validPodSpec,
  1427  					}},
  1428  			},
  1429  			wantWarningCount: 5,
  1430  		},
  1431  		"no labels in job": {
  1432  			job: &batch.Job{
  1433  				ObjectMeta: validObjectMeta,
  1434  				Spec: batch.JobSpec{
  1435  					Selector: defaultSelector,
  1436  					Template: api.PodTemplateSpec{
  1437  						Spec: validPodSpec,
  1438  					}},
  1439  			},
  1440  			wantJob: &batch.Job{
  1441  				ObjectMeta: validObjectMeta,
  1442  				Spec: batch.JobSpec{
  1443  					Selector: defaultSelector,
  1444  					Template: api.PodTemplateSpec{
  1445  						Spec: validPodSpec,
  1446  					}},
  1447  			},
  1448  			wantWarningCount: 5,
  1449  		},
  1450  		"manual selector; do not generate labels": {
  1451  			job: &batch.Job{
  1452  				ObjectMeta: validObjectMeta,
  1453  				Spec: batch.JobSpec{
  1454  					Selector: validSelector,
  1455  					Template: api.PodTemplateSpec{
  1456  						ObjectMeta: metav1.ObjectMeta{
  1457  							Labels: validSelector.MatchLabels,
  1458  						},
  1459  						Spec: validPodSpec,
  1460  					},
  1461  					Completions:    pointer.Int32Ptr(2),
  1462  					ManualSelector: pointer.BoolPtr(true),
  1463  				},
  1464  			},
  1465  			wantJob: &batch.Job{
  1466  				ObjectMeta: validObjectMeta,
  1467  				Spec: batch.JobSpec{
  1468  					Selector: validSelector,
  1469  					Template: api.PodTemplateSpec{
  1470  						ObjectMeta: metav1.ObjectMeta{
  1471  							Labels: validSelector.MatchLabels,
  1472  						},
  1473  						Spec: validPodSpec,
  1474  					},
  1475  					Completions:    pointer.Int32Ptr(2),
  1476  					ManualSelector: pointer.BoolPtr(true),
  1477  				},
  1478  			},
  1479  		},
  1480  		"valid job with extended configuration": {
  1481  			job: &batch.Job{
  1482  				ObjectMeta: validObjectMeta,
  1483  				Spec: batch.JobSpec{
  1484  					Selector:       defaultSelector,
  1485  					ManualSelector: pointer.Bool(false),
  1486  					Template: api.PodTemplateSpec{
  1487  						ObjectMeta: metav1.ObjectMeta{
  1488  							Labels: labelsWithNonBatch,
  1489  						},
  1490  						Spec: validPodSpec,
  1491  					},
  1492  					Completions:             pointer.Int32Ptr(2),
  1493  					Suspend:                 pointer.BoolPtr(true),
  1494  					TTLSecondsAfterFinished: pointer.Int32Ptr(0),
  1495  					CompletionMode:          completionModePtr(batch.IndexedCompletion),
  1496  				},
  1497  			},
  1498  			wantJob: &batch.Job{
  1499  				ObjectMeta: validObjectMeta,
  1500  				Spec: batch.JobSpec{
  1501  					Selector:       defaultSelector,
  1502  					ManualSelector: pointer.Bool(false),
  1503  					Template: api.PodTemplateSpec{
  1504  						ObjectMeta: metav1.ObjectMeta{
  1505  							Labels: labelsWithNonBatch,
  1506  						},
  1507  						Spec: validPodSpec,
  1508  					},
  1509  					Completions:             pointer.Int32Ptr(2),
  1510  					Suspend:                 pointer.BoolPtr(true),
  1511  					TTLSecondsAfterFinished: pointer.Int32Ptr(0),
  1512  					CompletionMode:          completionModePtr(batch.IndexedCompletion),
  1513  				},
  1514  			},
  1515  		},
  1516  		"fail validation due to invalid volume spec": {
  1517  			job: &batch.Job{
  1518  				ObjectMeta: validObjectMeta,
  1519  				Spec: batch.JobSpec{
  1520  					Selector:       defaultSelector,
  1521  					ManualSelector: pointer.Bool(false),
  1522  					Template: api.PodTemplateSpec{
  1523  						ObjectMeta: metav1.ObjectMeta{
  1524  							Labels: labelsWithNonBatch,
  1525  						},
  1526  						Spec: api.PodSpec{
  1527  							RestartPolicy: api.RestartPolicyOnFailure,
  1528  							DNSPolicy:     api.DNSClusterFirst,
  1529  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1530  							Volumes:       []api.Volume{{Name: "volume-name"}},
  1531  						},
  1532  					},
  1533  				},
  1534  			},
  1535  			wantJob: &batch.Job{
  1536  				ObjectMeta: validObjectMeta,
  1537  				Spec: batch.JobSpec{
  1538  					Selector:       defaultSelector,
  1539  					ManualSelector: pointer.Bool(false),
  1540  					Template: api.PodTemplateSpec{
  1541  						ObjectMeta: metav1.ObjectMeta{
  1542  							Labels: labelsWithNonBatch,
  1543  						},
  1544  						Spec: api.PodSpec{
  1545  							RestartPolicy: api.RestartPolicyOnFailure,
  1546  							DNSPolicy:     api.DNSClusterFirst,
  1547  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1548  							Volumes:       []api.Volume{{Name: "volume-name"}},
  1549  						},
  1550  					},
  1551  				},
  1552  			},
  1553  			wantWarningCount: 1,
  1554  		},
  1555  		"FailIndex action; when JobBackoffLimitPerIndex is disabled - validation error": {
  1556  			enableJobPodFailurePolicy:     true,
  1557  			enableJobBackoffLimitPerIndex: false,
  1558  			job: &batch.Job{
  1559  				ObjectMeta: validObjectMeta,
  1560  				Spec: batch.JobSpec{
  1561  					Selector:       validSelector,
  1562  					ManualSelector: pointer.Bool(true),
  1563  					Template: api.PodTemplateSpec{
  1564  						ObjectMeta: metav1.ObjectMeta{
  1565  							Labels: validSelector.MatchLabels,
  1566  						},
  1567  						Spec: validPodSpecNever,
  1568  					},
  1569  					PodFailurePolicy: &batch.PodFailurePolicy{
  1570  						Rules: []batch.PodFailurePolicyRule{
  1571  							{
  1572  								Action: batch.PodFailurePolicyActionFailIndex,
  1573  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1574  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1575  									Values:   []int32{1},
  1576  								},
  1577  							},
  1578  						},
  1579  					},
  1580  				},
  1581  			},
  1582  			wantJob: &batch.Job{
  1583  				ObjectMeta: validObjectMeta,
  1584  				Spec: batch.JobSpec{
  1585  					Selector:       validSelector,
  1586  					ManualSelector: pointer.Bool(true),
  1587  					Template: api.PodTemplateSpec{
  1588  						ObjectMeta: metav1.ObjectMeta{
  1589  							Labels: validSelector.MatchLabels,
  1590  						},
  1591  						Spec: validPodSpecNever,
  1592  					},
  1593  					PodFailurePolicy: &batch.PodFailurePolicy{
  1594  						Rules: []batch.PodFailurePolicyRule{
  1595  							{
  1596  								Action: batch.PodFailurePolicyActionFailIndex,
  1597  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1598  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1599  									Values:   []int32{1},
  1600  								},
  1601  							},
  1602  						},
  1603  					},
  1604  				},
  1605  			},
  1606  			wantWarningCount: 1,
  1607  		},
  1608  		"FailIndex action; when JobBackoffLimitPerIndex is enabled, but not used - validation error": {
  1609  			enableJobPodFailurePolicy:     true,
  1610  			enableJobBackoffLimitPerIndex: true,
  1611  			job: &batch.Job{
  1612  				ObjectMeta: validObjectMeta,
  1613  				Spec: batch.JobSpec{
  1614  					Selector:       validSelector,
  1615  					ManualSelector: pointer.Bool(true),
  1616  					Template: api.PodTemplateSpec{
  1617  						ObjectMeta: metav1.ObjectMeta{
  1618  							Labels: validSelector.MatchLabels,
  1619  						},
  1620  						Spec: validPodSpecNever,
  1621  					},
  1622  					PodFailurePolicy: &batch.PodFailurePolicy{
  1623  						Rules: []batch.PodFailurePolicyRule{
  1624  							{
  1625  								Action: batch.PodFailurePolicyActionFailIndex,
  1626  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1627  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1628  									Values:   []int32{1},
  1629  								},
  1630  							},
  1631  						},
  1632  					},
  1633  				},
  1634  			},
  1635  			wantJob: &batch.Job{
  1636  				ObjectMeta: validObjectMeta,
  1637  				Spec: batch.JobSpec{
  1638  					Selector:       validSelector,
  1639  					ManualSelector: pointer.Bool(true),
  1640  					Template: api.PodTemplateSpec{
  1641  						ObjectMeta: metav1.ObjectMeta{
  1642  							Labels: validSelector.MatchLabels,
  1643  						},
  1644  						Spec: validPodSpecNever,
  1645  					},
  1646  					PodFailurePolicy: &batch.PodFailurePolicy{
  1647  						Rules: []batch.PodFailurePolicyRule{
  1648  							{
  1649  								Action: batch.PodFailurePolicyActionFailIndex,
  1650  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1651  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1652  									Values:   []int32{1},
  1653  								},
  1654  							},
  1655  						},
  1656  					},
  1657  				},
  1658  			},
  1659  			wantWarningCount: 1,
  1660  		},
  1661  		"FailIndex action; when JobBackoffLimitPerIndex is enabled and used - no error": {
  1662  			enableJobPodFailurePolicy:     true,
  1663  			enableJobBackoffLimitPerIndex: true,
  1664  			job: &batch.Job{
  1665  				ObjectMeta: validObjectMeta,
  1666  				Spec: batch.JobSpec{
  1667  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1668  					Completions:          pointer.Int32(2),
  1669  					BackoffLimitPerIndex: pointer.Int32(1),
  1670  					Selector:             validSelector,
  1671  					ManualSelector:       pointer.Bool(true),
  1672  					Template: api.PodTemplateSpec{
  1673  						ObjectMeta: metav1.ObjectMeta{
  1674  							Labels: validSelector.MatchLabels,
  1675  						},
  1676  						Spec: validPodSpecNever,
  1677  					},
  1678  					PodFailurePolicy: &batch.PodFailurePolicy{
  1679  						Rules: []batch.PodFailurePolicyRule{
  1680  							{
  1681  								Action: batch.PodFailurePolicyActionFailIndex,
  1682  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1683  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1684  									Values:   []int32{1},
  1685  								},
  1686  							},
  1687  						},
  1688  					},
  1689  				},
  1690  			},
  1691  			wantJob: &batch.Job{
  1692  				ObjectMeta: validObjectMeta,
  1693  				Spec: batch.JobSpec{
  1694  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1695  					Completions:          pointer.Int32(2),
  1696  					BackoffLimitPerIndex: pointer.Int32(1),
  1697  					Selector:             validSelector,
  1698  					ManualSelector:       pointer.Bool(true),
  1699  					Template: api.PodTemplateSpec{
  1700  						ObjectMeta: metav1.ObjectMeta{
  1701  							Labels: validSelector.MatchLabels,
  1702  						},
  1703  						Spec: validPodSpecNever,
  1704  					},
  1705  					PodFailurePolicy: &batch.PodFailurePolicy{
  1706  						Rules: []batch.PodFailurePolicyRule{
  1707  							{
  1708  								Action: batch.PodFailurePolicyActionFailIndex,
  1709  								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
  1710  									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
  1711  									Values:   []int32{1},
  1712  								},
  1713  							},
  1714  						},
  1715  					},
  1716  				},
  1717  			},
  1718  		},
  1719  	}
  1720  	for name, tc := range testcases {
  1721  		t.Run(name, func(t *testing.T) {
  1722  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobPodFailurePolicy, tc.enableJobPodFailurePolicy)()
  1723  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobBackoffLimitPerIndex, tc.enableJobBackoffLimitPerIndex)()
  1724  			errs := Strategy.Validate(ctx, tc.job)
  1725  			if len(errs) != int(tc.wantWarningCount) {
  1726  				t.Errorf("want warnings %d but got %d, errors: %v", tc.wantWarningCount, len(errs), errs)
  1727  			}
  1728  			if diff := cmp.Diff(tc.wantJob, tc.job); diff != "" {
  1729  				t.Errorf("Unexpected job (-want,+got):\n%s", diff)
  1730  			}
  1731  		})
  1732  	}
  1733  }
  1734  
  1735  func TestStrategy_ResetFields(t *testing.T) {
  1736  	resetFields := Strategy.GetResetFields()
  1737  	if len(resetFields) != 1 {
  1738  		t.Errorf("ResetFields should have 1 element, but have %d", len(resetFields))
  1739  	}
  1740  }
  1741  
  1742  func TestJobStatusStrategy_ResetFields(t *testing.T) {
  1743  	resetFields := StatusStrategy.GetResetFields()
  1744  	if len(resetFields) != 1 {
  1745  		t.Errorf("ResetFields should have 1 element, but have %d", len(resetFields))
  1746  	}
  1747  }
  1748  
  1749  func TestStatusStrategy_PrepareForUpdate(t *testing.T) {
  1750  	ctx := genericapirequest.NewDefaultContext()
  1751  	validSelector := &metav1.LabelSelector{
  1752  		MatchLabels: map[string]string{"a": "b"},
  1753  	}
  1754  	validPodTemplateSpec := api.PodTemplateSpec{
  1755  		ObjectMeta: metav1.ObjectMeta{
  1756  			Labels: validSelector.MatchLabels,
  1757  		},
  1758  		Spec: api.PodSpec{
  1759  			RestartPolicy: api.RestartPolicyOnFailure,
  1760  			DNSPolicy:     api.DNSClusterFirst,
  1761  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1762  		},
  1763  	}
  1764  	validObjectMeta := metav1.ObjectMeta{
  1765  		Name:            "myjob",
  1766  		Namespace:       metav1.NamespaceDefault,
  1767  		ResourceVersion: "10",
  1768  	}
  1769  
  1770  	cases := map[string]struct {
  1771  		job     *batch.Job
  1772  		newJob  *batch.Job
  1773  		wantJob *batch.Job
  1774  	}{
  1775  		"job must allow status updates": {
  1776  			job: &batch.Job{
  1777  				ObjectMeta: validObjectMeta,
  1778  				Spec: batch.JobSpec{
  1779  					Selector:    validSelector,
  1780  					Template:    validPodTemplateSpec,
  1781  					Parallelism: pointer.Int32(4),
  1782  				},
  1783  				Status: batch.JobStatus{
  1784  					Active: 11,
  1785  				},
  1786  			},
  1787  			newJob: &batch.Job{
  1788  				ObjectMeta: validObjectMeta,
  1789  				Spec: batch.JobSpec{
  1790  					Selector:    validSelector,
  1791  					Template:    validPodTemplateSpec,
  1792  					Parallelism: pointer.Int32(4),
  1793  				},
  1794  				Status: batch.JobStatus{
  1795  					Active: 12,
  1796  				},
  1797  			},
  1798  			wantJob: &batch.Job{
  1799  				ObjectMeta: validObjectMeta,
  1800  				Spec: batch.JobSpec{
  1801  					Selector:    validSelector,
  1802  					Template:    validPodTemplateSpec,
  1803  					Parallelism: pointer.Int32(4),
  1804  				},
  1805  				Status: batch.JobStatus{
  1806  					Active: 12,
  1807  				},
  1808  			},
  1809  		},
  1810  		"parallelism changes not allowed": {
  1811  			job: &batch.Job{
  1812  				ObjectMeta: validObjectMeta,
  1813  				Spec: batch.JobSpec{
  1814  					Selector:    validSelector,
  1815  					Template:    validPodTemplateSpec,
  1816  					Parallelism: pointer.Int32(3),
  1817  				},
  1818  			},
  1819  			newJob: &batch.Job{
  1820  				ObjectMeta: validObjectMeta,
  1821  				Spec: batch.JobSpec{
  1822  					Selector:    validSelector,
  1823  					Template:    validPodTemplateSpec,
  1824  					Parallelism: pointer.Int32(4),
  1825  				},
  1826  			},
  1827  			wantJob: &batch.Job{
  1828  				ObjectMeta: validObjectMeta,
  1829  				Spec: batch.JobSpec{
  1830  					Selector:    validSelector,
  1831  					Template:    validPodTemplateSpec,
  1832  					Parallelism: pointer.Int32(3),
  1833  				},
  1834  			},
  1835  		},
  1836  	}
  1837  	for name, tc := range cases {
  1838  		t.Run(name, func(t *testing.T) {
  1839  			StatusStrategy.PrepareForUpdate(ctx, tc.newJob, tc.job)
  1840  			if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" {
  1841  				t.Errorf("Unexpected job (-want,+got):\n%s", diff)
  1842  			}
  1843  		})
  1844  	}
  1845  }
  1846  
  1847  func TestStatusStrategy_ValidateUpdate(t *testing.T) {
  1848  	ctx := genericapirequest.NewDefaultContext()
  1849  	validSelector := &metav1.LabelSelector{
  1850  		MatchLabels: map[string]string{"a": "b"},
  1851  	}
  1852  	validPodTemplateSpec := api.PodTemplateSpec{
  1853  		ObjectMeta: metav1.ObjectMeta{
  1854  			Labels: validSelector.MatchLabels,
  1855  		},
  1856  		Spec: api.PodSpec{
  1857  			RestartPolicy: api.RestartPolicyOnFailure,
  1858  			DNSPolicy:     api.DNSClusterFirst,
  1859  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1860  		},
  1861  	}
  1862  
  1863  	cases := map[string]struct {
  1864  		job     *batch.Job
  1865  		newJob  *batch.Job
  1866  		wantJob *batch.Job
  1867  	}{
  1868  		"incoming resource version on update should not be mutated": {
  1869  			job: &batch.Job{
  1870  				ObjectMeta: metav1.ObjectMeta{
  1871  					Name:            "myjob",
  1872  					Namespace:       metav1.NamespaceDefault,
  1873  					ResourceVersion: "10",
  1874  				},
  1875  				Spec: batch.JobSpec{
  1876  					Selector:    validSelector,
  1877  					Template:    validPodTemplateSpec,
  1878  					Parallelism: pointer.Int32(4),
  1879  				},
  1880  			},
  1881  			newJob: &batch.Job{
  1882  				ObjectMeta: metav1.ObjectMeta{
  1883  					Name:            "myjob",
  1884  					Namespace:       metav1.NamespaceDefault,
  1885  					ResourceVersion: "9",
  1886  				},
  1887  				Spec: batch.JobSpec{
  1888  					Selector:    validSelector,
  1889  					Template:    validPodTemplateSpec,
  1890  					Parallelism: pointer.Int32(4),
  1891  				},
  1892  			},
  1893  			wantJob: &batch.Job{
  1894  				ObjectMeta: metav1.ObjectMeta{
  1895  					Name:            "myjob",
  1896  					Namespace:       metav1.NamespaceDefault,
  1897  					ResourceVersion: "9",
  1898  				},
  1899  				Spec: batch.JobSpec{
  1900  					Selector:    validSelector,
  1901  					Template:    validPodTemplateSpec,
  1902  					Parallelism: pointer.Int32(4),
  1903  				},
  1904  			},
  1905  		},
  1906  	}
  1907  	for name, tc := range cases {
  1908  		t.Run(name, func(t *testing.T) {
  1909  			errs := StatusStrategy.ValidateUpdate(ctx, tc.newJob, tc.job)
  1910  			if len(errs) != 0 {
  1911  				t.Errorf("Unexpected error %v", errs)
  1912  			}
  1913  			if diff := cmp.Diff(tc.wantJob, tc.newJob); diff != "" {
  1914  				t.Errorf("Unexpected job (-want,+got):\n%s", diff)
  1915  			}
  1916  		})
  1917  	}
  1918  }
  1919  
  1920  func TestJobStrategy_GetAttrs(t *testing.T) {
  1921  	validSelector := &metav1.LabelSelector{
  1922  		MatchLabels: map[string]string{"a": "b"},
  1923  	}
  1924  	validPodTemplateSpec := api.PodTemplateSpec{
  1925  		ObjectMeta: metav1.ObjectMeta{
  1926  			Labels: validSelector.MatchLabels,
  1927  		},
  1928  		Spec: api.PodSpec{
  1929  			RestartPolicy: api.RestartPolicyOnFailure,
  1930  			DNSPolicy:     api.DNSClusterFirst,
  1931  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1932  		},
  1933  	}
  1934  
  1935  	cases := map[string]struct {
  1936  		job          *batch.Job
  1937  		wantErr      string
  1938  		nonJobObject *api.Pod
  1939  	}{
  1940  		"valid job with no labels": {
  1941  			job: &batch.Job{
  1942  				ObjectMeta: metav1.ObjectMeta{
  1943  					Name:            "myjob",
  1944  					Namespace:       metav1.NamespaceDefault,
  1945  					ResourceVersion: "0",
  1946  				},
  1947  				Spec: batch.JobSpec{
  1948  					Selector:       validSelector,
  1949  					Template:       validPodTemplateSpec,
  1950  					ManualSelector: pointer.BoolPtr(true),
  1951  					Parallelism:    pointer.Int32Ptr(1),
  1952  				},
  1953  			},
  1954  		},
  1955  		"valid job with a label": {
  1956  			job: &batch.Job{
  1957  				ObjectMeta: metav1.ObjectMeta{
  1958  					Name:            "myjob",
  1959  					Namespace:       metav1.NamespaceDefault,
  1960  					ResourceVersion: "0",
  1961  					Labels:          map[string]string{"a": "b"},
  1962  				},
  1963  				Spec: batch.JobSpec{
  1964  					Selector:       validSelector,
  1965  					Template:       validPodTemplateSpec,
  1966  					ManualSelector: pointer.BoolPtr(true),
  1967  					Parallelism:    pointer.Int32Ptr(1),
  1968  				},
  1969  			},
  1970  		},
  1971  		"pod instead": {
  1972  			job:          nil,
  1973  			nonJobObject: &api.Pod{},
  1974  			wantErr:      "given object is not a job.",
  1975  		},
  1976  	}
  1977  	for name, tc := range cases {
  1978  		t.Run(name, func(t *testing.T) {
  1979  			if tc.job == nil {
  1980  				_, _, err := GetAttrs(tc.nonJobObject)
  1981  				if diff := cmp.Diff(tc.wantErr, err.Error()); diff != "" {
  1982  					t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
  1983  				}
  1984  			} else {
  1985  				gotLabels, _, err := GetAttrs(tc.job)
  1986  				if err != nil {
  1987  					t.Errorf("Error %s supposed to be nil", err.Error())
  1988  				}
  1989  				if diff := cmp.Diff(labels.Set(tc.job.ObjectMeta.Labels), gotLabels); diff != "" {
  1990  					t.Errorf("Unexpected attrs (-want,+got):\n%s", diff)
  1991  				}
  1992  			}
  1993  		})
  1994  	}
  1995  }
  1996  
  1997  func TestJobToSelectiableFields(t *testing.T) {
  1998  	apitesting.TestSelectableFieldLabelConversionsOfKind(t,
  1999  		"batch/v1",
  2000  		"Job",
  2001  		JobToSelectableFields(&batch.Job{}),
  2002  		nil,
  2003  	)
  2004  }
  2005  
  2006  func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
  2007  	return &m
  2008  }
  2009  
  2010  func podReplacementPolicy(m batch.PodReplacementPolicy) *batch.PodReplacementPolicy {
  2011  	return &m
  2012  }
  2013  
  2014  func getValidObjectMeta(generation int64) metav1.ObjectMeta {
  2015  	return getValidObjectMetaWithAnnotations(generation, nil)
  2016  }
  2017  
  2018  func getValidUID() types.UID {
  2019  	return "1a2b3c4d5e6f7g8h9i0k"
  2020  }
  2021  
  2022  func getValidObjectMetaWithAnnotations(generation int64, annotations map[string]string) metav1.ObjectMeta {
  2023  	return metav1.ObjectMeta{
  2024  		Name:        "myjob",
  2025  		UID:         getValidUID(),
  2026  		Namespace:   metav1.NamespaceDefault,
  2027  		Generation:  generation,
  2028  		Annotations: annotations,
  2029  	}
  2030  }
  2031  
  2032  func getValidLabelSelector() *metav1.LabelSelector {
  2033  	return &metav1.LabelSelector{
  2034  		MatchLabels: map[string]string{"a": "b"},
  2035  	}
  2036  }
  2037  
  2038  func getValidBatchLabels() map[string]string {
  2039  	theUID := getValidUID()
  2040  	return map[string]string{batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: string(theUID), batch.ControllerUidLabel: string(theUID)}
  2041  }
  2042  
  2043  func getValidBatchLabelsWithNonBatch() map[string]string {
  2044  	theUID := getValidUID()
  2045  	return map[string]string{"a": "b", batch.LegacyJobNameLabel: "myjob", batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: string(theUID), batch.ControllerUidLabel: string(theUID)}
  2046  }
  2047  
  2048  func getValidPodTemplateSpecForSelector(validSelector *metav1.LabelSelector) api.PodTemplateSpec {
  2049  	return api.PodTemplateSpec{
  2050  		ObjectMeta: metav1.ObjectMeta{
  2051  			Labels: validSelector.MatchLabels,
  2052  		},
  2053  		Spec: api.PodSpec{
  2054  			RestartPolicy: api.RestartPolicyOnFailure,
  2055  			DNSPolicy:     api.DNSClusterFirst,
  2056  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2057  		},
  2058  	}
  2059  }