diff --git a/pkg/command/flag/flag.go b/pkg/command/flag/flag.go index feb60ba623..e47eb23e22 100644 --- a/pkg/command/flag/flag.go +++ b/pkg/command/flag/flag.go @@ -146,7 +146,6 @@ func (w Wrapper) interval(p *Duration) { func (w Wrapper) startDate(p *StartDate) { w.fs.VarP(p, "start-date", "s", usage["start-date"]) - w.MustMarkDeprecated("start-date", "use cron instead") } func (w Wrapper) numRetries(p *int, def int) { diff --git a/pkg/managerclient/model.go b/pkg/managerclient/model.go index 3e1acb6b35..82821df410 100644 --- a/pkg/managerclient/model.go +++ b/pkg/managerclient/model.go @@ -3,6 +3,7 @@ package managerclient import ( + "encoding/json" "fmt" "io" "sort" @@ -14,7 +15,9 @@ import ( "github.com/pkg/errors" "github.com/scylladb/go-set/strset" "github.com/scylladb/scylla-manager/v3/pkg/managerclient/table" + "github.com/scylladb/scylla-manager/v3/pkg/service/scheduler" "github.com/scylladb/scylla-manager/v3/pkg/util/inexlist" + "github.com/scylladb/scylla-manager/v3/pkg/util/timeutc" "github.com/scylladb/scylla-manager/v3/pkg/util/version" "github.com/scylladb/scylla-manager/v3/swagger/gen/scylla-manager/models" "github.com/scylladb/termtables" @@ -513,7 +516,18 @@ func (li TaskListItems) Render(w io.Writer) error { var schedule string if t.Schedule.Cron != "" { - schedule = t.Schedule.Cron + var cronSpec scheduler.CronSpecification + err := json.Unmarshal([]byte(t.Schedule.Cron), &cronSpec) + if err != nil { + schedule = t.Schedule.Cron + } else { + schedule = cronSpec.Spec + if cronSpec.StartDate.After(timeutc.Now()) { + c := scheduler.MustCron(cronSpec.Spec, cronSpec.StartDate) + schedule += fmt.Sprintf(" with first activation after %s", + c.Next(cronSpec.StartDate).Format("2006-01-02 15:04:05")) + } + } } else if t.Schedule.Interval != "" { schedule = t.Schedule.Interval } diff --git a/pkg/restapi/task.go b/pkg/restapi/task.go index c6cec1fd9e..e5af12e1f5 100644 --- a/pkg/restapi/task.go +++ b/pkg/restapi/task.go @@ -151,6 +151,9 @@ func (h *taskHandler) parseTask(r *http.Request) (*scheduler.Task, error) { if err := render.DecodeJSON(r.Body, &t); err != nil { return nil, err } + if !t.Sched.StartDate.IsZero() && t.Sched.Cron.Spec != "" { + t.Sched.Cron.StartDate = t.Sched.StartDate + } t.ClusterID = mustClusterIDFromCtx(r) return &t, nil } diff --git a/pkg/service/scheduler/model.go b/pkg/service/scheduler/model.go index 02e97500d4..c33cdc9d32 100644 --- a/pkg/service/scheduler/model.go +++ b/pkg/service/scheduler/model.go @@ -70,11 +70,13 @@ func (t *TaskType) UnmarshalText(text []byte) error { // Cron implements a trigger based on cron expression. // It supports the extended syntax including @monthly, @weekly, @daily, @midnight, @hourly, @every . type Cron struct { - cronSpecification + CronSpecification inner scheduler.Trigger } -type cronSpecification struct { +// CronSpecification combines specification for cron together with the start dates +// that defines the moment when the cron is being started. +type CronSpecification struct { Spec string `json:"spec"` StartDate time.Time `json:"start_date"` } @@ -86,7 +88,7 @@ func NewCron(spec string, startDate time.Time) (Cron, error) { } return Cron{ - cronSpecification: cronSpecification{ + CronSpecification: CronSpecification{ Spec: spec, StartDate: startDate, }, @@ -120,9 +122,9 @@ func (c Cron) Next(now time.Time) time.Time { } func (c Cron) MarshalText() (text []byte, err error) { - bytes, err := json.Marshal(c.cronSpecification) + bytes, err := json.Marshal(c.CronSpecification) if err != nil { - return nil, errors.Wrapf(err, "cannot json marshal {%v}", c.cronSpecification) + return nil, errors.Wrapf(err, "cannot json marshal {%v}", c.CronSpecification) } return bytes, nil } @@ -132,11 +134,11 @@ func (c *Cron) UnmarshalText(text []byte) error { return nil } - var cronSpec cronSpecification + var cronSpec CronSpecification err := json.Unmarshal(text, &cronSpec) if err != nil { // fallback to the < 3.2.6 approach where cron was not coupled with start date - cronSpec = cronSpecification{ + cronSpec = CronSpecification{ Spec: string(text), } } diff --git a/pkg/service/scheduler/model_test.go b/pkg/service/scheduler/model_test.go index 1cecbd5f43..a8bf966b50 100644 --- a/pkg/service/scheduler/model_test.go +++ b/pkg/service/scheduler/model_test.go @@ -66,12 +66,12 @@ func TestCronMarshalUnmarshal(t *testing.T) { for _, tc := range []struct { name string data []byte - expectedSpec cronSpecification + expectedSpec CronSpecification }{ { name: "(3.2.6 backward compatibility) unmarshal spec", data: []byte("@every 15s"), - expectedSpec: cronSpecification{ + expectedSpec: CronSpecification{ Spec: "@every 15s", StartDate: time.Time{}, }, @@ -79,7 +79,7 @@ func TestCronMarshalUnmarshal(t *testing.T) { { name: "unmarshal spec full struct zero time", data: []byte(`{"spec": "@every 15s", "start_date": "0001-01-01T00:00:00Z"}`), - expectedSpec: cronSpecification{ + expectedSpec: CronSpecification{ Spec: "@every 15s", StartDate: time.Time{}, }, @@ -87,7 +87,7 @@ func TestCronMarshalUnmarshal(t *testing.T) { { name: "unmarshal spec full struct non-zero time", data: []byte(`{"spec": "@every 15s", "start_date": "` + nonZeroTimeString + `"}`), - expectedSpec: cronSpecification{ + expectedSpec: CronSpecification{ Spec: "@every 15s", StartDate: nonZeroTime, },