mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-12 08:45:38 +00:00
408 lines
10 KiB
Go
408 lines
10 KiB
Go
package tasks
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
"github.com/garethgeorge/backrest/internal/config"
|
|
"github.com/garethgeorge/backrest/internal/oplog"
|
|
"github.com/garethgeorge/backrest/internal/oplog/memstore"
|
|
)
|
|
|
|
func TestScheduling(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping test on windows")
|
|
}
|
|
|
|
os.Setenv("TZ", "America/Los_Angeles")
|
|
defer os.Unsetenv("TZ")
|
|
|
|
cfg := &v1.Config{
|
|
Repos: []*v1.Repo{
|
|
{
|
|
Id: "repo-absolute",
|
|
CheckPolicy: &v1.CheckPolicy{
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
},
|
|
},
|
|
PrunePolicy: &v1.PrunePolicy{
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "repo-relative",
|
|
CheckPolicy: &v1.CheckPolicy{
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
|
|
},
|
|
},
|
|
PrunePolicy: &v1.PrunePolicy{
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Plans: []*v1.Plan{
|
|
{
|
|
Id: "plan-cron",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_Cron{
|
|
Cron: "0 0 * * *", // every day at midnight
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LOCAL,
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-cron-utc",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_Cron{
|
|
Cron: "0 0 * * *", // every day at midnight
|
|
},
|
|
Clock: v1.Schedule_CLOCK_UTC,
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-cron-since-last-run",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_Cron{
|
|
Cron: "0 0 * * *", // every day at midnight
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-max-frequency-days",
|
|
Repo: "repo1",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyDays{
|
|
MaxFrequencyDays: 1,
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LOCAL,
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-min-days-since-last-run",
|
|
Repo: "repo1",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyDays{
|
|
MaxFrequencyDays: 1,
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-max-frequency-hours",
|
|
Repo: "repo1",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "plan-min-hours-since-last-run",
|
|
Repo: "repo1",
|
|
Schedule: &v1.Schedule{
|
|
Schedule: &v1.Schedule_MaxFrequencyHours{
|
|
MaxFrequencyHours: 1,
|
|
},
|
|
Clock: v1.Schedule_CLOCK_LAST_RUN_TIME,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
now := time.Unix(100000, 0) // 1000 seconds after the epoch as an arbitrary time for the test
|
|
farFuture := time.Unix(999999, 0)
|
|
|
|
tests := []struct {
|
|
name string
|
|
task Task
|
|
ops []*v1.Operation // operations in the log
|
|
wantTime time.Time // time to run the next task
|
|
}{
|
|
{
|
|
name: "backup schedule max frequency days",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-days")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-max-frequency-days",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour * 24),
|
|
},
|
|
{
|
|
name: "backup schedule min days since last run",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-days-since-last-run")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-min-days-since-last-run",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: farFuture.Add(time.Hour * 24),
|
|
},
|
|
{
|
|
name: "backup schedule max frequency hours",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-hours")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-max-frequency-hours",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "backup schedule min hours since last run",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-hours-since-last-run")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-min-hours-since-last-run",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: farFuture.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "backup schedule cron",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-cron",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: mustParseTime(t, "1970-01-02T00:00:00-08:00"),
|
|
},
|
|
{
|
|
name: "backup schedule cron utc",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron-utc")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-cron-utc",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: mustParseTime(t, "1970-01-02T08:00:00Z"),
|
|
},
|
|
{
|
|
name: "backup schedule cron since last run",
|
|
task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron-since-last-run")),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo1",
|
|
PlanId: "plan-cron-since-last-run",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: mustParseTime(t, "1970-01-13T00:00:00-08:00"),
|
|
},
|
|
{
|
|
name: "check schedule absolute",
|
|
task: NewCheckTask("repo-absolute", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-absolute",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationCheck{
|
|
OperationCheck: &v1.OperationCheck{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "check schedule relative no backup yet",
|
|
task: NewCheckTask("repo-relative", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationCheck{
|
|
OperationCheck: &v1.OperationCheck{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "check schedule relative",
|
|
task: NewCheckTask("repo-relative", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationCheck{
|
|
OperationCheck: &v1.OperationCheck{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: farFuture.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "prune schedule absolute",
|
|
task: NewPruneTask("repo-absolute", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-absolute",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationPrune{
|
|
OperationPrune: &v1.OperationPrune{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "prune schedule relative no backup yet",
|
|
task: NewPruneTask("repo-relative", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationPrune{
|
|
OperationPrune: &v1.OperationPrune{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: now.Add(time.Hour),
|
|
},
|
|
{
|
|
name: "prune schedule relative",
|
|
task: NewPruneTask("repo-relative", "_system_", false),
|
|
ops: []*v1.Operation{
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationPrune{
|
|
OperationPrune: &v1.OperationPrune{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
{
|
|
InstanceId: "instance1",
|
|
RepoId: "repo-relative",
|
|
PlanId: "_system_",
|
|
Op: &v1.Operation_OperationBackup{
|
|
OperationBackup: &v1.OperationBackup{},
|
|
},
|
|
UnixTimeEndMs: farFuture.UnixMilli(),
|
|
},
|
|
},
|
|
wantTime: farFuture.Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
opstore := memstore.NewMemStore()
|
|
for _, op := range tc.ops {
|
|
if err := opstore.Add(op); err != nil {
|
|
t.Fatalf("failed to add operation to opstore: %v", err)
|
|
}
|
|
}
|
|
|
|
log, err := oplog.NewOpLog(opstore)
|
|
if err != nil {
|
|
t.Fatalf("failed to create oplog: %v", err)
|
|
}
|
|
|
|
runner := newTestTaskRunner(t, cfg, log)
|
|
|
|
st, err := tc.task.Next(now, runner)
|
|
if err != nil {
|
|
t.Fatalf("failed to get next task: %v", err)
|
|
}
|
|
|
|
if !st.RunAt.Equal(tc.wantTime) {
|
|
t.Errorf("got run at %v, want %v", st.RunAt.Format(time.RFC3339), tc.wantTime.Format(time.RFC3339))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustParseTime(t *testing.T, s string) time.Time {
|
|
t.Helper()
|
|
tm, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse time: %v", err)
|
|
}
|
|
return tm
|
|
}
|