k8s.io/kubernetes@v1.29.3/pkg/apis/batch/validation/validation_test.go (about)

     1  /*
     2  Copyright 2016 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 validation
    18  
    19  import (
    20  	_ "time/tzdata"
    21  
    22  	"fmt"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/validation/field"
    32  	"k8s.io/kubernetes/pkg/apis/batch"
    33  	api "k8s.io/kubernetes/pkg/apis/core"
    34  	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    35  	"k8s.io/utils/pointer"
    36  )
    37  
    38  var (
    39  	timeZoneEmpty      = ""
    40  	timeZoneLocal      = "LOCAL"
    41  	timeZoneUTC        = "UTC"
    42  	timeZoneCorrect    = "Europe/Rome"
    43  	timeZoneBadPrefix  = " Europe/Rome"
    44  	timeZoneBadSuffix  = "Europe/Rome "
    45  	timeZoneBadName    = "Europe/InvalidRome"
    46  	timeZoneEmptySpace = " "
    47  )
    48  
    49  var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
    50  
    51  func getValidManualSelector() *metav1.LabelSelector {
    52  	return &metav1.LabelSelector{
    53  		MatchLabels: map[string]string{"a": "b"},
    54  	}
    55  }
    56  
    57  func getValidPodTemplateSpecForManual(selector *metav1.LabelSelector) api.PodTemplateSpec {
    58  	return api.PodTemplateSpec{
    59  		ObjectMeta: metav1.ObjectMeta{
    60  			Labels: selector.MatchLabels,
    61  		},
    62  		Spec: api.PodSpec{
    63  			RestartPolicy: api.RestartPolicyOnFailure,
    64  			DNSPolicy:     api.DNSClusterFirst,
    65  			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
    66  		},
    67  	}
    68  }
    69  
    70  func getValidGeneratedSelector() *metav1.LabelSelector {
    71  	return &metav1.LabelSelector{
    72  		MatchLabels: map[string]string{batch.ControllerUidLabel: "1a2b3c", batch.LegacyControllerUidLabel: "1a2b3c", batch.JobNameLabel: "myjob", batch.LegacyJobNameLabel: "myjob"},
    73  	}
    74  }
    75  
    76  func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.PodTemplateSpec {
    77  	return api.PodTemplateSpec{
    78  		ObjectMeta: metav1.ObjectMeta{
    79  			Labels: selector.MatchLabels,
    80  		},
    81  		Spec: api.PodSpec{
    82  			RestartPolicy:  api.RestartPolicyOnFailure,
    83  			DNSPolicy:      api.DNSClusterFirst,
    84  			Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
    85  			InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
    86  		},
    87  	}
    88  }
    89  
    90  func TestValidateJob(t *testing.T) {
    91  	validJobObjectMeta := metav1.ObjectMeta{
    92  		Name:      "myjob",
    93  		Namespace: metav1.NamespaceDefault,
    94  		UID:       types.UID("1a2b3c"),
    95  	}
    96  	validManualSelector := getValidManualSelector()
    97  	failedPodReplacement := batch.Failed
    98  	terminatingOrFailedPodReplacement := batch.TerminatingOrFailed
    99  	validPodTemplateSpecForManual := getValidPodTemplateSpecForManual(validManualSelector)
   100  	validGeneratedSelector := getValidGeneratedSelector()
   101  	validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
   102  	validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
   103  	validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever
   104  	validHostNetPodTemplateSpec := func() api.PodTemplateSpec {
   105  		spec := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
   106  		spec.Spec.SecurityContext = &api.PodSecurityContext{
   107  			HostNetwork: true,
   108  		}
   109  		spec.Spec.Containers[0].Ports = []api.ContainerPort{{
   110  			ContainerPort: 12345,
   111  			Protocol:      api.ProtocolTCP,
   112  		}}
   113  		return spec
   114  	}()
   115  
   116  	successCases := map[string]struct {
   117  		opts JobValidationOptions
   118  		job  batch.Job
   119  	}{
   120  		"valid pod failure policy": {
   121  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   122  			job: batch.Job{
   123  				ObjectMeta: validJobObjectMeta,
   124  				Spec: batch.JobSpec{
   125  					Selector: validGeneratedSelector,
   126  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   127  					PodFailurePolicy: &batch.PodFailurePolicy{
   128  						Rules: []batch.PodFailurePolicyRule{{
   129  							Action: batch.PodFailurePolicyActionIgnore,
   130  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   131  								Type:   api.DisruptionTarget,
   132  								Status: api.ConditionTrue,
   133  							}},
   134  						}, {
   135  							Action: batch.PodFailurePolicyActionFailJob,
   136  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   137  								Type:   api.PodConditionType("CustomConditionType"),
   138  								Status: api.ConditionFalse,
   139  							}},
   140  						}, {
   141  							Action: batch.PodFailurePolicyActionCount,
   142  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   143  								ContainerName: pointer.String("abc"),
   144  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   145  								Values:        []int32{1, 2, 3},
   146  							},
   147  						}, {
   148  							Action: batch.PodFailurePolicyActionIgnore,
   149  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   150  								ContainerName: pointer.String("def"),
   151  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   152  								Values:        []int32{4},
   153  							},
   154  						}, {
   155  							Action: batch.PodFailurePolicyActionFailJob,
   156  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   157  								Operator: batch.PodFailurePolicyOnExitCodesOpNotIn,
   158  								Values:   []int32{5, 6, 7},
   159  							},
   160  						}},
   161  					},
   162  				},
   163  			},
   164  		},
   165  		"valid pod failure policy with FailIndex": {
   166  			job: batch.Job{
   167  				ObjectMeta: validJobObjectMeta,
   168  				Spec: batch.JobSpec{
   169  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   170  					Completions:          pointer.Int32(2),
   171  					BackoffLimitPerIndex: pointer.Int32(1),
   172  					Selector:             validGeneratedSelector,
   173  					ManualSelector:       pointer.Bool(true),
   174  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
   175  					PodFailurePolicy: &batch.PodFailurePolicy{
   176  						Rules: []batch.PodFailurePolicyRule{{
   177  							Action: batch.PodFailurePolicyActionFailIndex,
   178  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   179  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   180  								Values:   []int32{10},
   181  							},
   182  						}},
   183  					},
   184  				},
   185  			},
   186  		},
   187  		"valid manual selector": {
   188  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   189  			job: batch.Job{
   190  				ObjectMeta: metav1.ObjectMeta{
   191  					Name:        "myjob",
   192  					Namespace:   metav1.NamespaceDefault,
   193  					UID:         types.UID("1a2b3c"),
   194  					Annotations: map[string]string{"foo": "bar"},
   195  				},
   196  				Spec: batch.JobSpec{
   197  					Selector:       validManualSelector,
   198  					ManualSelector: pointer.Bool(true),
   199  					Template:       validPodTemplateSpecForManual,
   200  				},
   201  			},
   202  		},
   203  		"valid generated selector": {
   204  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   205  			job: batch.Job{
   206  				ObjectMeta: metav1.ObjectMeta{
   207  					Name:      "myjob",
   208  					Namespace: metav1.NamespaceDefault,
   209  					UID:       types.UID("1a2b3c"),
   210  				},
   211  				Spec: batch.JobSpec{
   212  					Selector: validGeneratedSelector,
   213  					Template: validPodTemplateSpecForGenerated,
   214  				},
   215  			},
   216  		},
   217  		"valid pod replacement": {
   218  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   219  			job: batch.Job{
   220  				ObjectMeta: metav1.ObjectMeta{
   221  					Name:      "myjob",
   222  					Namespace: metav1.NamespaceDefault,
   223  					UID:       types.UID("1a2b3c"),
   224  				},
   225  				Spec: batch.JobSpec{
   226  					Selector:             validGeneratedSelector,
   227  					Template:             validPodTemplateSpecForGenerated,
   228  					PodReplacementPolicy: &terminatingOrFailedPodReplacement,
   229  				},
   230  			},
   231  		},
   232  		"valid pod replacement with failed": {
   233  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   234  			job: batch.Job{
   235  				ObjectMeta: metav1.ObjectMeta{
   236  					Name:      "myjob",
   237  					Namespace: metav1.NamespaceDefault,
   238  					UID:       types.UID("1a2b3c"),
   239  				},
   240  				Spec: batch.JobSpec{
   241  					Selector:             validGeneratedSelector,
   242  					Template:             validPodTemplateSpecForGenerated,
   243  					PodReplacementPolicy: &failedPodReplacement,
   244  				},
   245  			},
   246  		},
   247  		"valid hostnet": {
   248  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   249  			job: batch.Job{
   250  				ObjectMeta: metav1.ObjectMeta{
   251  					Name:      "myjob",
   252  					Namespace: metav1.NamespaceDefault,
   253  					UID:       types.UID("1a2b3c"),
   254  				},
   255  				Spec: batch.JobSpec{
   256  					Selector: validGeneratedSelector,
   257  					Template: validHostNetPodTemplateSpec,
   258  				},
   259  			},
   260  		},
   261  		"valid NonIndexed completion mode": {
   262  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   263  			job: batch.Job{
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Name:      "myjob",
   266  					Namespace: metav1.NamespaceDefault,
   267  					UID:       types.UID("1a2b3c"),
   268  				},
   269  				Spec: batch.JobSpec{
   270  					Selector:       validGeneratedSelector,
   271  					Template:       validPodTemplateSpecForGenerated,
   272  					CompletionMode: completionModePtr(batch.NonIndexedCompletion),
   273  				},
   274  			},
   275  		},
   276  		"valid Indexed completion mode": {
   277  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   278  			job: batch.Job{
   279  				ObjectMeta: metav1.ObjectMeta{
   280  					Name:      "myjob",
   281  					Namespace: metav1.NamespaceDefault,
   282  					UID:       types.UID("1a2b3c"),
   283  				},
   284  				Spec: batch.JobSpec{
   285  					Selector:       validGeneratedSelector,
   286  					Template:       validPodTemplateSpecForGenerated,
   287  					CompletionMode: completionModePtr(batch.IndexedCompletion),
   288  					Completions:    pointer.Int32(2),
   289  					Parallelism:    pointer.Int32(100000),
   290  				},
   291  			},
   292  		},
   293  		"valid parallelism and maxFailedIndexes for high completions when backoffLimitPerIndex is used": {
   294  			job: batch.Job{
   295  				ObjectMeta: validJobObjectMeta,
   296  				Spec: batch.JobSpec{
   297  					Completions:          pointer.Int32(100_000),
   298  					Parallelism:          pointer.Int32(100_000),
   299  					MaxFailedIndexes:     pointer.Int32(100_000),
   300  					BackoffLimitPerIndex: pointer.Int32(1),
   301  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   302  					Selector:             validGeneratedSelector,
   303  					Template:             validPodTemplateSpecForGenerated,
   304  				},
   305  			},
   306  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   307  		},
   308  		"valid parallelism and maxFailedIndexes for unlimited completions when backoffLimitPerIndex is used": {
   309  			job: batch.Job{
   310  				ObjectMeta: validJobObjectMeta,
   311  				Spec: batch.JobSpec{
   312  					Completions:          pointer.Int32(1_000_000_000),
   313  					Parallelism:          pointer.Int32(10_000),
   314  					MaxFailedIndexes:     pointer.Int32(10_000),
   315  					BackoffLimitPerIndex: pointer.Int32(1),
   316  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   317  					Selector:             validGeneratedSelector,
   318  					Template:             validPodTemplateSpecForGenerated,
   319  				},
   320  			},
   321  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   322  		},
   323  		"valid job tracking annotation": {
   324  			opts: JobValidationOptions{
   325  				RequirePrefixedLabels: true,
   326  			},
   327  			job: batch.Job{
   328  				ObjectMeta: metav1.ObjectMeta{
   329  					Name:      "myjob",
   330  					Namespace: metav1.NamespaceDefault,
   331  					UID:       types.UID("1a2b3c"),
   332  				},
   333  				Spec: batch.JobSpec{
   334  					Selector: validGeneratedSelector,
   335  					Template: validPodTemplateSpecForGenerated,
   336  				},
   337  			},
   338  		},
   339  		"valid batch labels": {
   340  			opts: JobValidationOptions{
   341  				RequirePrefixedLabels: true,
   342  			},
   343  			job: batch.Job{
   344  				ObjectMeta: metav1.ObjectMeta{
   345  					Name:      "myjob",
   346  					Namespace: metav1.NamespaceDefault,
   347  					UID:       types.UID("1a2b3c"),
   348  				},
   349  				Spec: batch.JobSpec{
   350  					Selector: validGeneratedSelector,
   351  					Template: validPodTemplateSpecForGenerated,
   352  				},
   353  			},
   354  		},
   355  		"do not allow new batch labels": {
   356  			opts: JobValidationOptions{
   357  				RequirePrefixedLabels: false,
   358  			},
   359  			job: batch.Job{
   360  				ObjectMeta: metav1.ObjectMeta{
   361  					Name:      "myjob",
   362  					Namespace: metav1.NamespaceDefault,
   363  					UID:       types.UID("1a2b3c"),
   364  				},
   365  				Spec: batch.JobSpec{
   366  					Selector: &metav1.LabelSelector{
   367  						MatchLabels: map[string]string{batch.LegacyControllerUidLabel: "1a2b3c"},
   368  					},
   369  					Template: api.PodTemplateSpec{
   370  						ObjectMeta: metav1.ObjectMeta{
   371  							Labels: map[string]string{batch.LegacyControllerUidLabel: "1a2b3c", batch.LegacyJobNameLabel: "myjob"},
   372  						},
   373  						Spec: api.PodSpec{
   374  							RestartPolicy:  api.RestartPolicyOnFailure,
   375  							DNSPolicy:      api.DNSClusterFirst,
   376  							Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
   377  							InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
   378  						},
   379  					},
   380  				},
   381  			},
   382  		},
   383  	}
   384  	for k, v := range successCases {
   385  		t.Run(k, func(t *testing.T) {
   386  			if errs := ValidateJob(&v.job, v.opts); len(errs) != 0 {
   387  				t.Errorf("Got unexpected validation errors: %v", errs)
   388  			}
   389  		})
   390  	}
   391  	negative := int32(-1)
   392  	negative64 := int64(-1)
   393  	errorCases := map[string]struct {
   394  		opts JobValidationOptions
   395  		job  batch.Job
   396  	}{
   397  		`spec.podFailurePolicy.rules[0]: Invalid value: specifying one of OnExitCodes and OnPodConditions is required`: {
   398  			job: batch.Job{
   399  				ObjectMeta: validJobObjectMeta,
   400  				Spec: batch.JobSpec{
   401  					Selector: validGeneratedSelector,
   402  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   403  					PodFailurePolicy: &batch.PodFailurePolicy{
   404  						Rules: []batch.PodFailurePolicyRule{{
   405  							Action: batch.PodFailurePolicyActionFailJob,
   406  						}},
   407  					},
   408  				},
   409  			},
   410  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   411  		},
   412  		`spec.podFailurePolicy.rules[0].onExitCodes.values[1]: Duplicate value: 11`: {
   413  			job: batch.Job{
   414  				ObjectMeta: validJobObjectMeta,
   415  				Spec: batch.JobSpec{
   416  					Selector: validGeneratedSelector,
   417  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   418  					PodFailurePolicy: &batch.PodFailurePolicy{
   419  						Rules: []batch.PodFailurePolicyRule{{
   420  							Action: batch.PodFailurePolicyActionFailJob,
   421  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   422  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   423  								Values:   []int32{11, 11},
   424  							},
   425  						}},
   426  					},
   427  				},
   428  			},
   429  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   430  		},
   431  		`spec.podFailurePolicy.rules[0].onExitCodes.values: Too many: 256: must have at most 255 items`: {
   432  			job: batch.Job{
   433  				ObjectMeta: validJobObjectMeta,
   434  				Spec: batch.JobSpec{
   435  					Selector: validGeneratedSelector,
   436  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   437  					PodFailurePolicy: &batch.PodFailurePolicy{
   438  						Rules: []batch.PodFailurePolicyRule{{
   439  							Action: batch.PodFailurePolicyActionFailJob,
   440  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   441  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   442  								Values: func() (values []int32) {
   443  									tooManyValues := make([]int32, maxPodFailurePolicyOnExitCodesValues+1)
   444  									for i := range tooManyValues {
   445  										tooManyValues[i] = int32(i)
   446  									}
   447  									return tooManyValues
   448  								}(),
   449  							},
   450  						}},
   451  					},
   452  				},
   453  			},
   454  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   455  		},
   456  		`spec.podFailurePolicy.rules: Too many: 21: must have at most 20 items`: {
   457  			job: batch.Job{
   458  				ObjectMeta: validJobObjectMeta,
   459  				Spec: batch.JobSpec{
   460  					Selector: validGeneratedSelector,
   461  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   462  					PodFailurePolicy: &batch.PodFailurePolicy{
   463  						Rules: func() []batch.PodFailurePolicyRule {
   464  							tooManyRules := make([]batch.PodFailurePolicyRule, maxPodFailurePolicyRules+1)
   465  							for i := range tooManyRules {
   466  								tooManyRules[i] = batch.PodFailurePolicyRule{
   467  									Action: batch.PodFailurePolicyActionFailJob,
   468  									OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   469  										Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   470  										Values:   []int32{int32(i + 1)},
   471  									},
   472  								}
   473  							}
   474  							return tooManyRules
   475  						}(),
   476  					},
   477  				},
   478  			},
   479  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   480  		},
   481  		`spec.podFailurePolicy.rules[0].onPodConditions: Too many: 21: must have at most 20 items`: {
   482  			job: batch.Job{
   483  				ObjectMeta: validJobObjectMeta,
   484  				Spec: batch.JobSpec{
   485  					Selector: validGeneratedSelector,
   486  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   487  					PodFailurePolicy: &batch.PodFailurePolicy{
   488  						Rules: []batch.PodFailurePolicyRule{{
   489  							Action: batch.PodFailurePolicyActionFailJob,
   490  							OnPodConditions: func() []batch.PodFailurePolicyOnPodConditionsPattern {
   491  								tooManyPatterns := make([]batch.PodFailurePolicyOnPodConditionsPattern, maxPodFailurePolicyOnPodConditionsPatterns+1)
   492  								for i := range tooManyPatterns {
   493  									tooManyPatterns[i] = batch.PodFailurePolicyOnPodConditionsPattern{
   494  										Type:   api.PodConditionType(fmt.Sprintf("CustomType_%d", i)),
   495  										Status: api.ConditionTrue,
   496  									}
   497  								}
   498  								return tooManyPatterns
   499  							}(),
   500  						}},
   501  					},
   502  				},
   503  			},
   504  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   505  		},
   506  		`spec.podFailurePolicy.rules[0].onExitCodes.values[2]: Duplicate value: 13`: {
   507  			job: batch.Job{
   508  				ObjectMeta: validJobObjectMeta,
   509  				Spec: batch.JobSpec{
   510  					Selector: validGeneratedSelector,
   511  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   512  					PodFailurePolicy: &batch.PodFailurePolicy{
   513  						Rules: []batch.PodFailurePolicyRule{{
   514  							Action: batch.PodFailurePolicyActionFailJob,
   515  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   516  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   517  								Values:   []int32{12, 13, 13, 13},
   518  							},
   519  						}},
   520  					},
   521  				},
   522  			},
   523  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   524  		},
   525  		`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{19, 11}: must be ordered`: {
   526  			job: batch.Job{
   527  				ObjectMeta: validJobObjectMeta,
   528  				Spec: batch.JobSpec{
   529  					Selector: validGeneratedSelector,
   530  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   531  					PodFailurePolicy: &batch.PodFailurePolicy{
   532  						Rules: []batch.PodFailurePolicyRule{{
   533  							Action: batch.PodFailurePolicyActionFailJob,
   534  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   535  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   536  								Values:   []int32{19, 11},
   537  							},
   538  						}},
   539  					},
   540  				},
   541  			},
   542  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   543  		},
   544  		`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{}: at least one value is required`: {
   545  			job: batch.Job{
   546  				ObjectMeta: validJobObjectMeta,
   547  				Spec: batch.JobSpec{
   548  					Selector: validGeneratedSelector,
   549  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   550  					PodFailurePolicy: &batch.PodFailurePolicy{
   551  						Rules: []batch.PodFailurePolicyRule{{
   552  							Action: batch.PodFailurePolicyActionFailJob,
   553  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   554  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   555  								Values:   []int32{},
   556  							},
   557  						}},
   558  					},
   559  				},
   560  			},
   561  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   562  		},
   563  		`spec.podFailurePolicy.rules[0].action: Required value: valid values: ["Count" "FailIndex" "FailJob" "Ignore"]`: {
   564  			job: batch.Job{
   565  				ObjectMeta: validJobObjectMeta,
   566  				Spec: batch.JobSpec{
   567  					Selector: validGeneratedSelector,
   568  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   569  					PodFailurePolicy: &batch.PodFailurePolicy{
   570  						Rules: []batch.PodFailurePolicyRule{{
   571  							Action: "",
   572  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   573  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   574  								Values:   []int32{1, 2, 3},
   575  							},
   576  						}},
   577  					},
   578  				},
   579  			},
   580  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   581  		},
   582  		`spec.podFailurePolicy.rules[0].onExitCodes.operator: Required value: valid values: ["In" "NotIn"]`: {
   583  			job: batch.Job{
   584  				ObjectMeta: validJobObjectMeta,
   585  				Spec: batch.JobSpec{
   586  					Selector: validGeneratedSelector,
   587  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   588  					PodFailurePolicy: &batch.PodFailurePolicy{
   589  						Rules: []batch.PodFailurePolicyRule{{
   590  							Action: batch.PodFailurePolicyActionFailJob,
   591  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   592  								Operator: "",
   593  								Values:   []int32{1, 2, 3},
   594  							},
   595  						}},
   596  					},
   597  				},
   598  			},
   599  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   600  		},
   601  		`spec.podFailurePolicy.rules[0]: Invalid value: specifying both OnExitCodes and OnPodConditions is not supported`: {
   602  			job: batch.Job{
   603  				ObjectMeta: validJobObjectMeta,
   604  				Spec: batch.JobSpec{
   605  					Selector: validGeneratedSelector,
   606  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   607  					PodFailurePolicy: &batch.PodFailurePolicy{
   608  						Rules: []batch.PodFailurePolicyRule{{
   609  							Action: batch.PodFailurePolicyActionFailJob,
   610  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   611  								ContainerName: pointer.String("abc"),
   612  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   613  								Values:        []int32{1, 2, 3},
   614  							},
   615  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   616  								Type:   api.DisruptionTarget,
   617  								Status: api.ConditionTrue,
   618  							}},
   619  						}},
   620  					},
   621  				},
   622  			},
   623  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   624  		},
   625  		`spec.podFailurePolicy.rules[0].onExitCodes.values[1]: Invalid value: 0: must not be 0 for the In operator`: {
   626  			job: batch.Job{
   627  				ObjectMeta: validJobObjectMeta,
   628  				Spec: batch.JobSpec{
   629  					Selector: validGeneratedSelector,
   630  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   631  					PodFailurePolicy: &batch.PodFailurePolicy{
   632  						Rules: []batch.PodFailurePolicyRule{{
   633  							Action: batch.PodFailurePolicyActionIgnore,
   634  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   635  								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
   636  								Values:   []int32{1, 0, 2},
   637  							},
   638  						}},
   639  					},
   640  				},
   641  			},
   642  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   643  		},
   644  		`spec.podFailurePolicy.rules[1].onExitCodes.containerName: Invalid value: "xyz": must be one of the container or initContainer names in the pod template`: {
   645  			job: batch.Job{
   646  				ObjectMeta: validJobObjectMeta,
   647  				Spec: batch.JobSpec{
   648  					Selector: validGeneratedSelector,
   649  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   650  					PodFailurePolicy: &batch.PodFailurePolicy{
   651  						Rules: []batch.PodFailurePolicyRule{{
   652  							Action: batch.PodFailurePolicyActionIgnore,
   653  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   654  								ContainerName: pointer.String("abc"),
   655  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   656  								Values:        []int32{1, 2, 3},
   657  							},
   658  						}, {
   659  							Action: batch.PodFailurePolicyActionFailJob,
   660  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   661  								ContainerName: pointer.String("xyz"),
   662  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   663  								Values:        []int32{5, 6, 7},
   664  							},
   665  						}},
   666  					},
   667  				},
   668  			},
   669  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   670  		},
   671  		`spec.podFailurePolicy.rules[0].action: Unsupported value: "UnknownAction": supported values: "Count", "FailIndex", "FailJob", "Ignore"`: {
   672  			job: batch.Job{
   673  				ObjectMeta: validJobObjectMeta,
   674  				Spec: batch.JobSpec{
   675  					Selector: validGeneratedSelector,
   676  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   677  					PodFailurePolicy: &batch.PodFailurePolicy{
   678  						Rules: []batch.PodFailurePolicyRule{{
   679  							Action: "UnknownAction",
   680  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   681  								ContainerName: pointer.String("abc"),
   682  								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
   683  								Values:        []int32{1, 2, 3},
   684  							},
   685  						}},
   686  					},
   687  				},
   688  			},
   689  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   690  		},
   691  		`spec.podFailurePolicy.rules[0].onExitCodes.operator: Unsupported value: "UnknownOperator": supported values: "In", "NotIn"`: {
   692  			job: batch.Job{
   693  				ObjectMeta: validJobObjectMeta,
   694  				Spec: batch.JobSpec{
   695  					Selector: validGeneratedSelector,
   696  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   697  					PodFailurePolicy: &batch.PodFailurePolicy{
   698  						Rules: []batch.PodFailurePolicyRule{{
   699  							Action: batch.PodFailurePolicyActionIgnore,
   700  							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
   701  								Operator: "UnknownOperator",
   702  								Values:   []int32{1, 2, 3},
   703  							},
   704  						}},
   705  					},
   706  				},
   707  			},
   708  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   709  		},
   710  		`spec.podFailurePolicy.rules[0].onPodConditions[0].status: Required value: valid values: ["False" "True" "Unknown"]`: {
   711  			job: batch.Job{
   712  				ObjectMeta: validJobObjectMeta,
   713  				Spec: batch.JobSpec{
   714  					Selector: validGeneratedSelector,
   715  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   716  					PodFailurePolicy: &batch.PodFailurePolicy{
   717  						Rules: []batch.PodFailurePolicyRule{{
   718  							Action: batch.PodFailurePolicyActionIgnore,
   719  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   720  								Type: api.DisruptionTarget,
   721  							}},
   722  						}},
   723  					},
   724  				},
   725  			},
   726  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   727  		},
   728  		`spec.podFailurePolicy.rules[0].onPodConditions[0].status: Unsupported value: "UnknownStatus": supported values: "False", "True", "Unknown"`: {
   729  			job: batch.Job{
   730  				ObjectMeta: validJobObjectMeta,
   731  				Spec: batch.JobSpec{
   732  					Selector: validGeneratedSelector,
   733  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   734  					PodFailurePolicy: &batch.PodFailurePolicy{
   735  						Rules: []batch.PodFailurePolicyRule{{
   736  							Action: batch.PodFailurePolicyActionIgnore,
   737  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   738  								Type:   api.DisruptionTarget,
   739  								Status: "UnknownStatus",
   740  							}},
   741  						}},
   742  					},
   743  				},
   744  			},
   745  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   746  		},
   747  		`spec.podFailurePolicy.rules[0].onPodConditions[0].type: Invalid value: "": name part must be non-empty`: {
   748  			job: batch.Job{
   749  				ObjectMeta: validJobObjectMeta,
   750  				Spec: batch.JobSpec{
   751  					Selector: validGeneratedSelector,
   752  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   753  					PodFailurePolicy: &batch.PodFailurePolicy{
   754  						Rules: []batch.PodFailurePolicyRule{{
   755  							Action: batch.PodFailurePolicyActionIgnore,
   756  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   757  								Status: api.ConditionTrue,
   758  							}},
   759  						}},
   760  					},
   761  				},
   762  			},
   763  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   764  		},
   765  		`spec.podFailurePolicy.rules[0].onPodConditions[0].type: Invalid value: "Invalid Condition Type": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`: {
   766  			job: batch.Job{
   767  				ObjectMeta: validJobObjectMeta,
   768  				Spec: batch.JobSpec{
   769  					Selector: validGeneratedSelector,
   770  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
   771  					PodFailurePolicy: &batch.PodFailurePolicy{
   772  						Rules: []batch.PodFailurePolicyRule{{
   773  							Action: batch.PodFailurePolicyActionIgnore,
   774  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   775  								Type:   api.PodConditionType("Invalid Condition Type"),
   776  								Status: api.ConditionTrue,
   777  							}},
   778  						}},
   779  					},
   780  				},
   781  			},
   782  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   783  		},
   784  		`spec.podReplacementPolicy: Unsupported value: "TerminatingOrFailed": supported values: "Failed"`: {
   785  			job: batch.Job{
   786  				ObjectMeta: validJobObjectMeta,
   787  				Spec: batch.JobSpec{
   788  					Selector:             validGeneratedSelector,
   789  					PodReplacementPolicy: &terminatingOrFailedPodReplacement,
   790  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
   791  					PodFailurePolicy: &batch.PodFailurePolicy{
   792  						Rules: []batch.PodFailurePolicyRule{{
   793  							Action: batch.PodFailurePolicyActionIgnore,
   794  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
   795  								Type:   api.DisruptionTarget,
   796  								Status: api.ConditionTrue,
   797  							}},
   798  						},
   799  						},
   800  					},
   801  				},
   802  			},
   803  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   804  		},
   805  		`spec.podReplacementPolicy: Unsupported value: "": supported values: "Failed", "TerminatingOrFailed"`: {
   806  			job: batch.Job{
   807  				ObjectMeta: validJobObjectMeta,
   808  				Spec: batch.JobSpec{
   809  					PodReplacementPolicy: (*batch.PodReplacementPolicy)(pointer.String("")),
   810  					Selector:             validGeneratedSelector,
   811  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
   812  				},
   813  			},
   814  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   815  		},
   816  		`spec.template.spec.restartPolicy: Invalid value: "OnFailure": only "Never" is supported when podFailurePolicy is specified`: {
   817  			job: batch.Job{
   818  				ObjectMeta: validJobObjectMeta,
   819  				Spec: batch.JobSpec{
   820  					Selector: validGeneratedSelector,
   821  					Template: api.PodTemplateSpec{
   822  						ObjectMeta: metav1.ObjectMeta{
   823  							Labels: validGeneratedSelector.MatchLabels,
   824  						},
   825  						Spec: api.PodSpec{
   826  							RestartPolicy: api.RestartPolicyOnFailure,
   827  							DNSPolicy:     api.DNSClusterFirst,
   828  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
   829  						},
   830  					},
   831  					PodFailurePolicy: &batch.PodFailurePolicy{
   832  						Rules: []batch.PodFailurePolicyRule{},
   833  					},
   834  				},
   835  			},
   836  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   837  		},
   838  		"spec.parallelism:must be greater than or equal to 0": {
   839  			job: batch.Job{
   840  				ObjectMeta: metav1.ObjectMeta{
   841  					Name:      "myjob",
   842  					Namespace: metav1.NamespaceDefault,
   843  					UID:       types.UID("1a2b3c"),
   844  				},
   845  				Spec: batch.JobSpec{
   846  					Parallelism: &negative,
   847  					Selector:    validGeneratedSelector,
   848  					Template:    validPodTemplateSpecForGenerated,
   849  				},
   850  			},
   851  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   852  		},
   853  		"spec.backoffLimit:must be greater than or equal to 0": {
   854  			job: batch.Job{
   855  				ObjectMeta: metav1.ObjectMeta{
   856  					Name:      "myjob",
   857  					Namespace: metav1.NamespaceDefault,
   858  					UID:       types.UID("1a2b3c"),
   859  				},
   860  				Spec: batch.JobSpec{
   861  					BackoffLimit: pointer.Int32(-1),
   862  					Selector:     validGeneratedSelector,
   863  					Template:     validPodTemplateSpecForGenerated,
   864  				},
   865  			},
   866  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   867  		},
   868  		"spec.backoffLimitPerIndex: Invalid value: 1: requires indexed completion mode": {
   869  			job: batch.Job{
   870  				ObjectMeta: validJobObjectMeta,
   871  				Spec: batch.JobSpec{
   872  					BackoffLimitPerIndex: pointer.Int32(1),
   873  					Selector:             validGeneratedSelector,
   874  					Template:             validPodTemplateSpecForGenerated,
   875  				},
   876  			},
   877  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   878  		},
   879  		"spec.backoffLimitPerIndex:must be greater than or equal to 0": {
   880  			job: batch.Job{
   881  				ObjectMeta: validJobObjectMeta,
   882  				Spec: batch.JobSpec{
   883  					BackoffLimitPerIndex: pointer.Int32(-1),
   884  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   885  					Selector:             validGeneratedSelector,
   886  					Template:             validPodTemplateSpecForGenerated,
   887  				},
   888  			},
   889  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   890  		},
   891  		"spec.maxFailedIndexes: Invalid value: 11: must be less than or equal to completions": {
   892  			job: batch.Job{
   893  				ObjectMeta: validJobObjectMeta,
   894  				Spec: batch.JobSpec{
   895  					Completions:          pointer.Int32(10),
   896  					MaxFailedIndexes:     pointer.Int32(11),
   897  					BackoffLimitPerIndex: pointer.Int32(1),
   898  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   899  					Selector:             validGeneratedSelector,
   900  					Template:             validPodTemplateSpecForGenerated,
   901  				},
   902  			},
   903  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   904  		},
   905  		"spec.maxFailedIndexes: Required value: must be specified when completions is above 100000": {
   906  			job: batch.Job{
   907  				ObjectMeta: validJobObjectMeta,
   908  				Spec: batch.JobSpec{
   909  					Completions:          pointer.Int32(100_001),
   910  					BackoffLimitPerIndex: pointer.Int32(1),
   911  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   912  					Selector:             validGeneratedSelector,
   913  					Template:             validPodTemplateSpecForGenerated,
   914  				},
   915  			},
   916  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   917  		},
   918  		"spec.parallelism: Invalid value: 50000: must be less than or equal to 10000 when completions are above 100000 and used with backoff limit per index": {
   919  			job: batch.Job{
   920  				ObjectMeta: validJobObjectMeta,
   921  				Spec: batch.JobSpec{
   922  					Completions:          pointer.Int32(100_001),
   923  					Parallelism:          pointer.Int32(50_000),
   924  					BackoffLimitPerIndex: pointer.Int32(1),
   925  					MaxFailedIndexes:     pointer.Int32(1),
   926  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   927  					Selector:             validGeneratedSelector,
   928  					Template:             validPodTemplateSpecForGenerated,
   929  				},
   930  			},
   931  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   932  		},
   933  		"spec.maxFailedIndexes: Invalid value: 100001: must be less than or equal to 100000": {
   934  			job: batch.Job{
   935  				ObjectMeta: validJobObjectMeta,
   936  				Spec: batch.JobSpec{
   937  					Completions:          pointer.Int32(100_001),
   938  					BackoffLimitPerIndex: pointer.Int32(1),
   939  					MaxFailedIndexes:     pointer.Int32(100_001),
   940  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   941  					Selector:             validGeneratedSelector,
   942  					Template:             validPodTemplateSpecForGenerated,
   943  				},
   944  			},
   945  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   946  		},
   947  		"spec.maxFailedIndexes: Invalid value: 50000: must be less than or equal to 10000 when completions are above 100000 and used with backoff limit per index": {
   948  			job: batch.Job{
   949  				ObjectMeta: validJobObjectMeta,
   950  				Spec: batch.JobSpec{
   951  					Completions:          pointer.Int32(100_001),
   952  					BackoffLimitPerIndex: pointer.Int32(1),
   953  					MaxFailedIndexes:     pointer.Int32(50_000),
   954  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   955  					Selector:             validGeneratedSelector,
   956  					Template:             validPodTemplateSpecForGenerated,
   957  				},
   958  			},
   959  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   960  		},
   961  		"spec.maxFailedIndexes:must be greater than or equal to 0": {
   962  			job: batch.Job{
   963  				ObjectMeta: validJobObjectMeta,
   964  				Spec: batch.JobSpec{
   965  					BackoffLimitPerIndex: pointer.Int32(1),
   966  					MaxFailedIndexes:     pointer.Int32(-1),
   967  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
   968  					Selector:             validGeneratedSelector,
   969  					Template:             validPodTemplateSpecForGenerated,
   970  				},
   971  			},
   972  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   973  		},
   974  		"spec.backoffLimitPerIndex: Required value: when maxFailedIndexes is specified": {
   975  			job: batch.Job{
   976  				ObjectMeta: validJobObjectMeta,
   977  				Spec: batch.JobSpec{
   978  					MaxFailedIndexes: pointer.Int32(1),
   979  					CompletionMode:   completionModePtr(batch.IndexedCompletion),
   980  					Selector:         validGeneratedSelector,
   981  					Template:         validPodTemplateSpecForGenerated,
   982  				},
   983  			},
   984  			opts: JobValidationOptions{RequirePrefixedLabels: true},
   985  		},
   986  		"spec.completions:must be greater than or equal to 0": {
   987  			job: batch.Job{
   988  				ObjectMeta: metav1.ObjectMeta{
   989  					Name:      "myjob",
   990  					Namespace: metav1.NamespaceDefault,
   991  					UID:       types.UID("1a2b3c"),
   992  				},
   993  				Spec: batch.JobSpec{
   994  					Completions: &negative,
   995  					Selector:    validGeneratedSelector,
   996  					Template:    validPodTemplateSpecForGenerated,
   997  				},
   998  			},
   999  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1000  		},
  1001  		"spec.activeDeadlineSeconds:must be greater than or equal to 0": {
  1002  			job: batch.Job{
  1003  				ObjectMeta: metav1.ObjectMeta{
  1004  					Name:      "myjob",
  1005  					Namespace: metav1.NamespaceDefault,
  1006  					UID:       types.UID("1a2b3c"),
  1007  				},
  1008  				Spec: batch.JobSpec{
  1009  					ActiveDeadlineSeconds: &negative64,
  1010  					Selector:              validGeneratedSelector,
  1011  					Template:              validPodTemplateSpecForGenerated,
  1012  				},
  1013  			},
  1014  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1015  		},
  1016  		"spec.selector:Required value": {
  1017  			job: batch.Job{
  1018  				ObjectMeta: metav1.ObjectMeta{
  1019  					Name:      "myjob",
  1020  					Namespace: metav1.NamespaceDefault,
  1021  					UID:       types.UID("1a2b3c"),
  1022  				},
  1023  				Spec: batch.JobSpec{
  1024  					Template: validPodTemplateSpecForGenerated,
  1025  				},
  1026  			},
  1027  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1028  		},
  1029  		"spec.template.metadata.labels: Invalid value: map[string]string{\"y\":\"z\"}: `selector` does not match template `labels`": {
  1030  			job: batch.Job{
  1031  				ObjectMeta: metav1.ObjectMeta{
  1032  					Name:      "myjob",
  1033  					Namespace: metav1.NamespaceDefault,
  1034  					UID:       types.UID("1a2b3c"),
  1035  				},
  1036  				Spec: batch.JobSpec{
  1037  					Selector:       validManualSelector,
  1038  					ManualSelector: pointer.Bool(true),
  1039  					Template: api.PodTemplateSpec{
  1040  						ObjectMeta: metav1.ObjectMeta{
  1041  							Labels: map[string]string{"y": "z"},
  1042  						},
  1043  						Spec: api.PodSpec{
  1044  							RestartPolicy: api.RestartPolicyOnFailure,
  1045  							DNSPolicy:     api.DNSClusterFirst,
  1046  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1047  						},
  1048  					},
  1049  				},
  1050  			},
  1051  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1052  		},
  1053  		"spec.template.metadata.labels: Invalid value: map[string]string{\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
  1054  			job: batch.Job{
  1055  				ObjectMeta: metav1.ObjectMeta{
  1056  					Name:      "myjob",
  1057  					Namespace: metav1.NamespaceDefault,
  1058  					UID:       types.UID("1a2b3c"),
  1059  				},
  1060  				Spec: batch.JobSpec{
  1061  					Selector:       validManualSelector,
  1062  					ManualSelector: pointer.Bool(true),
  1063  					Template: api.PodTemplateSpec{
  1064  						ObjectMeta: metav1.ObjectMeta{
  1065  							Labels: map[string]string{"controller-uid": "4d5e6f"},
  1066  						},
  1067  						Spec: api.PodSpec{
  1068  							RestartPolicy: api.RestartPolicyOnFailure,
  1069  							DNSPolicy:     api.DNSClusterFirst,
  1070  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1071  						},
  1072  					},
  1073  				},
  1074  			},
  1075  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1076  		},
  1077  		"spec.template.spec.restartPolicy: Required value": {
  1078  			job: batch.Job{
  1079  				ObjectMeta: metav1.ObjectMeta{
  1080  					Name:      "myjob",
  1081  					Namespace: metav1.NamespaceDefault,
  1082  					UID:       types.UID("1a2b3c"),
  1083  				},
  1084  				Spec: batch.JobSpec{
  1085  					Selector:       validManualSelector,
  1086  					ManualSelector: pointer.Bool(true),
  1087  					Template: api.PodTemplateSpec{
  1088  						ObjectMeta: metav1.ObjectMeta{
  1089  							Labels: validManualSelector.MatchLabels,
  1090  						},
  1091  						Spec: api.PodSpec{
  1092  							RestartPolicy: api.RestartPolicyAlways,
  1093  							DNSPolicy:     api.DNSClusterFirst,
  1094  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1095  						},
  1096  					},
  1097  				},
  1098  			},
  1099  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1100  		},
  1101  		"spec.template.spec.restartPolicy: Unsupported value": {
  1102  			job: batch.Job{
  1103  				ObjectMeta: metav1.ObjectMeta{
  1104  					Name:      "myjob",
  1105  					Namespace: metav1.NamespaceDefault,
  1106  					UID:       types.UID("1a2b3c"),
  1107  				},
  1108  				Spec: batch.JobSpec{
  1109  					Selector:       validManualSelector,
  1110  					ManualSelector: pointer.Bool(true),
  1111  					Template: api.PodTemplateSpec{
  1112  						ObjectMeta: metav1.ObjectMeta{
  1113  							Labels: validManualSelector.MatchLabels,
  1114  						},
  1115  						Spec: api.PodSpec{
  1116  							RestartPolicy: "Invalid",
  1117  							DNSPolicy:     api.DNSClusterFirst,
  1118  							Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1119  						},
  1120  					},
  1121  				},
  1122  			},
  1123  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1124  		},
  1125  		"spec.ttlSecondsAfterFinished: must be greater than or equal to 0": {
  1126  			job: batch.Job{
  1127  				ObjectMeta: metav1.ObjectMeta{
  1128  					Name:      "myjob",
  1129  					Namespace: metav1.NamespaceDefault,
  1130  					UID:       types.UID("1a2b3c"),
  1131  				},
  1132  				Spec: batch.JobSpec{
  1133  					TTLSecondsAfterFinished: &negative,
  1134  					Selector:                validGeneratedSelector,
  1135  					Template:                validPodTemplateSpecForGenerated,
  1136  				},
  1137  			},
  1138  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1139  		},
  1140  		"spec.completions: Required value: when completion mode is Indexed": {
  1141  			job: batch.Job{
  1142  				ObjectMeta: metav1.ObjectMeta{
  1143  					Name:      "myjob",
  1144  					Namespace: metav1.NamespaceDefault,
  1145  					UID:       types.UID("1a2b3c"),
  1146  				},
  1147  				Spec: batch.JobSpec{
  1148  					Selector:       validGeneratedSelector,
  1149  					Template:       validPodTemplateSpecForGenerated,
  1150  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1151  				},
  1152  			},
  1153  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1154  		},
  1155  		"spec.parallelism: must be less than or equal to 100000 when completion mode is Indexed": {
  1156  			job: batch.Job{
  1157  				ObjectMeta: metav1.ObjectMeta{
  1158  					Name:      "myjob",
  1159  					Namespace: metav1.NamespaceDefault,
  1160  					UID:       types.UID("1a2b3c"),
  1161  				},
  1162  				Spec: batch.JobSpec{
  1163  					Selector:       validGeneratedSelector,
  1164  					Template:       validPodTemplateSpecForGenerated,
  1165  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1166  					Completions:    pointer.Int32(2),
  1167  					Parallelism:    pointer.Int32(100001),
  1168  				},
  1169  			},
  1170  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1171  		},
  1172  		"spec.template.metadata.labels[controller-uid]: Required value: must be '1a2b3c'": {
  1173  			job: batch.Job{
  1174  				ObjectMeta: metav1.ObjectMeta{
  1175  					Name:      "myjob",
  1176  					Namespace: metav1.NamespaceDefault,
  1177  					UID:       types.UID("1a2b3c"),
  1178  				},
  1179  				Spec: batch.JobSpec{
  1180  					Selector: &metav1.LabelSelector{
  1181  						MatchLabels: map[string]string{batch.LegacyControllerUidLabel: "1a2b3c"},
  1182  					},
  1183  					Template: api.PodTemplateSpec{
  1184  						ObjectMeta: metav1.ObjectMeta{
  1185  							Labels: map[string]string{batch.LegacyJobNameLabel: "myjob"},
  1186  						},
  1187  						Spec: api.PodSpec{
  1188  							RestartPolicy:  api.RestartPolicyOnFailure,
  1189  							DNSPolicy:      api.DNSClusterFirst,
  1190  							Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1191  							InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1192  						},
  1193  					},
  1194  				},
  1195  			},
  1196  			opts: JobValidationOptions{},
  1197  		},
  1198  		"metadata.uid: Required value": {
  1199  			job: batch.Job{
  1200  				ObjectMeta: metav1.ObjectMeta{
  1201  					Name:      "myjob",
  1202  					Namespace: metav1.NamespaceDefault,
  1203  				},
  1204  				Spec: batch.JobSpec{
  1205  					Selector: &metav1.LabelSelector{
  1206  						MatchLabels: map[string]string{batch.LegacyControllerUidLabel: "test"},
  1207  					},
  1208  					Template: api.PodTemplateSpec{
  1209  						ObjectMeta: metav1.ObjectMeta{
  1210  							Labels: map[string]string{batch.LegacyJobNameLabel: "myjob"},
  1211  						},
  1212  						Spec: api.PodSpec{
  1213  							RestartPolicy:  api.RestartPolicyOnFailure,
  1214  							DNSPolicy:      api.DNSClusterFirst,
  1215  							Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1216  							InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1217  						},
  1218  					},
  1219  				},
  1220  			},
  1221  			opts: JobValidationOptions{},
  1222  		},
  1223  		"spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{\"a\":\"b\"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: `selector` not auto-generated": {
  1224  			job: batch.Job{
  1225  				ObjectMeta: metav1.ObjectMeta{
  1226  					Name:      "myjob",
  1227  					Namespace: metav1.NamespaceDefault,
  1228  					UID:       types.UID("1a2b3c"),
  1229  				},
  1230  				Spec: batch.JobSpec{
  1231  					Selector: &metav1.LabelSelector{
  1232  						MatchLabels: map[string]string{"a": "b"},
  1233  					},
  1234  					Template: validPodTemplateSpecForGenerated,
  1235  				},
  1236  			},
  1237  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1238  		},
  1239  		"spec.template.metadata.labels[batch.kubernetes.io/controller-uid]: Required value: must be '1a2b3c'": {
  1240  			job: batch.Job{
  1241  				ObjectMeta: metav1.ObjectMeta{
  1242  					Name:      "myjob",
  1243  					Namespace: metav1.NamespaceDefault,
  1244  					UID:       types.UID("1a2b3c"),
  1245  				},
  1246  				Spec: batch.JobSpec{
  1247  					Selector: &metav1.LabelSelector{
  1248  						MatchLabels: map[string]string{batch.ControllerUidLabel: "1a2b3c"},
  1249  					},
  1250  					Template: api.PodTemplateSpec{
  1251  						ObjectMeta: metav1.ObjectMeta{
  1252  							Labels: map[string]string{batch.JobNameLabel: "myjob", batch.LegacyControllerUidLabel: "1a2b3c", batch.LegacyJobNameLabel: "myjob"},
  1253  						},
  1254  						Spec: api.PodSpec{
  1255  							RestartPolicy:  api.RestartPolicyOnFailure,
  1256  							DNSPolicy:      api.DNSClusterFirst,
  1257  							Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1258  							InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  1259  						},
  1260  					},
  1261  				},
  1262  			},
  1263  			opts: JobValidationOptions{RequirePrefixedLabels: true},
  1264  		},
  1265  	}
  1266  
  1267  	for k, v := range errorCases {
  1268  		t.Run(k, func(t *testing.T) {
  1269  			errs := ValidateJob(&v.job, v.opts)
  1270  			if len(errs) == 0 {
  1271  				t.Errorf("expected failure for %s", k)
  1272  			} else {
  1273  				s := strings.SplitN(k, ":", 2)
  1274  				err := errs[0]
  1275  				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  1276  					t.Errorf("unexpected error: %v, expected: %s", err, k)
  1277  				}
  1278  			}
  1279  		})
  1280  	}
  1281  }
  1282  
  1283  func TestValidateJobUpdate(t *testing.T) {
  1284  	validGeneratedSelector := getValidGeneratedSelector()
  1285  	validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
  1286  	validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
  1287  	validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever
  1288  
  1289  	validNodeAffinity := &api.Affinity{
  1290  		NodeAffinity: &api.NodeAffinity{
  1291  			RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
  1292  				NodeSelectorTerms: []api.NodeSelectorTerm{{
  1293  					MatchExpressions: []api.NodeSelectorRequirement{{
  1294  						Key:      "foo",
  1295  						Operator: api.NodeSelectorOpIn,
  1296  						Values:   []string{"bar", "value2"},
  1297  					}},
  1298  				}},
  1299  			},
  1300  		},
  1301  	}
  1302  	validPodTemplateWithAffinity := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
  1303  	validPodTemplateWithAffinity.Spec.Affinity = &api.Affinity{
  1304  		NodeAffinity: &api.NodeAffinity{
  1305  			RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
  1306  				NodeSelectorTerms: []api.NodeSelectorTerm{{
  1307  					MatchExpressions: []api.NodeSelectorRequirement{{
  1308  						Key:      "foo",
  1309  						Operator: api.NodeSelectorOpIn,
  1310  						Values:   []string{"bar", "value"},
  1311  					}},
  1312  				}},
  1313  			},
  1314  		},
  1315  	}
  1316  	// This is to test immutability of the selector, both the new and old
  1317  	// selector should match the labels in the template, which is immutable
  1318  	// on its own; therfore, the only way to test selector immutability is
  1319  	// when the new selector is changed but still matches the existing labels.
  1320  	newSelector := getValidGeneratedSelector()
  1321  	newSelector.MatchLabels["foo"] = "bar"
  1322  	validTolerations := []api.Toleration{{
  1323  		Key:      "foo",
  1324  		Operator: api.TolerationOpEqual,
  1325  		Value:    "bar",
  1326  		Effect:   api.TaintEffectPreferNoSchedule,
  1327  	}}
  1328  	cases := map[string]struct {
  1329  		old    batch.Job
  1330  		update func(*batch.Job)
  1331  		opts   JobValidationOptions
  1332  		err    *field.Error
  1333  	}{
  1334  		"mutable fields": {
  1335  			old: batch.Job{
  1336  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1337  				Spec: batch.JobSpec{
  1338  					Selector:                validGeneratedSelector,
  1339  					Template:                validPodTemplateSpecForGenerated,
  1340  					Parallelism:             pointer.Int32(5),
  1341  					ActiveDeadlineSeconds:   pointer.Int64(2),
  1342  					TTLSecondsAfterFinished: pointer.Int32(1),
  1343  				},
  1344  			},
  1345  			update: func(job *batch.Job) {
  1346  				job.Spec.Parallelism = pointer.Int32(2)
  1347  				job.Spec.ActiveDeadlineSeconds = pointer.Int64(3)
  1348  				job.Spec.TTLSecondsAfterFinished = pointer.Int32(2)
  1349  				job.Spec.ManualSelector = pointer.Bool(true)
  1350  			},
  1351  		},
  1352  		"immutable completions for non-indexed jobs": {
  1353  			old: batch.Job{
  1354  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1355  				Spec: batch.JobSpec{
  1356  					Selector: validGeneratedSelector,
  1357  					Template: validPodTemplateSpecForGenerated,
  1358  				},
  1359  			},
  1360  			update: func(job *batch.Job) {
  1361  				job.Spec.Completions = pointer.Int32(1)
  1362  			},
  1363  			err: &field.Error{
  1364  				Type:  field.ErrorTypeInvalid,
  1365  				Field: "spec.completions",
  1366  			},
  1367  		},
  1368  		"immutable completions for indexed job when AllowElasticIndexedJobs is false": {
  1369  			old: batch.Job{
  1370  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1371  				Spec: batch.JobSpec{
  1372  					Selector: validGeneratedSelector,
  1373  					Template: validPodTemplateSpecForGenerated,
  1374  				},
  1375  			},
  1376  			update: func(job *batch.Job) {
  1377  				job.Spec.Completions = pointer.Int32(1)
  1378  			},
  1379  			err: &field.Error{
  1380  				Type:  field.ErrorTypeInvalid,
  1381  				Field: "spec.completions",
  1382  			},
  1383  		},
  1384  		"immutable selector": {
  1385  			old: batch.Job{
  1386  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1387  				Spec: batch.JobSpec{
  1388  					Selector: validGeneratedSelector,
  1389  					Template: getValidPodTemplateSpecForGenerated(newSelector),
  1390  				},
  1391  			},
  1392  			update: func(job *batch.Job) {
  1393  				job.Spec.Selector = newSelector
  1394  			},
  1395  			err: &field.Error{
  1396  				Type:  field.ErrorTypeInvalid,
  1397  				Field: "spec.selector",
  1398  			},
  1399  		},
  1400  		"add pod failure policy": {
  1401  			old: batch.Job{
  1402  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1403  				Spec: batch.JobSpec{
  1404  					Selector: validGeneratedSelector,
  1405  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
  1406  				},
  1407  			},
  1408  			update: func(job *batch.Job) {
  1409  				job.Spec.PodFailurePolicy = &batch.PodFailurePolicy{
  1410  					Rules: []batch.PodFailurePolicyRule{{
  1411  						Action: batch.PodFailurePolicyActionIgnore,
  1412  						OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
  1413  							Type:   api.DisruptionTarget,
  1414  							Status: api.ConditionTrue,
  1415  						}},
  1416  					}},
  1417  				}
  1418  			},
  1419  			err: &field.Error{
  1420  				Type:  field.ErrorTypeInvalid,
  1421  				Field: "spec.podFailurePolicy",
  1422  			},
  1423  		},
  1424  		"remove pod failure policy": {
  1425  			old: batch.Job{
  1426  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1427  				Spec: batch.JobSpec{
  1428  					Selector: validGeneratedSelector,
  1429  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
  1430  					PodFailurePolicy: &batch.PodFailurePolicy{
  1431  						Rules: []batch.PodFailurePolicyRule{{
  1432  							Action: batch.PodFailurePolicyActionIgnore,
  1433  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
  1434  								Type:   api.DisruptionTarget,
  1435  								Status: api.ConditionTrue,
  1436  							}},
  1437  						}},
  1438  					},
  1439  				},
  1440  			},
  1441  			update: func(job *batch.Job) {
  1442  				job.Spec.PodFailurePolicy.Rules = append(job.Spec.PodFailurePolicy.Rules, batch.PodFailurePolicyRule{
  1443  					Action: batch.PodFailurePolicyActionCount,
  1444  					OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
  1445  						Type:   api.DisruptionTarget,
  1446  						Status: api.ConditionTrue,
  1447  					}},
  1448  				})
  1449  			},
  1450  			err: &field.Error{
  1451  				Type:  field.ErrorTypeInvalid,
  1452  				Field: "spec.podFailurePolicy",
  1453  			},
  1454  		},
  1455  		"update pod failure policy": {
  1456  			old: batch.Job{
  1457  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1458  				Spec: batch.JobSpec{
  1459  					Selector: validGeneratedSelector,
  1460  					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
  1461  					PodFailurePolicy: &batch.PodFailurePolicy{
  1462  						Rules: []batch.PodFailurePolicyRule{{
  1463  							Action: batch.PodFailurePolicyActionIgnore,
  1464  							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{{
  1465  								Type:   api.DisruptionTarget,
  1466  								Status: api.ConditionTrue,
  1467  							}},
  1468  						}},
  1469  					},
  1470  				},
  1471  			},
  1472  			update: func(job *batch.Job) {
  1473  				job.Spec.PodFailurePolicy = nil
  1474  			},
  1475  			err: &field.Error{
  1476  				Type:  field.ErrorTypeInvalid,
  1477  				Field: "spec.podFailurePolicy",
  1478  			},
  1479  		},
  1480  		"set backoff limit per index": {
  1481  			old: batch.Job{
  1482  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1483  				Spec: batch.JobSpec{
  1484  					Selector:       validGeneratedSelector,
  1485  					Template:       validPodTemplateSpecForGeneratedRestartPolicyNever,
  1486  					Completions:    pointer.Int32(3),
  1487  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1488  				},
  1489  			},
  1490  			update: func(job *batch.Job) {
  1491  				job.Spec.BackoffLimitPerIndex = pointer.Int32(1)
  1492  			},
  1493  			err: &field.Error{
  1494  				Type:  field.ErrorTypeInvalid,
  1495  				Field: "spec.backoffLimitPerIndex",
  1496  			},
  1497  		},
  1498  		"unset backoff limit per index": {
  1499  			old: batch.Job{
  1500  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1501  				Spec: batch.JobSpec{
  1502  					Selector:             validGeneratedSelector,
  1503  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
  1504  					Completions:          pointer.Int32(3),
  1505  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1506  					BackoffLimitPerIndex: pointer.Int32(1),
  1507  				},
  1508  			},
  1509  			update: func(job *batch.Job) {
  1510  				job.Spec.BackoffLimitPerIndex = nil
  1511  			},
  1512  			err: &field.Error{
  1513  				Type:  field.ErrorTypeInvalid,
  1514  				Field: "spec.backoffLimitPerIndex",
  1515  			},
  1516  		},
  1517  		"update backoff limit per index": {
  1518  			old: batch.Job{
  1519  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1520  				Spec: batch.JobSpec{
  1521  					Selector:             validGeneratedSelector,
  1522  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
  1523  					Completions:          pointer.Int32(3),
  1524  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1525  					BackoffLimitPerIndex: pointer.Int32(1),
  1526  				},
  1527  			},
  1528  			update: func(job *batch.Job) {
  1529  				job.Spec.BackoffLimitPerIndex = pointer.Int32(2)
  1530  			},
  1531  			err: &field.Error{
  1532  				Type:  field.ErrorTypeInvalid,
  1533  				Field: "spec.backoffLimitPerIndex",
  1534  			},
  1535  		},
  1536  		"set max failed indexes": {
  1537  			old: batch.Job{
  1538  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1539  				Spec: batch.JobSpec{
  1540  					Selector:             validGeneratedSelector,
  1541  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
  1542  					Completions:          pointer.Int32(3),
  1543  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1544  					BackoffLimitPerIndex: pointer.Int32(1),
  1545  				},
  1546  			},
  1547  			update: func(job *batch.Job) {
  1548  				job.Spec.MaxFailedIndexes = pointer.Int32(1)
  1549  			},
  1550  		},
  1551  		"unset max failed indexes": {
  1552  			old: batch.Job{
  1553  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1554  				Spec: batch.JobSpec{
  1555  					Selector:             validGeneratedSelector,
  1556  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
  1557  					Completions:          pointer.Int32(3),
  1558  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1559  					BackoffLimitPerIndex: pointer.Int32(1),
  1560  					MaxFailedIndexes:     pointer.Int32(1),
  1561  				},
  1562  			},
  1563  			update: func(job *batch.Job) {
  1564  				job.Spec.MaxFailedIndexes = nil
  1565  			},
  1566  		},
  1567  		"update max failed indexes": {
  1568  			old: batch.Job{
  1569  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1570  				Spec: batch.JobSpec{
  1571  					Selector:             validGeneratedSelector,
  1572  					Template:             validPodTemplateSpecForGeneratedRestartPolicyNever,
  1573  					Completions:          pointer.Int32(3),
  1574  					CompletionMode:       completionModePtr(batch.IndexedCompletion),
  1575  					BackoffLimitPerIndex: pointer.Int32(1),
  1576  					MaxFailedIndexes:     pointer.Int32(1),
  1577  				},
  1578  			},
  1579  			update: func(job *batch.Job) {
  1580  				job.Spec.MaxFailedIndexes = pointer.Int32(2)
  1581  			},
  1582  		},
  1583  		"immutable pod template": {
  1584  			old: batch.Job{
  1585  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1586  				Spec: batch.JobSpec{
  1587  					Selector:       validGeneratedSelector,
  1588  					Template:       validPodTemplateSpecForGenerated,
  1589  					Completions:    pointer.Int32(3),
  1590  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1591  				},
  1592  			},
  1593  			update: func(job *batch.Job) {
  1594  				job.Spec.Template.Spec.DNSPolicy = api.DNSClusterFirstWithHostNet
  1595  			},
  1596  			err: &field.Error{
  1597  				Type:  field.ErrorTypeInvalid,
  1598  				Field: "spec.template",
  1599  			},
  1600  		},
  1601  		"immutable completion mode": {
  1602  			old: batch.Job{
  1603  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1604  				Spec: batch.JobSpec{
  1605  					Selector:       validGeneratedSelector,
  1606  					Template:       validPodTemplateSpecForGenerated,
  1607  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1608  					Completions:    pointer.Int32(2),
  1609  				},
  1610  			},
  1611  			update: func(job *batch.Job) {
  1612  				job.Spec.CompletionMode = completionModePtr(batch.NonIndexedCompletion)
  1613  			},
  1614  			err: &field.Error{
  1615  				Type:  field.ErrorTypeInvalid,
  1616  				Field: "spec.completionMode",
  1617  			},
  1618  		},
  1619  		"immutable completions for non-indexed job when AllowElasticIndexedJobs is true": {
  1620  			old: batch.Job{
  1621  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1622  				Spec: batch.JobSpec{
  1623  					Selector:       validGeneratedSelector,
  1624  					Template:       validPodTemplateSpecForGenerated,
  1625  					CompletionMode: completionModePtr(batch.NonIndexedCompletion),
  1626  					Completions:    pointer.Int32(2),
  1627  				},
  1628  			},
  1629  			update: func(job *batch.Job) {
  1630  				job.Spec.Completions = pointer.Int32(4)
  1631  			},
  1632  			err: &field.Error{
  1633  				Type:  field.ErrorTypeInvalid,
  1634  				Field: "spec.completions",
  1635  			},
  1636  			opts: JobValidationOptions{AllowElasticIndexedJobs: true},
  1637  		},
  1638  
  1639  		"immutable node affinity": {
  1640  			old: batch.Job{
  1641  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1642  				Spec: batch.JobSpec{
  1643  					Selector: validGeneratedSelector,
  1644  					Template: validPodTemplateSpecForGenerated,
  1645  				},
  1646  			},
  1647  			update: func(job *batch.Job) {
  1648  				job.Spec.Template.Spec.Affinity = validNodeAffinity
  1649  			},
  1650  			err: &field.Error{
  1651  				Type:  field.ErrorTypeInvalid,
  1652  				Field: "spec.template",
  1653  			},
  1654  		},
  1655  		"add node affinity": {
  1656  			old: batch.Job{
  1657  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1658  				Spec: batch.JobSpec{
  1659  					Selector: validGeneratedSelector,
  1660  					Template: validPodTemplateSpecForGenerated,
  1661  				},
  1662  			},
  1663  			update: func(job *batch.Job) {
  1664  				job.Spec.Template.Spec.Affinity = validNodeAffinity
  1665  			},
  1666  			opts: JobValidationOptions{
  1667  				AllowMutableSchedulingDirectives: true,
  1668  			},
  1669  		},
  1670  		"update node affinity": {
  1671  			old: batch.Job{
  1672  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1673  				Spec: batch.JobSpec{
  1674  					Selector: validGeneratedSelector,
  1675  					Template: validPodTemplateWithAffinity,
  1676  				},
  1677  			},
  1678  			update: func(job *batch.Job) {
  1679  				job.Spec.Template.Spec.Affinity = validNodeAffinity
  1680  			},
  1681  			opts: JobValidationOptions{
  1682  				AllowMutableSchedulingDirectives: true,
  1683  			},
  1684  		},
  1685  		"remove node affinity": {
  1686  			old: batch.Job{
  1687  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1688  				Spec: batch.JobSpec{
  1689  					Selector: validGeneratedSelector,
  1690  					Template: validPodTemplateWithAffinity,
  1691  				},
  1692  			},
  1693  			update: func(job *batch.Job) {
  1694  				job.Spec.Template.Spec.Affinity.NodeAffinity = nil
  1695  			},
  1696  			opts: JobValidationOptions{
  1697  				AllowMutableSchedulingDirectives: true,
  1698  			},
  1699  		},
  1700  		"remove affinity": {
  1701  			old: batch.Job{
  1702  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1703  				Spec: batch.JobSpec{
  1704  					Selector: validGeneratedSelector,
  1705  					Template: validPodTemplateWithAffinity,
  1706  				},
  1707  			},
  1708  			update: func(job *batch.Job) {
  1709  				job.Spec.Template.Spec.Affinity = nil
  1710  			},
  1711  			opts: JobValidationOptions{
  1712  				AllowMutableSchedulingDirectives: true,
  1713  			},
  1714  		},
  1715  		"immutable tolerations": {
  1716  			old: batch.Job{
  1717  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1718  				Spec: batch.JobSpec{
  1719  					Selector: validGeneratedSelector,
  1720  					Template: validPodTemplateSpecForGenerated,
  1721  				},
  1722  			},
  1723  			update: func(job *batch.Job) {
  1724  				job.Spec.Template.Spec.Tolerations = validTolerations
  1725  			},
  1726  			err: &field.Error{
  1727  				Type:  field.ErrorTypeInvalid,
  1728  				Field: "spec.template",
  1729  			},
  1730  		},
  1731  		"mutable tolerations": {
  1732  			old: batch.Job{
  1733  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1734  				Spec: batch.JobSpec{
  1735  					Selector: validGeneratedSelector,
  1736  					Template: validPodTemplateSpecForGenerated,
  1737  				},
  1738  			},
  1739  			update: func(job *batch.Job) {
  1740  				job.Spec.Template.Spec.Tolerations = validTolerations
  1741  			},
  1742  			opts: JobValidationOptions{
  1743  				AllowMutableSchedulingDirectives: true,
  1744  			},
  1745  		},
  1746  		"immutable node selector": {
  1747  			old: batch.Job{
  1748  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1749  				Spec: batch.JobSpec{
  1750  					Selector: validGeneratedSelector,
  1751  					Template: validPodTemplateSpecForGenerated,
  1752  				},
  1753  			},
  1754  			update: func(job *batch.Job) {
  1755  				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
  1756  			},
  1757  			err: &field.Error{
  1758  				Type:  field.ErrorTypeInvalid,
  1759  				Field: "spec.template",
  1760  			},
  1761  		},
  1762  		"mutable node selector": {
  1763  			old: batch.Job{
  1764  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1765  				Spec: batch.JobSpec{
  1766  					Selector: validGeneratedSelector,
  1767  					Template: validPodTemplateSpecForGenerated,
  1768  				},
  1769  			},
  1770  			update: func(job *batch.Job) {
  1771  				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
  1772  			},
  1773  			opts: JobValidationOptions{
  1774  				AllowMutableSchedulingDirectives: true,
  1775  			},
  1776  		},
  1777  		"immutable annotations": {
  1778  			old: batch.Job{
  1779  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1780  				Spec: batch.JobSpec{
  1781  					Selector: validGeneratedSelector,
  1782  					Template: validPodTemplateSpecForGenerated,
  1783  				},
  1784  			},
  1785  			update: func(job *batch.Job) {
  1786  				job.Spec.Template.Annotations = map[string]string{"foo": "baz"}
  1787  			},
  1788  			err: &field.Error{
  1789  				Type:  field.ErrorTypeInvalid,
  1790  				Field: "spec.template",
  1791  			},
  1792  		},
  1793  		"mutable annotations": {
  1794  			old: batch.Job{
  1795  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1796  				Spec: batch.JobSpec{
  1797  					Selector: validGeneratedSelector,
  1798  					Template: validPodTemplateSpecForGenerated,
  1799  				},
  1800  			},
  1801  			update: func(job *batch.Job) {
  1802  				job.Spec.Template.Annotations = map[string]string{"foo": "baz"}
  1803  			},
  1804  			opts: JobValidationOptions{
  1805  				AllowMutableSchedulingDirectives: true,
  1806  			},
  1807  		},
  1808  		"immutable labels": {
  1809  			old: batch.Job{
  1810  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1811  				Spec: batch.JobSpec{
  1812  					Selector: validGeneratedSelector,
  1813  					Template: validPodTemplateSpecForGenerated,
  1814  				},
  1815  			},
  1816  			update: func(job *batch.Job) {
  1817  				newLabels := getValidGeneratedSelector().MatchLabels
  1818  				newLabels["bar"] = "baz"
  1819  				job.Spec.Template.Labels = newLabels
  1820  			},
  1821  			err: &field.Error{
  1822  				Type:  field.ErrorTypeInvalid,
  1823  				Field: "spec.template",
  1824  			},
  1825  		},
  1826  		"mutable labels": {
  1827  			old: batch.Job{
  1828  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1829  				Spec: batch.JobSpec{
  1830  					Selector: validGeneratedSelector,
  1831  					Template: validPodTemplateSpecForGenerated,
  1832  				},
  1833  			},
  1834  			update: func(job *batch.Job) {
  1835  				newLabels := getValidGeneratedSelector().MatchLabels
  1836  				newLabels["bar"] = "baz"
  1837  				job.Spec.Template.Labels = newLabels
  1838  			},
  1839  			opts: JobValidationOptions{
  1840  				AllowMutableSchedulingDirectives: true,
  1841  			},
  1842  		},
  1843  		"immutable schedulingGates": {
  1844  			old: batch.Job{
  1845  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1846  				Spec: batch.JobSpec{
  1847  					Selector: validGeneratedSelector,
  1848  					Template: validPodTemplateSpecForGenerated,
  1849  				},
  1850  			},
  1851  			update: func(job *batch.Job) {
  1852  				job.Spec.Template.Spec.SchedulingGates = append(job.Spec.Template.Spec.SchedulingGates, api.PodSchedulingGate{Name: "gate"})
  1853  			},
  1854  			err: &field.Error{
  1855  				Type:  field.ErrorTypeInvalid,
  1856  				Field: "spec.template",
  1857  			},
  1858  		},
  1859  		"mutable schedulingGates": {
  1860  			old: batch.Job{
  1861  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1862  				Spec: batch.JobSpec{
  1863  					Selector: validGeneratedSelector,
  1864  					Template: validPodTemplateSpecForGenerated,
  1865  				},
  1866  			},
  1867  			update: func(job *batch.Job) {
  1868  				job.Spec.Template.Spec.SchedulingGates = append(job.Spec.Template.Spec.SchedulingGates, api.PodSchedulingGate{Name: "gate"})
  1869  			},
  1870  			opts: JobValidationOptions{
  1871  				AllowMutableSchedulingDirectives: true,
  1872  			},
  1873  		},
  1874  		"update completions and parallelism to same value is valid": {
  1875  			old: batch.Job{
  1876  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1877  				Spec: batch.JobSpec{
  1878  					Selector:       validGeneratedSelector,
  1879  					Template:       validPodTemplateSpecForGenerated,
  1880  					Completions:    pointer.Int32(1),
  1881  					Parallelism:    pointer.Int32(1),
  1882  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1883  				},
  1884  			},
  1885  			update: func(job *batch.Job) {
  1886  				job.Spec.Completions = pointer.Int32(2)
  1887  				job.Spec.Parallelism = pointer.Int32(2)
  1888  			},
  1889  			opts: JobValidationOptions{
  1890  				AllowElasticIndexedJobs: true,
  1891  			},
  1892  		},
  1893  		"previous parallelism != previous completions, new parallelism == new completions": {
  1894  			old: batch.Job{
  1895  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1896  				Spec: batch.JobSpec{
  1897  					Selector:       validGeneratedSelector,
  1898  					Template:       validPodTemplateSpecForGenerated,
  1899  					Completions:    pointer.Int32(1),
  1900  					Parallelism:    pointer.Int32(2),
  1901  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1902  				},
  1903  			},
  1904  			update: func(job *batch.Job) {
  1905  				job.Spec.Completions = pointer.Int32(3)
  1906  				job.Spec.Parallelism = pointer.Int32(3)
  1907  			},
  1908  			opts: JobValidationOptions{
  1909  				AllowElasticIndexedJobs: true,
  1910  			},
  1911  		},
  1912  		"indexed job updating completions and parallelism to different values is invalid": {
  1913  			old: batch.Job{
  1914  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1915  				Spec: batch.JobSpec{
  1916  					Selector:       validGeneratedSelector,
  1917  					Template:       validPodTemplateSpecForGenerated,
  1918  					Completions:    pointer.Int32(1),
  1919  					Parallelism:    pointer.Int32(1),
  1920  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1921  				},
  1922  			},
  1923  			update: func(job *batch.Job) {
  1924  				job.Spec.Completions = pointer.Int32(2)
  1925  				job.Spec.Parallelism = pointer.Int32(3)
  1926  			},
  1927  			opts: JobValidationOptions{
  1928  				AllowElasticIndexedJobs: true,
  1929  			},
  1930  			err: &field.Error{
  1931  				Type:  field.ErrorTypeInvalid,
  1932  				Field: "spec.completions",
  1933  			},
  1934  		},
  1935  		"indexed job with completions set updated to nil does not panic": {
  1936  			old: batch.Job{
  1937  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1938  				Spec: batch.JobSpec{
  1939  					Selector:       validGeneratedSelector,
  1940  					Template:       validPodTemplateSpecForGenerated,
  1941  					Completions:    pointer.Int32(1),
  1942  					Parallelism:    pointer.Int32(1),
  1943  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1944  				},
  1945  			},
  1946  			update: func(job *batch.Job) {
  1947  				job.Spec.Completions = nil
  1948  				job.Spec.Parallelism = pointer.Int32(3)
  1949  			},
  1950  			opts: JobValidationOptions{
  1951  				AllowElasticIndexedJobs: true,
  1952  			},
  1953  			err: &field.Error{
  1954  				Type:  field.ErrorTypeRequired,
  1955  				Field: "spec.completions",
  1956  			},
  1957  		},
  1958  		"indexed job with completions unchanged, parallelism reduced to less than completions": {
  1959  			old: batch.Job{
  1960  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1961  				Spec: batch.JobSpec{
  1962  					Selector:       validGeneratedSelector,
  1963  					Template:       validPodTemplateSpecForGenerated,
  1964  					Completions:    pointer.Int32(2),
  1965  					Parallelism:    pointer.Int32(2),
  1966  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1967  				},
  1968  			},
  1969  			update: func(job *batch.Job) {
  1970  				job.Spec.Completions = pointer.Int32(2)
  1971  				job.Spec.Parallelism = pointer.Int32(1)
  1972  			},
  1973  			opts: JobValidationOptions{
  1974  				AllowElasticIndexedJobs: true,
  1975  			},
  1976  		},
  1977  		"indexed job with completions unchanged, parallelism increased higher than completions": {
  1978  			old: batch.Job{
  1979  				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
  1980  				Spec: batch.JobSpec{
  1981  					Selector:       validGeneratedSelector,
  1982  					Template:       validPodTemplateSpecForGenerated,
  1983  					Completions:    pointer.Int32(2),
  1984  					Parallelism:    pointer.Int32(2),
  1985  					CompletionMode: completionModePtr(batch.IndexedCompletion),
  1986  				},
  1987  			},
  1988  			update: func(job *batch.Job) {
  1989  				job.Spec.Completions = pointer.Int32(2)
  1990  				job.Spec.Parallelism = pointer.Int32(3)
  1991  			},
  1992  			opts: JobValidationOptions{
  1993  				AllowElasticIndexedJobs: true,
  1994  			},
  1995  		},
  1996  	}
  1997  	ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
  1998  	for k, tc := range cases {
  1999  		t.Run(k, func(t *testing.T) {
  2000  			tc.old.ResourceVersion = "1"
  2001  			update := tc.old.DeepCopy()
  2002  			tc.update(update)
  2003  			errs := ValidateJobUpdate(update, &tc.old, tc.opts)
  2004  			var wantErrs field.ErrorList
  2005  			if tc.err != nil {
  2006  				wantErrs = append(wantErrs, tc.err)
  2007  			}
  2008  			if diff := cmp.Diff(wantErrs, errs, ignoreValueAndDetail); diff != "" {
  2009  				t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
  2010  			}
  2011  		})
  2012  	}
  2013  }
  2014  
  2015  func TestValidateJobUpdateStatus(t *testing.T) {
  2016  	cases := map[string]struct {
  2017  		old      batch.Job
  2018  		update   batch.Job
  2019  		wantErrs field.ErrorList
  2020  	}{
  2021  		"valid": {
  2022  			old: batch.Job{
  2023  				ObjectMeta: metav1.ObjectMeta{
  2024  					Name:            "abc",
  2025  					Namespace:       metav1.NamespaceDefault,
  2026  					ResourceVersion: "1",
  2027  				},
  2028  				Status: batch.JobStatus{
  2029  					Active:      1,
  2030  					Succeeded:   2,
  2031  					Failed:      3,
  2032  					Terminating: pointer.Int32(4),
  2033  				},
  2034  			},
  2035  			update: batch.Job{
  2036  				ObjectMeta: metav1.ObjectMeta{
  2037  					Name:            "abc",
  2038  					Namespace:       metav1.NamespaceDefault,
  2039  					ResourceVersion: "1",
  2040  				},
  2041  				Status: batch.JobStatus{
  2042  					Active:      2,
  2043  					Succeeded:   3,
  2044  					Failed:      4,
  2045  					Ready:       pointer.Int32(1),
  2046  					Terminating: pointer.Int32(4),
  2047  				},
  2048  			},
  2049  		},
  2050  		"nil ready and terminating": {
  2051  			old: batch.Job{
  2052  				ObjectMeta: metav1.ObjectMeta{
  2053  					Name:            "abc",
  2054  					Namespace:       metav1.NamespaceDefault,
  2055  					ResourceVersion: "1",
  2056  				},
  2057  				Status: batch.JobStatus{
  2058  					Active:    1,
  2059  					Succeeded: 2,
  2060  					Failed:    3,
  2061  				},
  2062  			},
  2063  			update: batch.Job{
  2064  				ObjectMeta: metav1.ObjectMeta{
  2065  					Name:            "abc",
  2066  					Namespace:       metav1.NamespaceDefault,
  2067  					ResourceVersion: "1",
  2068  				},
  2069  				Status: batch.JobStatus{
  2070  					Active:    2,
  2071  					Succeeded: 3,
  2072  					Failed:    4,
  2073  				},
  2074  			},
  2075  		},
  2076  		"negative counts": {
  2077  			old: batch.Job{
  2078  				ObjectMeta: metav1.ObjectMeta{
  2079  					Name:            "abc",
  2080  					Namespace:       metav1.NamespaceDefault,
  2081  					ResourceVersion: "10",
  2082  				},
  2083  				Status: batch.JobStatus{
  2084  					Active:      1,
  2085  					Succeeded:   2,
  2086  					Failed:      3,
  2087  					Terminating: pointer.Int32(4),
  2088  				},
  2089  			},
  2090  			update: batch.Job{
  2091  				ObjectMeta: metav1.ObjectMeta{
  2092  					Name:            "abc",
  2093  					Namespace:       metav1.NamespaceDefault,
  2094  					ResourceVersion: "10",
  2095  				},
  2096  				Status: batch.JobStatus{
  2097  					Active:      -1,
  2098  					Succeeded:   -2,
  2099  					Failed:      -3,
  2100  					Ready:       pointer.Int32(-1),
  2101  					Terminating: pointer.Int32(-2),
  2102  				},
  2103  			},
  2104  			wantErrs: field.ErrorList{
  2105  				{Type: field.ErrorTypeInvalid, Field: "status.active"},
  2106  				{Type: field.ErrorTypeInvalid, Field: "status.succeeded"},
  2107  				{Type: field.ErrorTypeInvalid, Field: "status.failed"},
  2108  				{Type: field.ErrorTypeInvalid, Field: "status.ready"},
  2109  				{Type: field.ErrorTypeInvalid, Field: "status.terminating"},
  2110  			},
  2111  		},
  2112  		"empty and duplicated uncounted pods": {
  2113  			old: batch.Job{
  2114  				ObjectMeta: metav1.ObjectMeta{
  2115  					Name:            "abc",
  2116  					Namespace:       metav1.NamespaceDefault,
  2117  					ResourceVersion: "5",
  2118  				},
  2119  			},
  2120  			update: batch.Job{
  2121  				ObjectMeta: metav1.ObjectMeta{
  2122  					Name:            "abc",
  2123  					Namespace:       metav1.NamespaceDefault,
  2124  					ResourceVersion: "5",
  2125  				},
  2126  				Status: batch.JobStatus{
  2127  					UncountedTerminatedPods: &batch.UncountedTerminatedPods{
  2128  						Succeeded: []types.UID{"a", "b", "c", "a", ""},
  2129  						Failed:    []types.UID{"c", "d", "e", "d", ""},
  2130  					},
  2131  				},
  2132  			},
  2133  			wantErrs: field.ErrorList{
  2134  				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.succeeded[3]"},
  2135  				{Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods.succeeded[4]"},
  2136  				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.failed[0]"},
  2137  				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.failed[3]"},
  2138  				{Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods.failed[4]"},
  2139  			},
  2140  		},
  2141  	}
  2142  	for name, tc := range cases {
  2143  		t.Run(name, func(t *testing.T) {
  2144  			errs := ValidateJobUpdateStatus(&tc.update, &tc.old)
  2145  			if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" {
  2146  				t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
  2147  			}
  2148  		})
  2149  	}
  2150  }
  2151  
  2152  func TestValidateCronJob(t *testing.T) {
  2153  	validManualSelector := getValidManualSelector()
  2154  	validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
  2155  	validPodTemplateSpec.Labels = map[string]string{}
  2156  	validHostNetPodTemplateSpec := func() api.PodTemplateSpec {
  2157  		spec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
  2158  		spec.Spec.SecurityContext = &api.PodSecurityContext{
  2159  			HostNetwork: true,
  2160  		}
  2161  		spec.Spec.Containers[0].Ports = []api.ContainerPort{{
  2162  			ContainerPort: 12345,
  2163  			Protocol:      api.ProtocolTCP,
  2164  		}}
  2165  		return spec
  2166  	}()
  2167  
  2168  	successCases := map[string]batch.CronJob{
  2169  		"basic scheduled job": {
  2170  			ObjectMeta: metav1.ObjectMeta{
  2171  				Name:      "mycronjob",
  2172  				Namespace: metav1.NamespaceDefault,
  2173  				UID:       types.UID("1a2b3c"),
  2174  			},
  2175  			Spec: batch.CronJobSpec{
  2176  				Schedule:          "* * * * ?",
  2177  				ConcurrencyPolicy: batch.AllowConcurrent,
  2178  				JobTemplate: batch.JobTemplateSpec{
  2179  					Spec: batch.JobSpec{
  2180  						Template: validPodTemplateSpec,
  2181  					},
  2182  				},
  2183  			},
  2184  		},
  2185  		"hostnet job": {
  2186  			ObjectMeta: metav1.ObjectMeta{
  2187  				Name:      "mycronjob",
  2188  				Namespace: metav1.NamespaceDefault,
  2189  				UID:       types.UID("1a2b3c"),
  2190  			},
  2191  			Spec: batch.CronJobSpec{
  2192  				Schedule:          "* * * * ?",
  2193  				ConcurrencyPolicy: batch.AllowConcurrent,
  2194  				JobTemplate: batch.JobTemplateSpec{
  2195  					Spec: batch.JobSpec{
  2196  						Template: validHostNetPodTemplateSpec,
  2197  					},
  2198  				},
  2199  			},
  2200  		},
  2201  		"non-standard scheduled": {
  2202  			ObjectMeta: metav1.ObjectMeta{
  2203  				Name:      "mycronjob",
  2204  				Namespace: metav1.NamespaceDefault,
  2205  				UID:       types.UID("1a2b3c"),
  2206  			},
  2207  			Spec: batch.CronJobSpec{
  2208  				Schedule:          "@hourly",
  2209  				ConcurrencyPolicy: batch.AllowConcurrent,
  2210  				JobTemplate: batch.JobTemplateSpec{
  2211  					Spec: batch.JobSpec{
  2212  						Template: validPodTemplateSpec,
  2213  					},
  2214  				},
  2215  			},
  2216  		},
  2217  		"correct timeZone value": {
  2218  			ObjectMeta: metav1.ObjectMeta{
  2219  				Name:      "mycronjob",
  2220  				Namespace: metav1.NamespaceDefault,
  2221  				UID:       types.UID("1a2b3c"),
  2222  			},
  2223  			Spec: batch.CronJobSpec{
  2224  				Schedule:          "0 * * * *",
  2225  				TimeZone:          &timeZoneCorrect,
  2226  				ConcurrencyPolicy: batch.AllowConcurrent,
  2227  				JobTemplate: batch.JobTemplateSpec{
  2228  					Spec: batch.JobSpec{
  2229  						Template: validPodTemplateSpec,
  2230  					},
  2231  				},
  2232  			},
  2233  		},
  2234  	}
  2235  	for k, v := range successCases {
  2236  		t.Run(k, func(t *testing.T) {
  2237  			if errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2238  				t.Errorf("expected success for %s: %v", k, errs)
  2239  			}
  2240  
  2241  			// Update validation should pass same success cases
  2242  			// copy to avoid polluting the testcase object, set a resourceVersion to allow validating update, and test a no-op update
  2243  			v = *v.DeepCopy()
  2244  			v.ResourceVersion = "1"
  2245  			if errs := ValidateCronJobUpdate(&v, &v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
  2246  				t.Errorf("expected success for %s: %v", k, errs)
  2247  			}
  2248  		})
  2249  	}
  2250  
  2251  	negative := int32(-1)
  2252  	negative64 := int64(-1)
  2253  
  2254  	errorCases := map[string]batch.CronJob{
  2255  		"spec.schedule: Invalid value": {
  2256  			ObjectMeta: metav1.ObjectMeta{
  2257  				Name:      "mycronjob",
  2258  				Namespace: metav1.NamespaceDefault,
  2259  				UID:       types.UID("1a2b3c"),
  2260  			},
  2261  			Spec: batch.CronJobSpec{
  2262  				Schedule:          "error",
  2263  				ConcurrencyPolicy: batch.AllowConcurrent,
  2264  				JobTemplate: batch.JobTemplateSpec{
  2265  					Spec: batch.JobSpec{
  2266  						Template: validPodTemplateSpec,
  2267  					},
  2268  				},
  2269  			},
  2270  		},
  2271  		"spec.schedule: Required value": {
  2272  			ObjectMeta: metav1.ObjectMeta{
  2273  				Name:      "mycronjob",
  2274  				Namespace: metav1.NamespaceDefault,
  2275  				UID:       types.UID("1a2b3c"),
  2276  			},
  2277  			Spec: batch.CronJobSpec{
  2278  				Schedule:          "",
  2279  				ConcurrencyPolicy: batch.AllowConcurrent,
  2280  				JobTemplate: batch.JobTemplateSpec{
  2281  					Spec: batch.JobSpec{
  2282  						Template: validPodTemplateSpec,
  2283  					},
  2284  				},
  2285  			},
  2286  		},
  2287  		"spec.timeZone: timeZone must be nil or non-empty string": {
  2288  			ObjectMeta: metav1.ObjectMeta{
  2289  				Name:      "mycronjob",
  2290  				Namespace: metav1.NamespaceDefault,
  2291  				UID:       types.UID("1a2b3c"),
  2292  			},
  2293  			Spec: batch.CronJobSpec{
  2294  				Schedule:          "0 * * * *",
  2295  				TimeZone:          &timeZoneEmpty,
  2296  				ConcurrencyPolicy: batch.AllowConcurrent,
  2297  				JobTemplate: batch.JobTemplateSpec{
  2298  					Spec: batch.JobSpec{
  2299  						Template: validPodTemplateSpec,
  2300  					},
  2301  				},
  2302  			},
  2303  		},
  2304  		"spec.timeZone: timeZone must be an explicit time zone as defined in https://www.iana.org/time-zones": {
  2305  			ObjectMeta: metav1.ObjectMeta{
  2306  				Name:      "mycronjob",
  2307  				Namespace: metav1.NamespaceDefault,
  2308  				UID:       types.UID("1a2b3c"),
  2309  			},
  2310  			Spec: batch.CronJobSpec{
  2311  				Schedule:          "0 * * * *",
  2312  				TimeZone:          &timeZoneLocal,
  2313  				ConcurrencyPolicy: batch.AllowConcurrent,
  2314  				JobTemplate: batch.JobTemplateSpec{
  2315  					Spec: batch.JobSpec{
  2316  						Template: validPodTemplateSpec,
  2317  					},
  2318  				},
  2319  			},
  2320  		},
  2321  		"spec.timeZone: Invalid value: \" Continent/Zone\": unknown time zone  Continent/Zone": {
  2322  			ObjectMeta: metav1.ObjectMeta{
  2323  				Name:      "mycronjob",
  2324  				Namespace: metav1.NamespaceDefault,
  2325  				UID:       types.UID("1a2b3c"),
  2326  			},
  2327  			Spec: batch.CronJobSpec{
  2328  				Schedule:          "0 * * * *",
  2329  				TimeZone:          &timeZoneBadPrefix,
  2330  				ConcurrencyPolicy: batch.AllowConcurrent,
  2331  				JobTemplate: batch.JobTemplateSpec{
  2332  					Spec: batch.JobSpec{
  2333  						Template: validPodTemplateSpec,
  2334  					},
  2335  				},
  2336  			},
  2337  		},
  2338  		"spec.timeZone: Invalid value: \"Continent/InvalidZone\": unknown time zone  Continent/InvalidZone": {
  2339  			ObjectMeta: metav1.ObjectMeta{
  2340  				Name:      "mycronjob",
  2341  				Namespace: metav1.NamespaceDefault,
  2342  				UID:       types.UID("1a2b3c"),
  2343  			},
  2344  			Spec: batch.CronJobSpec{
  2345  				Schedule:          "0 * * * *",
  2346  				TimeZone:          &timeZoneBadName,
  2347  				ConcurrencyPolicy: batch.AllowConcurrent,
  2348  				JobTemplate: batch.JobTemplateSpec{
  2349  					Spec: batch.JobSpec{
  2350  						Template: validPodTemplateSpec,
  2351  					},
  2352  				},
  2353  			},
  2354  		},
  2355  		"spec.timeZone: Invalid value: \" \": unknown time zone  ": {
  2356  			ObjectMeta: metav1.ObjectMeta{
  2357  				Name:      "mycronjob",
  2358  				Namespace: metav1.NamespaceDefault,
  2359  				UID:       types.UID("1a2b3c"),
  2360  			},
  2361  			Spec: batch.CronJobSpec{
  2362  				Schedule:          "0 * * * *",
  2363  				TimeZone:          &timeZoneEmptySpace,
  2364  				ConcurrencyPolicy: batch.AllowConcurrent,
  2365  				JobTemplate: batch.JobTemplateSpec{
  2366  					Spec: batch.JobSpec{
  2367  						Template: validPodTemplateSpec,
  2368  					},
  2369  				},
  2370  			},
  2371  		},
  2372  		"spec.timeZone: Invalid value: \"Continent/Zone \": unknown time zone Continent/Zone ": {
  2373  			ObjectMeta: metav1.ObjectMeta{
  2374  				Name:      "mycronjob",
  2375  				Namespace: metav1.NamespaceDefault,
  2376  				UID:       types.UID("1a2b3c"),
  2377  			},
  2378  			Spec: batch.CronJobSpec{
  2379  				Schedule:          "0 * * * *",
  2380  				TimeZone:          &timeZoneBadSuffix,
  2381  				ConcurrencyPolicy: batch.AllowConcurrent,
  2382  				JobTemplate: batch.JobTemplateSpec{
  2383  					Spec: batch.JobSpec{
  2384  						Template: validPodTemplateSpec,
  2385  					},
  2386  				},
  2387  			},
  2388  		},
  2389  		"spec.startingDeadlineSeconds:must be greater than or equal to 0": {
  2390  			ObjectMeta: metav1.ObjectMeta{
  2391  				Name:      "mycronjob",
  2392  				Namespace: metav1.NamespaceDefault,
  2393  				UID:       types.UID("1a2b3c"),
  2394  			},
  2395  			Spec: batch.CronJobSpec{
  2396  				Schedule:                "* * * * ?",
  2397  				ConcurrencyPolicy:       batch.AllowConcurrent,
  2398  				StartingDeadlineSeconds: &negative64,
  2399  				JobTemplate: batch.JobTemplateSpec{
  2400  					Spec: batch.JobSpec{
  2401  						Template: validPodTemplateSpec,
  2402  					},
  2403  				},
  2404  			},
  2405  		},
  2406  		"spec.successfulJobsHistoryLimit: must be greater than or equal to 0": {
  2407  			ObjectMeta: metav1.ObjectMeta{
  2408  				Name:      "mycronjob",
  2409  				Namespace: metav1.NamespaceDefault,
  2410  				UID:       types.UID("1a2b3c"),
  2411  			},
  2412  			Spec: batch.CronJobSpec{
  2413  				Schedule:                   "* * * * ?",
  2414  				ConcurrencyPolicy:          batch.AllowConcurrent,
  2415  				SuccessfulJobsHistoryLimit: &negative,
  2416  				JobTemplate: batch.JobTemplateSpec{
  2417  					Spec: batch.JobSpec{
  2418  						Template: validPodTemplateSpec,
  2419  					},
  2420  				},
  2421  			},
  2422  		},
  2423  		"spec.failedJobsHistoryLimit: must be greater than or equal to 0": {
  2424  			ObjectMeta: metav1.ObjectMeta{
  2425  				Name:      "mycronjob",
  2426  				Namespace: metav1.NamespaceDefault,
  2427  				UID:       types.UID("1a2b3c"),
  2428  			},
  2429  			Spec: batch.CronJobSpec{
  2430  				Schedule:               "* * * * ?",
  2431  				ConcurrencyPolicy:      batch.AllowConcurrent,
  2432  				FailedJobsHistoryLimit: &negative,
  2433  				JobTemplate: batch.JobTemplateSpec{
  2434  					Spec: batch.JobSpec{
  2435  						Template: validPodTemplateSpec,
  2436  					},
  2437  				},
  2438  			},
  2439  		},
  2440  		"spec.concurrencyPolicy: Required value": {
  2441  			ObjectMeta: metav1.ObjectMeta{
  2442  				Name:      "mycronjob",
  2443  				Namespace: metav1.NamespaceDefault,
  2444  				UID:       types.UID("1a2b3c"),
  2445  			},
  2446  			Spec: batch.CronJobSpec{
  2447  				Schedule: "* * * * ?",
  2448  				JobTemplate: batch.JobTemplateSpec{
  2449  					Spec: batch.JobSpec{
  2450  						Template: validPodTemplateSpec,
  2451  					},
  2452  				},
  2453  			},
  2454  		},
  2455  		"spec.jobTemplate.spec.parallelism:must be greater than or equal to 0": {
  2456  			ObjectMeta: metav1.ObjectMeta{
  2457  				Name:      "mycronjob",
  2458  				Namespace: metav1.NamespaceDefault,
  2459  				UID:       types.UID("1a2b3c"),
  2460  			},
  2461  			Spec: batch.CronJobSpec{
  2462  				Schedule:          "* * * * ?",
  2463  				ConcurrencyPolicy: batch.AllowConcurrent,
  2464  				JobTemplate: batch.JobTemplateSpec{
  2465  					Spec: batch.JobSpec{
  2466  						Parallelism: &negative,
  2467  						Template:    validPodTemplateSpec,
  2468  					},
  2469  				},
  2470  			},
  2471  		},
  2472  		"spec.jobTemplate.spec.completions:must be greater than or equal to 0": {
  2473  			ObjectMeta: metav1.ObjectMeta{
  2474  				Name:      "mycronjob",
  2475  				Namespace: metav1.NamespaceDefault,
  2476  				UID:       types.UID("1a2b3c"),
  2477  			},
  2478  			Spec: batch.CronJobSpec{
  2479  				Schedule:          "* * * * ?",
  2480  				ConcurrencyPolicy: batch.AllowConcurrent,
  2481  				JobTemplate: batch.JobTemplateSpec{
  2482  
  2483  					Spec: batch.JobSpec{
  2484  						Completions: &negative,
  2485  						Template:    validPodTemplateSpec,
  2486  					},
  2487  				},
  2488  			},
  2489  		},
  2490  		"spec.jobTemplate.spec.activeDeadlineSeconds:must be greater than or equal to 0": {
  2491  			ObjectMeta: metav1.ObjectMeta{
  2492  				Name:      "mycronjob",
  2493  				Namespace: metav1.NamespaceDefault,
  2494  				UID:       types.UID("1a2b3c"),
  2495  			},
  2496  			Spec: batch.CronJobSpec{
  2497  				Schedule:          "* * * * ?",
  2498  				ConcurrencyPolicy: batch.AllowConcurrent,
  2499  				JobTemplate: batch.JobTemplateSpec{
  2500  					Spec: batch.JobSpec{
  2501  						ActiveDeadlineSeconds: &negative64,
  2502  						Template:              validPodTemplateSpec,
  2503  					},
  2504  				},
  2505  			},
  2506  		},
  2507  		"spec.jobTemplate.spec.selector: Invalid value: {\"matchLabels\":{\"a\":\"b\"}}: `selector` will be auto-generated": {
  2508  			ObjectMeta: metav1.ObjectMeta{
  2509  				Name:      "mycronjob",
  2510  				Namespace: metav1.NamespaceDefault,
  2511  				UID:       types.UID("1a2b3c"),
  2512  			},
  2513  			Spec: batch.CronJobSpec{
  2514  				Schedule:          "* * * * ?",
  2515  				ConcurrencyPolicy: batch.AllowConcurrent,
  2516  				JobTemplate: batch.JobTemplateSpec{
  2517  					Spec: batch.JobSpec{
  2518  						Selector: validManualSelector,
  2519  						Template: validPodTemplateSpec,
  2520  					},
  2521  				},
  2522  			},
  2523  		},
  2524  		"metadata.name: must be no more than 52 characters": {
  2525  			ObjectMeta: metav1.ObjectMeta{
  2526  				Name:      "10000000002000000000300000000040000000005000000000123",
  2527  				Namespace: metav1.NamespaceDefault,
  2528  				UID:       types.UID("1a2b3c"),
  2529  			},
  2530  			Spec: batch.CronJobSpec{
  2531  				Schedule:          "* * * * ?",
  2532  				ConcurrencyPolicy: batch.AllowConcurrent,
  2533  				JobTemplate: batch.JobTemplateSpec{
  2534  					Spec: batch.JobSpec{
  2535  						Template: validPodTemplateSpec,
  2536  					},
  2537  				},
  2538  			},
  2539  		},
  2540  		"spec.jobTemplate.spec.manualSelector: Unsupported value": {
  2541  			ObjectMeta: metav1.ObjectMeta{
  2542  				Name:      "mycronjob",
  2543  				Namespace: metav1.NamespaceDefault,
  2544  				UID:       types.UID("1a2b3c"),
  2545  			},
  2546  			Spec: batch.CronJobSpec{
  2547  				Schedule:          "* * * * ?",
  2548  				ConcurrencyPolicy: batch.AllowConcurrent,
  2549  				JobTemplate: batch.JobTemplateSpec{
  2550  					Spec: batch.JobSpec{
  2551  						ManualSelector: pointer.Bool(true),
  2552  						Template:       validPodTemplateSpec,
  2553  					},
  2554  				},
  2555  			},
  2556  		},
  2557  		"spec.jobTemplate.spec.template.spec.restartPolicy: Required value": {
  2558  			ObjectMeta: metav1.ObjectMeta{
  2559  				Name:      "mycronjob",
  2560  				Namespace: metav1.NamespaceDefault,
  2561  				UID:       types.UID("1a2b3c"),
  2562  			},
  2563  			Spec: batch.CronJobSpec{
  2564  				Schedule:          "* * * * ?",
  2565  				ConcurrencyPolicy: batch.AllowConcurrent,
  2566  				JobTemplate: batch.JobTemplateSpec{
  2567  					Spec: batch.JobSpec{
  2568  						Template: api.PodTemplateSpec{
  2569  							Spec: api.PodSpec{
  2570  								RestartPolicy: api.RestartPolicyAlways,
  2571  								DNSPolicy:     api.DNSClusterFirst,
  2572  								Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2573  							},
  2574  						},
  2575  					},
  2576  				},
  2577  			},
  2578  		},
  2579  		"spec.jobTemplate.spec.template.spec.restartPolicy: Unsupported value": {
  2580  			ObjectMeta: metav1.ObjectMeta{
  2581  				Name:      "mycronjob",
  2582  				Namespace: metav1.NamespaceDefault,
  2583  				UID:       types.UID("1a2b3c"),
  2584  			},
  2585  			Spec: batch.CronJobSpec{
  2586  				Schedule:          "* * * * ?",
  2587  				ConcurrencyPolicy: batch.AllowConcurrent,
  2588  				JobTemplate: batch.JobTemplateSpec{
  2589  					Spec: batch.JobSpec{
  2590  						Template: api.PodTemplateSpec{
  2591  							Spec: api.PodSpec{
  2592  								RestartPolicy: "Invalid",
  2593  								DNSPolicy:     api.DNSClusterFirst,
  2594  								Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
  2595  							},
  2596  						},
  2597  					},
  2598  				},
  2599  			},
  2600  		},
  2601  		"spec.jobTemplate.spec.ttlSecondsAfterFinished:must be greater than or equal to 0": {
  2602  			ObjectMeta: metav1.ObjectMeta{
  2603  				Name:      "mycronjob",
  2604  				Namespace: metav1.NamespaceDefault,
  2605  				UID:       types.UID("1a2b3c"),
  2606  			},
  2607  			Spec: batch.CronJobSpec{
  2608  				Schedule:          "* * * * ?",
  2609  				ConcurrencyPolicy: batch.AllowConcurrent,
  2610  				JobTemplate: batch.JobTemplateSpec{
  2611  					Spec: batch.JobSpec{
  2612  						TTLSecondsAfterFinished: &negative,
  2613  						Template:                validPodTemplateSpec,
  2614  					},
  2615  				},
  2616  			},
  2617  		},
  2618  	}
  2619  
  2620  	for k, v := range errorCases {
  2621  		t.Run(k, func(t *testing.T) {
  2622  			errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{})
  2623  			if len(errs) == 0 {
  2624  				t.Errorf("expected failure for %s", k)
  2625  			} else {
  2626  				s := strings.Split(k, ":")
  2627  				err := errs[0]
  2628  				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  2629  					t.Errorf("unexpected error: %v, expected: %s", err, k)
  2630  				}
  2631  			}
  2632  
  2633  			// Update validation should fail all failure cases other than the 52 character name limit
  2634  			// copy to avoid polluting the testcase object, set a resourceVersion to allow validating update, and test a no-op update
  2635  			oldSpec := *v.DeepCopy()
  2636  			oldSpec.ResourceVersion = "1"
  2637  			oldSpec.Spec.TimeZone = nil
  2638  
  2639  			newSpec := *v.DeepCopy()
  2640  			newSpec.ResourceVersion = "2"
  2641  
  2642  			errs = ValidateCronJobUpdate(&newSpec, &oldSpec, corevalidation.PodValidationOptions{})
  2643  			if len(errs) == 0 {
  2644  				if k == "metadata.name: must be no more than 52 characters" {
  2645  					return
  2646  				}
  2647  				t.Errorf("expected failure for %s", k)
  2648  			} else {
  2649  				s := strings.Split(k, ":")
  2650  				err := errs[0]
  2651  				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  2652  					t.Errorf("unexpected error: %v, expected: %s", err, k)
  2653  				}
  2654  			}
  2655  		})
  2656  	}
  2657  }
  2658  
  2659  func TestValidateCronJobScheduleTZ(t *testing.T) {
  2660  	validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
  2661  	validPodTemplateSpec.Labels = map[string]string{}
  2662  	validSchedule := "0 * * * *"
  2663  	invalidSchedule := "TZ=UTC 0 * * * *"
  2664  	invalidCronJob := &batch.CronJob{
  2665  		ObjectMeta: metav1.ObjectMeta{
  2666  			Name:      "mycronjob",
  2667  			Namespace: metav1.NamespaceDefault,
  2668  			UID:       types.UID("1a2b3c"),
  2669  		},
  2670  		Spec: batch.CronJobSpec{
  2671  			Schedule:          invalidSchedule,
  2672  			ConcurrencyPolicy: batch.AllowConcurrent,
  2673  			JobTemplate: batch.JobTemplateSpec{
  2674  				Spec: batch.JobSpec{
  2675  					Template: validPodTemplateSpec,
  2676  				},
  2677  			},
  2678  		},
  2679  	}
  2680  	validCronJob := &batch.CronJob{
  2681  		ObjectMeta: metav1.ObjectMeta{
  2682  			Name:      "mycronjob",
  2683  			Namespace: metav1.NamespaceDefault,
  2684  			UID:       types.UID("1a2b3c"),
  2685  		},
  2686  		Spec: batch.CronJobSpec{
  2687  			Schedule:          validSchedule,
  2688  			ConcurrencyPolicy: batch.AllowConcurrent,
  2689  			JobTemplate: batch.JobTemplateSpec{
  2690  				Spec: batch.JobSpec{
  2691  					Template: validPodTemplateSpec,
  2692  				},
  2693  			},
  2694  		},
  2695  	}
  2696  
  2697  	testCases := map[string]struct {
  2698  		cronJob   *batch.CronJob
  2699  		createErr string
  2700  		update    func(*batch.CronJob)
  2701  		updateErr string
  2702  	}{
  2703  		"update removing TZ should work": {
  2704  			cronJob:   invalidCronJob,
  2705  			createErr: "cannot use TZ or CRON_TZ in schedule",
  2706  			update: func(cj *batch.CronJob) {
  2707  				cj.Spec.Schedule = validSchedule
  2708  			},
  2709  		},
  2710  		"update not modifying TZ should work": {
  2711  			cronJob:   invalidCronJob,
  2712  			createErr: "cannot use TZ or CRON_TZ in schedule, use timeZone field instead",
  2713  			update: func(cj *batch.CronJob) {
  2714  				cj.Spec.Schedule = invalidSchedule
  2715  			},
  2716  		},
  2717  		"update not modifying TZ but adding .spec.timeZone should fail": {
  2718  			cronJob:   invalidCronJob,
  2719  			createErr: "cannot use TZ or CRON_TZ in schedule, use timeZone field instead",
  2720  			update: func(cj *batch.CronJob) {
  2721  				cj.Spec.TimeZone = &timeZoneUTC
  2722  			},
  2723  			updateErr: "cannot use both timeZone field and TZ or CRON_TZ in schedule",
  2724  		},
  2725  		"update adding TZ should fail": {
  2726  			cronJob: validCronJob,
  2727  			update: func(cj *batch.CronJob) {
  2728  				cj.Spec.Schedule = invalidSchedule
  2729  			},
  2730  			updateErr: "cannot use TZ or CRON_TZ in schedule",
  2731  		},
  2732  	}
  2733  
  2734  	for k, v := range testCases {
  2735  		t.Run(k, func(t *testing.T) {
  2736  			errs := ValidateCronJobCreate(v.cronJob, corevalidation.PodValidationOptions{})
  2737  			if len(errs) > 0 {
  2738  				err := errs[0]
  2739  				if len(v.createErr) == 0 {
  2740  					t.Errorf("unexpected error: %#v, none expected", err)
  2741  					return
  2742  				}
  2743  				if !strings.Contains(err.Error(), v.createErr) {
  2744  					t.Errorf("unexpected error: %v, expected: %s", err, v.createErr)
  2745  				}
  2746  			} else if len(v.createErr) != 0 {
  2747  				t.Errorf("no error, expected %v", v.createErr)
  2748  				return
  2749  			}
  2750  
  2751  			oldSpec := v.cronJob.DeepCopy()
  2752  			oldSpec.ResourceVersion = "1"
  2753  
  2754  			newSpec := v.cronJob.DeepCopy()
  2755  			newSpec.ResourceVersion = "2"
  2756  			if v.update != nil {
  2757  				v.update(newSpec)
  2758  			}
  2759  
  2760  			errs = ValidateCronJobUpdate(newSpec, oldSpec, corevalidation.PodValidationOptions{})
  2761  			if len(errs) > 0 {
  2762  				err := errs[0]
  2763  				if len(v.updateErr) == 0 {
  2764  					t.Errorf("unexpected error: %#v, none expected", err)
  2765  					return
  2766  				}
  2767  				if !strings.Contains(err.Error(), v.updateErr) {
  2768  					t.Errorf("unexpected error: %v, expected: %s", err, v.updateErr)
  2769  				}
  2770  			} else if len(v.updateErr) != 0 {
  2771  				t.Errorf("no error, expected %v", v.updateErr)
  2772  				return
  2773  			}
  2774  		})
  2775  	}
  2776  }
  2777  
  2778  func TestValidateCronJobSpec(t *testing.T) {
  2779  	validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
  2780  	validPodTemplateSpec.Labels = map[string]string{}
  2781  
  2782  	type testCase struct {
  2783  		old       *batch.CronJobSpec
  2784  		new       *batch.CronJobSpec
  2785  		expectErr bool
  2786  	}
  2787  
  2788  	cases := map[string]testCase{
  2789  		"no validation because timeZone is nil for old and new": {
  2790  			old: &batch.CronJobSpec{
  2791  				Schedule:          "0 * * * *",
  2792  				TimeZone:          nil,
  2793  				ConcurrencyPolicy: batch.AllowConcurrent,
  2794  				JobTemplate: batch.JobTemplateSpec{
  2795  					Spec: batch.JobSpec{
  2796  						Template: validPodTemplateSpec,
  2797  					},
  2798  				},
  2799  			},
  2800  			new: &batch.CronJobSpec{
  2801  				Schedule:          "0 * * * *",
  2802  				TimeZone:          nil,
  2803  				ConcurrencyPolicy: batch.AllowConcurrent,
  2804  				JobTemplate: batch.JobTemplateSpec{
  2805  					Spec: batch.JobSpec{
  2806  						Template: validPodTemplateSpec,
  2807  					},
  2808  				},
  2809  			},
  2810  		},
  2811  		"check validation because timeZone is different for new": {
  2812  			old: &batch.CronJobSpec{
  2813  				Schedule:          "0 * * * *",
  2814  				TimeZone:          nil,
  2815  				ConcurrencyPolicy: batch.AllowConcurrent,
  2816  				JobTemplate: batch.JobTemplateSpec{
  2817  					Spec: batch.JobSpec{
  2818  						Template: validPodTemplateSpec,
  2819  					},
  2820  				},
  2821  			},
  2822  			new: &batch.CronJobSpec{
  2823  				Schedule:          "0 * * * *",
  2824  				TimeZone:          pointer.String("America/New_York"),
  2825  				ConcurrencyPolicy: batch.AllowConcurrent,
  2826  				JobTemplate: batch.JobTemplateSpec{
  2827  					Spec: batch.JobSpec{
  2828  						Template: validPodTemplateSpec,
  2829  					},
  2830  				},
  2831  			},
  2832  		},
  2833  		"check validation because timeZone is different for new and invalid": {
  2834  			old: &batch.CronJobSpec{
  2835  				Schedule:          "0 * * * *",
  2836  				TimeZone:          nil,
  2837  				ConcurrencyPolicy: batch.AllowConcurrent,
  2838  				JobTemplate: batch.JobTemplateSpec{
  2839  					Spec: batch.JobSpec{
  2840  						Template: validPodTemplateSpec,
  2841  					},
  2842  				},
  2843  			},
  2844  			new: &batch.CronJobSpec{
  2845  				Schedule:          "0 * * * *",
  2846  				TimeZone:          pointer.String("broken"),
  2847  				ConcurrencyPolicy: batch.AllowConcurrent,
  2848  				JobTemplate: batch.JobTemplateSpec{
  2849  					Spec: batch.JobSpec{
  2850  						Template: validPodTemplateSpec,
  2851  					},
  2852  				},
  2853  			},
  2854  			expectErr: true,
  2855  		},
  2856  		"old timeZone and new timeZone are valid": {
  2857  			old: &batch.CronJobSpec{
  2858  				Schedule:          "0 * * * *",
  2859  				TimeZone:          pointer.String("America/New_York"),
  2860  				ConcurrencyPolicy: batch.AllowConcurrent,
  2861  				JobTemplate: batch.JobTemplateSpec{
  2862  					Spec: batch.JobSpec{
  2863  						Template: validPodTemplateSpec,
  2864  					},
  2865  				},
  2866  			},
  2867  			new: &batch.CronJobSpec{
  2868  				Schedule:          "0 * * * *",
  2869  				TimeZone:          pointer.String("America/Chicago"),
  2870  				ConcurrencyPolicy: batch.AllowConcurrent,
  2871  				JobTemplate: batch.JobTemplateSpec{
  2872  					Spec: batch.JobSpec{
  2873  						Template: validPodTemplateSpec,
  2874  					},
  2875  				},
  2876  			},
  2877  		},
  2878  		"old timeZone is valid, but new timeZone is invalid": {
  2879  			old: &batch.CronJobSpec{
  2880  				Schedule:          "0 * * * *",
  2881  				TimeZone:          pointer.String("America/New_York"),
  2882  				ConcurrencyPolicy: batch.AllowConcurrent,
  2883  				JobTemplate: batch.JobTemplateSpec{
  2884  					Spec: batch.JobSpec{
  2885  						Template: validPodTemplateSpec,
  2886  					},
  2887  				},
  2888  			},
  2889  			new: &batch.CronJobSpec{
  2890  				Schedule:          "0 * * * *",
  2891  				TimeZone:          pointer.String("broken"),
  2892  				ConcurrencyPolicy: batch.AllowConcurrent,
  2893  				JobTemplate: batch.JobTemplateSpec{
  2894  					Spec: batch.JobSpec{
  2895  						Template: validPodTemplateSpec,
  2896  					},
  2897  				},
  2898  			},
  2899  			expectErr: true,
  2900  		},
  2901  		"old timeZone and new timeZone are invalid, but unchanged": {
  2902  			old: &batch.CronJobSpec{
  2903  				Schedule:          "0 * * * *",
  2904  				TimeZone:          pointer.String("broken"),
  2905  				ConcurrencyPolicy: batch.AllowConcurrent,
  2906  				JobTemplate: batch.JobTemplateSpec{
  2907  					Spec: batch.JobSpec{
  2908  						Template: validPodTemplateSpec,
  2909  					},
  2910  				},
  2911  			},
  2912  			new: &batch.CronJobSpec{
  2913  				Schedule:          "0 * * * *",
  2914  				TimeZone:          pointer.String("broken"),
  2915  				ConcurrencyPolicy: batch.AllowConcurrent,
  2916  				JobTemplate: batch.JobTemplateSpec{
  2917  					Spec: batch.JobSpec{
  2918  						Template: validPodTemplateSpec,
  2919  					},
  2920  				},
  2921  			},
  2922  		},
  2923  		"old timeZone and new timeZone are invalid, but different": {
  2924  			old: &batch.CronJobSpec{
  2925  				Schedule:          "0 * * * *",
  2926  				TimeZone:          pointer.String("broken"),
  2927  				ConcurrencyPolicy: batch.AllowConcurrent,
  2928  				JobTemplate: batch.JobTemplateSpec{
  2929  					Spec: batch.JobSpec{
  2930  						Template: validPodTemplateSpec,
  2931  					},
  2932  				},
  2933  			},
  2934  			new: &batch.CronJobSpec{
  2935  				Schedule:          "0 * * * *",
  2936  				TimeZone:          pointer.String("still broken"),
  2937  				ConcurrencyPolicy: batch.AllowConcurrent,
  2938  				JobTemplate: batch.JobTemplateSpec{
  2939  					Spec: batch.JobSpec{
  2940  						Template: validPodTemplateSpec,
  2941  					},
  2942  				},
  2943  			},
  2944  			expectErr: true,
  2945  		},
  2946  		"old timeZone is invalid, but new timeZone is valid": {
  2947  			old: &batch.CronJobSpec{
  2948  				Schedule:          "0 * * * *",
  2949  				TimeZone:          pointer.String("broken"),
  2950  				ConcurrencyPolicy: batch.AllowConcurrent,
  2951  				JobTemplate: batch.JobTemplateSpec{
  2952  					Spec: batch.JobSpec{
  2953  						Template: validPodTemplateSpec,
  2954  					},
  2955  				},
  2956  			},
  2957  			new: &batch.CronJobSpec{
  2958  				Schedule:          "0 * * * *",
  2959  				TimeZone:          pointer.String("America/New_York"),
  2960  				ConcurrencyPolicy: batch.AllowConcurrent,
  2961  				JobTemplate: batch.JobTemplateSpec{
  2962  					Spec: batch.JobSpec{
  2963  						Template: validPodTemplateSpec,
  2964  					},
  2965  				},
  2966  			},
  2967  		},
  2968  	}
  2969  
  2970  	for k, v := range cases {
  2971  		errs := validateCronJobSpec(v.new, v.old, field.NewPath("spec"), corevalidation.PodValidationOptions{})
  2972  		if len(errs) > 0 && !v.expectErr {
  2973  			t.Errorf("unexpected error for %s: %v", k, errs)
  2974  		} else if len(errs) == 0 && v.expectErr {
  2975  			t.Errorf("expected error for %s but got nil", k)
  2976  		}
  2977  	}
  2978  }
  2979  
  2980  func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
  2981  	return &m
  2982  }
  2983  
  2984  func TestTimeZones(t *testing.T) {
  2985  	// all valid time zones as of go1.19 release on 2022-08-02
  2986  	data := []string{
  2987  		`Africa/Abidjan`,
  2988  		`Africa/Accra`,
  2989  		`Africa/Addis_Ababa`,
  2990  		`Africa/Algiers`,
  2991  		`Africa/Asmara`,
  2992  		`Africa/Asmera`,
  2993  		`Africa/Bamako`,
  2994  		`Africa/Bangui`,
  2995  		`Africa/Banjul`,
  2996  		`Africa/Bissau`,
  2997  		`Africa/Blantyre`,
  2998  		`Africa/Brazzaville`,
  2999  		`Africa/Bujumbura`,
  3000  		`Africa/Cairo`,
  3001  		`Africa/Casablanca`,
  3002  		`Africa/Ceuta`,
  3003  		`Africa/Conakry`,
  3004  		`Africa/Dakar`,
  3005  		`Africa/Dar_es_Salaam`,
  3006  		`Africa/Djibouti`,
  3007  		`Africa/Douala`,
  3008  		`Africa/El_Aaiun`,
  3009  		`Africa/Freetown`,
  3010  		`Africa/Gaborone`,
  3011  		`Africa/Harare`,
  3012  		`Africa/Johannesburg`,
  3013  		`Africa/Juba`,
  3014  		`Africa/Kampala`,
  3015  		`Africa/Khartoum`,
  3016  		`Africa/Kigali`,
  3017  		`Africa/Kinshasa`,
  3018  		`Africa/Lagos`,
  3019  		`Africa/Libreville`,
  3020  		`Africa/Lome`,
  3021  		`Africa/Luanda`,
  3022  		`Africa/Lubumbashi`,
  3023  		`Africa/Lusaka`,
  3024  		`Africa/Malabo`,
  3025  		`Africa/Maputo`,
  3026  		`Africa/Maseru`,
  3027  		`Africa/Mbabane`,
  3028  		`Africa/Mogadishu`,
  3029  		`Africa/Monrovia`,
  3030  		`Africa/Nairobi`,
  3031  		`Africa/Ndjamena`,
  3032  		`Africa/Niamey`,
  3033  		`Africa/Nouakchott`,
  3034  		`Africa/Ouagadougou`,
  3035  		`Africa/Porto-Novo`,
  3036  		`Africa/Sao_Tome`,
  3037  		`Africa/Timbuktu`,
  3038  		`Africa/Tripoli`,
  3039  		`Africa/Tunis`,
  3040  		`Africa/Windhoek`,
  3041  		`America/Adak`,
  3042  		`America/Anchorage`,
  3043  		`America/Anguilla`,
  3044  		`America/Antigua`,
  3045  		`America/Araguaina`,
  3046  		`America/Argentina/Buenos_Aires`,
  3047  		`America/Argentina/Catamarca`,
  3048  		`America/Argentina/ComodRivadavia`,
  3049  		`America/Argentina/Cordoba`,
  3050  		`America/Argentina/Jujuy`,
  3051  		`America/Argentina/La_Rioja`,
  3052  		`America/Argentina/Mendoza`,
  3053  		`America/Argentina/Rio_Gallegos`,
  3054  		`America/Argentina/Salta`,
  3055  		`America/Argentina/San_Juan`,
  3056  		`America/Argentina/San_Luis`,
  3057  		`America/Argentina/Tucuman`,
  3058  		`America/Argentina/Ushuaia`,
  3059  		`America/Aruba`,
  3060  		`America/Asuncion`,
  3061  		`America/Atikokan`,
  3062  		`America/Atka`,
  3063  		`America/Bahia`,
  3064  		`America/Bahia_Banderas`,
  3065  		`America/Barbados`,
  3066  		`America/Belem`,
  3067  		`America/Belize`,
  3068  		`America/Blanc-Sablon`,
  3069  		`America/Boa_Vista`,
  3070  		`America/Bogota`,
  3071  		`America/Boise`,
  3072  		`America/Buenos_Aires`,
  3073  		`America/Cambridge_Bay`,
  3074  		`America/Campo_Grande`,
  3075  		`America/Cancun`,
  3076  		`America/Caracas`,
  3077  		`America/Catamarca`,
  3078  		`America/Cayenne`,
  3079  		`America/Cayman`,
  3080  		`America/Chicago`,
  3081  		`America/Chihuahua`,
  3082  		`America/Coral_Harbour`,
  3083  		`America/Cordoba`,
  3084  		`America/Costa_Rica`,
  3085  		`America/Creston`,
  3086  		`America/Cuiaba`,
  3087  		`America/Curacao`,
  3088  		`America/Danmarkshavn`,
  3089  		`America/Dawson`,
  3090  		`America/Dawson_Creek`,
  3091  		`America/Denver`,
  3092  		`America/Detroit`,
  3093  		`America/Dominica`,
  3094  		`America/Edmonton`,
  3095  		`America/Eirunepe`,
  3096  		`America/El_Salvador`,
  3097  		`America/Ensenada`,
  3098  		`America/Fort_Nelson`,
  3099  		`America/Fort_Wayne`,
  3100  		`America/Fortaleza`,
  3101  		`America/Glace_Bay`,
  3102  		`America/Godthab`,
  3103  		`America/Goose_Bay`,
  3104  		`America/Grand_Turk`,
  3105  		`America/Grenada`,
  3106  		`America/Guadeloupe`,
  3107  		`America/Guatemala`,
  3108  		`America/Guayaquil`,
  3109  		`America/Guyana`,
  3110  		`America/Halifax`,
  3111  		`America/Havana`,
  3112  		`America/Hermosillo`,
  3113  		`America/Indiana/Indianapolis`,
  3114  		`America/Indiana/Knox`,
  3115  		`America/Indiana/Marengo`,
  3116  		`America/Indiana/Petersburg`,
  3117  		`America/Indiana/Tell_City`,
  3118  		`America/Indiana/Vevay`,
  3119  		`America/Indiana/Vincennes`,
  3120  		`America/Indiana/Winamac`,
  3121  		`America/Indianapolis`,
  3122  		`America/Inuvik`,
  3123  		`America/Iqaluit`,
  3124  		`America/Jamaica`,
  3125  		`America/Jujuy`,
  3126  		`America/Juneau`,
  3127  		`America/Kentucky/Louisville`,
  3128  		`America/Kentucky/Monticello`,
  3129  		`America/Knox_IN`,
  3130  		`America/Kralendijk`,
  3131  		`America/La_Paz`,
  3132  		`America/Lima`,
  3133  		`America/Los_Angeles`,
  3134  		`America/Louisville`,
  3135  		`America/Lower_Princes`,
  3136  		`America/Maceio`,
  3137  		`America/Managua`,
  3138  		`America/Manaus`,
  3139  		`America/Marigot`,
  3140  		`America/Martinique`,
  3141  		`America/Matamoros`,
  3142  		`America/Mazatlan`,
  3143  		`America/Mendoza`,
  3144  		`America/Menominee`,
  3145  		`America/Merida`,
  3146  		`America/Metlakatla`,
  3147  		`America/Mexico_City`,
  3148  		`America/Miquelon`,
  3149  		`America/Moncton`,
  3150  		`America/Monterrey`,
  3151  		`America/Montevideo`,
  3152  		`America/Montreal`,
  3153  		`America/Montserrat`,
  3154  		`America/Nassau`,
  3155  		`America/New_York`,
  3156  		`America/Nipigon`,
  3157  		`America/Nome`,
  3158  		`America/Noronha`,
  3159  		`America/North_Dakota/Beulah`,
  3160  		`America/North_Dakota/Center`,
  3161  		`America/North_Dakota/New_Salem`,
  3162  		`America/Nuuk`,
  3163  		`America/Ojinaga`,
  3164  		`America/Panama`,
  3165  		`America/Pangnirtung`,
  3166  		`America/Paramaribo`,
  3167  		`America/Phoenix`,
  3168  		`America/Port-au-Prince`,
  3169  		`America/Port_of_Spain`,
  3170  		`America/Porto_Acre`,
  3171  		`America/Porto_Velho`,
  3172  		`America/Puerto_Rico`,
  3173  		`America/Punta_Arenas`,
  3174  		`America/Rainy_River`,
  3175  		`America/Rankin_Inlet`,
  3176  		`America/Recife`,
  3177  		`America/Regina`,
  3178  		`America/Resolute`,
  3179  		`America/Rio_Branco`,
  3180  		`America/Rosario`,
  3181  		`America/Santa_Isabel`,
  3182  		`America/Santarem`,
  3183  		`America/Santiago`,
  3184  		`America/Santo_Domingo`,
  3185  		`America/Sao_Paulo`,
  3186  		`America/Scoresbysund`,
  3187  		`America/Shiprock`,
  3188  		`America/Sitka`,
  3189  		`America/St_Barthelemy`,
  3190  		`America/St_Johns`,
  3191  		`America/St_Kitts`,
  3192  		`America/St_Lucia`,
  3193  		`America/St_Thomas`,
  3194  		`America/St_Vincent`,
  3195  		`America/Swift_Current`,
  3196  		`America/Tegucigalpa`,
  3197  		`America/Thule`,
  3198  		`America/Thunder_Bay`,
  3199  		`America/Tijuana`,
  3200  		`America/Toronto`,
  3201  		`America/Tortola`,
  3202  		`America/Vancouver`,
  3203  		`America/Virgin`,
  3204  		`America/Whitehorse`,
  3205  		`America/Winnipeg`,
  3206  		`America/Yakutat`,
  3207  		`America/Yellowknife`,
  3208  		`Antarctica/Casey`,
  3209  		`Antarctica/Davis`,
  3210  		`Antarctica/DumontDUrville`,
  3211  		`Antarctica/Macquarie`,
  3212  		`Antarctica/Mawson`,
  3213  		`Antarctica/McMurdo`,
  3214  		`Antarctica/Palmer`,
  3215  		`Antarctica/Rothera`,
  3216  		`Antarctica/South_Pole`,
  3217  		`Antarctica/Syowa`,
  3218  		`Antarctica/Troll`,
  3219  		`Antarctica/Vostok`,
  3220  		`Arctic/Longyearbyen`,
  3221  		`Asia/Aden`,
  3222  		`Asia/Almaty`,
  3223  		`Asia/Amman`,
  3224  		`Asia/Anadyr`,
  3225  		`Asia/Aqtau`,
  3226  		`Asia/Aqtobe`,
  3227  		`Asia/Ashgabat`,
  3228  		`Asia/Ashkhabad`,
  3229  		`Asia/Atyrau`,
  3230  		`Asia/Baghdad`,
  3231  		`Asia/Bahrain`,
  3232  		`Asia/Baku`,
  3233  		`Asia/Bangkok`,
  3234  		`Asia/Barnaul`,
  3235  		`Asia/Beirut`,
  3236  		`Asia/Bishkek`,
  3237  		`Asia/Brunei`,
  3238  		`Asia/Calcutta`,
  3239  		`Asia/Chita`,
  3240  		`Asia/Choibalsan`,
  3241  		`Asia/Chongqing`,
  3242  		`Asia/Chungking`,
  3243  		`Asia/Colombo`,
  3244  		`Asia/Dacca`,
  3245  		`Asia/Damascus`,
  3246  		`Asia/Dhaka`,
  3247  		`Asia/Dili`,
  3248  		`Asia/Dubai`,
  3249  		`Asia/Dushanbe`,
  3250  		`Asia/Famagusta`,
  3251  		`Asia/Gaza`,
  3252  		`Asia/Harbin`,
  3253  		`Asia/Hebron`,
  3254  		`Asia/Ho_Chi_Minh`,
  3255  		`Asia/Hong_Kong`,
  3256  		`Asia/Hovd`,
  3257  		`Asia/Irkutsk`,
  3258  		`Asia/Istanbul`,
  3259  		`Asia/Jakarta`,
  3260  		`Asia/Jayapura`,
  3261  		`Asia/Jerusalem`,
  3262  		`Asia/Kabul`,
  3263  		`Asia/Kamchatka`,
  3264  		`Asia/Karachi`,
  3265  		`Asia/Kashgar`,
  3266  		`Asia/Kathmandu`,
  3267  		`Asia/Katmandu`,
  3268  		`Asia/Khandyga`,
  3269  		`Asia/Kolkata`,
  3270  		`Asia/Krasnoyarsk`,
  3271  		`Asia/Kuala_Lumpur`,
  3272  		`Asia/Kuching`,
  3273  		`Asia/Kuwait`,
  3274  		`Asia/Macao`,
  3275  		`Asia/Macau`,
  3276  		`Asia/Magadan`,
  3277  		`Asia/Makassar`,
  3278  		`Asia/Manila`,
  3279  		`Asia/Muscat`,
  3280  		`Asia/Nicosia`,
  3281  		`Asia/Novokuznetsk`,
  3282  		`Asia/Novosibirsk`,
  3283  		`Asia/Omsk`,
  3284  		`Asia/Oral`,
  3285  		`Asia/Phnom_Penh`,
  3286  		`Asia/Pontianak`,
  3287  		`Asia/Pyongyang`,
  3288  		`Asia/Qatar`,
  3289  		`Asia/Qostanay`,
  3290  		`Asia/Qyzylorda`,
  3291  		`Asia/Rangoon`,
  3292  		`Asia/Riyadh`,
  3293  		`Asia/Saigon`,
  3294  		`Asia/Sakhalin`,
  3295  		`Asia/Samarkand`,
  3296  		`Asia/Seoul`,
  3297  		`Asia/Shanghai`,
  3298  		`Asia/Singapore`,
  3299  		`Asia/Srednekolymsk`,
  3300  		`Asia/Taipei`,
  3301  		`Asia/Tashkent`,
  3302  		`Asia/Tbilisi`,
  3303  		`Asia/Tehran`,
  3304  		`Asia/Tel_Aviv`,
  3305  		`Asia/Thimbu`,
  3306  		`Asia/Thimphu`,
  3307  		`Asia/Tokyo`,
  3308  		`Asia/Tomsk`,
  3309  		`Asia/Ujung_Pandang`,
  3310  		`Asia/Ulaanbaatar`,
  3311  		`Asia/Ulan_Bator`,
  3312  		`Asia/Urumqi`,
  3313  		`Asia/Ust-Nera`,
  3314  		`Asia/Vientiane`,
  3315  		`Asia/Vladivostok`,
  3316  		`Asia/Yakutsk`,
  3317  		`Asia/Yangon`,
  3318  		`Asia/Yekaterinburg`,
  3319  		`Asia/Yerevan`,
  3320  		`Atlantic/Azores`,
  3321  		`Atlantic/Bermuda`,
  3322  		`Atlantic/Canary`,
  3323  		`Atlantic/Cape_Verde`,
  3324  		`Atlantic/Faeroe`,
  3325  		`Atlantic/Faroe`,
  3326  		`Atlantic/Jan_Mayen`,
  3327  		`Atlantic/Madeira`,
  3328  		`Atlantic/Reykjavik`,
  3329  		`Atlantic/South_Georgia`,
  3330  		`Atlantic/St_Helena`,
  3331  		`Atlantic/Stanley`,
  3332  		`Australia/ACT`,
  3333  		`Australia/Adelaide`,
  3334  		`Australia/Brisbane`,
  3335  		`Australia/Broken_Hill`,
  3336  		`Australia/Canberra`,
  3337  		`Australia/Currie`,
  3338  		`Australia/Darwin`,
  3339  		`Australia/Eucla`,
  3340  		`Australia/Hobart`,
  3341  		`Australia/LHI`,
  3342  		`Australia/Lindeman`,
  3343  		`Australia/Lord_Howe`,
  3344  		`Australia/Melbourne`,
  3345  		`Australia/North`,
  3346  		`Australia/NSW`,
  3347  		`Australia/Perth`,
  3348  		`Australia/Queensland`,
  3349  		`Australia/South`,
  3350  		`Australia/Sydney`,
  3351  		`Australia/Tasmania`,
  3352  		`Australia/Victoria`,
  3353  		`Australia/West`,
  3354  		`Australia/Yancowinna`,
  3355  		`Brazil/Acre`,
  3356  		`Brazil/DeNoronha`,
  3357  		`Brazil/East`,
  3358  		`Brazil/West`,
  3359  		`Canada/Atlantic`,
  3360  		`Canada/Central`,
  3361  		`Canada/Eastern`,
  3362  		`Canada/Mountain`,
  3363  		`Canada/Newfoundland`,
  3364  		`Canada/Pacific`,
  3365  		`Canada/Saskatchewan`,
  3366  		`Canada/Yukon`,
  3367  		`CET`,
  3368  		`Chile/Continental`,
  3369  		`Chile/EasterIsland`,
  3370  		`CST6CDT`,
  3371  		`Cuba`,
  3372  		`EET`,
  3373  		`Egypt`,
  3374  		`Eire`,
  3375  		`EST`,
  3376  		`EST5EDT`,
  3377  		`Etc/GMT`,
  3378  		`Etc/GMT+0`,
  3379  		`Etc/GMT+1`,
  3380  		`Etc/GMT+10`,
  3381  		`Etc/GMT+11`,
  3382  		`Etc/GMT+12`,
  3383  		`Etc/GMT+2`,
  3384  		`Etc/GMT+3`,
  3385  		`Etc/GMT+4`,
  3386  		`Etc/GMT+5`,
  3387  		`Etc/GMT+6`,
  3388  		`Etc/GMT+7`,
  3389  		`Etc/GMT+8`,
  3390  		`Etc/GMT+9`,
  3391  		`Etc/GMT-0`,
  3392  		`Etc/GMT-1`,
  3393  		`Etc/GMT-10`,
  3394  		`Etc/GMT-11`,
  3395  		`Etc/GMT-12`,
  3396  		`Etc/GMT-13`,
  3397  		`Etc/GMT-14`,
  3398  		`Etc/GMT-2`,
  3399  		`Etc/GMT-3`,
  3400  		`Etc/GMT-4`,
  3401  		`Etc/GMT-5`,
  3402  		`Etc/GMT-6`,
  3403  		`Etc/GMT-7`,
  3404  		`Etc/GMT-8`,
  3405  		`Etc/GMT-9`,
  3406  		`Etc/GMT0`,
  3407  		`Etc/Greenwich`,
  3408  		`Etc/UCT`,
  3409  		`Etc/Universal`,
  3410  		`Etc/UTC`,
  3411  		`Etc/Zulu`,
  3412  		`Europe/Amsterdam`,
  3413  		`Europe/Andorra`,
  3414  		`Europe/Astrakhan`,
  3415  		`Europe/Athens`,
  3416  		`Europe/Belfast`,
  3417  		`Europe/Belgrade`,
  3418  		`Europe/Berlin`,
  3419  		`Europe/Bratislava`,
  3420  		`Europe/Brussels`,
  3421  		`Europe/Bucharest`,
  3422  		`Europe/Budapest`,
  3423  		`Europe/Busingen`,
  3424  		`Europe/Chisinau`,
  3425  		`Europe/Copenhagen`,
  3426  		`Europe/Dublin`,
  3427  		`Europe/Gibraltar`,
  3428  		`Europe/Guernsey`,
  3429  		`Europe/Helsinki`,
  3430  		`Europe/Isle_of_Man`,
  3431  		`Europe/Istanbul`,
  3432  		`Europe/Jersey`,
  3433  		`Europe/Kaliningrad`,
  3434  		`Europe/Kiev`,
  3435  		`Europe/Kirov`,
  3436  		`Europe/Lisbon`,
  3437  		`Europe/Ljubljana`,
  3438  		`Europe/London`,
  3439  		`Europe/Luxembourg`,
  3440  		`Europe/Madrid`,
  3441  		`Europe/Malta`,
  3442  		`Europe/Mariehamn`,
  3443  		`Europe/Minsk`,
  3444  		`Europe/Monaco`,
  3445  		`Europe/Moscow`,
  3446  		`Europe/Nicosia`,
  3447  		`Europe/Oslo`,
  3448  		`Europe/Paris`,
  3449  		`Europe/Podgorica`,
  3450  		`Europe/Prague`,
  3451  		`Europe/Riga`,
  3452  		`Europe/Rome`,
  3453  		`Europe/Samara`,
  3454  		`Europe/San_Marino`,
  3455  		`Europe/Sarajevo`,
  3456  		`Europe/Saratov`,
  3457  		`Europe/Simferopol`,
  3458  		`Europe/Skopje`,
  3459  		`Europe/Sofia`,
  3460  		`Europe/Stockholm`,
  3461  		`Europe/Tallinn`,
  3462  		`Europe/Tirane`,
  3463  		`Europe/Tiraspol`,
  3464  		`Europe/Ulyanovsk`,
  3465  		`Europe/Uzhgorod`,
  3466  		`Europe/Vaduz`,
  3467  		`Europe/Vatican`,
  3468  		`Europe/Vienna`,
  3469  		`Europe/Vilnius`,
  3470  		`Europe/Volgograd`,
  3471  		`Europe/Warsaw`,
  3472  		`Europe/Zagreb`,
  3473  		`Europe/Zaporozhye`,
  3474  		`Europe/Zurich`,
  3475  		`Factory`,
  3476  		`GB`,
  3477  		`GB-Eire`,
  3478  		`GMT`,
  3479  		`GMT+0`,
  3480  		`GMT-0`,
  3481  		`GMT0`,
  3482  		`Greenwich`,
  3483  		`Hongkong`,
  3484  		`HST`,
  3485  		`Iceland`,
  3486  		`Indian/Antananarivo`,
  3487  		`Indian/Chagos`,
  3488  		`Indian/Christmas`,
  3489  		`Indian/Cocos`,
  3490  		`Indian/Comoro`,
  3491  		`Indian/Kerguelen`,
  3492  		`Indian/Mahe`,
  3493  		`Indian/Maldives`,
  3494  		`Indian/Mauritius`,
  3495  		`Indian/Mayotte`,
  3496  		`Indian/Reunion`,
  3497  		`Iran`,
  3498  		`Israel`,
  3499  		`Jamaica`,
  3500  		`Japan`,
  3501  		`Kwajalein`,
  3502  		`Libya`,
  3503  		`MET`,
  3504  		`Mexico/BajaNorte`,
  3505  		`Mexico/BajaSur`,
  3506  		`Mexico/General`,
  3507  		`MST`,
  3508  		`MST7MDT`,
  3509  		`Navajo`,
  3510  		`NZ`,
  3511  		`NZ-CHAT`,
  3512  		`Pacific/Apia`,
  3513  		`Pacific/Auckland`,
  3514  		`Pacific/Bougainville`,
  3515  		`Pacific/Chatham`,
  3516  		`Pacific/Chuuk`,
  3517  		`Pacific/Easter`,
  3518  		`Pacific/Efate`,
  3519  		`Pacific/Enderbury`,
  3520  		`Pacific/Fakaofo`,
  3521  		`Pacific/Fiji`,
  3522  		`Pacific/Funafuti`,
  3523  		`Pacific/Galapagos`,
  3524  		`Pacific/Gambier`,
  3525  		`Pacific/Guadalcanal`,
  3526  		`Pacific/Guam`,
  3527  		`Pacific/Honolulu`,
  3528  		`Pacific/Johnston`,
  3529  		`Pacific/Kanton`,
  3530  		`Pacific/Kiritimati`,
  3531  		`Pacific/Kosrae`,
  3532  		`Pacific/Kwajalein`,
  3533  		`Pacific/Majuro`,
  3534  		`Pacific/Marquesas`,
  3535  		`Pacific/Midway`,
  3536  		`Pacific/Nauru`,
  3537  		`Pacific/Niue`,
  3538  		`Pacific/Norfolk`,
  3539  		`Pacific/Noumea`,
  3540  		`Pacific/Pago_Pago`,
  3541  		`Pacific/Palau`,
  3542  		`Pacific/Pitcairn`,
  3543  		`Pacific/Pohnpei`,
  3544  		`Pacific/Ponape`,
  3545  		`Pacific/Port_Moresby`,
  3546  		`Pacific/Rarotonga`,
  3547  		`Pacific/Saipan`,
  3548  		`Pacific/Samoa`,
  3549  		`Pacific/Tahiti`,
  3550  		`Pacific/Tarawa`,
  3551  		`Pacific/Tongatapu`,
  3552  		`Pacific/Truk`,
  3553  		`Pacific/Wake`,
  3554  		`Pacific/Wallis`,
  3555  		`Pacific/Yap`,
  3556  		`Poland`,
  3557  		`Portugal`,
  3558  		`PRC`,
  3559  		`PST8PDT`,
  3560  		`ROC`,
  3561  		`ROK`,
  3562  		`Singapore`,
  3563  		`Turkey`,
  3564  		`UCT`,
  3565  		`Universal`,
  3566  		`US/Alaska`,
  3567  		`US/Aleutian`,
  3568  		`US/Arizona`,
  3569  		`US/Central`,
  3570  		`US/East-Indiana`,
  3571  		`US/Eastern`,
  3572  		`US/Hawaii`,
  3573  		`US/Indiana-Starke`,
  3574  		`US/Michigan`,
  3575  		`US/Mountain`,
  3576  		`US/Pacific`,
  3577  		`US/Samoa`,
  3578  		`UTC`,
  3579  		`W-SU`,
  3580  		`WET`,
  3581  		`Zulu`,
  3582  	}
  3583  	for _, tz := range data {
  3584  		errs := validateTimeZone(&tz, nil)
  3585  		if len(errs) > 0 {
  3586  			t.Errorf("%s failed: %v", tz, errs)
  3587  		}
  3588  	}
  3589  }