mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-21 18:11:22 +00:00
Merge pull request #108032 from deejross/kep3140-cronjob-timezone
KEP 3140: TimeZone support for CronJob
This commit is contained in:
@@ -376,6 +376,12 @@ type CronJobSpec struct {
|
||||
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
|
||||
Schedule string
|
||||
|
||||
// The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.
|
||||
// If not specified, this will rely on the time zone of the kube-controller-manager process.
|
||||
// ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.
|
||||
// +optional
|
||||
TimeZone *string
|
||||
|
||||
// Optional deadline in seconds for starting the job if it misses scheduled
|
||||
// time for any reason. Missed jobs executions will be counted as failed ones.
|
||||
// +optional
|
||||
|
2
pkg/apis/batch/v1/zz_generated.conversion.go
generated
2
pkg/apis/batch/v1/zz_generated.conversion.go
generated
@@ -231,6 +231,7 @@ func Convert_batch_CronJobList_To_v1_CronJobList(in *batch.CronJobList, out *v1.
|
||||
|
||||
func autoConvert_v1_CronJobSpec_To_batch_CronJobSpec(in *v1.CronJobSpec, out *batch.CronJobSpec, s conversion.Scope) error {
|
||||
out.Schedule = in.Schedule
|
||||
out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone))
|
||||
out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds))
|
||||
out.ConcurrencyPolicy = batch.ConcurrencyPolicy(in.ConcurrencyPolicy)
|
||||
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
|
||||
@@ -249,6 +250,7 @@ func Convert_v1_CronJobSpec_To_batch_CronJobSpec(in *v1.CronJobSpec, out *batch.
|
||||
|
||||
func autoConvert_batch_CronJobSpec_To_v1_CronJobSpec(in *batch.CronJobSpec, out *v1.CronJobSpec, s conversion.Scope) error {
|
||||
out.Schedule = in.Schedule
|
||||
out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone))
|
||||
out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds))
|
||||
out.ConcurrencyPolicy = v1.ConcurrencyPolicy(in.ConcurrencyPolicy)
|
||||
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
|
||||
|
@@ -180,6 +180,7 @@ func Convert_batch_CronJobList_To_v1beta1_CronJobList(in *batch.CronJobList, out
|
||||
|
||||
func autoConvert_v1beta1_CronJobSpec_To_batch_CronJobSpec(in *v1beta1.CronJobSpec, out *batch.CronJobSpec, s conversion.Scope) error {
|
||||
out.Schedule = in.Schedule
|
||||
out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone))
|
||||
out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds))
|
||||
out.ConcurrencyPolicy = batch.ConcurrencyPolicy(in.ConcurrencyPolicy)
|
||||
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
|
||||
@@ -198,6 +199,7 @@ func Convert_v1beta1_CronJobSpec_To_batch_CronJobSpec(in *v1beta1.CronJobSpec, o
|
||||
|
||||
func autoConvert_batch_CronJobSpec_To_v1beta1_CronJobSpec(in *batch.CronJobSpec, out *v1beta1.CronJobSpec, s conversion.Scope) error {
|
||||
out.Schedule = in.Schedule
|
||||
out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone))
|
||||
out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds))
|
||||
out.ConcurrencyPolicy = v1beta1.ConcurrencyPolicy(in.ConcurrencyPolicy)
|
||||
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
|
||||
|
@@ -18,6 +18,8 @@ package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -29,6 +31,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// maxParallelismForIndexJob is the maximum parallelism that an Indexed Job
|
||||
@@ -277,11 +280,11 @@ func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCronJob validates a CronJob and returns an ErrorList with any errors.
|
||||
func ValidateCronJob(cronJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
// ValidateCronJobCreate validates a CronJob on creation and returns an ErrorList with any errors.
|
||||
func ValidateCronJobCreate(cronJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
// CronJobs and rcs have the same name validation
|
||||
allErrs := apivalidation.ValidateObjectMeta(&cronJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateCronJobSpec(&cronJob.Spec, field.NewPath("spec"), opts)...)
|
||||
allErrs = append(allErrs, validateCronJobSpec(&cronJob.Spec, nil, field.NewPath("spec"), opts)...)
|
||||
if len(cronJob.ObjectMeta.Name) > apimachineryvalidation.DNS1035LabelMaxLength-11 {
|
||||
// The cronjob controller appends a 11-character suffix to the cronjob (`-$TIMESTAMP`) when
|
||||
// creating a job. The job name length limit is 63 characters.
|
||||
@@ -295,24 +298,31 @@ func ValidateCronJob(cronJob *batch.CronJob, opts apivalidation.PodValidationOpt
|
||||
// ValidateCronJobUpdate validates an update to a CronJob and returns an ErrorList with any errors.
|
||||
func ValidateCronJobUpdate(job, oldJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateCronJobSpec(&job.Spec, field.NewPath("spec"), opts)...)
|
||||
allErrs = append(allErrs, validateCronJobSpec(&job.Spec, &oldJob.Spec, field.NewPath("spec"), opts)...)
|
||||
|
||||
// skip the 52-character name validation limit on update validation
|
||||
// to allow old cronjobs with names > 52 chars to be updated/deleted
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateCronJobSpec validates a CronJobSpec and returns an ErrorList with any errors.
|
||||
func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
// validateCronJobSpec validates a CronJobSpec and returns an ErrorList with any errors.
|
||||
func validateCronJobSpec(spec, oldSpec *batch.CronJobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(spec.Schedule) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
|
||||
} else {
|
||||
allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...)
|
||||
allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, spec.TimeZone, fldPath.Child("schedule"))...)
|
||||
}
|
||||
|
||||
if spec.StartingDeadlineSeconds != nil {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
|
||||
}
|
||||
|
||||
if oldSpec == nil || !pointer.StringEqual(oldSpec.TimeZone, spec.TimeZone) {
|
||||
allErrs = append(allErrs, validateTimeZone(spec.TimeZone, fldPath.Child("timeZone"))...)
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"), opts)...)
|
||||
|
||||
@@ -343,11 +353,36 @@ func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPa
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
|
||||
func validateScheduleFormat(schedule string, timeZone *string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if _, err := cron.ParseStandard(schedule); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
|
||||
}
|
||||
if strings.Contains(schedule, "TZ") && timeZone != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, schedule, "cannot use both timeZone field and TZ or CRON_TZ in schedule"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateTimeZone(timeZone *string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if timeZone == nil {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if len(*timeZone) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, timeZone, "timeZone must be nil or non-empty string"))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if strings.EqualFold(*timeZone, "Local") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, timeZone, "timeZone must be an explicit time zone as defined in https://www.iana.org/time-zones"))
|
||||
}
|
||||
|
||||
if _, err := time.LoadLocation(*timeZone); err != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, timeZone, err.Error()))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
@@ -31,6 +31,18 @@ import (
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
var (
|
||||
timeZoneEmpty = ""
|
||||
timeZoneLocal = "LOCAL"
|
||||
timeZoneUTC = "UTC"
|
||||
timeZoneCorrectCasing = "America/New_York"
|
||||
timeZoneBadCasing = "AMERICA/new_york"
|
||||
timeZoneBadPrefix = " America/New_York"
|
||||
timeZoneBadSuffix = "America/New_York "
|
||||
timeZoneBadName = "America/New York"
|
||||
timeZoneEmptySpace = " "
|
||||
)
|
||||
|
||||
var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
|
||||
|
||||
func getValidManualSelector() *metav1.LabelSelector {
|
||||
@@ -902,9 +914,26 @@ func TestValidateCronJob(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"correct timeZone value casing": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycronjob",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
UID: types.UID("1a2b3c"),
|
||||
},
|
||||
Spec: batch.CronJobSpec{
|
||||
Schedule: "0 * * * *",
|
||||
TimeZone: &timeZoneCorrectCasing,
|
||||
ConcurrencyPolicy: batch.AllowConcurrent,
|
||||
JobTemplate: batch.JobTemplateSpec{
|
||||
Spec: batch.JobSpec{
|
||||
Template: validPodTemplateSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range successCases {
|
||||
if errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
|
||||
if errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success for %s: %v", k, errs)
|
||||
}
|
||||
|
||||
@@ -953,6 +982,142 @@ func TestValidateCronJob(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"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: \"AMERICA/new_york\": unknown time zone AMERICA/new_york": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycronjob",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
UID: types.UID("1a2b3c"),
|
||||
},
|
||||
Spec: batch.CronJobSpec{
|
||||
Schedule: "0 * * * *",
|
||||
TimeZone: &timeZoneBadCasing,
|
||||
ConcurrencyPolicy: batch.AllowConcurrent,
|
||||
JobTemplate: batch.JobTemplateSpec{
|
||||
Spec: batch.JobSpec{
|
||||
Template: validPodTemplateSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec.timeZone: Invalid value: \" America/New_York\": unknown time zone America/New_York": {
|
||||
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: \"America/New_York \": unknown time zone America/New_York ": {
|
||||
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.timeZone: Invalid value: \"America/New York\": unknown time zone America/New York": {
|
||||
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.startingDeadlineSeconds:must be greater than or equal to 0": {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycronjob",
|
||||
@@ -1185,7 +1350,7 @@ func TestValidateCronJob(t *testing.T) {
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{})
|
||||
errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
} else {
|
||||
@@ -1198,9 +1363,14 @@ func TestValidateCronJob(t *testing.T) {
|
||||
|
||||
// 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
|
||||
v = *v.DeepCopy()
|
||||
v.ResourceVersion = "1"
|
||||
errs = ValidateCronJobUpdate(&v, &v, corevalidation.PodValidationOptions{})
|
||||
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" {
|
||||
continue
|
||||
@@ -1216,6 +1386,208 @@ func TestValidateCronJob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
5
pkg/apis/batch/zz_generated.deepcopy.go
generated
5
pkg/apis/batch/zz_generated.deepcopy.go
generated
@@ -92,6 +92,11 @@ func (in *CronJobList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) {
|
||||
*out = *in
|
||||
if in.TimeZone != nil {
|
||||
in, out := &in.TimeZone, &out.TimeZone
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.StartingDeadlineSeconds != nil {
|
||||
in, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds
|
||||
*out = new(int64)
|
||||
|
Reference in New Issue
Block a user