Promote CronJob TZ to GA

This commit is contained in:
Maciej Szulik 2023-02-20 10:52:55 +01:00
parent 3489796d5c
commit 1b825c179b
No known key found for this signature in database
GPG Key ID: F15E55D276FA84C4
7 changed files with 10 additions and 37 deletions

View File

@ -501,7 +501,6 @@ type CronJobSpec struct {
// configuration, the controller will stop creating new new Jobs and will create a system event with the // configuration, the controller will stop creating new new Jobs and will create a system event with the
// reason UnknownTimeZone. // reason UnknownTimeZone.
// More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones // More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
// This is beta field and must be enabled via the `CronJobTimeZone` feature gate.
// +optional // +optional
TimeZone *string TimeZone *string

View File

@ -35,7 +35,6 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
batchv1informers "k8s.io/client-go/informers/batch/v1" batchv1informers "k8s.io/client-go/informers/batch/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
@ -48,7 +47,6 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/cronjob/metrics" "k8s.io/kubernetes/pkg/controller/cronjob/metrics"
"k8s.io/kubernetes/pkg/features"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
) )
@ -380,7 +378,6 @@ func (jm *ControllerV2) enqueueControllerAfter(obj interface{}, t time.Duration)
// updateCronJob re-queues the CronJob for next scheduled time if there is a // updateCronJob re-queues the CronJob for next scheduled time if there is a
// change in spec.schedule otherwise it re-queues it now // change in spec.schedule otherwise it re-queues it now
func (jm *ControllerV2) updateCronJob(logger klog.Logger, old interface{}, curr interface{}) { func (jm *ControllerV2) updateCronJob(logger klog.Logger, old interface{}, curr interface{}) {
timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone)
oldCJ, okOld := old.(*batchv1.CronJob) oldCJ, okOld := old.(*batchv1.CronJob)
newCJ, okNew := curr.(*batchv1.CronJob) newCJ, okNew := curr.(*batchv1.CronJob)
@ -391,9 +388,9 @@ func (jm *ControllerV2) updateCronJob(logger klog.Logger, old interface{}, curr
// if the change in schedule results in next requeue having to be sooner than it already was, // if the change in schedule results in next requeue having to be sooner than it already was,
// it will be handled here by the queue. If the next requeue is further than previous schedule, // it will be handled here by the queue. If the next requeue is further than previous schedule,
// the sync loop will essentially be a no-op for the already queued key with old schedule. // the sync loop will essentially be a no-op for the already queued key with old schedule.
if oldCJ.Spec.Schedule != newCJ.Spec.Schedule || (timeZoneEnabled && !pointer.StringEqual(oldCJ.Spec.TimeZone, newCJ.Spec.TimeZone)) { if oldCJ.Spec.Schedule != newCJ.Spec.Schedule || !pointer.StringEqual(oldCJ.Spec.TimeZone, newCJ.Spec.TimeZone) {
// schedule changed, change the requeue time, pass nil recorder so that syncCronJob will output any warnings // schedule changed, change the requeue time, pass nil recorder so that syncCronJob will output any warnings
sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, newCJ, nil)) sched, err := cron.ParseStandard(formatSchedule(newCJ, nil))
if err != nil { if err != nil {
// this is likely a user error in defining the spec value // this is likely a user error in defining the spec value
// we should log the error and not reconcile this cronjob until an update to spec // we should log the error and not reconcile this cronjob until an update to spec
@ -430,7 +427,6 @@ func (jm *ControllerV2) syncCronJob(
cronJob = cronJob.DeepCopy() cronJob = cronJob.DeepCopy()
now := jm.now() now := jm.now()
updateStatus := false updateStatus := false
timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone)
childrenJobs := make(map[types.UID]bool) childrenJobs := make(map[types.UID]bool)
for _, j := range jobs { for _, j := range jobs {
@ -499,9 +495,9 @@ func (jm *ControllerV2) syncCronJob(
} }
logger := klog.FromContext(ctx) logger := klog.FromContext(ctx)
if timeZoneEnabled && cronJob.Spec.TimeZone != nil { if cronJob.Spec.TimeZone != nil {
if _, err := time.LoadLocation(*cronJob.Spec.TimeZone); err != nil { timeZone := pointer.StringDeref(cronJob.Spec.TimeZone, "")
timeZone := pointer.StringDeref(cronJob.Spec.TimeZone, "") if _, err := time.LoadLocation(timeZone); err != nil {
logger.V(4).Info("Not starting job because timeZone is invalid", "cronjob", klog.KObj(cronJob), "timeZone", timeZone, "err", err) logger.V(4).Info("Not starting job because timeZone is invalid", "cronjob", klog.KObj(cronJob), "timeZone", timeZone, "err", err)
jm.recorder.Eventf(cronJob, corev1.EventTypeWarning, "UnknownTimeZone", "invalid timeZone: %q: %s", timeZone, err) jm.recorder.Eventf(cronJob, corev1.EventTypeWarning, "UnknownTimeZone", "invalid timeZone: %q: %s", timeZone, err)
return cronJob, nil, updateStatus, nil return cronJob, nil, updateStatus, nil
@ -513,7 +509,7 @@ func (jm *ControllerV2) syncCronJob(
return cronJob, nil, updateStatus, nil return cronJob, nil, updateStatus, nil
} }
sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, cronJob, jm.recorder)) sched, err := cron.ParseStandard(formatSchedule(cronJob, jm.recorder))
if err != nil { if err != nil {
// this is likely a user error in defining the spec value // this is likely a user error in defining the spec value
// we should log the error and not reconcile this cronjob until an update to spec // we should log the error and not reconcile this cronjob until an update to spec
@ -733,7 +729,7 @@ func getRef(object runtime.Object) (*corev1.ObjectReference, error) {
return ref.GetReference(scheme.Scheme, object) return ref.GetReference(scheme.Scheme, object)
} }
func formatSchedule(timeZoneEnabled bool, cj *batchv1.CronJob, recorder record.EventRecorder) string { func formatSchedule(cj *batchv1.CronJob, recorder record.EventRecorder) string {
if strings.Contains(cj.Spec.Schedule, "TZ") { if strings.Contains(cj.Spec.Schedule, "TZ") {
if recorder != nil { if recorder != nil {
recorder.Eventf(cj, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cj.Spec.Schedule) recorder.Eventf(cj, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cj.Spec.Schedule)
@ -742,7 +738,7 @@ func formatSchedule(timeZoneEnabled bool, cj *batchv1.CronJob, recorder record.E
return cj.Spec.Schedule return cj.Spec.Schedule
} }
if timeZoneEnabled && cj.Spec.TimeZone != nil { if cj.Spec.TimeZone != nil {
if _, err := time.LoadLocation(*cj.Spec.TimeZone); err != nil { if _, err := time.LoadLocation(*cj.Spec.TimeZone); err != nil {
return cj.Spec.Schedule return cj.Spec.Schedule
} }

View File

@ -30,17 +30,14 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/klog/v2/ktesting" "k8s.io/klog/v2/ktesting"
_ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/features"
) )
var ( var (
@ -203,7 +200,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
now time.Time now time.Time
jobCreateError error jobCreateError error
jobGetErr error jobGetErr error
enableTimeZone bool
// expectations // expectations
expectCreate bool expectCreate bool
@ -251,7 +247,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
deadline: noDead, deadline: noDead,
jobCreationTime: justAfterThePriorHour(), jobCreationTime: justAfterThePriorHour(),
now: justBeforeTheHour(), now: justBeforeTheHour(),
enableTimeZone: true,
expectedWarnings: 1, expectedWarnings: 1,
jobPresentInCJActiveStatus: true, jobPresentInCJActiveStatus: true,
}, },
@ -291,7 +286,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
deadline: noDead, deadline: noDead,
jobCreationTime: justAfterThePriorHour(), jobCreationTime: justAfterThePriorHour(),
now: justBeforeTheHour(), now: justBeforeTheHour(),
enableTimeZone: true,
expectRequeueAfter: true, expectRequeueAfter: true,
expectedRequeueDuration: 1*time.Minute + nextScheduleDelta, expectedRequeueDuration: 1*time.Minute + nextScheduleDelta,
jobPresentInCJActiveStatus: true, jobPresentInCJActiveStatus: true,
@ -342,7 +336,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
deadline: noDead, deadline: noDead,
jobCreationTime: justAfterThePriorHour(), jobCreationTime: justAfterThePriorHour(),
now: justAfterTheHourInZone(newYork), now: justAfterTheHourInZone(newYork),
enableTimeZone: false,
expectCreate: true, expectCreate: true,
expectActive: 1, expectActive: 1,
expectRequeueAfter: true, expectRequeueAfter: true,
@ -357,7 +350,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
deadline: noDead, deadline: noDead,
jobCreationTime: justAfterThePriorHour(), jobCreationTime: justAfterThePriorHour(),
now: justAfterTheHourInZone(newYork), now: justAfterTheHourInZone(newYork),
enableTimeZone: true,
expectCreate: true, expectCreate: true,
expectActive: 1, expectActive: 1,
expectRequeueAfter: true, expectRequeueAfter: true,
@ -372,7 +364,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
deadline: noDead, deadline: noDead,
jobCreationTime: justAfterThePriorHour(), jobCreationTime: justAfterThePriorHour(),
now: justAfterTheHourInZone(newYork), now: justAfterTheHourInZone(newYork),
enableTimeZone: true,
expectCreate: true, expectCreate: true,
expectedWarnings: 1, expectedWarnings: 1,
expectRequeueAfter: true, expectRequeueAfter: true,
@ -1172,8 +1163,6 @@ func TestControllerV2SyncCronJob(t *testing.T) {
tc := tc tc := tc
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, tc.enableTimeZone)()
cj := cronJob() cj := cronJob()
cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
cj.Spec.Suspend = &tc.suspend cj.Spec.Suspend = &tc.suspend

View File

@ -188,6 +188,7 @@ const (
// kep: https://kep.k8s.io/3140 // kep: https://kep.k8s.io/3140
// alpha: v1.24 // alpha: v1.24
// beta: v1.25 // beta: v1.25
// GA: 1.27
// //
// Enables support for time zones in CronJobs. // Enables support for time zones in CronJobs.
CronJobTimeZone featuregate.Feature = "CronJobTimeZone" CronJobTimeZone featuregate.Feature = "CronJobTimeZone"
@ -900,7 +901,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
ConsistentHTTPGetHandlers: {Default: true, PreRelease: featuregate.GA}, ConsistentHTTPGetHandlers: {Default: true, PreRelease: featuregate.GA},
CronJobTimeZone: {Default: true, PreRelease: featuregate.Beta}, CronJobTimeZone: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.29
DelegateFSGroupToCSIDriver: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.28 DelegateFSGroupToCSIDriver: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.28

View File

@ -30,12 +30,10 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
batchvalidation "k8s.io/kubernetes/pkg/apis/batch/validation" batchvalidation "k8s.io/kubernetes/pkg/apis/batch/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
@ -91,10 +89,6 @@ func (cronJobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object)
cronJob.Generation = 1 cronJob.Generation = 1
if !utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) {
cronJob.Spec.TimeZone = nil
}
pod.DropDisabledTemplateFields(&cronJob.Spec.JobTemplate.Spec.Template, nil) pod.DropDisabledTemplateFields(&cronJob.Spec.JobTemplate.Spec.Template, nil)
} }
@ -104,10 +98,6 @@ func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob
oldCronJob := old.(*batch.CronJob) oldCronJob := old.(*batch.CronJob)
newCronJob.Status = oldCronJob.Status newCronJob.Status = oldCronJob.Status
if !utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) && oldCronJob.Spec.TimeZone == nil {
newCronJob.Spec.TimeZone = nil
}
pod.DropDisabledTemplateFields(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template) pod.DropDisabledTemplateFields(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template)
// Any changes to the spec increment the generation number. // Any changes to the spec increment the generation number.

View File

@ -517,7 +517,6 @@ type CronJobSpec struct {
// configuration, the controller will stop creating new new Jobs and will create a system event with the // configuration, the controller will stop creating new new Jobs and will create a system event with the
// reason UnknownTimeZone. // reason UnknownTimeZone.
// More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones // More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
// This is beta field and must be enabled via the `CronJobTimeZone` feature gate.
// +optional // +optional
TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"` TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"`

View File

@ -95,7 +95,6 @@ type CronJobSpec struct {
// configuration, the controller will stop creating new new Jobs and will create a system event with the // configuration, the controller will stop creating new new Jobs and will create a system event with the
// reason UnknownTimeZone. // reason UnknownTimeZone.
// More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones // More information can be found in https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
// This is beta field and must be enabled via the `CronJobTimeZone` feature gate.
// +optional // +optional
TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"` TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"`