Files
kopia/internal/server/server_maintenance_test.go
Jarek Kowalski af1550ad81 feat(server): reduce server refreshes of the repository (#3265)
* feat(server): reduce server refreshes of the repository

Previously each source would refresh itself from the repository
very frequently to determine the upcoming snapshot time. This change
refactors source manager so it does not own the repository connection
on its own but instead delegates all policy reads through the server.

Also introduces a new server scheduler that is responsible for
centrally managing the snapshot schedule and triggering snapshots
when they are due.

* Update cli/command_server_start.go

Co-authored-by: Shikhar Mall <mall.shikhar.in@gmail.com>

* Update internal/server/server.go

Co-authored-by: Shikhar Mall <mall.shikhar.in@gmail.com>

* Update internal/server/server_maintenance.go

Co-authored-by: Shikhar Mall <mall.shikhar.in@gmail.com>

* pr feedback

---------

Co-authored-by: Shikhar Mall <mall.shikhar.in@gmail.com>
2023-09-15 12:08:35 -07:00

96 lines
2.2 KiB
Go

package server
import (
"context"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/maintenance"
)
type testServer struct {
mu sync.Mutex
runCounter atomic.Int32
refreshSchedulerCount atomic.Int32
log func(msg string, args ...any)
// +checklocks:mu
err error
}
func (s *testServer) runMaintenanceTask(ctx context.Context, dr repo.DirectRepository) error {
s.runCounter.Add(1)
if s.log != nil {
s.log("runMaintenanceTask")
}
s.mu.Lock()
ne := s.err
s.err = nil
s.mu.Unlock()
return ne
}
func (s *testServer) refreshScheduler(reason string) {
s.refreshSchedulerCount.Add(1)
}
func TestServerMaintenance(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
require.NoError(t, repo.DirectWriteSession(ctx, env.RepositoryWriter, repo.WriteSessionOptions{}, func(ctx context.Context, dw repo.DirectRepositoryWriter) error {
return maintenance.SetParams(ctx, dw, &maintenance.Params{
Owner: env.Repository.ClientOptions().UsernameAtHost(),
QuickCycle: maintenance.CycleParams{
Enabled: true,
Interval: 5 * time.Second,
},
FullCycle: maintenance.CycleParams{
Enabled: true,
Interval: 10 * time.Second,
},
})
}))
ts := &testServer{log: t.Logf}
mm := startMaintenanceManager(ctx, env.RepositoryWriter, ts, time.Minute)
require.Equal(t, time.Time{}, mm.nextMaintenanceNoEarlierThan)
defer mm.stop(ctx)
// trigger and make sure it runs
mm.trigger()
require.Eventually(t, func() bool {
return ts.runCounter.Load() == 1 && ts.refreshSchedulerCount.Load() == 1
}, 3*time.Second, 10*time.Millisecond)
ts.mu.Lock()
ts.err = errors.Errorf("some error")
ts.mu.Unlock()
mm.trigger()
require.Eventually(t, func() bool {
mm.mu.Lock()
defer mm.mu.Unlock()
return ts.runCounter.Load() == 2 && !mm.nextMaintenanceNoEarlierThan.IsZero()
}, 3*time.Second, 10*time.Millisecond)
// after a failure next maintenance time should be deferred by a minute.
require.Greater(t, mm.nextMaintenanceTime().Sub(clock.Now()), 50*time.Second)
}