2017-08-02 17:27:17 +00:00
|
|
|
/*
|
2019-03-20 19:32:48 +00:00
|
|
|
Copyright 2017 the Velero contributors.
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package controller
|
|
|
|
|
|
|
|
import (
|
2017-12-11 22:10:52 +00:00
|
|
|
"encoding/json"
|
2017-08-02 17:27:17 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/robfig/cron"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2019-01-07 22:35:07 +00:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2017-08-02 17:27:17 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/util/clock"
|
|
|
|
core "k8s.io/client-go/testing"
|
|
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
|
2019-09-30 21:26:56 +00:00
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake"
|
|
|
|
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/metrics"
|
|
|
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestProcessSchedule(t *testing.T) {
|
2019-07-31 14:46:48 +00:00
|
|
|
newScheduleBuilder := func(phase velerov1api.SchedulePhase) *builder.ScheduleBuilder {
|
|
|
|
return builder.ForSchedule("ns", "name").Phase(phase)
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
tests := []struct {
|
2018-04-09 22:50:20 +00:00
|
|
|
name string
|
|
|
|
scheduleKey string
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule *velerov1api.Schedule
|
2018-04-09 22:50:20 +00:00
|
|
|
fakeClockTime string
|
|
|
|
expectedErr bool
|
|
|
|
expectedPhase string
|
|
|
|
expectedValidationErrors []string
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedBackupCreate *velerov1api.Backup
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedLastBackup string
|
2017-08-02 17:27:17 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "invalid key returns error",
|
|
|
|
scheduleKey: "invalid/key/value",
|
|
|
|
expectedErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "missing schedule returns early without an error",
|
|
|
|
scheduleKey: "foo/bar",
|
|
|
|
expectedErr: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "schedule with phase FailedValidation does not get processed",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseFailedValidation).Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
expectedErr: false,
|
|
|
|
},
|
|
|
|
{
|
2018-04-09 22:50:20 +00:00
|
|
|
name: "schedule with phase New gets validated and failed if invalid",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseNew).Result(),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedErr: false,
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedPhase: string(velerov1api.SchedulePhaseFailedValidation),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedValidationErrors: []string{"Schedule must be a non-empty valid Cron expression"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2018-04-09 22:50:20 +00:00
|
|
|
name: "schedule with phase <blank> gets validated and failed if invalid",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhase("")).Result(),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedErr: false,
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedPhase: string(velerov1api.SchedulePhaseFailedValidation),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedValidationErrors: []string{"Schedule must be a non-empty valid Cron expression"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2018-04-09 22:50:20 +00:00
|
|
|
name: "schedule with phase Enabled gets re-validated and failed if invalid",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseEnabled).Result(),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedErr: false,
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedPhase: string(velerov1api.SchedulePhaseFailedValidation),
|
2018-04-09 22:50:20 +00:00
|
|
|
expectedValidationErrors: []string{"Schedule must be a non-empty valid Cron expression"},
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2017-12-11 22:10:52 +00:00
|
|
|
name: "schedule with phase New gets validated and triggers a backup",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseNew).CronSchedule("@every 5m").Result(),
|
2017-12-11 22:10:52 +00:00
|
|
|
fakeClockTime: "2017-01-01 12:00:00",
|
|
|
|
expectedErr: false,
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedPhase: string(velerov1api.SchedulePhaseEnabled),
|
2019-08-23 20:03:51 +00:00
|
|
|
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
|
2017-12-11 22:10:52 +00:00
|
|
|
expectedLastBackup: "2017-01-01 12:00:00",
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "schedule with phase Enabled gets re-validated and triggers a backup if valid",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseEnabled).CronSchedule("@every 5m").Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
fakeClockTime: "2017-01-01 12:00:00",
|
|
|
|
expectedErr: false,
|
2019-08-23 20:03:51 +00:00
|
|
|
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
|
2017-12-11 22:10:52 +00:00
|
|
|
expectedLastBackup: "2017-01-01 12:00:00",
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2019-07-31 14:46:48 +00:00
|
|
|
name: "schedule that's already run gets LastBackup updated",
|
|
|
|
schedule: newScheduleBuilder(velerov1api.SchedulePhaseEnabled).CronSchedule("@every 5m").LastBackupTime("2000-01-01 00:00:00").Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
fakeClockTime: "2017-01-01 12:00:00",
|
|
|
|
expectedErr: false,
|
2019-08-23 20:03:51 +00:00
|
|
|
expectedBackupCreate: builder.ForBackup("ns", "name-20170101120000").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "name")).Result(),
|
2017-12-11 22:10:52 +00:00
|
|
|
expectedLastBackup: "2017-01-01 12:00:00",
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
var (
|
|
|
|
client = fake.NewSimpleClientset()
|
|
|
|
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
2019-01-25 03:33:07 +00:00
|
|
|
logger = velerotest.NewLogger()
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
c := NewScheduleController(
|
2017-12-22 14:43:44 +00:00
|
|
|
"namespace",
|
2019-01-25 03:33:07 +00:00
|
|
|
client.VeleroV1(),
|
|
|
|
client.VeleroV1(),
|
|
|
|
sharedInformers.Velero().V1().Schedules(),
|
2017-09-14 21:27:31 +00:00
|
|
|
logger,
|
2018-07-20 13:03:44 +00:00
|
|
|
metrics.NewServerMetrics(),
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testTime time.Time
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
if test.fakeClockTime != "" {
|
|
|
|
testTime, err = time.Parse("2006-01-02 15:04:05", test.fakeClockTime)
|
2017-08-07 15:20:38 +00:00
|
|
|
require.NoError(t, err, "unable to parse test.fakeClockTime: %v", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
c.clock = clock.NewFakeClock(testTime)
|
|
|
|
|
|
|
|
if test.schedule != nil {
|
2019-01-25 03:33:07 +00:00
|
|
|
sharedInformers.Velero().V1().Schedules().Informer().GetStore().Add(test.schedule)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-12-11 22:10:52 +00:00
|
|
|
// this is necessary so the Patch() call returns the appropriate object
|
|
|
|
client.PrependReactor("patch", "schedules", func(action core.Action) (bool, runtime.Object, error) {
|
|
|
|
var (
|
|
|
|
patch = action.(core.PatchAction).GetPatch()
|
|
|
|
patchMap = make(map[string]interface{})
|
|
|
|
res = test.schedule.DeepCopy()
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := json.Unmarshal(patch, &patchMap); err != nil {
|
|
|
|
t.Logf("error unmarshalling patch: %s\n", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
return false, nil, err
|
|
|
|
}
|
2017-12-11 22:10:52 +00:00
|
|
|
|
|
|
|
// these are the fields that may be updated by the controller
|
2019-01-07 22:35:07 +00:00
|
|
|
phase, found, err := unstructured.NestedString(patchMap, "status", "phase")
|
|
|
|
if err == nil && found {
|
2019-07-31 14:46:48 +00:00
|
|
|
res.Status.Phase = velerov1api.SchedulePhase(phase)
|
2017-12-11 22:10:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-07 22:35:07 +00:00
|
|
|
lastBackupStr, found, err := unstructured.NestedString(patchMap, "status", "lastBackup")
|
|
|
|
if err == nil && found {
|
2017-12-11 22:10:52 +00:00
|
|
|
parsed, err := time.Parse(time.RFC3339, lastBackupStr)
|
|
|
|
if err != nil {
|
|
|
|
t.Logf("error parsing status.lastBackup: %s\n", err)
|
|
|
|
return false, nil, err
|
|
|
|
}
|
2019-10-14 16:20:28 +00:00
|
|
|
res.Status.LastBackup = &metav1.Time{Time: parsed}
|
2017-12-11 22:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true, res, nil
|
2017-08-02 17:27:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
key := test.scheduleKey
|
|
|
|
if key == "" && test.schedule != nil {
|
|
|
|
key, err = cache.MetaNamespaceKeyFunc(test.schedule)
|
2017-08-07 15:20:38 +00:00
|
|
|
require.NoError(t, err, "error getting key from test.schedule: %v", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = c.processSchedule(key)
|
|
|
|
|
|
|
|
assert.Equal(t, test.expectedErr, err != nil, "got error %v", err)
|
|
|
|
|
2017-12-11 22:10:52 +00:00
|
|
|
actions := client.Actions()
|
|
|
|
index := 0
|
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
type PatchStatus struct {
|
2019-07-31 14:46:48 +00:00
|
|
|
ValidationErrors []string `json:"validationErrors"`
|
|
|
|
Phase velerov1api.SchedulePhase `json:"phase"`
|
|
|
|
LastBackup time.Time `json:"lastBackup"`
|
2018-04-09 22:50:20 +00:00
|
|
|
}
|
2017-12-11 22:10:52 +00:00
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
type Patch struct {
|
|
|
|
Status PatchStatus `json:"status"`
|
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
decode := func(decoder *json.Decoder) (interface{}, error) {
|
|
|
|
actual := new(Patch)
|
|
|
|
err := decoder.Decode(actual)
|
2017-12-11 22:10:52 +00:00
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
return *actual, err
|
|
|
|
}
|
2017-12-11 22:10:52 +00:00
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
if test.expectedPhase != "" {
|
|
|
|
require.True(t, len(actions) > index, "len(actions) is too small")
|
2017-12-11 22:10:52 +00:00
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
expected := Patch{
|
|
|
|
Status: PatchStatus{
|
|
|
|
ValidationErrors: test.expectedValidationErrors,
|
2019-07-31 14:46:48 +00:00
|
|
|
Phase: velerov1api.SchedulePhase(test.expectedPhase),
|
2018-04-09 22:50:20 +00:00
|
|
|
},
|
2017-12-11 22:10:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
velerotest.ValidatePatch(t, actions[index], expected, decode)
|
2017-12-11 22:10:52 +00:00
|
|
|
|
|
|
|
index++
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if created := test.expectedBackupCreate; created != nil {
|
2017-12-11 22:10:52 +00:00
|
|
|
require.True(t, len(actions) > index, "len(actions) is too small")
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
action := core.NewCreateAction(
|
2019-07-31 14:46:48 +00:00
|
|
|
velerov1api.SchemeGroupVersion.WithResource("backups"),
|
2017-08-02 17:27:17 +00:00
|
|
|
created.Namespace,
|
|
|
|
created)
|
|
|
|
|
2017-12-11 22:10:52 +00:00
|
|
|
assert.Equal(t, action, actions[index])
|
|
|
|
|
|
|
|
index++
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-12-11 22:10:52 +00:00
|
|
|
if test.expectedLastBackup != "" {
|
|
|
|
require.True(t, len(actions) > index, "len(actions) is too small")
|
|
|
|
|
2018-04-09 22:50:20 +00:00
|
|
|
expected := Patch{
|
|
|
|
Status: PatchStatus{
|
|
|
|
LastBackup: parseTime(test.expectedLastBackup),
|
|
|
|
},
|
|
|
|
}
|
2017-12-11 22:10:52 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
velerotest.ValidatePatch(t, actions[index], expected, decode)
|
2017-12-11 22:10:52 +00:00
|
|
|
}
|
2017-08-02 17:27:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-11 22:10:52 +00:00
|
|
|
func parseTime(timeString string) time.Time {
|
|
|
|
res, _ := time.Parse("2006-01-02 15:04:05", timeString)
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
func TestGetNextRunTime(t *testing.T) {
|
2019-07-31 14:46:48 +00:00
|
|
|
defaultSchedule := func() *velerov1api.Schedule {
|
|
|
|
return builder.ForSchedule("velero", "schedule-1").CronSchedule("@every 5m").Result()
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule *velerov1api.Schedule
|
2017-08-02 17:27:17 +00:00
|
|
|
lastRanOffset string
|
|
|
|
expectedDue bool
|
|
|
|
expectedNextRunTimeOffset string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "first run",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: defaultSchedule(),
|
2017-08-02 17:27:17 +00:00
|
|
|
expectedDue: true,
|
|
|
|
expectedNextRunTimeOffset: "5m",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "just ran",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: defaultSchedule(),
|
2017-08-02 17:27:17 +00:00
|
|
|
lastRanOffset: "0s",
|
|
|
|
expectedDue: false,
|
|
|
|
expectedNextRunTimeOffset: "5m",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "almost but not quite time to run",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: defaultSchedule(),
|
2017-08-02 17:27:17 +00:00
|
|
|
lastRanOffset: "4m59s",
|
|
|
|
expectedDue: false,
|
|
|
|
expectedNextRunTimeOffset: "5m",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "time to run again",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: defaultSchedule(),
|
2017-08-02 17:27:17 +00:00
|
|
|
lastRanOffset: "5m",
|
|
|
|
expectedDue: true,
|
|
|
|
expectedNextRunTimeOffset: "5m",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "several runs missed",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: defaultSchedule(),
|
2017-08-02 17:27:17 +00:00
|
|
|
lastRanOffset: "5h",
|
|
|
|
expectedDue: true,
|
|
|
|
expectedNextRunTimeOffset: "5m",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
cronSchedule, err := cron.Parse(test.schedule.Spec.Schedule)
|
2017-08-07 15:20:38 +00:00
|
|
|
require.NoError(t, err, "unable to parse test.schedule.Spec.Schedule: %v", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
testClock := clock.NewFakeClock(time.Now())
|
|
|
|
|
|
|
|
if test.lastRanOffset != "" {
|
|
|
|
offsetDuration, err := time.ParseDuration(test.lastRanOffset)
|
2017-08-07 15:20:38 +00:00
|
|
|
require.NoError(t, err, "unable to parse test.lastRanOffset: %v", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2019-10-14 16:20:28 +00:00
|
|
|
test.schedule.Status.LastBackup = &metav1.Time{Time: testClock.Now().Add(-offsetDuration)}
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nextRunTimeOffset, err := time.ParseDuration(test.expectedNextRunTimeOffset)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2019-10-14 16:20:28 +00:00
|
|
|
|
|
|
|
// calculate expected next run time (if the schedule hasn't run yet, this
|
|
|
|
// will be the zero value which will trigger an immediate backup)
|
|
|
|
var baseTime time.Time
|
|
|
|
if test.lastRanOffset != "" {
|
|
|
|
baseTime = test.schedule.Status.LastBackup.Time
|
|
|
|
}
|
|
|
|
expectedNextRunTime := baseTime.Add(nextRunTimeOffset)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
due, nextRunTime := getNextRunTime(test.schedule, cronSchedule, testClock.Now())
|
|
|
|
|
|
|
|
assert.Equal(t, test.expectedDue, due)
|
|
|
|
// ignore diffs of under a second. the cron library does some rounding.
|
|
|
|
assert.WithinDuration(t, expectedNextRunTime, nextRunTime, time.Second)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-10 16:44:00 +00:00
|
|
|
func TestParseCronSchedule(t *testing.T) {
|
2019-09-30 21:26:56 +00:00
|
|
|
// From https://github.com/vmware-tanzu/velero/issues/30, where we originally were using cron.Parse(),
|
2017-08-10 16:44:00 +00:00
|
|
|
// which treats the first field as seconds, and not minutes. We want to use cron.ParseStandard()
|
|
|
|
// instead, which has the first field as minutes.
|
|
|
|
|
|
|
|
now := time.Date(2017, 8, 10, 12, 27, 0, 0, time.UTC)
|
|
|
|
|
|
|
|
// Start with a Schedule with:
|
|
|
|
// - schedule: once a day at 9am
|
|
|
|
// - last backup: 2017-08-10 12:27:00 (just happened)
|
2019-07-31 14:46:48 +00:00
|
|
|
s := builder.ForSchedule("velero", "schedule-1").CronSchedule("0 9 * * *").LastBackupTime(now.Format("2006-01-02 15:04:05")).Result()
|
2017-08-10 16:44:00 +00:00
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
logger := velerotest.NewLogger()
|
2017-09-14 21:27:31 +00:00
|
|
|
|
|
|
|
c, errs := parseCronSchedule(s, logger)
|
2017-08-10 16:44:00 +00:00
|
|
|
require.Empty(t, errs)
|
|
|
|
|
|
|
|
// make sure we're not due and next backup is tomorrow at 9am
|
|
|
|
due, next := getNextRunTime(s, c, now)
|
|
|
|
assert.False(t, due)
|
|
|
|
assert.Equal(t, time.Date(2017, 8, 11, 9, 0, 0, 0, time.UTC), next)
|
|
|
|
|
|
|
|
// advance the clock a couple of hours and make sure nothing has changed
|
|
|
|
now = now.Add(2 * time.Hour)
|
|
|
|
due, next = getNextRunTime(s, c, now)
|
|
|
|
assert.False(t, due)
|
|
|
|
assert.Equal(t, time.Date(2017, 8, 11, 9, 0, 0, 0, time.UTC), next)
|
|
|
|
|
|
|
|
// advance clock to 1 minute after due time, make sure due=true
|
|
|
|
now = time.Date(2017, 8, 11, 9, 1, 0, 0, time.UTC)
|
|
|
|
due, next = getNextRunTime(s, c, now)
|
|
|
|
assert.True(t, due)
|
|
|
|
assert.Equal(t, time.Date(2017, 8, 11, 9, 0, 0, 0, time.UTC), next)
|
|
|
|
|
|
|
|
// record backup time
|
2019-10-14 16:20:28 +00:00
|
|
|
s.Status.LastBackup = &metav1.Time{Time: now}
|
2017-08-10 16:44:00 +00:00
|
|
|
|
|
|
|
// advance clock 1 minute, make sure we're not due and next backup is tomorrow at 9am
|
|
|
|
now = time.Date(2017, 8, 11, 9, 2, 0, 0, time.UTC)
|
|
|
|
due, next = getNextRunTime(s, c, now)
|
|
|
|
assert.False(t, due)
|
|
|
|
assert.Equal(t, time.Date(2017, 8, 12, 9, 0, 0, 0, time.UTC), next)
|
|
|
|
}
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
func TestGetBackup(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule *velerov1api.Schedule
|
2017-08-02 17:27:17 +00:00
|
|
|
testClockTime string
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedBackup *velerov1api.Backup
|
2017-08-02 17:27:17 +00:00
|
|
|
}{
|
|
|
|
{
|
2019-07-31 14:46:48 +00:00
|
|
|
name: "ensure name is formatted correctly (AM time)",
|
|
|
|
schedule: builder.ForSchedule("foo", "bar").Result(),
|
|
|
|
testClockTime: "2017-07-25 09:15:00",
|
|
|
|
expectedBackup: builder.ForBackup("foo", "bar-20170725091500").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "bar")).Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
2019-07-31 14:46:48 +00:00
|
|
|
name: "ensure name is formatted correctly (PM time)",
|
|
|
|
schedule: builder.ForSchedule("foo", "bar").Result(),
|
|
|
|
testClockTime: "2017-07-25 14:15:00",
|
|
|
|
expectedBackup: builder.ForBackup("foo", "bar-20170725141500").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "bar")).Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "ensure schedule backup template is copied",
|
2019-07-31 14:46:48 +00:00
|
|
|
schedule: builder.ForSchedule("foo", "bar").
|
|
|
|
Template(builder.ForBackup("", "").
|
|
|
|
IncludedNamespaces("ns-1", "ns-2").
|
|
|
|
ExcludedNamespaces("ns-3").
|
|
|
|
IncludedResources("foo", "bar").
|
|
|
|
ExcludedResources("baz").
|
|
|
|
LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"label": "value"}}).
|
|
|
|
TTL(time.Duration(300)).
|
|
|
|
Result().
|
|
|
|
Spec).
|
|
|
|
Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
testClockTime: "2017-07-25 09:15:00",
|
2019-07-31 14:46:48 +00:00
|
|
|
expectedBackup: builder.ForBackup("foo", "bar-20170725091500").
|
|
|
|
ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "bar")).
|
|
|
|
IncludedNamespaces("ns-1", "ns-2").
|
|
|
|
ExcludedNamespaces("ns-3").
|
|
|
|
IncludedResources("foo", "bar").
|
|
|
|
ExcludedResources("baz").
|
|
|
|
LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"label": "value"}}).
|
|
|
|
TTL(time.Duration(300)).
|
|
|
|
Result(),
|
2017-08-02 17:27:17 +00:00
|
|
|
},
|
2018-09-03 13:01:03 +00:00
|
|
|
{
|
2019-07-31 14:46:48 +00:00
|
|
|
name: "ensure schedule labels is copied",
|
|
|
|
schedule: builder.ForSchedule("foo", "bar").ObjectMeta(builder.WithLabels("foo", "bar", "bar", "baz")).Result(),
|
|
|
|
testClockTime: "2017-07-25 14:15:00",
|
|
|
|
expectedBackup: builder.ForBackup("foo", "bar-20170725141500").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "bar", "bar", "baz", "foo", "bar")).Result(),
|
2018-09-03 13:01:03 +00:00
|
|
|
},
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
testTime, err := time.Parse("2006-01-02 15:04:05", test.testClockTime)
|
2017-08-07 15:20:38 +00:00
|
|
|
require.NoError(t, err, "unable to parse test.testClockTime: %v", err)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
backup := getBackup(test.schedule, clock.NewFakeClock(testTime).Now())
|
|
|
|
|
|
|
|
assert.Equal(t, test.expectedBackup.Namespace, backup.Namespace)
|
|
|
|
assert.Equal(t, test.expectedBackup.Name, backup.Name)
|
2018-09-03 13:01:03 +00:00
|
|
|
assert.Equal(t, test.expectedBackup.Labels, backup.Labels)
|
2017-08-02 17:27:17 +00:00
|
|
|
assert.Equal(t, test.expectedBackup.Spec, backup.Spec)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|