mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2948 lines
		
	
	
		
			84 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			2948 lines
		
	
	
		
			84 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2016 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package validation
 | 
						|
 | 
						|
import (
 | 
						|
	_ "time/tzdata"
 | 
						|
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/google/go-cmp/cmp"
 | 
						|
	"github.com/google/go-cmp/cmp/cmpopts"
 | 
						|
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						|
	"k8s.io/kubernetes/pkg/apis/batch"
 | 
						|
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
						|
	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
 | 
						|
	"k8s.io/utils/pointer"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	timeZoneEmpty      = ""
 | 
						|
	timeZoneLocal      = "LOCAL"
 | 
						|
	timeZoneUTC        = "UTC"
 | 
						|
	timeZoneCorrect    = "Europe/Rome"
 | 
						|
	timeZoneBadPrefix  = " Europe/Rome"
 | 
						|
	timeZoneBadSuffix  = "Europe/Rome "
 | 
						|
	timeZoneBadName    = "Europe/InvalidRome"
 | 
						|
	timeZoneEmptySpace = " "
 | 
						|
)
 | 
						|
 | 
						|
var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
 | 
						|
 | 
						|
func getValidManualSelector() *metav1.LabelSelector {
 | 
						|
	return &metav1.LabelSelector{
 | 
						|
		MatchLabels: map[string]string{"a": "b"},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getValidPodTemplateSpecForManual(selector *metav1.LabelSelector) api.PodTemplateSpec {
 | 
						|
	return api.PodTemplateSpec{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Labels: selector.MatchLabels,
 | 
						|
		},
 | 
						|
		Spec: api.PodSpec{
 | 
						|
			RestartPolicy: api.RestartPolicyOnFailure,
 | 
						|
			DNSPolicy:     api.DNSClusterFirst,
 | 
						|
			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getValidGeneratedSelector() *metav1.LabelSelector {
 | 
						|
	return &metav1.LabelSelector{
 | 
						|
		MatchLabels: map[string]string{"controller-uid": "1a2b3c", "job-name": "myjob"},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.PodTemplateSpec {
 | 
						|
	return api.PodTemplateSpec{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Labels: selector.MatchLabels,
 | 
						|
		},
 | 
						|
		Spec: api.PodSpec{
 | 
						|
			RestartPolicy:  api.RestartPolicyOnFailure,
 | 
						|
			DNSPolicy:      api.DNSClusterFirst,
 | 
						|
			Containers:     []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
			InitContainers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestValidateJob(t *testing.T) {
 | 
						|
	validJobObjectMeta := metav1.ObjectMeta{
 | 
						|
		Name:      "myjob",
 | 
						|
		Namespace: metav1.NamespaceDefault,
 | 
						|
		UID:       types.UID("1a2b3c"),
 | 
						|
	}
 | 
						|
	validManualSelector := getValidManualSelector()
 | 
						|
	validPodTemplateSpecForManual := getValidPodTemplateSpecForManual(validManualSelector)
 | 
						|
	validGeneratedSelector := getValidGeneratedSelector()
 | 
						|
	validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
 | 
						|
	validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
 | 
						|
	validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever
 | 
						|
 | 
						|
	successCases := map[string]struct {
 | 
						|
		opts JobValidationOptions
 | 
						|
		job  batch.Job
 | 
						|
	}{
 | 
						|
		"valid pod failure policy": {
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: validJobObjectMeta,
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
					PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
						Rules: []batch.PodFailurePolicyRule{
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
								OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
									{
 | 
						|
										Type:   api.DisruptionTarget,
 | 
						|
										Status: api.ConditionTrue,
 | 
						|
									},
 | 
						|
								},
 | 
						|
							},
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
								OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
									{
 | 
						|
										Type:   api.PodConditionType("CustomConditionType"),
 | 
						|
										Status: api.ConditionFalse,
 | 
						|
									},
 | 
						|
								},
 | 
						|
							},
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionCount,
 | 
						|
								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
									ContainerName: pointer.String("abc"),
 | 
						|
									Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
									Values:        []int32{1, 2, 3},
 | 
						|
								},
 | 
						|
							},
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
									ContainerName: pointer.String("def"),
 | 
						|
									Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
									Values:        []int32{4},
 | 
						|
								},
 | 
						|
							},
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
									Operator: batch.PodFailurePolicyOnExitCodesOpNotIn,
 | 
						|
									Values:   []int32{5, 6, 7},
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"valid manual selector": {
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:        "myjob",
 | 
						|
					Namespace:   metav1.NamespaceDefault,
 | 
						|
					UID:         types.UID("1a2b3c"),
 | 
						|
					Annotations: map[string]string{"foo": "bar"},
 | 
						|
				},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validManualSelector,
 | 
						|
					ManualSelector: pointer.Bool(true),
 | 
						|
					Template:       validPodTemplateSpecForManual,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"valid generated selector": {
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:      "myjob",
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					UID:       types.UID("1a2b3c"),
 | 
						|
				},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"valid NonIndexed completion mode": {
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:      "myjob",
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					UID:       types.UID("1a2b3c"),
 | 
						|
				},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					CompletionMode: completionModePtr(batch.NonIndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"valid Indexed completion mode": {
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:      "myjob",
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					UID:       types.UID("1a2b3c"),
 | 
						|
				},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
					Completions:    pointer.Int32(2),
 | 
						|
					Parallelism:    pointer.Int32(100000),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"valid job tracking annotation": {
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowTrackingAnnotation: true,
 | 
						|
			},
 | 
						|
			job: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:      "myjob",
 | 
						|
					Namespace: metav1.NamespaceDefault,
 | 
						|
					UID:       types.UID("1a2b3c"),
 | 
						|
					Annotations: map[string]string{
 | 
						|
						batch.JobTrackingFinalizer: "",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for k, v := range successCases {
 | 
						|
		t.Run(k, func(t *testing.T) {
 | 
						|
			if errs := ValidateJob(&v.job, v.opts); len(errs) != 0 {
 | 
						|
				t.Errorf("Got unexpected validation errors: %v", errs)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
	negative := int32(-1)
 | 
						|
	negative64 := int64(-1)
 | 
						|
	errorCases := map[string]batch.Job{
 | 
						|
		`spec.podFailurePolicy.rules[0]: Invalid value: specifying one of OnExitCodes and OnPodConditions is required`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values[1]: Duplicate value: 11`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{11, 11},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values: Too many: 256: must have at most 255 items`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values: func() (values []int32) {
 | 
						|
									tooManyValues := make([]int32, maxPodFailurePolicyOnExitCodesValues+1)
 | 
						|
									for i := range tooManyValues {
 | 
						|
										tooManyValues[i] = int32(i)
 | 
						|
									}
 | 
						|
									return tooManyValues
 | 
						|
								}(),
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules: Too many: 21: must have at most 20 items`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: func() []batch.PodFailurePolicyRule {
 | 
						|
						tooManyRules := make([]batch.PodFailurePolicyRule, maxPodFailurePolicyRules+1)
 | 
						|
						for i := range tooManyRules {
 | 
						|
							tooManyRules[i] = batch.PodFailurePolicyRule{
 | 
						|
								Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
								OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
									Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
									Values:   []int32{int32(i + 1)},
 | 
						|
								},
 | 
						|
							}
 | 
						|
						}
 | 
						|
						return tooManyRules
 | 
						|
					}(),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onPodConditions: Too many: 21: must have at most 20 items`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnPodConditions: func() []batch.PodFailurePolicyOnPodConditionsPattern {
 | 
						|
								tooManyPatterns := make([]batch.PodFailurePolicyOnPodConditionsPattern, maxPodFailurePolicyOnPodConditionsPatterns+1)
 | 
						|
								for i := range tooManyPatterns {
 | 
						|
									tooManyPatterns[i] = batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
										Type:   api.PodConditionType(fmt.Sprintf("CustomType_%d", i)),
 | 
						|
										Status: api.ConditionTrue,
 | 
						|
									}
 | 
						|
								}
 | 
						|
								return tooManyPatterns
 | 
						|
							}(),
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values[2]: Duplicate value: 13`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{12, 13, 13, 13},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{19, 11}: must be ordered`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{19, 11},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{}: at least one value is required`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].action: Required value: valid values: ["Count" "FailJob" "Ignore"]`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: "",
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.operator: Required value: valid values: ["In" "NotIn"]`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: "",
 | 
						|
								Values:   []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0]: Invalid value: specifying both OnExitCodes and OnPodConditions is not supported`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								ContainerName: pointer.String("abc"),
 | 
						|
								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:        []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Type:   api.DisruptionTarget,
 | 
						|
									Status: api.ConditionTrue,
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.values[1]: Invalid value: 0: must not be 0 for the In operator`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:   []int32{1, 0, 2},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[1].onExitCodes.containerName: Invalid value: "xyz": must be one of the container or initContainer names in the pod template`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								ContainerName: pointer.String("abc"),
 | 
						|
								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:        []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
						},
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionFailJob,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								ContainerName: pointer.String("xyz"),
 | 
						|
								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:        []int32{5, 6, 7},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].action: Unsupported value: "UnknownAction": supported values: "Count", "FailJob", "Ignore"`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: "UnknownAction",
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								ContainerName: pointer.String("abc"),
 | 
						|
								Operator:      batch.PodFailurePolicyOnExitCodesOpIn,
 | 
						|
								Values:        []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onExitCodes.operator: Unsupported value: "UnknownOperator": supported values: "In", "NotIn"`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnExitCodes: &batch.PodFailurePolicyOnExitCodesRequirement{
 | 
						|
								Operator: "UnknownOperator",
 | 
						|
								Values:   []int32{1, 2, 3},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onPodConditions[0].status: Required value: valid values: ["False" "True" "Unknown"]`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Type: api.DisruptionTarget,
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onPodConditions[0].status: Unsupported value: "UnknownStatus": supported values: "False", "True", "Unknown"`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Type:   api.DisruptionTarget,
 | 
						|
									Status: "UnknownStatus",
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.podFailurePolicy.rules[0].onPodConditions[0].type: Invalid value: "": name part must be non-empty`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Status: api.ConditionTrue,
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`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]')`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Type:   api.PodConditionType("Invalid Condition Type"),
 | 
						|
									Status: api.ConditionTrue,
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		`spec.template.spec.restartPolicy: Invalid value: "OnFailure": only "Never" is supported when podFailurePolicy is specified`: {
 | 
						|
			ObjectMeta: validJobObjectMeta,
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: api.PodTemplateSpec{
 | 
						|
					ObjectMeta: metav1.ObjectMeta{
 | 
						|
						Labels: validGeneratedSelector.MatchLabels,
 | 
						|
					},
 | 
						|
					Spec: api.PodSpec{
 | 
						|
						RestartPolicy: api.RestartPolicyOnFailure,
 | 
						|
						DNSPolicy:     api.DNSClusterFirst,
 | 
						|
						Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
					},
 | 
						|
				},
 | 
						|
				PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.parallelism:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Parallelism: &negative,
 | 
						|
				Selector:    validGeneratedSelector,
 | 
						|
				Template:    validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.backoffLimit:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				BackoffLimit: pointer.Int32(-1),
 | 
						|
				Selector:     validGeneratedSelector,
 | 
						|
				Template:     validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		"spec.completions:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Completions: &negative,
 | 
						|
				Selector:    validGeneratedSelector,
 | 
						|
				Template:    validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.activeDeadlineSeconds:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				ActiveDeadlineSeconds: &negative64,
 | 
						|
				Selector:              validGeneratedSelector,
 | 
						|
				Template:              validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.selector:Required value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Template: validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.template.metadata.labels: Invalid value: map[string]string{\"y\":\"z\"}: `selector` does not match template `labels`": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validManualSelector,
 | 
						|
				ManualSelector: pointer.Bool(true),
 | 
						|
				Template: api.PodTemplateSpec{
 | 
						|
					ObjectMeta: metav1.ObjectMeta{
 | 
						|
						Labels: map[string]string{"y": "z"},
 | 
						|
					},
 | 
						|
					Spec: api.PodSpec{
 | 
						|
						RestartPolicy: api.RestartPolicyOnFailure,
 | 
						|
						DNSPolicy:     api.DNSClusterFirst,
 | 
						|
						Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.template.metadata.labels: Invalid value: map[string]string{\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validManualSelector,
 | 
						|
				ManualSelector: pointer.Bool(true),
 | 
						|
				Template: api.PodTemplateSpec{
 | 
						|
					ObjectMeta: metav1.ObjectMeta{
 | 
						|
						Labels: map[string]string{"controller-uid": "4d5e6f"},
 | 
						|
					},
 | 
						|
					Spec: api.PodSpec{
 | 
						|
						RestartPolicy: api.RestartPolicyOnFailure,
 | 
						|
						DNSPolicy:     api.DNSClusterFirst,
 | 
						|
						Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.template.spec.restartPolicy: Required value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validManualSelector,
 | 
						|
				ManualSelector: pointer.Bool(true),
 | 
						|
				Template: api.PodTemplateSpec{
 | 
						|
					ObjectMeta: metav1.ObjectMeta{
 | 
						|
						Labels: validManualSelector.MatchLabels,
 | 
						|
					},
 | 
						|
					Spec: api.PodSpec{
 | 
						|
						RestartPolicy: api.RestartPolicyAlways,
 | 
						|
						DNSPolicy:     api.DNSClusterFirst,
 | 
						|
						Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.template.spec.restartPolicy: Unsupported value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validManualSelector,
 | 
						|
				ManualSelector: pointer.Bool(true),
 | 
						|
				Template: api.PodTemplateSpec{
 | 
						|
					ObjectMeta: metav1.ObjectMeta{
 | 
						|
						Labels: validManualSelector.MatchLabels,
 | 
						|
					},
 | 
						|
					Spec: api.PodSpec{
 | 
						|
						RestartPolicy: "Invalid",
 | 
						|
						DNSPolicy:     api.DNSClusterFirst,
 | 
						|
						Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.ttlSecondsAfterFinished: must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				TTLSecondsAfterFinished: &negative,
 | 
						|
				Selector:                validGeneratedSelector,
 | 
						|
				Template:                validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.completions: Required value: when completion mode is Indexed": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validGeneratedSelector,
 | 
						|
				Template:       validPodTemplateSpecForGenerated,
 | 
						|
				CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.parallelism: must be less than or equal to 100000 when completion mode is Indexed": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector:       validGeneratedSelector,
 | 
						|
				Template:       validPodTemplateSpecForGenerated,
 | 
						|
				CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				Completions:    pointer.Int32(2),
 | 
						|
				Parallelism:    pointer.Int32(100001),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"metadata.annotations[batch.kubernetes.io/job-tracking]: cannot add this annotation": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "myjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
				Annotations: map[string]string{
 | 
						|
					batch.JobTrackingFinalizer: "",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			Spec: batch.JobSpec{
 | 
						|
				Selector: validGeneratedSelector,
 | 
						|
				Template: validPodTemplateSpecForGenerated,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range errorCases {
 | 
						|
		t.Run(k, func(t *testing.T) {
 | 
						|
			errs := ValidateJob(&v, JobValidationOptions{})
 | 
						|
			if len(errs) == 0 {
 | 
						|
				t.Errorf("expected failure for %s", k)
 | 
						|
			} else {
 | 
						|
				s := strings.SplitN(k, ":", 2)
 | 
						|
				err := errs[0]
 | 
						|
				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
 | 
						|
					t.Errorf("unexpected error: %v, expected: %s", err, k)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestValidateJobUpdate(t *testing.T) {
 | 
						|
	validGeneratedSelector := getValidGeneratedSelector()
 | 
						|
	validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
 | 
						|
	validPodTemplateSpecForGeneratedRestartPolicyNever := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
 | 
						|
	validPodTemplateSpecForGeneratedRestartPolicyNever.Spec.RestartPolicy = api.RestartPolicyNever
 | 
						|
 | 
						|
	validNodeAffinity := &api.Affinity{
 | 
						|
		NodeAffinity: &api.NodeAffinity{
 | 
						|
			RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
 | 
						|
				NodeSelectorTerms: []api.NodeSelectorTerm{
 | 
						|
					{
 | 
						|
						MatchExpressions: []api.NodeSelectorRequirement{
 | 
						|
							{
 | 
						|
								Key:      "foo",
 | 
						|
								Operator: api.NodeSelectorOpIn,
 | 
						|
								Values:   []string{"bar", "value2"},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	validPodTemplateWithAffinity := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
 | 
						|
	validPodTemplateWithAffinity.Spec.Affinity = &api.Affinity{
 | 
						|
		NodeAffinity: &api.NodeAffinity{
 | 
						|
			RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
 | 
						|
				NodeSelectorTerms: []api.NodeSelectorTerm{
 | 
						|
					{
 | 
						|
						MatchExpressions: []api.NodeSelectorRequirement{
 | 
						|
							{
 | 
						|
								Key:      "foo",
 | 
						|
								Operator: api.NodeSelectorOpIn,
 | 
						|
								Values:   []string{"bar", "value"},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	// This is to test immutability of the selector, both the new and old
 | 
						|
	// selector should match the labels in the template, which is immutable
 | 
						|
	// on its own; therfore, the only way to test selector immutability is
 | 
						|
	// when the new selector is changed but still matches the existing labels.
 | 
						|
	newSelector := getValidGeneratedSelector()
 | 
						|
	newSelector.MatchLabels["foo"] = "bar"
 | 
						|
	validTolerations := []api.Toleration{{
 | 
						|
		Key:      "foo",
 | 
						|
		Operator: api.TolerationOpEqual,
 | 
						|
		Value:    "bar",
 | 
						|
		Effect:   api.TaintEffectPreferNoSchedule,
 | 
						|
	}}
 | 
						|
	cases := map[string]struct {
 | 
						|
		old    batch.Job
 | 
						|
		update func(*batch.Job)
 | 
						|
		opts   JobValidationOptions
 | 
						|
		err    *field.Error
 | 
						|
	}{
 | 
						|
		"mutable fields": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:                validGeneratedSelector,
 | 
						|
					Template:                validPodTemplateSpecForGenerated,
 | 
						|
					Parallelism:             pointer.Int32(5),
 | 
						|
					ActiveDeadlineSeconds:   pointer.Int64(2),
 | 
						|
					TTLSecondsAfterFinished: pointer.Int32(1),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Parallelism = pointer.Int32(2)
 | 
						|
				job.Spec.ActiveDeadlineSeconds = pointer.Int64(3)
 | 
						|
				job.Spec.TTLSecondsAfterFinished = pointer.Int32(2)
 | 
						|
				job.Spec.ManualSelector = pointer.Bool(true)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable completions for non-indexed jobs": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(1)
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.completions",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable completions for indexed job when AllowElasticIndexedJobs is false": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32(1)
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.completions",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable selector": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: getValidPodTemplateSpecForGenerated(newSelector),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Selector = newSelector
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.selector",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"add pod failure policy": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.PodFailurePolicy = &batch.PodFailurePolicy{
 | 
						|
					Rules: []batch.PodFailurePolicyRule{
 | 
						|
						{
 | 
						|
							Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
							OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
								{
 | 
						|
									Type:   api.DisruptionTarget,
 | 
						|
									Status: api.ConditionTrue,
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				}
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.podFailurePolicy",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"remove pod failure policy": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
					PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
						Rules: []batch.PodFailurePolicyRule{
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
								OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
									{
 | 
						|
										Type:   api.DisruptionTarget,
 | 
						|
										Status: api.ConditionTrue,
 | 
						|
									},
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.PodFailurePolicy.Rules = append(job.Spec.PodFailurePolicy.Rules, batch.PodFailurePolicyRule{
 | 
						|
					Action: batch.PodFailurePolicyActionCount,
 | 
						|
					OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
						{
 | 
						|
							Type:   api.DisruptionTarget,
 | 
						|
							Status: api.ConditionTrue,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				})
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.podFailurePolicy",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"update pod failure policy": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGeneratedRestartPolicyNever,
 | 
						|
					PodFailurePolicy: &batch.PodFailurePolicy{
 | 
						|
						Rules: []batch.PodFailurePolicyRule{
 | 
						|
							{
 | 
						|
								Action: batch.PodFailurePolicyActionIgnore,
 | 
						|
								OnPodConditions: []batch.PodFailurePolicyOnPodConditionsPattern{
 | 
						|
									{
 | 
						|
										Type:   api.DisruptionTarget,
 | 
						|
										Status: api.ConditionTrue,
 | 
						|
									},
 | 
						|
								},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.PodFailurePolicy = nil
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.podFailurePolicy",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable pod template": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.DNSPolicy = api.DNSClusterFirstWithHostNet
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable completion mode": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
					Completions:    pointer.Int32(2),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.CompletionMode = completionModePtr(batch.NonIndexedCompletion)
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.completionMode",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable completions for non-indexed job when AllowElasticIndexedJobs is true": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					CompletionMode: completionModePtr(batch.NonIndexedCompletion),
 | 
						|
					Completions:    pointer.Int32Ptr(2),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(4)
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.completions",
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{AllowElasticIndexedJobs: true},
 | 
						|
		},
 | 
						|
 | 
						|
		"immutable node affinity": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Affinity = validNodeAffinity
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"add node affinity": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Affinity = validNodeAffinity
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"update node affinity": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateWithAffinity,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Affinity = validNodeAffinity
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"remove node affinity": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateWithAffinity,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Affinity.NodeAffinity = nil
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"remove affinity": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateWithAffinity,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Affinity = nil
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable tolerations": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Tolerations = validTolerations
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"mutable tolerations": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.Tolerations = validTolerations
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable node selector": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"mutable node selector": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"}
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable annotations": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Annotations = map[string]string{"foo": "baz"}
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"mutable annotations": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Annotations = map[string]string{"foo": "baz"}
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable labels": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				newLabels := getValidGeneratedSelector().MatchLabels
 | 
						|
				newLabels["bar"] = "baz"
 | 
						|
				job.Spec.Template.Labels = newLabels
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"mutable labels": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				newLabels := getValidGeneratedSelector().MatchLabels
 | 
						|
				newLabels["bar"] = "baz"
 | 
						|
				job.Spec.Template.Labels = newLabels
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"immutable schedulingGates": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.SchedulingGates = append(job.Spec.Template.Spec.SchedulingGates, api.PodSchedulingGate{Name: "gate"})
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.template",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"mutable schedulingGates": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector: validGeneratedSelector,
 | 
						|
					Template: validPodTemplateSpecForGenerated,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Template.Spec.SchedulingGates = append(job.Spec.Template.Spec.SchedulingGates, api.PodSchedulingGate{Name: "gate"})
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowMutableSchedulingDirectives: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"update completions and parallelism to same value is valid": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(1),
 | 
						|
					Parallelism:    pointer.Int32Ptr(1),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(2)
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(2)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"previous parallelism != previous completions, new parallelism == new completions": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(1),
 | 
						|
					Parallelism:    pointer.Int32Ptr(2),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(3)
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(3)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"indexed job updating completions and parallelism to different values is invalid": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(1),
 | 
						|
					Parallelism:    pointer.Int32Ptr(1),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(2)
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(3)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeInvalid,
 | 
						|
				Field: "spec.completions",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"indexed job with completions set updated to nil does not panic": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(1),
 | 
						|
					Parallelism:    pointer.Int32Ptr(1),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = nil
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(3)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
			err: &field.Error{
 | 
						|
				Type:  field.ErrorTypeRequired,
 | 
						|
				Field: "spec.completions",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"indexed job with completions unchanged, parallelism reduced to less than completions": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(2),
 | 
						|
					Parallelism:    pointer.Int32Ptr(2),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(2)
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(1)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"indexed job with completions unchanged, parallelism increased higher than completions": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
 | 
						|
				Spec: batch.JobSpec{
 | 
						|
					Selector:       validGeneratedSelector,
 | 
						|
					Template:       validPodTemplateSpecForGenerated,
 | 
						|
					Completions:    pointer.Int32Ptr(2),
 | 
						|
					Parallelism:    pointer.Int32Ptr(2),
 | 
						|
					CompletionMode: completionModePtr(batch.IndexedCompletion),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: func(job *batch.Job) {
 | 
						|
				job.Spec.Completions = pointer.Int32Ptr(2)
 | 
						|
				job.Spec.Parallelism = pointer.Int32Ptr(3)
 | 
						|
			},
 | 
						|
			opts: JobValidationOptions{
 | 
						|
				AllowElasticIndexedJobs: true,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
 | 
						|
	for k, tc := range cases {
 | 
						|
		t.Run(k, func(t *testing.T) {
 | 
						|
			tc.old.ResourceVersion = "1"
 | 
						|
			update := tc.old.DeepCopy()
 | 
						|
			tc.update(update)
 | 
						|
			errs := ValidateJobUpdate(update, &tc.old, tc.opts)
 | 
						|
			var wantErrs field.ErrorList
 | 
						|
			if tc.err != nil {
 | 
						|
				wantErrs = append(wantErrs, tc.err)
 | 
						|
			}
 | 
						|
			if diff := cmp.Diff(wantErrs, errs, ignoreValueAndDetail); diff != "" {
 | 
						|
				t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestValidateJobUpdateStatus(t *testing.T) {
 | 
						|
	cases := map[string]struct {
 | 
						|
		old      batch.Job
 | 
						|
		update   batch.Job
 | 
						|
		wantErrs field.ErrorList
 | 
						|
	}{
 | 
						|
		"valid": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "1",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    1,
 | 
						|
					Succeeded: 2,
 | 
						|
					Failed:    3,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "1",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    2,
 | 
						|
					Succeeded: 3,
 | 
						|
					Failed:    4,
 | 
						|
					Ready:     pointer.Int32(1),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"nil ready": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "1",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    1,
 | 
						|
					Succeeded: 2,
 | 
						|
					Failed:    3,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "1",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    2,
 | 
						|
					Succeeded: 3,
 | 
						|
					Failed:    4,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"negative counts": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "10",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    1,
 | 
						|
					Succeeded: 2,
 | 
						|
					Failed:    3,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "10",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					Active:    -1,
 | 
						|
					Succeeded: -2,
 | 
						|
					Failed:    -3,
 | 
						|
					Ready:     pointer.Int32(-1),
 | 
						|
				},
 | 
						|
			},
 | 
						|
			wantErrs: field.ErrorList{
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.active"},
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.succeeded"},
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.failed"},
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.ready"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"empty and duplicated uncounted pods": {
 | 
						|
			old: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "5",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			update: batch.Job{
 | 
						|
				ObjectMeta: metav1.ObjectMeta{
 | 
						|
					Name:            "abc",
 | 
						|
					Namespace:       metav1.NamespaceDefault,
 | 
						|
					ResourceVersion: "5",
 | 
						|
				},
 | 
						|
				Status: batch.JobStatus{
 | 
						|
					UncountedTerminatedPods: &batch.UncountedTerminatedPods{
 | 
						|
						Succeeded: []types.UID{"a", "b", "c", "a", ""},
 | 
						|
						Failed:    []types.UID{"c", "d", "e", "d", ""},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			wantErrs: field.ErrorList{
 | 
						|
				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.succeeded[3]"},
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods.succeeded[4]"},
 | 
						|
				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.failed[0]"},
 | 
						|
				{Type: field.ErrorTypeDuplicate, Field: "status.uncountedTerminatedPods.failed[3]"},
 | 
						|
				{Type: field.ErrorTypeInvalid, Field: "status.uncountedTerminatedPods.failed[4]"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for name, tc := range cases {
 | 
						|
		t.Run(name, func(t *testing.T) {
 | 
						|
			errs := ValidateJobUpdateStatus(&tc.update, &tc.old)
 | 
						|
			if diff := cmp.Diff(tc.wantErrs, errs, ignoreErrValueDetail); diff != "" {
 | 
						|
				t.Errorf("Unexpected errors (-want,+got):\n%s", diff)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestValidateCronJob(t *testing.T) {
 | 
						|
	validManualSelector := getValidManualSelector()
 | 
						|
	validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
 | 
						|
	validPodTemplateSpec.Labels = map[string]string{}
 | 
						|
 | 
						|
	successCases := map[string]batch.CronJob{
 | 
						|
		"basic scheduled job": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"non-standard scheduled": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "@hourly",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"correct timeZone value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneCorrect,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for k, v := range successCases {
 | 
						|
		t.Run(k, func(t *testing.T) {
 | 
						|
			if errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
 | 
						|
				t.Errorf("expected success for %s: %v", k, errs)
 | 
						|
			}
 | 
						|
 | 
						|
			// Update validation should pass same success cases
 | 
						|
			// copy to avoid polluting the testcase object, set a resourceVersion to allow validating update, and test a no-op update
 | 
						|
			v = *v.DeepCopy()
 | 
						|
			v.ResourceVersion = "1"
 | 
						|
			if errs := ValidateCronJobUpdate(&v, &v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
 | 
						|
				t.Errorf("expected success for %s: %v", k, errs)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	negative := int32(-1)
 | 
						|
	negative64 := int64(-1)
 | 
						|
 | 
						|
	errorCases := map[string]batch.CronJob{
 | 
						|
		"spec.schedule: Invalid value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "error",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.schedule: Required value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.schedule: cannot use both timeZone field and TZ or CRON_TZ in schedule": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "TZ=UTC 0 * * * *",
 | 
						|
				TimeZone:          &timeZoneUTC,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: timeZone must be nil or non-empty string": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneEmpty,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: timeZone must be an explicit time zone as defined in https://www.iana.org/time-zones": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneLocal,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: Invalid value: \" Continent/Zone\": unknown time zone  Continent/Zone": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneBadPrefix,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: Invalid value: \"Continent/InvalidZone\": unknown time zone  Continent/InvalidZone": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneBadName,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: Invalid value: \" \": unknown time zone  ": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneEmptySpace,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.timeZone: Invalid value: \"Continent/Zone \": unknown time zone Continent/Zone ": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          &timeZoneBadSuffix,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.startingDeadlineSeconds:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:                "* * * * ?",
 | 
						|
				ConcurrencyPolicy:       batch.AllowConcurrent,
 | 
						|
				StartingDeadlineSeconds: &negative64,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.successfulJobsHistoryLimit: must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:                   "* * * * ?",
 | 
						|
				ConcurrencyPolicy:          batch.AllowConcurrent,
 | 
						|
				SuccessfulJobsHistoryLimit: &negative,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.failedJobsHistoryLimit: must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:               "* * * * ?",
 | 
						|
				ConcurrencyPolicy:      batch.AllowConcurrent,
 | 
						|
				FailedJobsHistoryLimit: &negative,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.concurrencyPolicy: Required value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule: "* * * * ?",
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.parallelism:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Parallelism: &negative,
 | 
						|
						Template:    validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.completions:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Completions: &negative,
 | 
						|
						Template:    validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.activeDeadlineSeconds:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						ActiveDeadlineSeconds: &negative64,
 | 
						|
						Template:              validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.selector: Invalid value: {\"matchLabels\":{\"a\":\"b\"}}: `selector` will be auto-generated": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Selector: validManualSelector,
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"metadata.name: must be no more than 52 characters": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "10000000002000000000300000000040000000005000000000123",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.manualSelector: Unsupported value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						ManualSelector: pointer.Bool(true),
 | 
						|
						Template:       validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.template.spec.restartPolicy: Required value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: api.PodTemplateSpec{
 | 
						|
							Spec: api.PodSpec{
 | 
						|
								RestartPolicy: api.RestartPolicyAlways,
 | 
						|
								DNSPolicy:     api.DNSClusterFirst,
 | 
						|
								Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.template.spec.restartPolicy: Unsupported value": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: api.PodTemplateSpec{
 | 
						|
							Spec: api.PodSpec{
 | 
						|
								RestartPolicy: "Invalid",
 | 
						|
								DNSPolicy:     api.DNSClusterFirst,
 | 
						|
								Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
 | 
						|
							},
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"spec.jobTemplate.spec.ttlSecondsAfterFinished:must be greater than or equal to 0": {
 | 
						|
			ObjectMeta: metav1.ObjectMeta{
 | 
						|
				Name:      "mycronjob",
 | 
						|
				Namespace: metav1.NamespaceDefault,
 | 
						|
				UID:       types.UID("1a2b3c"),
 | 
						|
			},
 | 
						|
			Spec: batch.CronJobSpec{
 | 
						|
				Schedule:          "* * * * ?",
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						TTLSecondsAfterFinished: &negative,
 | 
						|
						Template:                validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range errorCases {
 | 
						|
		t.Run(k, func(t *testing.T) {
 | 
						|
			errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{})
 | 
						|
			if len(errs) == 0 {
 | 
						|
				t.Errorf("expected failure for %s", k)
 | 
						|
			} else {
 | 
						|
				s := strings.Split(k, ":")
 | 
						|
				err := errs[0]
 | 
						|
				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
 | 
						|
					t.Errorf("unexpected error: %v, expected: %s", err, k)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Update validation should fail all failure cases other than the 52 character name limit
 | 
						|
			// copy to avoid polluting the testcase object, set a resourceVersion to allow validating update, and test a no-op update
 | 
						|
			oldSpec := *v.DeepCopy()
 | 
						|
			oldSpec.ResourceVersion = "1"
 | 
						|
			oldSpec.Spec.TimeZone = nil
 | 
						|
 | 
						|
			newSpec := *v.DeepCopy()
 | 
						|
			newSpec.ResourceVersion = "2"
 | 
						|
 | 
						|
			errs = ValidateCronJobUpdate(&newSpec, &oldSpec, corevalidation.PodValidationOptions{})
 | 
						|
			if len(errs) == 0 {
 | 
						|
				if k == "metadata.name: must be no more than 52 characters" {
 | 
						|
					return
 | 
						|
				}
 | 
						|
				t.Errorf("expected failure for %s", k)
 | 
						|
			} else {
 | 
						|
				s := strings.Split(k, ":")
 | 
						|
				err := errs[0]
 | 
						|
				if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
 | 
						|
					t.Errorf("unexpected error: %v, expected: %s", err, k)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestValidateCronJobSpec(t *testing.T) {
 | 
						|
	validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
 | 
						|
	validPodTemplateSpec.Labels = map[string]string{}
 | 
						|
 | 
						|
	type testCase struct {
 | 
						|
		old       *batch.CronJobSpec
 | 
						|
		new       *batch.CronJobSpec
 | 
						|
		expectErr bool
 | 
						|
	}
 | 
						|
 | 
						|
	cases := map[string]testCase{
 | 
						|
		"no validation because timeZone is nil for old and new": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          nil,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          nil,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"check validation because timeZone is different for new": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          nil,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("America/New_York"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"check validation because timeZone is different for new and invalid": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          nil,
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		"old timeZone and new timeZone are valid": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("America/New_York"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("America/Chicago"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"old timeZone is valid, but new timeZone is invalid": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("America/New_York"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		"old timeZone and new timeZone are invalid, but unchanged": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		"old timeZone and new timeZone are invalid, but different": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("still broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		"old timeZone is invalid, but new timeZone is valid": {
 | 
						|
			old: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("broken"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			new: &batch.CronJobSpec{
 | 
						|
				Schedule:          "0 * * * *",
 | 
						|
				TimeZone:          pointer.String("America/New_York"),
 | 
						|
				ConcurrencyPolicy: batch.AllowConcurrent,
 | 
						|
				JobTemplate: batch.JobTemplateSpec{
 | 
						|
					Spec: batch.JobSpec{
 | 
						|
						Template: validPodTemplateSpec,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range cases {
 | 
						|
		errs := validateCronJobSpec(v.new, v.old, field.NewPath("spec"), corevalidation.PodValidationOptions{})
 | 
						|
		if len(errs) > 0 && !v.expectErr {
 | 
						|
			t.Errorf("unexpected error for %s: %v", k, errs)
 | 
						|
		} else if len(errs) == 0 && v.expectErr {
 | 
						|
			t.Errorf("expected error for %s but got nil", k)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
 | 
						|
	return &m
 | 
						|
}
 | 
						|
 | 
						|
func TestTimeZones(t *testing.T) {
 | 
						|
	// all valid time zones as of go1.19 release on 2022-08-02
 | 
						|
	data := []string{
 | 
						|
		`Africa/Abidjan`,
 | 
						|
		`Africa/Accra`,
 | 
						|
		`Africa/Addis_Ababa`,
 | 
						|
		`Africa/Algiers`,
 | 
						|
		`Africa/Asmara`,
 | 
						|
		`Africa/Asmera`,
 | 
						|
		`Africa/Bamako`,
 | 
						|
		`Africa/Bangui`,
 | 
						|
		`Africa/Banjul`,
 | 
						|
		`Africa/Bissau`,
 | 
						|
		`Africa/Blantyre`,
 | 
						|
		`Africa/Brazzaville`,
 | 
						|
		`Africa/Bujumbura`,
 | 
						|
		`Africa/Cairo`,
 | 
						|
		`Africa/Casablanca`,
 | 
						|
		`Africa/Ceuta`,
 | 
						|
		`Africa/Conakry`,
 | 
						|
		`Africa/Dakar`,
 | 
						|
		`Africa/Dar_es_Salaam`,
 | 
						|
		`Africa/Djibouti`,
 | 
						|
		`Africa/Douala`,
 | 
						|
		`Africa/El_Aaiun`,
 | 
						|
		`Africa/Freetown`,
 | 
						|
		`Africa/Gaborone`,
 | 
						|
		`Africa/Harare`,
 | 
						|
		`Africa/Johannesburg`,
 | 
						|
		`Africa/Juba`,
 | 
						|
		`Africa/Kampala`,
 | 
						|
		`Africa/Khartoum`,
 | 
						|
		`Africa/Kigali`,
 | 
						|
		`Africa/Kinshasa`,
 | 
						|
		`Africa/Lagos`,
 | 
						|
		`Africa/Libreville`,
 | 
						|
		`Africa/Lome`,
 | 
						|
		`Africa/Luanda`,
 | 
						|
		`Africa/Lubumbashi`,
 | 
						|
		`Africa/Lusaka`,
 | 
						|
		`Africa/Malabo`,
 | 
						|
		`Africa/Maputo`,
 | 
						|
		`Africa/Maseru`,
 | 
						|
		`Africa/Mbabane`,
 | 
						|
		`Africa/Mogadishu`,
 | 
						|
		`Africa/Monrovia`,
 | 
						|
		`Africa/Nairobi`,
 | 
						|
		`Africa/Ndjamena`,
 | 
						|
		`Africa/Niamey`,
 | 
						|
		`Africa/Nouakchott`,
 | 
						|
		`Africa/Ouagadougou`,
 | 
						|
		`Africa/Porto-Novo`,
 | 
						|
		`Africa/Sao_Tome`,
 | 
						|
		`Africa/Timbuktu`,
 | 
						|
		`Africa/Tripoli`,
 | 
						|
		`Africa/Tunis`,
 | 
						|
		`Africa/Windhoek`,
 | 
						|
		`America/Adak`,
 | 
						|
		`America/Anchorage`,
 | 
						|
		`America/Anguilla`,
 | 
						|
		`America/Antigua`,
 | 
						|
		`America/Araguaina`,
 | 
						|
		`America/Argentina/Buenos_Aires`,
 | 
						|
		`America/Argentina/Catamarca`,
 | 
						|
		`America/Argentina/ComodRivadavia`,
 | 
						|
		`America/Argentina/Cordoba`,
 | 
						|
		`America/Argentina/Jujuy`,
 | 
						|
		`America/Argentina/La_Rioja`,
 | 
						|
		`America/Argentina/Mendoza`,
 | 
						|
		`America/Argentina/Rio_Gallegos`,
 | 
						|
		`America/Argentina/Salta`,
 | 
						|
		`America/Argentina/San_Juan`,
 | 
						|
		`America/Argentina/San_Luis`,
 | 
						|
		`America/Argentina/Tucuman`,
 | 
						|
		`America/Argentina/Ushuaia`,
 | 
						|
		`America/Aruba`,
 | 
						|
		`America/Asuncion`,
 | 
						|
		`America/Atikokan`,
 | 
						|
		`America/Atka`,
 | 
						|
		`America/Bahia`,
 | 
						|
		`America/Bahia_Banderas`,
 | 
						|
		`America/Barbados`,
 | 
						|
		`America/Belem`,
 | 
						|
		`America/Belize`,
 | 
						|
		`America/Blanc-Sablon`,
 | 
						|
		`America/Boa_Vista`,
 | 
						|
		`America/Bogota`,
 | 
						|
		`America/Boise`,
 | 
						|
		`America/Buenos_Aires`,
 | 
						|
		`America/Cambridge_Bay`,
 | 
						|
		`America/Campo_Grande`,
 | 
						|
		`America/Cancun`,
 | 
						|
		`America/Caracas`,
 | 
						|
		`America/Catamarca`,
 | 
						|
		`America/Cayenne`,
 | 
						|
		`America/Cayman`,
 | 
						|
		`America/Chicago`,
 | 
						|
		`America/Chihuahua`,
 | 
						|
		`America/Coral_Harbour`,
 | 
						|
		`America/Cordoba`,
 | 
						|
		`America/Costa_Rica`,
 | 
						|
		`America/Creston`,
 | 
						|
		`America/Cuiaba`,
 | 
						|
		`America/Curacao`,
 | 
						|
		`America/Danmarkshavn`,
 | 
						|
		`America/Dawson`,
 | 
						|
		`America/Dawson_Creek`,
 | 
						|
		`America/Denver`,
 | 
						|
		`America/Detroit`,
 | 
						|
		`America/Dominica`,
 | 
						|
		`America/Edmonton`,
 | 
						|
		`America/Eirunepe`,
 | 
						|
		`America/El_Salvador`,
 | 
						|
		`America/Ensenada`,
 | 
						|
		`America/Fort_Nelson`,
 | 
						|
		`America/Fort_Wayne`,
 | 
						|
		`America/Fortaleza`,
 | 
						|
		`America/Glace_Bay`,
 | 
						|
		`America/Godthab`,
 | 
						|
		`America/Goose_Bay`,
 | 
						|
		`America/Grand_Turk`,
 | 
						|
		`America/Grenada`,
 | 
						|
		`America/Guadeloupe`,
 | 
						|
		`America/Guatemala`,
 | 
						|
		`America/Guayaquil`,
 | 
						|
		`America/Guyana`,
 | 
						|
		`America/Halifax`,
 | 
						|
		`America/Havana`,
 | 
						|
		`America/Hermosillo`,
 | 
						|
		`America/Indiana/Indianapolis`,
 | 
						|
		`America/Indiana/Knox`,
 | 
						|
		`America/Indiana/Marengo`,
 | 
						|
		`America/Indiana/Petersburg`,
 | 
						|
		`America/Indiana/Tell_City`,
 | 
						|
		`America/Indiana/Vevay`,
 | 
						|
		`America/Indiana/Vincennes`,
 | 
						|
		`America/Indiana/Winamac`,
 | 
						|
		`America/Indianapolis`,
 | 
						|
		`America/Inuvik`,
 | 
						|
		`America/Iqaluit`,
 | 
						|
		`America/Jamaica`,
 | 
						|
		`America/Jujuy`,
 | 
						|
		`America/Juneau`,
 | 
						|
		`America/Kentucky/Louisville`,
 | 
						|
		`America/Kentucky/Monticello`,
 | 
						|
		`America/Knox_IN`,
 | 
						|
		`America/Kralendijk`,
 | 
						|
		`America/La_Paz`,
 | 
						|
		`America/Lima`,
 | 
						|
		`America/Los_Angeles`,
 | 
						|
		`America/Louisville`,
 | 
						|
		`America/Lower_Princes`,
 | 
						|
		`America/Maceio`,
 | 
						|
		`America/Managua`,
 | 
						|
		`America/Manaus`,
 | 
						|
		`America/Marigot`,
 | 
						|
		`America/Martinique`,
 | 
						|
		`America/Matamoros`,
 | 
						|
		`America/Mazatlan`,
 | 
						|
		`America/Mendoza`,
 | 
						|
		`America/Menominee`,
 | 
						|
		`America/Merida`,
 | 
						|
		`America/Metlakatla`,
 | 
						|
		`America/Mexico_City`,
 | 
						|
		`America/Miquelon`,
 | 
						|
		`America/Moncton`,
 | 
						|
		`America/Monterrey`,
 | 
						|
		`America/Montevideo`,
 | 
						|
		`America/Montreal`,
 | 
						|
		`America/Montserrat`,
 | 
						|
		`America/Nassau`,
 | 
						|
		`America/New_York`,
 | 
						|
		`America/Nipigon`,
 | 
						|
		`America/Nome`,
 | 
						|
		`America/Noronha`,
 | 
						|
		`America/North_Dakota/Beulah`,
 | 
						|
		`America/North_Dakota/Center`,
 | 
						|
		`America/North_Dakota/New_Salem`,
 | 
						|
		`America/Nuuk`,
 | 
						|
		`America/Ojinaga`,
 | 
						|
		`America/Panama`,
 | 
						|
		`America/Pangnirtung`,
 | 
						|
		`America/Paramaribo`,
 | 
						|
		`America/Phoenix`,
 | 
						|
		`America/Port-au-Prince`,
 | 
						|
		`America/Port_of_Spain`,
 | 
						|
		`America/Porto_Acre`,
 | 
						|
		`America/Porto_Velho`,
 | 
						|
		`America/Puerto_Rico`,
 | 
						|
		`America/Punta_Arenas`,
 | 
						|
		`America/Rainy_River`,
 | 
						|
		`America/Rankin_Inlet`,
 | 
						|
		`America/Recife`,
 | 
						|
		`America/Regina`,
 | 
						|
		`America/Resolute`,
 | 
						|
		`America/Rio_Branco`,
 | 
						|
		`America/Rosario`,
 | 
						|
		`America/Santa_Isabel`,
 | 
						|
		`America/Santarem`,
 | 
						|
		`America/Santiago`,
 | 
						|
		`America/Santo_Domingo`,
 | 
						|
		`America/Sao_Paulo`,
 | 
						|
		`America/Scoresbysund`,
 | 
						|
		`America/Shiprock`,
 | 
						|
		`America/Sitka`,
 | 
						|
		`America/St_Barthelemy`,
 | 
						|
		`America/St_Johns`,
 | 
						|
		`America/St_Kitts`,
 | 
						|
		`America/St_Lucia`,
 | 
						|
		`America/St_Thomas`,
 | 
						|
		`America/St_Vincent`,
 | 
						|
		`America/Swift_Current`,
 | 
						|
		`America/Tegucigalpa`,
 | 
						|
		`America/Thule`,
 | 
						|
		`America/Thunder_Bay`,
 | 
						|
		`America/Tijuana`,
 | 
						|
		`America/Toronto`,
 | 
						|
		`America/Tortola`,
 | 
						|
		`America/Vancouver`,
 | 
						|
		`America/Virgin`,
 | 
						|
		`America/Whitehorse`,
 | 
						|
		`America/Winnipeg`,
 | 
						|
		`America/Yakutat`,
 | 
						|
		`America/Yellowknife`,
 | 
						|
		`Antarctica/Casey`,
 | 
						|
		`Antarctica/Davis`,
 | 
						|
		`Antarctica/DumontDUrville`,
 | 
						|
		`Antarctica/Macquarie`,
 | 
						|
		`Antarctica/Mawson`,
 | 
						|
		`Antarctica/McMurdo`,
 | 
						|
		`Antarctica/Palmer`,
 | 
						|
		`Antarctica/Rothera`,
 | 
						|
		`Antarctica/South_Pole`,
 | 
						|
		`Antarctica/Syowa`,
 | 
						|
		`Antarctica/Troll`,
 | 
						|
		`Antarctica/Vostok`,
 | 
						|
		`Arctic/Longyearbyen`,
 | 
						|
		`Asia/Aden`,
 | 
						|
		`Asia/Almaty`,
 | 
						|
		`Asia/Amman`,
 | 
						|
		`Asia/Anadyr`,
 | 
						|
		`Asia/Aqtau`,
 | 
						|
		`Asia/Aqtobe`,
 | 
						|
		`Asia/Ashgabat`,
 | 
						|
		`Asia/Ashkhabad`,
 | 
						|
		`Asia/Atyrau`,
 | 
						|
		`Asia/Baghdad`,
 | 
						|
		`Asia/Bahrain`,
 | 
						|
		`Asia/Baku`,
 | 
						|
		`Asia/Bangkok`,
 | 
						|
		`Asia/Barnaul`,
 | 
						|
		`Asia/Beirut`,
 | 
						|
		`Asia/Bishkek`,
 | 
						|
		`Asia/Brunei`,
 | 
						|
		`Asia/Calcutta`,
 | 
						|
		`Asia/Chita`,
 | 
						|
		`Asia/Choibalsan`,
 | 
						|
		`Asia/Chongqing`,
 | 
						|
		`Asia/Chungking`,
 | 
						|
		`Asia/Colombo`,
 | 
						|
		`Asia/Dacca`,
 | 
						|
		`Asia/Damascus`,
 | 
						|
		`Asia/Dhaka`,
 | 
						|
		`Asia/Dili`,
 | 
						|
		`Asia/Dubai`,
 | 
						|
		`Asia/Dushanbe`,
 | 
						|
		`Asia/Famagusta`,
 | 
						|
		`Asia/Gaza`,
 | 
						|
		`Asia/Harbin`,
 | 
						|
		`Asia/Hebron`,
 | 
						|
		`Asia/Ho_Chi_Minh`,
 | 
						|
		`Asia/Hong_Kong`,
 | 
						|
		`Asia/Hovd`,
 | 
						|
		`Asia/Irkutsk`,
 | 
						|
		`Asia/Istanbul`,
 | 
						|
		`Asia/Jakarta`,
 | 
						|
		`Asia/Jayapura`,
 | 
						|
		`Asia/Jerusalem`,
 | 
						|
		`Asia/Kabul`,
 | 
						|
		`Asia/Kamchatka`,
 | 
						|
		`Asia/Karachi`,
 | 
						|
		`Asia/Kashgar`,
 | 
						|
		`Asia/Kathmandu`,
 | 
						|
		`Asia/Katmandu`,
 | 
						|
		`Asia/Khandyga`,
 | 
						|
		`Asia/Kolkata`,
 | 
						|
		`Asia/Krasnoyarsk`,
 | 
						|
		`Asia/Kuala_Lumpur`,
 | 
						|
		`Asia/Kuching`,
 | 
						|
		`Asia/Kuwait`,
 | 
						|
		`Asia/Macao`,
 | 
						|
		`Asia/Macau`,
 | 
						|
		`Asia/Magadan`,
 | 
						|
		`Asia/Makassar`,
 | 
						|
		`Asia/Manila`,
 | 
						|
		`Asia/Muscat`,
 | 
						|
		`Asia/Nicosia`,
 | 
						|
		`Asia/Novokuznetsk`,
 | 
						|
		`Asia/Novosibirsk`,
 | 
						|
		`Asia/Omsk`,
 | 
						|
		`Asia/Oral`,
 | 
						|
		`Asia/Phnom_Penh`,
 | 
						|
		`Asia/Pontianak`,
 | 
						|
		`Asia/Pyongyang`,
 | 
						|
		`Asia/Qatar`,
 | 
						|
		`Asia/Qostanay`,
 | 
						|
		`Asia/Qyzylorda`,
 | 
						|
		`Asia/Rangoon`,
 | 
						|
		`Asia/Riyadh`,
 | 
						|
		`Asia/Saigon`,
 | 
						|
		`Asia/Sakhalin`,
 | 
						|
		`Asia/Samarkand`,
 | 
						|
		`Asia/Seoul`,
 | 
						|
		`Asia/Shanghai`,
 | 
						|
		`Asia/Singapore`,
 | 
						|
		`Asia/Srednekolymsk`,
 | 
						|
		`Asia/Taipei`,
 | 
						|
		`Asia/Tashkent`,
 | 
						|
		`Asia/Tbilisi`,
 | 
						|
		`Asia/Tehran`,
 | 
						|
		`Asia/Tel_Aviv`,
 | 
						|
		`Asia/Thimbu`,
 | 
						|
		`Asia/Thimphu`,
 | 
						|
		`Asia/Tokyo`,
 | 
						|
		`Asia/Tomsk`,
 | 
						|
		`Asia/Ujung_Pandang`,
 | 
						|
		`Asia/Ulaanbaatar`,
 | 
						|
		`Asia/Ulan_Bator`,
 | 
						|
		`Asia/Urumqi`,
 | 
						|
		`Asia/Ust-Nera`,
 | 
						|
		`Asia/Vientiane`,
 | 
						|
		`Asia/Vladivostok`,
 | 
						|
		`Asia/Yakutsk`,
 | 
						|
		`Asia/Yangon`,
 | 
						|
		`Asia/Yekaterinburg`,
 | 
						|
		`Asia/Yerevan`,
 | 
						|
		`Atlantic/Azores`,
 | 
						|
		`Atlantic/Bermuda`,
 | 
						|
		`Atlantic/Canary`,
 | 
						|
		`Atlantic/Cape_Verde`,
 | 
						|
		`Atlantic/Faeroe`,
 | 
						|
		`Atlantic/Faroe`,
 | 
						|
		`Atlantic/Jan_Mayen`,
 | 
						|
		`Atlantic/Madeira`,
 | 
						|
		`Atlantic/Reykjavik`,
 | 
						|
		`Atlantic/South_Georgia`,
 | 
						|
		`Atlantic/St_Helena`,
 | 
						|
		`Atlantic/Stanley`,
 | 
						|
		`Australia/ACT`,
 | 
						|
		`Australia/Adelaide`,
 | 
						|
		`Australia/Brisbane`,
 | 
						|
		`Australia/Broken_Hill`,
 | 
						|
		`Australia/Canberra`,
 | 
						|
		`Australia/Currie`,
 | 
						|
		`Australia/Darwin`,
 | 
						|
		`Australia/Eucla`,
 | 
						|
		`Australia/Hobart`,
 | 
						|
		`Australia/LHI`,
 | 
						|
		`Australia/Lindeman`,
 | 
						|
		`Australia/Lord_Howe`,
 | 
						|
		`Australia/Melbourne`,
 | 
						|
		`Australia/North`,
 | 
						|
		`Australia/NSW`,
 | 
						|
		`Australia/Perth`,
 | 
						|
		`Australia/Queensland`,
 | 
						|
		`Australia/South`,
 | 
						|
		`Australia/Sydney`,
 | 
						|
		`Australia/Tasmania`,
 | 
						|
		`Australia/Victoria`,
 | 
						|
		`Australia/West`,
 | 
						|
		`Australia/Yancowinna`,
 | 
						|
		`Brazil/Acre`,
 | 
						|
		`Brazil/DeNoronha`,
 | 
						|
		`Brazil/East`,
 | 
						|
		`Brazil/West`,
 | 
						|
		`Canada/Atlantic`,
 | 
						|
		`Canada/Central`,
 | 
						|
		`Canada/Eastern`,
 | 
						|
		`Canada/Mountain`,
 | 
						|
		`Canada/Newfoundland`,
 | 
						|
		`Canada/Pacific`,
 | 
						|
		`Canada/Saskatchewan`,
 | 
						|
		`Canada/Yukon`,
 | 
						|
		`CET`,
 | 
						|
		`Chile/Continental`,
 | 
						|
		`Chile/EasterIsland`,
 | 
						|
		`CST6CDT`,
 | 
						|
		`Cuba`,
 | 
						|
		`EET`,
 | 
						|
		`Egypt`,
 | 
						|
		`Eire`,
 | 
						|
		`EST`,
 | 
						|
		`EST5EDT`,
 | 
						|
		`Etc/GMT`,
 | 
						|
		`Etc/GMT+0`,
 | 
						|
		`Etc/GMT+1`,
 | 
						|
		`Etc/GMT+10`,
 | 
						|
		`Etc/GMT+11`,
 | 
						|
		`Etc/GMT+12`,
 | 
						|
		`Etc/GMT+2`,
 | 
						|
		`Etc/GMT+3`,
 | 
						|
		`Etc/GMT+4`,
 | 
						|
		`Etc/GMT+5`,
 | 
						|
		`Etc/GMT+6`,
 | 
						|
		`Etc/GMT+7`,
 | 
						|
		`Etc/GMT+8`,
 | 
						|
		`Etc/GMT+9`,
 | 
						|
		`Etc/GMT-0`,
 | 
						|
		`Etc/GMT-1`,
 | 
						|
		`Etc/GMT-10`,
 | 
						|
		`Etc/GMT-11`,
 | 
						|
		`Etc/GMT-12`,
 | 
						|
		`Etc/GMT-13`,
 | 
						|
		`Etc/GMT-14`,
 | 
						|
		`Etc/GMT-2`,
 | 
						|
		`Etc/GMT-3`,
 | 
						|
		`Etc/GMT-4`,
 | 
						|
		`Etc/GMT-5`,
 | 
						|
		`Etc/GMT-6`,
 | 
						|
		`Etc/GMT-7`,
 | 
						|
		`Etc/GMT-8`,
 | 
						|
		`Etc/GMT-9`,
 | 
						|
		`Etc/GMT0`,
 | 
						|
		`Etc/Greenwich`,
 | 
						|
		`Etc/UCT`,
 | 
						|
		`Etc/Universal`,
 | 
						|
		`Etc/UTC`,
 | 
						|
		`Etc/Zulu`,
 | 
						|
		`Europe/Amsterdam`,
 | 
						|
		`Europe/Andorra`,
 | 
						|
		`Europe/Astrakhan`,
 | 
						|
		`Europe/Athens`,
 | 
						|
		`Europe/Belfast`,
 | 
						|
		`Europe/Belgrade`,
 | 
						|
		`Europe/Berlin`,
 | 
						|
		`Europe/Bratislava`,
 | 
						|
		`Europe/Brussels`,
 | 
						|
		`Europe/Bucharest`,
 | 
						|
		`Europe/Budapest`,
 | 
						|
		`Europe/Busingen`,
 | 
						|
		`Europe/Chisinau`,
 | 
						|
		`Europe/Copenhagen`,
 | 
						|
		`Europe/Dublin`,
 | 
						|
		`Europe/Gibraltar`,
 | 
						|
		`Europe/Guernsey`,
 | 
						|
		`Europe/Helsinki`,
 | 
						|
		`Europe/Isle_of_Man`,
 | 
						|
		`Europe/Istanbul`,
 | 
						|
		`Europe/Jersey`,
 | 
						|
		`Europe/Kaliningrad`,
 | 
						|
		`Europe/Kiev`,
 | 
						|
		`Europe/Kirov`,
 | 
						|
		`Europe/Lisbon`,
 | 
						|
		`Europe/Ljubljana`,
 | 
						|
		`Europe/London`,
 | 
						|
		`Europe/Luxembourg`,
 | 
						|
		`Europe/Madrid`,
 | 
						|
		`Europe/Malta`,
 | 
						|
		`Europe/Mariehamn`,
 | 
						|
		`Europe/Minsk`,
 | 
						|
		`Europe/Monaco`,
 | 
						|
		`Europe/Moscow`,
 | 
						|
		`Europe/Nicosia`,
 | 
						|
		`Europe/Oslo`,
 | 
						|
		`Europe/Paris`,
 | 
						|
		`Europe/Podgorica`,
 | 
						|
		`Europe/Prague`,
 | 
						|
		`Europe/Riga`,
 | 
						|
		`Europe/Rome`,
 | 
						|
		`Europe/Samara`,
 | 
						|
		`Europe/San_Marino`,
 | 
						|
		`Europe/Sarajevo`,
 | 
						|
		`Europe/Saratov`,
 | 
						|
		`Europe/Simferopol`,
 | 
						|
		`Europe/Skopje`,
 | 
						|
		`Europe/Sofia`,
 | 
						|
		`Europe/Stockholm`,
 | 
						|
		`Europe/Tallinn`,
 | 
						|
		`Europe/Tirane`,
 | 
						|
		`Europe/Tiraspol`,
 | 
						|
		`Europe/Ulyanovsk`,
 | 
						|
		`Europe/Uzhgorod`,
 | 
						|
		`Europe/Vaduz`,
 | 
						|
		`Europe/Vatican`,
 | 
						|
		`Europe/Vienna`,
 | 
						|
		`Europe/Vilnius`,
 | 
						|
		`Europe/Volgograd`,
 | 
						|
		`Europe/Warsaw`,
 | 
						|
		`Europe/Zagreb`,
 | 
						|
		`Europe/Zaporozhye`,
 | 
						|
		`Europe/Zurich`,
 | 
						|
		`Factory`,
 | 
						|
		`GB`,
 | 
						|
		`GB-Eire`,
 | 
						|
		`GMT`,
 | 
						|
		`GMT+0`,
 | 
						|
		`GMT-0`,
 | 
						|
		`GMT0`,
 | 
						|
		`Greenwich`,
 | 
						|
		`Hongkong`,
 | 
						|
		`HST`,
 | 
						|
		`Iceland`,
 | 
						|
		`Indian/Antananarivo`,
 | 
						|
		`Indian/Chagos`,
 | 
						|
		`Indian/Christmas`,
 | 
						|
		`Indian/Cocos`,
 | 
						|
		`Indian/Comoro`,
 | 
						|
		`Indian/Kerguelen`,
 | 
						|
		`Indian/Mahe`,
 | 
						|
		`Indian/Maldives`,
 | 
						|
		`Indian/Mauritius`,
 | 
						|
		`Indian/Mayotte`,
 | 
						|
		`Indian/Reunion`,
 | 
						|
		`Iran`,
 | 
						|
		`Israel`,
 | 
						|
		`Jamaica`,
 | 
						|
		`Japan`,
 | 
						|
		`Kwajalein`,
 | 
						|
		`Libya`,
 | 
						|
		`MET`,
 | 
						|
		`Mexico/BajaNorte`,
 | 
						|
		`Mexico/BajaSur`,
 | 
						|
		`Mexico/General`,
 | 
						|
		`MST`,
 | 
						|
		`MST7MDT`,
 | 
						|
		`Navajo`,
 | 
						|
		`NZ`,
 | 
						|
		`NZ-CHAT`,
 | 
						|
		`Pacific/Apia`,
 | 
						|
		`Pacific/Auckland`,
 | 
						|
		`Pacific/Bougainville`,
 | 
						|
		`Pacific/Chatham`,
 | 
						|
		`Pacific/Chuuk`,
 | 
						|
		`Pacific/Easter`,
 | 
						|
		`Pacific/Efate`,
 | 
						|
		`Pacific/Enderbury`,
 | 
						|
		`Pacific/Fakaofo`,
 | 
						|
		`Pacific/Fiji`,
 | 
						|
		`Pacific/Funafuti`,
 | 
						|
		`Pacific/Galapagos`,
 | 
						|
		`Pacific/Gambier`,
 | 
						|
		`Pacific/Guadalcanal`,
 | 
						|
		`Pacific/Guam`,
 | 
						|
		`Pacific/Honolulu`,
 | 
						|
		`Pacific/Johnston`,
 | 
						|
		`Pacific/Kanton`,
 | 
						|
		`Pacific/Kiritimati`,
 | 
						|
		`Pacific/Kosrae`,
 | 
						|
		`Pacific/Kwajalein`,
 | 
						|
		`Pacific/Majuro`,
 | 
						|
		`Pacific/Marquesas`,
 | 
						|
		`Pacific/Midway`,
 | 
						|
		`Pacific/Nauru`,
 | 
						|
		`Pacific/Niue`,
 | 
						|
		`Pacific/Norfolk`,
 | 
						|
		`Pacific/Noumea`,
 | 
						|
		`Pacific/Pago_Pago`,
 | 
						|
		`Pacific/Palau`,
 | 
						|
		`Pacific/Pitcairn`,
 | 
						|
		`Pacific/Pohnpei`,
 | 
						|
		`Pacific/Ponape`,
 | 
						|
		`Pacific/Port_Moresby`,
 | 
						|
		`Pacific/Rarotonga`,
 | 
						|
		`Pacific/Saipan`,
 | 
						|
		`Pacific/Samoa`,
 | 
						|
		`Pacific/Tahiti`,
 | 
						|
		`Pacific/Tarawa`,
 | 
						|
		`Pacific/Tongatapu`,
 | 
						|
		`Pacific/Truk`,
 | 
						|
		`Pacific/Wake`,
 | 
						|
		`Pacific/Wallis`,
 | 
						|
		`Pacific/Yap`,
 | 
						|
		`Poland`,
 | 
						|
		`Portugal`,
 | 
						|
		`PRC`,
 | 
						|
		`PST8PDT`,
 | 
						|
		`ROC`,
 | 
						|
		`ROK`,
 | 
						|
		`Singapore`,
 | 
						|
		`Turkey`,
 | 
						|
		`UCT`,
 | 
						|
		`Universal`,
 | 
						|
		`US/Alaska`,
 | 
						|
		`US/Aleutian`,
 | 
						|
		`US/Arizona`,
 | 
						|
		`US/Central`,
 | 
						|
		`US/East-Indiana`,
 | 
						|
		`US/Eastern`,
 | 
						|
		`US/Hawaii`,
 | 
						|
		`US/Indiana-Starke`,
 | 
						|
		`US/Michigan`,
 | 
						|
		`US/Mountain`,
 | 
						|
		`US/Pacific`,
 | 
						|
		`US/Samoa`,
 | 
						|
		`UTC`,
 | 
						|
		`W-SU`,
 | 
						|
		`WET`,
 | 
						|
		`Zulu`,
 | 
						|
	}
 | 
						|
	for _, tz := range data {
 | 
						|
		errs := validateTimeZone(&tz, nil)
 | 
						|
		if len(errs) > 0 {
 | 
						|
			t.Errorf("%s failed: %v", tz, errs)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |