mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	Support handling of pod failures with respect to the specified rules
This commit is contained in:
		| @@ -22,6 +22,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/robfig/cron/v3" | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| @@ -39,6 +40,33 @@ import ( | ||||
| // .status.completedIndexes. | ||||
| const maxParallelismForIndexedJob = 100000 | ||||
|  | ||||
| const ( | ||||
| 	// maximum number of rules in pod failure policy | ||||
| 	maxPodFailurePolicyRules = 20 | ||||
|  | ||||
| 	// maximum number of values for a OnExitCodes requirement in pod failure policy | ||||
| 	maxPodFailurePolicyOnExitCodesValues = 255 | ||||
|  | ||||
| 	// maximum number of patterns for a OnPodConditions requirement in pod failure policy | ||||
| 	maxPodFailurePolicyOnPodConditionsPatterns = 20 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	supportedPodFailurePolicyActions sets.String = sets.NewString( | ||||
| 		string(batch.PodFailurePolicyActionCount), | ||||
| 		string(batch.PodFailurePolicyActionFailJob), | ||||
| 		string(batch.PodFailurePolicyActionIgnore)) | ||||
|  | ||||
| 	supportedPodFailurePolicyOnExitCodesOperator sets.String = sets.NewString( | ||||
| 		string(batch.PodFailurePolicyOnExitCodesOpIn), | ||||
| 		string(batch.PodFailurePolicyOnExitCodesOpNotIn)) | ||||
|  | ||||
| 	supportedPodFailurePolicyOnPodConditionsStatus sets.String = sets.NewString( | ||||
| 		string(v1.ConditionFalse), | ||||
| 		string(v1.ConditionTrue), | ||||
| 		string(v1.ConditionUnknown)) | ||||
| ) | ||||
|  | ||||
| // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object | ||||
| // metadata, and the labels on the pod template are as generated. | ||||
| // | ||||
| @@ -168,6 +196,10 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidatio | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if spec.PodFailurePolicy != nil { | ||||
| 		allErrs = append(allErrs, validatePodFailurePolicy(spec, fldPath.Child("podFailurePolicy"))...) | ||||
| 	} | ||||
|  | ||||
| 	allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"), opts)...) | ||||
|  | ||||
| 	// spec.Template.Spec.RestartPolicy can be defaulted as RestartPolicyAlways | ||||
| @@ -179,10 +211,113 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidatio | ||||
| 	} else if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { | ||||
| 		allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), | ||||
| 			spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) | ||||
| 	} else if spec.PodFailurePolicy != nil && spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { | ||||
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "spec", "restartPolicy"), | ||||
| 			spec.Template.Spec.RestartPolicy, fmt.Sprintf("only %q is supported when podFailurePolicy is specified", api.RestartPolicyNever))) | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func validatePodFailurePolicy(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList { | ||||
| 	var allErrs field.ErrorList | ||||
| 	rulesPath := fldPath.Child("rules") | ||||
| 	if len(spec.PodFailurePolicy.Rules) > maxPodFailurePolicyRules { | ||||
| 		allErrs = append(allErrs, field.TooMany(rulesPath, len(spec.PodFailurePolicy.Rules), maxPodFailurePolicyRules)) | ||||
| 	} | ||||
| 	containerNames := sets.NewString() | ||||
| 	for _, containerSpec := range spec.Template.Spec.Containers { | ||||
| 		containerNames.Insert(containerSpec.Name) | ||||
| 	} | ||||
| 	for _, containerSpec := range spec.Template.Spec.InitContainers { | ||||
| 		containerNames.Insert(containerSpec.Name) | ||||
| 	} | ||||
| 	for i, rule := range spec.PodFailurePolicy.Rules { | ||||
| 		allErrs = append(allErrs, validatePodFailurePolicyRule(&rule, rulesPath.Index(i), containerNames)...) | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func validatePodFailurePolicyRule(rule *batch.PodFailurePolicyRule, rulePath *field.Path, containerNames sets.String) field.ErrorList { | ||||
| 	var allErrs field.ErrorList | ||||
| 	actionPath := rulePath.Child("action") | ||||
| 	if rule.Action == "" { | ||||
| 		allErrs = append(allErrs, field.Required(actionPath, fmt.Sprintf("valid values: %q", supportedPodFailurePolicyActions.List()))) | ||||
| 	} else if !supportedPodFailurePolicyActions.Has(string(rule.Action)) { | ||||
| 		allErrs = append(allErrs, field.NotSupported(actionPath, rule.Action, supportedPodFailurePolicyActions.List())) | ||||
| 	} | ||||
| 	if rule.OnExitCodes != nil { | ||||
| 		allErrs = append(allErrs, validatePodFailurePolicyRuleOnExitCodes(rule.OnExitCodes, rulePath.Child("onExitCodes"), containerNames)...) | ||||
| 	} | ||||
| 	if len(rule.OnPodConditions) > 0 { | ||||
| 		allErrs = append(allErrs, validatePodFailurePolicyRuleOnPodConditions(rule.OnPodConditions, rulePath.Child("onPodConditions"))...) | ||||
| 	} | ||||
| 	if rule.OnExitCodes != nil && len(rule.OnPodConditions) > 0 { | ||||
| 		allErrs = append(allErrs, field.Invalid(rulePath, field.OmitValueType{}, "specifying both OnExitCodes and OnPodConditions is not supported")) | ||||
| 	} | ||||
| 	if rule.OnExitCodes == nil && len(rule.OnPodConditions) == 0 { | ||||
| 		allErrs = append(allErrs, field.Invalid(rulePath, field.OmitValueType{}, "specifying one of OnExitCodes and OnPodConditions is required")) | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func validatePodFailurePolicyRuleOnPodConditions(onPodConditions []batch.PodFailurePolicyOnPodConditionsPattern, onPodConditionsPath *field.Path) field.ErrorList { | ||||
| 	var allErrs field.ErrorList | ||||
| 	if len(onPodConditions) > maxPodFailurePolicyOnPodConditionsPatterns { | ||||
| 		allErrs = append(allErrs, field.TooMany(onPodConditionsPath, len(onPodConditions), maxPodFailurePolicyOnPodConditionsPatterns)) | ||||
| 	} | ||||
| 	for j, pattern := range onPodConditions { | ||||
| 		patternPath := onPodConditionsPath.Index(j) | ||||
| 		statusPath := patternPath.Child("status") | ||||
| 		allErrs = append(allErrs, apivalidation.ValidateQualifiedName(string(pattern.Type), patternPath.Child("type"))...) | ||||
| 		if pattern.Status == "" { | ||||
| 			allErrs = append(allErrs, field.Required(statusPath, fmt.Sprintf("valid values: %q", supportedPodFailurePolicyOnPodConditionsStatus.List()))) | ||||
| 		} else if !supportedPodFailurePolicyOnPodConditionsStatus.Has(string(pattern.Status)) { | ||||
| 			allErrs = append(allErrs, field.NotSupported(statusPath, pattern.Status, supportedPodFailurePolicyOnPodConditionsStatus.List())) | ||||
| 		} | ||||
| 	} | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| func validatePodFailurePolicyRuleOnExitCodes(onExitCode *batch.PodFailurePolicyOnExitCodesRequirement, onExitCodesPath *field.Path, containerNames sets.String) field.ErrorList { | ||||
| 	var allErrs field.ErrorList | ||||
| 	operatorPath := onExitCodesPath.Child("operator") | ||||
| 	if onExitCode.Operator == "" { | ||||
| 		allErrs = append(allErrs, field.Required(operatorPath, fmt.Sprintf("valid values: %q", supportedPodFailurePolicyOnExitCodesOperator.List()))) | ||||
| 	} else if !supportedPodFailurePolicyOnExitCodesOperator.Has(string(onExitCode.Operator)) { | ||||
| 		allErrs = append(allErrs, field.NotSupported(operatorPath, onExitCode.Operator, supportedPodFailurePolicyOnExitCodesOperator.List())) | ||||
| 	} | ||||
| 	if onExitCode.ContainerName != nil && !containerNames.Has(*onExitCode.ContainerName) { | ||||
| 		allErrs = append(allErrs, field.Invalid(onExitCodesPath.Child("containerName"), *onExitCode.ContainerName, "must be one of the container or initContainer names in the pod template")) | ||||
| 	} | ||||
| 	valuesPath := onExitCodesPath.Child("values") | ||||
| 	if len(onExitCode.Values) == 0 { | ||||
| 		allErrs = append(allErrs, field.Invalid(valuesPath, onExitCode.Values, "at least one value is required")) | ||||
| 	} else if len(onExitCode.Values) > maxPodFailurePolicyOnExitCodesValues { | ||||
| 		allErrs = append(allErrs, field.TooMany(valuesPath, len(onExitCode.Values), maxPodFailurePolicyOnExitCodesValues)) | ||||
| 	} | ||||
| 	isOrdered := true | ||||
| 	uniqueValues := sets.NewInt32() | ||||
| 	for j, exitCodeValue := range onExitCode.Values { | ||||
| 		valuePath := valuesPath.Index(j) | ||||
| 		if onExitCode.Operator == batch.PodFailurePolicyOnExitCodesOpIn && exitCodeValue == 0 { | ||||
| 			allErrs = append(allErrs, field.Invalid(valuePath, exitCodeValue, "must not be 0 for the In operator")) | ||||
| 		} | ||||
| 		if uniqueValues.Has(exitCodeValue) { | ||||
| 			allErrs = append(allErrs, field.Duplicate(valuePath, exitCodeValue)) | ||||
| 		} else { | ||||
| 			uniqueValues.Insert(exitCodeValue) | ||||
| 		} | ||||
| 		if j > 0 && onExitCode.Values[j-1] > exitCodeValue { | ||||
| 			isOrdered = false | ||||
| 		} | ||||
| 	} | ||||
| 	if !isOrdered { | ||||
| 		allErrs = append(allErrs, field.Invalid(valuesPath, onExitCode.Values, "must be ordered")) | ||||
| 	} | ||||
|  | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
| // validateJobStatus validates a JobStatus and returns an ErrorList with any errors. | ||||
| func validateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList { | ||||
| 	allErrs := field.ErrorList{} | ||||
| @@ -241,6 +376,7 @@ func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path, opt | ||||
| 	allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...) | ||||
| 	allErrs = append(allErrs, validatePodTemplateUpdate(spec, oldSpec, fldPath, opts)...) | ||||
| 	allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.CompletionMode, oldSpec.CompletionMode, fldPath.Child("completionMode"))...) | ||||
| 	allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.PodFailurePolicy, oldSpec.PodFailurePolicy, fldPath.Child("podFailurePolicy"))...) | ||||
| 	return allErrs | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ package validation | ||||
|  | ||||
| import ( | ||||
| 	"archive/zip" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| @@ -80,23 +81,85 @@ func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.Pod | ||||
| 			Labels: selector.MatchLabels, | ||||
| 		}, | ||||
| 		Spec: api.PodSpec{ | ||||
| 			RestartPolicy: api.RestartPolicyOnFailure, | ||||
| 			DNSPolicy:     api.DNSClusterFirst, | ||||
| 			Containers:    []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, | ||||
| 			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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 										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{ | ||||
| @@ -185,6 +248,402 @@ func TestValidateJob(t *testing.T) { | ||||
| 	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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 									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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		`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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 									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", | ||||
| @@ -388,6 +847,9 @@ func TestValidateJob(t *testing.T) { | ||||
| 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{ | ||||
| @@ -491,6 +953,100 @@ func TestValidateJobUpdate(t *testing.T) { | ||||
| 				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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 									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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 										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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 							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.AlphaNoCompatGuaranteeDisruptionTarget, | ||||
| 										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}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user