mirror of
https://github.com/kopia/kopia.git
synced 2026-02-18 15:05:46 -05:00
Avoid starting a maintenance task on the server when the repository connection (configuration) is read-only. Also: * Check for read-only repo before running maintenance. * Move direct repo check to startMaintenanceManager. * Rename function as maybeStartMaintenanceManager. The name reflects that the function may not start a maintenance task manager. * Add connect options to repotesting. * Add test for maintenance on read-only repo. - Fixes: #4373
141 lines
3.5 KiB
Go
141 lines
3.5 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/notification/notifytemplate"
|
|
"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 (s *testServer) enableErrorNotifications() bool {
|
|
return false
|
|
}
|
|
|
|
func (s *testServer) notificationTemplateOptions() notifytemplate.Options {
|
|
return notifytemplate.DefaultOptions
|
|
}
|
|
|
|
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 := maybeStartMaintenanceManager(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.New("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)
|
|
}
|
|
|
|
func TestServerMaintenanceReadOnlyRepoConnection(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,
|
|
},
|
|
})
|
|
}))
|
|
|
|
dr, ok := env.Repository.(repo.DirectRepository)
|
|
require.True(t, ok, "not a direct repository connection")
|
|
|
|
dr.Refresh(ctx)
|
|
|
|
// make repo read-only
|
|
co := env.Repository.ClientOptions()
|
|
co.ReadOnly = true
|
|
|
|
repo.SetClientOptions(ctx, env.ConfigFile(), co)
|
|
|
|
env.MustReopen(t)
|
|
|
|
ts := &testServer{log: t.Logf}
|
|
mm := maybeStartMaintenanceManager(ctx, env.RepositoryWriter, ts, time.Minute)
|
|
|
|
require.Nil(t, mm, "maintenance task should not be created on read-only repo")
|
|
}
|