diff --git a/go.mod b/go.mod index 44c52b57b..415a94d99 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/onsi/gomega v1.29.0 github.com/prometheus/client_golang v1.16.0 github.com/rakyll/gotest v0.0.6 + github.com/robfig/cron v1.2.0 github.com/spf13/pflag v1.0.5 go.uber.org/mock v0.2.0 go.uber.org/zap v1.26.0 diff --git a/go.sum b/go.sum index 391620d68..055112d69 100644 --- a/go.sum +++ b/go.sum @@ -438,6 +438,8 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rakyll/gotest v0.0.6 h1:hBTqkO3jiuwYW/M9gL4bu0oTYcm8J6knQAAPUsJsz1I= github.com/rakyll/gotest v0.0.6/go.mod h1:SkoesdNCWmiD4R2dljIUcfSnNdVZ12y8qK4ojDkc2Sc= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= diff --git a/internal/health/condition/check_backup_ready.go b/internal/health/condition/check_backup_ready.go index 06b4472fe..56f9010b8 100644 --- a/internal/health/condition/check_backup_ready.go +++ b/internal/health/condition/check_backup_ready.go @@ -10,6 +10,7 @@ import ( "time" druidv1alpha1 "github.com/gardener/etcd-druid/api/v1alpha1" + "github.com/gardener/etcd-druid/internal/utils" coordinationv1 "k8s.io/api/coordination/v1" "k8s.io/apimachinery/pkg/types" @@ -54,19 +55,26 @@ func (f *fullSnapshotBackupReadyCheck) Check(ctx context.Context, etcd druidv1al //Fetch snapshot leases var ( - fullSnapErr error - fullSnapLease = &coordinationv1.Lease{} + fullSnapErr error + fullSnapLease = &coordinationv1.Lease{} + fullSnapshotInterval = 24 * time.Hour + err error ) fullSnapErr = f.cl.Get(ctx, types.NamespacedName{Name: getFullSnapLeaseName(&etcd), Namespace: etcd.ObjectMeta.Namespace}, fullSnapLease) fullLeaseRenewTime := fullSnapLease.Spec.RenewTime fullLeaseCreationTime := fullSnapLease.ObjectMeta.CreationTimestamp + if etcd.Spec.Backup.FullSnapshotSchedule != nil { + if fullSnapshotInterval, err = utils.ComputeScheduleInterval(*etcd.Spec.Backup.FullSnapshotSchedule); err != nil { + return result + } + } //Set status to Unknown if errors in fetching full snapshot lease if fullSnapErr != nil { return result } if fullLeaseRenewTime == nil { - if time.Since(fullLeaseCreationTime.Time) < 24*time.Hour { + if time.Since(fullLeaseCreationTime.Time) < fullSnapshotInterval { return result } else { result.status = druidv1alpha1.ConditionFalse @@ -75,7 +83,7 @@ func (f *fullSnapshotBackupReadyCheck) Check(ctx context.Context, etcd druidv1al return result } } else { - if time.Since(fullLeaseRenewTime.Time) < 24*time.Hour { + if time.Since(fullLeaseRenewTime.Time) < fullSnapshotInterval { result.status = druidv1alpha1.ConditionTrue result.reason = SnapshotUploadedOnSchedule result.message = fmt.Sprintf("Full snapshot uploaded successfully %v ago", time.Since(fullLeaseRenewTime.Time)) diff --git a/internal/utils/miscellaneous.go b/internal/utils/miscellaneous.go index b116019de..6e9fdf79e 100644 --- a/internal/utils/miscellaneous.go +++ b/internal/utils/miscellaneous.go @@ -8,7 +8,9 @@ import ( "fmt" "maps" "strings" + "time" + "github.com/robfig/cron" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -76,3 +78,19 @@ func IfConditionOr[T any](condition bool, trueVal, falseVal T) T { func PointerOf[T any](val T) *T { return &val } + +// ComputeScheduleInterval computes the interval between two activations for the given cron schedule. +// Assumes that every cron activation is at equal intervals apart, based on cron schedules such as +// "once every X hours", "once every Y days", "at 1:00pm on every Tuesday", etc. +// TODO: write a new function to accurately compute the previous activation time from the cron schedule +// in order to compute when the previous activation of the cron schedule was supposed to have occurred, +// instead of relying on the assumption that all the cron activations are evenly spaced. +func ComputeScheduleInterval(cronSchedule string) (time.Duration, error) { + schedule, err := cron.ParseStandard(cronSchedule) + if err != nil { + return 0, err + } + nextScheduledTime := schedule.Next(time.Now()) + nextNextScheduledTime := schedule.Next(nextScheduledTime) + return nextNextScheduledTime.Sub(nextScheduledTime), nil +}