mirror of
https://github.com/kopia/kopia.git
synced 2026-01-23 05:47:57 -05:00
server: fixed next snapshot time computation (#1418)
Fixes #1405 Moved logic to SchedulingPolicy, added unit tests.
This commit is contained in:
@@ -312,29 +312,12 @@ func (s *sourceManager) snapshotInternal(ctx context.Context, ctrl uitask.Contro
|
||||
}
|
||||
|
||||
func (s *sourceManager) findClosestNextSnapshotTime() *time.Time {
|
||||
var nextSnapshotTime *time.Time
|
||||
|
||||
// compute next snapshot time based on interval
|
||||
if interval := s.pol.IntervalSeconds; interval != 0 {
|
||||
interval := time.Duration(interval) * time.Second
|
||||
nt := s.lastSnapshot.StartTime.Add(interval).Truncate(interval)
|
||||
nextSnapshotTime = &nt
|
||||
t, ok := s.pol.NextSnapshotTime(s.lastCompleteSnapshot.StartTime, clock.Now())
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tod := range s.pol.TimesOfDay {
|
||||
nowLocalTime := clock.Now().Local()
|
||||
localSnapshotTime := time.Date(nowLocalTime.Year(), nowLocalTime.Month(), nowLocalTime.Day(), tod.Hour, tod.Minute, 0, 0, time.Local)
|
||||
|
||||
if tod.Hour < nowLocalTime.Hour() || (tod.Hour == nowLocalTime.Hour() && tod.Minute < nowLocalTime.Minute()) {
|
||||
localSnapshotTime = localSnapshotTime.Add(oneDay)
|
||||
}
|
||||
|
||||
if nextSnapshotTime == nil || localSnapshotTime.Before(*nextSnapshotTime) {
|
||||
nextSnapshotTime = &localSnapshotTime
|
||||
}
|
||||
}
|
||||
|
||||
return nextSnapshotTime
|
||||
return &t
|
||||
}
|
||||
|
||||
func (s *sourceManager) refreshStatus(ctx context.Context) {
|
||||
|
||||
@@ -70,6 +70,46 @@ func (p *SchedulingPolicy) SetInterval(d time.Duration) {
|
||||
p.IntervalSeconds = int64(d.Seconds())
|
||||
}
|
||||
|
||||
// NextSnapshotTime computes next snapshot time given previous
|
||||
// snapshot time and current wall clock time.
|
||||
func (p *SchedulingPolicy) NextSnapshotTime(previousSnapshotTime, now time.Time) (time.Time, bool) {
|
||||
const oneDay = 24 * time.Hour
|
||||
|
||||
var (
|
||||
nextSnapshotTime time.Time
|
||||
ok bool
|
||||
)
|
||||
|
||||
// compute next snapshot time based on interval
|
||||
if interval := p.IntervalSeconds; interval != 0 {
|
||||
interval := time.Duration(interval) * time.Second
|
||||
|
||||
nt := previousSnapshotTime.Add(interval).Truncate(interval)
|
||||
nextSnapshotTime = nt
|
||||
ok = true
|
||||
|
||||
if nextSnapshotTime.Before(now) {
|
||||
nextSnapshotTime = now
|
||||
}
|
||||
}
|
||||
|
||||
for _, tod := range p.TimesOfDay {
|
||||
nowLocalTime := now.Local()
|
||||
localSnapshotTime := time.Date(nowLocalTime.Year(), nowLocalTime.Month(), nowLocalTime.Day(), tod.Hour, tod.Minute, 0, 0, time.Local)
|
||||
|
||||
if now.After(localSnapshotTime) {
|
||||
localSnapshotTime = localSnapshotTime.Add(oneDay)
|
||||
}
|
||||
|
||||
if !ok || localSnapshotTime.Before(nextSnapshotTime) {
|
||||
nextSnapshotTime = localSnapshotTime
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
return nextSnapshotTime, ok
|
||||
}
|
||||
|
||||
// Merge applies default values from the provided policy.
|
||||
func (p *SchedulingPolicy) Merge(src SchedulingPolicy) {
|
||||
if p.IntervalSeconds == 0 {
|
||||
|
||||
162
snapshot/policy/scheduling_policy_test.go
Normal file
162
snapshot/policy/scheduling_policy_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package policy_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/kopia/kopia/snapshot/policy"
|
||||
)
|
||||
|
||||
func TestNextSnapshotTime(t *testing.T) {
|
||||
cases := []struct {
|
||||
pol policy.SchedulingPolicy
|
||||
now time.Time
|
||||
previousSnapshotTime time.Time
|
||||
wantTime time.Time
|
||||
wantOK bool
|
||||
}{
|
||||
{}, // empty policy, no snapshot
|
||||
{
|
||||
// next snapshot is 1 minute after last, which is in the past
|
||||
pol: policy.SchedulingPolicy{IntervalSeconds: 60},
|
||||
now: time.Date(2020, time.January, 1, 12, 3, 0, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 12, 3, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{IntervalSeconds: 60},
|
||||
now: time.Date(2020, time.January, 1, 11, 50, 30, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 51, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{IntervalSeconds: 300},
|
||||
now: time.Date(2020, time.January, 1, 11, 50, 30, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 51, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 55, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
// next time after 11:50 truncated to 20 full minutes, which is 12:00
|
||||
pol: policy.SchedulingPolicy{IntervalSeconds: 1200},
|
||||
now: time.Date(2020, time.January, 1, 11, 50, 30, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
// next time after 11:50 truncated to 20 full minutes, which is 12:00
|
||||
pol: policy.SchedulingPolicy{IntervalSeconds: 1200},
|
||||
now: time.Date(2020, time.January, 1, 11, 50, 30, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 55}, {11, 57}},
|
||||
},
|
||||
now: time.Date(2020, time.January, 1, 11, 50, 30, 0, time.Local),
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 55, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 55}, {11, 57}},
|
||||
},
|
||||
now: time.Date(2020, time.January, 1, 11, 55, 30, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 57, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
IntervalSeconds: 300, // every 5 minutes
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 53, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 54, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
IntervalSeconds: 300, // every 5 minutes
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 54, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 54, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
IntervalSeconds: 300, // every 5 minutes
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 54, 1, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 55, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
IntervalSeconds: 300, // every 5 minutes
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 55, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 55, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
IntervalSeconds: 300, // every 5 minutes
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 55, 1, 0, time.Local),
|
||||
// interval-based snapshot is overdue
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 55, 1, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 56, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 57, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 57, 0, 0, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 1, 11, 57, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
pol: policy.SchedulingPolicy{
|
||||
TimesOfDay: []policy.TimeOfDay{{11, 54}, {11, 57}},
|
||||
},
|
||||
previousSnapshotTime: time.Date(2020, time.January, 1, 11, 50, 0, 0, time.Local),
|
||||
now: time.Date(2020, time.January, 1, 11, 57, 0, 1, time.Local),
|
||||
wantTime: time.Date(2020, time.January, 2, 11, 54, 0, 0, time.Local),
|
||||
wantOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
gotTime, gotOK := tc.pol.NextSnapshotTime(tc.previousSnapshotTime, tc.now)
|
||||
|
||||
require.Equal(t, tc.wantTime, gotTime)
|
||||
require.Equal(t, tc.wantOK, gotOK)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user