From 9556a39a89105b4a32771ba03684b4279445987e Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Mon, 3 Nov 2025 15:09:37 +0800 Subject: [PATCH] repo provider interface refactor for repo static configuration Signed-off-by: Lyndon-Li --- changelogs/unreleased/9379-Lyndon-Li | 1 + pkg/repository/manager/manager.go | 68 +++++++++++++++++++- pkg/repository/manager/manager_test.go | 17 +++++ pkg/repository/mocks/ConfigManager.go | 84 +++++++++++++++++++++++++ pkg/repository/mocks/Manager.go | 42 ++++++++++--- pkg/repository/provider/provider.go | 14 ++++- pkg/repository/provider/restic.go | 6 +- pkg/repository/provider/unified_repo.go | 35 +++++++++-- 8 files changed, 249 insertions(+), 18 deletions(-) create mode 100644 changelogs/unreleased/9379-Lyndon-Li create mode 100644 pkg/repository/mocks/ConfigManager.go diff --git a/changelogs/unreleased/9379-Lyndon-Li b/changelogs/unreleased/9379-Lyndon-Li new file mode 100644 index 000000000..481bf8bdd --- /dev/null +++ b/changelogs/unreleased/9379-Lyndon-Li @@ -0,0 +1 @@ +Refactor repo provider interface for static configuration \ No newline at end of file diff --git a/pkg/repository/manager/manager.go b/pkg/repository/manager/manager.go index 7027c9650..ebbf2e8e2 100644 --- a/pkg/repository/manager/manager.go +++ b/pkg/repository/manager/manager.go @@ -60,7 +60,19 @@ type Manager interface { BatchForget(context.Context, *velerov1api.BackupRepository, []string) []error // DefaultMaintenanceFrequency returns the default maintenance frequency from the specific repo - DefaultMaintenanceFrequency(repo *velerov1api.BackupRepository) (time.Duration, error) + DefaultMaintenanceFrequency(*velerov1api.BackupRepository) (time.Duration, error) + + // ClientSideCacheLimit returns the max cache size required on client side + ClientSideCacheLimit(*velerov1api.BackupRepository) (int64, error) +} + +// ConfigProvider defines the methods to get configurations of a backup repository +type ConfigManager interface { + // DefaultMaintenanceFrequency returns the default maintenance frequency from the specific repo + DefaultMaintenanceFrequency(string) (time.Duration, error) + + // ClientSideCacheLimit returns the max cache size required on client side + ClientSideCacheLimit(string, map[string]string) (int64, error) } type manager struct { @@ -74,6 +86,11 @@ type manager struct { log logrus.FieldLogger } +type configManager struct { + providers map[string]provider.ConfigProvider + log logrus.FieldLogger +} + // NewManager create a new repository manager. func NewManager( namespace string, @@ -101,6 +118,20 @@ func NewManager( return mgr } +// NewConfigManager create a new repository config manager. +func NewConfigManager( + log logrus.FieldLogger, +) ConfigManager { + mgr := &configManager{ + providers: map[string]provider.ConfigProvider{}, + log: log, + } + + mgr.providers[velerov1api.BackupRepositoryTypeKopia] = provider.NewUnifiedRepoConfigProvider(velerov1api.BackupRepositoryTypeKopia, mgr.log) + + return mgr +} + func (m *manager) InitRepo(repo *velerov1api.BackupRepository) error { m.repoLocker.LockExclusive(repo.Name) defer m.repoLocker.UnlockExclusive(repo.Name) @@ -227,12 +258,16 @@ func (m *manager) DefaultMaintenanceFrequency(repo *velerov1api.BackupRepository return 0, errors.WithStack(err) } - param, err := m.assembleRepoParam(repo) + return prd.DefaultMaintenanceFrequency(), nil +} + +func (m *manager) ClientSideCacheLimit(repo *velerov1api.BackupRepository) (int64, error) { + prd, err := m.getRepositoryProvider(repo) if err != nil { return 0, errors.WithStack(err) } - return prd.DefaultMaintenanceFrequency(context.Background(), param), nil + return prd.ClientSideCacheLimit(repo.Spec.RepositoryConfig), nil } func (m *manager) getRepositoryProvider(repo *velerov1api.BackupRepository) (provider.Provider, error) { @@ -256,3 +291,30 @@ func (m *manager) assembleRepoParam(repo *velerov1api.BackupRepository) (provide BackupRepo: repo, }, nil } + +func (cm *configManager) DefaultMaintenanceFrequency(repoType string) (time.Duration, error) { + prd, err := cm.getRepositoryProvider(repoType) + if err != nil { + return 0, errors.WithStack(err) + } + + return prd.DefaultMaintenanceFrequency(), nil +} + +func (cm *configManager) ClientSideCacheLimit(repoType string, repoOption map[string]string) (int64, error) { + prd, err := cm.getRepositoryProvider(repoType) + if err != nil { + return 0, errors.WithStack(err) + } + + return prd.ClientSideCacheLimit(repoOption), nil +} + +func (cm *configManager) getRepositoryProvider(repoType string) (provider.ConfigProvider, error) { + switch repoType { + case velerov1api.BackupRepositoryTypeKopia: + return cm.providers[velerov1api.BackupRepositoryTypeKopia], nil + default: + return nil, fmt.Errorf("failed to get provider for repository %s", repoType) + } +} diff --git a/pkg/repository/manager/manager_test.go b/pkg/repository/manager/manager_test.go index d743e267a..ea38c7448 100644 --- a/pkg/repository/manager/manager_test.go +++ b/pkg/repository/manager/manager_test.go @@ -47,3 +47,20 @@ func TestGetRepositoryProvider(t *testing.T) { _, err = mgr.getRepositoryProvider(repo) require.Error(t, err) } + +func TestGetRepositoryConfigProvider(t *testing.T) { + mgr := NewConfigManager(nil).(*configManager) + + // empty repository type + _, err := mgr.getRepositoryProvider("") + require.Error(t, err) + + // valid repository type + provider, err := mgr.getRepositoryProvider(velerov1.BackupRepositoryTypeKopia) + require.NoError(t, err) + assert.NotNil(t, provider) + + // invalid repository type + _, err = mgr.getRepositoryProvider(velerov1.BackupRepositoryTypeRestic) + require.Error(t, err) +} diff --git a/pkg/repository/mocks/ConfigManager.go b/pkg/repository/mocks/ConfigManager.go new file mode 100644 index 000000000..590f49c33 --- /dev/null +++ b/pkg/repository/mocks/ConfigManager.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.2. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + time "time" +) + +// ConfigManager is an autogenerated mock type for the ConfigManager type +type ConfigManager struct { + mock.Mock +} + +// ClientSideCacheLimit provides a mock function with given fields: repoType, repoOption +func (_m *ConfigManager) ClientSideCacheLimit(repoType string, repoOption map[string]string) (int64, error) { + ret := _m.Called(repoType, repoOption) + + if len(ret) == 0 { + panic("no return value specified for ClientSideCacheLimit") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(string, map[string]string) (int64, error)); ok { + return rf(repoType, repoOption) + } + if rf, ok := ret.Get(0).(func(string, map[string]string) int64); ok { + r0 = rf(repoType, repoOption) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(string, map[string]string) error); ok { + r1 = rf(repoType, repoOption) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DefaultMaintenanceFrequency provides a mock function with given fields: repoType +func (_m *ConfigManager) DefaultMaintenanceFrequency(repoType string) (time.Duration, error) { + ret := _m.Called(repoType) + + if len(ret) == 0 { + panic("no return value specified for DefaultMaintenanceFrequency") + } + + var r0 time.Duration + var r1 error + if rf, ok := ret.Get(0).(func(string) (time.Duration, error)); ok { + return rf(repoType) + } + if rf, ok := ret.Get(0).(func(string) time.Duration); ok { + r0 = rf(repoType) + } else { + r0 = ret.Get(0).(time.Duration) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(repoType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewConfigManager creates a new instance of ConfigManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewConfigManager(t interface { + mock.TestingT + Cleanup(func()) +}) *ConfigManager { + mock := &ConfigManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/repository/mocks/Manager.go b/pkg/repository/mocks/Manager.go index 226411775..954967adc 100644 --- a/pkg/repository/mocks/Manager.go +++ b/pkg/repository/mocks/Manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.39.1. DO NOT EDIT. +// Code generated by mockery v2.53.2. DO NOT EDIT. package mocks @@ -37,6 +37,34 @@ func (_m *Manager) BatchForget(_a0 context.Context, _a1 *v1.BackupRepository, _a return r0 } +// ClientSideCacheLimit provides a mock function with given fields: _a0 +func (_m *Manager) ClientSideCacheLimit(_a0 *v1.BackupRepository) (int64, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ClientSideCacheLimit") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(*v1.BackupRepository) (int64, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*v1.BackupRepository) int64); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(*v1.BackupRepository) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ConnectToRepo provides a mock function with given fields: repo func (_m *Manager) ConnectToRepo(repo *v1.BackupRepository) error { ret := _m.Called(repo) @@ -55,9 +83,9 @@ func (_m *Manager) ConnectToRepo(repo *v1.BackupRepository) error { return r0 } -// DefaultMaintenanceFrequency provides a mock function with given fields: repo -func (_m *Manager) DefaultMaintenanceFrequency(repo *v1.BackupRepository) (time.Duration, error) { - ret := _m.Called(repo) +// DefaultMaintenanceFrequency provides a mock function with given fields: _a0 +func (_m *Manager) DefaultMaintenanceFrequency(_a0 *v1.BackupRepository) (time.Duration, error) { + ret := _m.Called(_a0) if len(ret) == 0 { panic("no return value specified for DefaultMaintenanceFrequency") @@ -66,16 +94,16 @@ func (_m *Manager) DefaultMaintenanceFrequency(repo *v1.BackupRepository) (time. var r0 time.Duration var r1 error if rf, ok := ret.Get(0).(func(*v1.BackupRepository) (time.Duration, error)); ok { - return rf(repo) + return rf(_a0) } if rf, ok := ret.Get(0).(func(*v1.BackupRepository) time.Duration); ok { - r0 = rf(repo) + r0 = rf(_a0) } else { r0 = ret.Get(0).(time.Duration) } if rf, ok := ret.Get(1).(func(*v1.BackupRepository) error); ok { - r1 = rf(repo) + r1 = rf(_a0) } else { r1 = ret.Error(1) } diff --git a/pkg/repository/provider/provider.go b/pkg/repository/provider/provider.go index 822e69aba..7681d830b 100644 --- a/pkg/repository/provider/provider.go +++ b/pkg/repository/provider/provider.go @@ -32,6 +32,8 @@ type RepoParam struct { // Provider defines the methods to manipulate a backup repository type Provider interface { + ConfigProvider + // InitRepo is to initialize a repository from a new storage place InitRepo(ctx context.Context, param RepoParam) error @@ -60,7 +62,13 @@ type Provider interface { // BatchForget is to delete a list of snapshots from the repository BatchForget(ctx context.Context, snapshotIDs []string, param RepoParam) []error - - // DefaultMaintenanceFrequency returns the default frequency to run maintenance - DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration +} + +// ConfigProvider defines the methods to get configurations of a backup repository +type ConfigProvider interface { + // DefaultMaintenanceFrequency returns the default frequency to run maintenance + DefaultMaintenanceFrequency() time.Duration + + // ClientSideCacheLimit returns the max cache size required on client side + ClientSideCacheLimit(repoOption map[string]string) int64 } diff --git a/pkg/repository/provider/restic.go b/pkg/repository/provider/restic.go index 3ca5988cb..ad4e0a257 100644 --- a/pkg/repository/provider/restic.go +++ b/pkg/repository/provider/restic.go @@ -90,6 +90,10 @@ func (r *resticRepositoryProvider) BatchForget(ctx context.Context, snapshotIDs return errs } -func (r *resticRepositoryProvider) DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration { +func (r *resticRepositoryProvider) DefaultMaintenanceFrequency() time.Duration { return r.svc.DefaultMaintenanceFrequency() } + +func (r *resticRepositoryProvider) ClientSideCacheLimit(repoOption map[string]string) int64 { + return 0 +} diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index c13665d2d..be3dfc089 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -46,6 +46,12 @@ type unifiedRepoProvider struct { log logrus.FieldLogger } +type unifiedRepoConfigProvider struct { + repoService udmrepo.BackupRepoService + repoBackend string + log logrus.FieldLogger +} + // this func is assigned to a package-level variable so it can be // replaced when unit-testing var getS3Credentials = repoconfig.GetS3Credentials @@ -86,9 +92,18 @@ func NewUnifiedRepoProvider( return &repo } -func GetUnifiedRepoClientSideCacheLimit(repoOption map[string]string, repoBackend string, log logrus.FieldLogger) int64 { - repoService := createRepoService(repoBackend, log) - return repoService.ClientSideCacheLimit(repoOption) +func NewUnifiedRepoConfigProvider( + repoBackend string, + log logrus.FieldLogger, +) ConfigProvider { + repo := unifiedRepoConfigProvider{ + repoBackend: repoBackend, + log: log, + } + + repo.repoService = createRepoService(repoBackend, log) + + return &repo } func (urp *unifiedRepoProvider) InitRepo(ctx context.Context, param RepoParam) error { @@ -375,10 +390,14 @@ func (urp *unifiedRepoProvider) BatchForget(ctx context.Context, snapshotIDs []s return errs } -func (urp *unifiedRepoProvider) DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration { +func (urp *unifiedRepoProvider) DefaultMaintenanceFrequency() time.Duration { return urp.repoService.DefaultMaintenanceFrequency() } +func (urp *unifiedRepoProvider) ClientSideCacheLimit(repoOption map[string]string) int64 { + return urp.repoService.ClientSideCacheLimit(repoOption) +} + func (urp *unifiedRepoProvider) GetPassword(param any) (string, error) { _, ok := param.(RepoParam) if !ok { @@ -429,6 +448,14 @@ func (urp *unifiedRepoProvider) GetStoreOptions(param any) (map[string]string, e return storeOptions, nil } +func (urcp *unifiedRepoConfigProvider) DefaultMaintenanceFrequency() time.Duration { + return urcp.repoService.DefaultMaintenanceFrequency() +} + +func (urcp *unifiedRepoConfigProvider) ClientSideCacheLimit(repoOption map[string]string) int64 { + return urcp.repoService.ClientSideCacheLimit(repoOption) +} + func getRepoPassword(secretStore credentials.SecretStore) (string, error) { if secretStore == nil { return "", errors.New("invalid credentials interface")