diff --git a/cmd/backup/main.go b/cmd/backup/main.go index a9f96bee..3b1da77e 100644 --- a/cmd/backup/main.go +++ b/cmd/backup/main.go @@ -73,7 +73,7 @@ func startService(configFile string, remote bool) error { return fmt.Errorf("failed to load configuration: %w", err) } - appLogger := setDefaultLoggers(ctx, config) + appLogger := setDefaultLoggers(ctx, config.ServiceConfig.GetLoggerOrDefault()) slog.Info("Aerospike Backup Service", "commit", commit, "buildTime", buildTime) // schedule all configured backups @@ -83,13 +83,12 @@ func startService(configFile string, remote bool) error { configApplier := service.NewDefaultConfigApplier( scheduler, - config, backends, clientManager, backupHandlers, ) - err = configApplier.ApplyNewConfig(ctx) + err = configApplier.ApplyNewRoutines(ctx, config.Routines()) if err != nil { return fmt.Errorf("failed to apply new config: %w", err) } @@ -98,7 +97,7 @@ func startService(configFile string, remote bool) error { service.NewMetricsCollector(backupHandlers, restoreJobs).Start(ctx, 1*time.Second) restoreMgr := service.NewRestoreManager( - backends, config, service.NewRestore(), clientManager, restoreJobs, nsValidator) + backends, service.NewRestore(), clientManager, restoreJobs, nsValidator) httpService := handlers.NewService( config, @@ -121,9 +120,9 @@ func startService(configFile string, remote bool) error { return err } -func setDefaultLoggers(ctx context.Context, config *model.Config) *slog.Logger { +func setDefaultLoggers(ctx context.Context, loggerConfig *model.LoggerConfig) *slog.Logger { appLogger := slog.New( - util.LogHandler(config.ServiceConfig.GetLoggerOrDefault()), + util.LogHandler(loggerConfig), ) slog.SetDefault(appLogger) logger.SetDefault(util.NewQuartzLogger(ctx)) diff --git a/internal/server/handlers/backup.go b/internal/server/handlers/backup.go index d2a9ccfe..5f17e242 100644 --- a/internal/server/handlers/backup.go +++ b/internal/server/handlers/backup.go @@ -103,7 +103,7 @@ func (s *Service) readAllBackups(w http.ResponseWriter, r *http.Request, isFullB return } - response, err := dto.Serialize(dto.ConvertBackupDetailsMap(backups, s.config), dto.JSON) + response, err := dto.Serialize(dto.ConvertBackupDetailsMap(backups, s.config.BackupConfigCopy()), dto.JSON) if err != nil { hLogger.Error("failed to marshal backup list", slog.Any("error", err), @@ -169,8 +169,9 @@ func (s *Service) readBackupsForRoutine(w http.ResponseWriter, r *http.Request, http.Error(w, "failed to retrieve backup list: "+err.Error(), http.StatusInternalServerError) return } + backupConfig := s.config.BackupConfigCopy() backupDetails := dto.ConvertModelsToDTO(backups, func(m *model.BackupDetails) *dto.BackupDetails { - return dto.NewBackupDetailsFromModel(m, s.config) + return dto.NewBackupDetailsFromModel(m, backupConfig) }) response, err := dto.Serialize(backupDetails, dto.JSON) if err != nil { diff --git a/internal/server/handlers/config.go b/internal/server/handlers/config.go index 3cc0e429..e0206d9c 100644 --- a/internal/server/handlers/config.go +++ b/internal/server/handlers/config.go @@ -76,8 +76,9 @@ func (s *Service) updateConfig(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } + err = s.changeConfig(r.Context(), func(config *model.Config) error { - config.CopyFrom(newConfigModel) + config.SetBackupConfig(newConfigModel.BackupConfigCopy()) return nil }) @@ -112,6 +113,7 @@ func (s *Service) ApplyConfig(w http.ResponseWriter, r *http.Request) { return } + // validate static fields. newConfig := dto.NewConfigFromModel(s.config) oldConfig := dto.NewConfigFromModel(config) if err := validation.ValidateStaticFieldChanges(oldConfig, newConfig); err != nil { @@ -123,7 +125,10 @@ func (s *Service) ApplyConfig(w http.ResponseWriter, r *http.Request) { return } - err = s.applyConfig(r.Context(), config) + backupConfig := config.BackupConfigCopy() + s.config.SetBackupConfig(backupConfig) + err = s.configApplier.ApplyNewRoutines(r.Context(), backupConfig.BackupRoutines) + if err != nil { hLogger.Error("failed to apply config", slog.Any("error", err), @@ -136,9 +141,6 @@ func (s *Service) ApplyConfig(w http.ResponseWriter, r *http.Request) { } func (s *Service) changeConfig(ctx context.Context, updateFunc func(*model.Config) error) error { - s.Lock() - defer s.Unlock() - err := updateFunc(s.config) if err != nil { return fmt.Errorf("cannot update configuration: %w", err) @@ -149,19 +151,10 @@ func (s *Service) changeConfig(ctx context.Context, updateFunc func(*model.Confi return fmt.Errorf("failed to write configuration: %w", err) } - err = s.configApplier.ApplyNewConfig(ctx) + err = s.configApplier.ApplyNewRoutines(ctx, s.config.Routines()) if err != nil { return fmt.Errorf("failed to apply new configuration: %w", err) } return nil } - -func (s *Service) applyConfig(ctx context.Context, c *model.Config) error { - s.Lock() - defer s.Unlock() - - s.config.CopyFrom(c) - - return s.configApplier.ApplyNewConfig(ctx) -} diff --git a/internal/server/handlers/config_cluster.go b/internal/server/handlers/config_cluster.go index da498032..e45bdf4d 100644 --- a/internal/server/handlers/config_cluster.go +++ b/internal/server/handlers/config_cluster.go @@ -89,8 +89,10 @@ func (s *Service) addAerospikeCluster(w http.ResponseWriter, r *http.Request) { func (s *Service) ReadAerospikeClusters(w http.ResponseWriter, _ *http.Request) { hLogger := s.logger.With(slog.String("handler", "ReadAerospikeClusters")) - toDTO := dto.ConvertModelMapToDTO(s.config.AerospikeClusters, func(m *model.AerospikeCluster) *dto.AerospikeCluster { - return dto.NewClusterFromModel(m, s.config) + backupConfig := s.config.BackupConfigCopy() + clusters := backupConfig.AerospikeClusters + toDTO := dto.ConvertModelMapToDTO(clusters, func(m *model.AerospikeCluster) *dto.AerospikeCluster { + return dto.NewClusterFromModel(m, backupConfig) }) jsonResponse, err := dto.Serialize(toDTO, dto.JSON) if err != nil { @@ -132,7 +134,8 @@ func (s *Service) readAerospikeCluster(w http.ResponseWriter, r *http.Request) { http.Error(w, clusterNameNotSpecifiedMsg, http.StatusBadRequest) return } - cluster, ok := s.config.AerospikeClusters[clusterName] + backupConfig := s.config.BackupConfigCopy() + cluster, ok := backupConfig.AerospikeClusters[clusterName] if !ok { hLogger.Error("cluster not found", slog.String("name", clusterName), @@ -140,7 +143,7 @@ func (s *Service) readAerospikeCluster(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("cluster %s could not be found", clusterName), http.StatusNotFound) return } - jsonResponse, err := dto.Serialize(dto.NewClusterFromModel(cluster, s.config), dto.JSON) + jsonResponse, err := dto.Serialize(dto.NewClusterFromModel(cluster, backupConfig), dto.JSON) if err != nil { hLogger.Error("failed to marshal cluster", slog.Any("error", err), @@ -194,7 +197,7 @@ func (s *Service) updateAerospikeCluster(w http.ResponseWriter, r *http.Request) return } - err = s.nsValidator.ValidateRoutines(cluster, s.config) + err = s.nsValidator.ValidateRoutines(cluster, s.config.Routines()) if err != nil { hLogger.Error("cluster namespace validation failed", slog.String("name", clusterName), diff --git a/internal/server/handlers/config_policy.go b/internal/server/handlers/config_policy.go index be758b65..b8a17264 100644 --- a/internal/server/handlers/config_policy.go +++ b/internal/server/handlers/config_policy.go @@ -81,7 +81,7 @@ func (s *Service) addPolicy(w http.ResponseWriter, r *http.Request) { func (s *Service) ReadPolicies(w http.ResponseWriter, _ *http.Request) { hLogger := s.logger.With(slog.String("handler", "ReadPolicies")) - policies := dto.ConvertModelMapToDTO(s.config.BackupPolicies, dto.NewBackupPolicyFromModel) + policies := dto.ConvertModelMapToDTO(s.config.BackupConfigCopy().BackupPolicies, dto.NewBackupPolicyFromModel) jsonResponse, err := dto.Serialize(policies, dto.JSON) if err != nil { hLogger.Error("failed to marshal backup policies", @@ -121,7 +121,7 @@ func (s *Service) readPolicy(w http.ResponseWriter, r *http.Request) { http.Error(w, policyNameNotSpecifiedMsg, http.StatusBadRequest) return } - policy, ok := s.config.BackupPolicies[policyName] + policy, ok := s.config.BackupConfigCopy().BackupPolicies[policyName] if !ok { hLogger.Error("policy not found") http.Error(w, fmt.Sprintf("policy %s could not be found", policyName), http.StatusNotFound) diff --git a/internal/server/handlers/config_routine.go b/internal/server/handlers/config_routine.go index 72e130e5..52722de3 100644 --- a/internal/server/handlers/config_routine.go +++ b/internal/server/handlers/config_routine.go @@ -55,7 +55,7 @@ func (s *Service) addRoutine(w http.ResponseWriter, r *http.Request) { http.Error(w, routineNameNotSpecifiedMsg, http.StatusBadRequest) return } - toModel, err := newRoutine.ToModel(s.config, s.nsValidator) + toModel, err := newRoutine.ToModel(s.config.BackupConfigCopy(), s.nsValidator) if err != nil { hLogger.Error("failed to create routine", slog.String("name", name), @@ -91,7 +91,7 @@ func (s *Service) addRoutine(w http.ResponseWriter, r *http.Request) { func (s *Service) ReadRoutines(w http.ResponseWriter, _ *http.Request) { hLogger := s.logger.With(slog.String("handler", "ReadRoutines")) - toDTO := dto.ConvertModelMapToDTO(s.config.BackupRoutines, func(m *model.BackupRoutine) *dto.BackupRoutine { + toDTO := dto.ConvertModelMapToDTO(s.config.Routines(), func(m *model.BackupRoutine) *dto.BackupRoutine { return dto.NewRoutineFromModel(m, s.config) }) @@ -124,8 +124,6 @@ func (s *Service) ReadRoutines(w http.ResponseWriter, _ *http.Request) { // @Success 200 {object} dto.BackupRoutine // @Response 400 {string} string // @Failure 404 {string} string "The specified cluster could not be found" -// -//nolint:dupl func (s *Service) readRoutine(w http.ResponseWriter, r *http.Request) { hLogger := s.logger.With(slog.String("handler", "readRoutine")) @@ -135,7 +133,7 @@ func (s *Service) readRoutine(w http.ResponseWriter, r *http.Request) { http.Error(w, routineNameNotSpecifiedMsg, http.StatusBadRequest) return } - routine, ok := s.config.BackupRoutines[routineName] + routine, ok := s.config.Routines()[routineName] if !ok { http.Error(w, fmt.Sprintf("Routine %s could not be found", routineName), http.StatusNotFound) return @@ -190,7 +188,7 @@ func (s *Service) updateRoutine(w http.ResponseWriter, r *http.Request) { return } - toModel, err := updatedRoutine.ToModel(s.config, s.nsValidator) + toModel, err := updatedRoutine.ToModel(s.config.BackupConfigCopy(), s.nsValidator) if err != nil { hLogger.Error("failed to create routine", slog.String("name", name), diff --git a/internal/server/handlers/config_storage.go b/internal/server/handlers/config_storage.go index 9f055c04..a7ccb8df 100644 --- a/internal/server/handlers/config_storage.go +++ b/internal/server/handlers/config_storage.go @@ -82,7 +82,8 @@ func (s *Service) addStorage(w http.ResponseWriter, r *http.Request) { func (s *Service) ReadAllStorage(w http.ResponseWriter, _ *http.Request) { hLogger := s.logger.With(slog.String("handler", "ReadAllStorage")) - toDTO := dto.ConvertStorageMapToDTO(s.config.Storage, s.config) + backupConfig := s.config.BackupConfigCopy() + toDTO := dto.ConvertStorageMapToDTO(backupConfig.Storage, backupConfig) jsonResponse, err := dto.Serialize(toDTO, dto.JSON) if err != nil { hLogger.Error("failed to marshal storage", @@ -113,8 +114,6 @@ func (s *Service) ReadAllStorage(w http.ResponseWriter, _ *http.Request) { // @Response 400 {string} string // @Failure 404 {string} string "The specified storage could not be found" // @Failure 500 {string} string -// -//nolint:dupl func (s *Service) readStorage(w http.ResponseWriter, r *http.Request) { hLogger := s.logger.With(slog.String("handler", "readStorage")) @@ -124,13 +123,14 @@ func (s *Service) readStorage(w http.ResponseWriter, r *http.Request) { http.Error(w, storageNameNotSpecifiedMsg, http.StatusBadRequest) return } - storage, ok := s.config.Storage[storageName] + backupConfig := s.config.BackupConfigCopy() + storage, ok := backupConfig.Storage[storageName] if !ok { http.Error(w, fmt.Sprintf("Storage %s could not be found", storageName), http.StatusNotFound) return } - jsonResponse, err := dto.Serialize(dto.NewStorageFromModel(storage, s.config), dto.JSON) + jsonResponse, err := dto.Serialize(dto.NewStorageFromModel(storage, backupConfig), dto.JSON) if err != nil { hLogger.Error("failed to marshal storage", slog.Any("error", err), diff --git a/internal/server/handlers/handlers_test.go b/internal/server/handlers/handlers_test.go index 24848004..a69d84ba 100644 --- a/internal/server/handlers/handlers_test.go +++ b/internal/server/handlers/handlers_test.go @@ -183,7 +183,7 @@ func (mock backendsHolderMock) Get(routineName string) (*service.BackupBackend, return &service.BackupBackend{}, true } -func (mock backendsHolderMock) Init(_ *model.Config) { +func (mock backendsHolderMock) Init(_ map[string]*model.BackupRoutine) { } func (mock backendsHolderMock) GetAllReaders() map[string]service.BackupListReader { @@ -209,7 +209,7 @@ func (mock configurationManagerMock) Write(_ context.Context, config *model.Conf type MockConfigApplier struct{} -func (a *MockConfigApplier) ApplyNewConfig(context.Context) error { +func (a *MockConfigApplier) ApplyNewRoutines(_ context.Context, _ map[string]*model.BackupRoutine) error { return nil } @@ -219,7 +219,7 @@ func (m *MockNamespaceValidator) MissingNamespaces(_ *model.AerospikeCluster, _ return nil } -func (m *MockNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ *model.Config) error { +func (m *MockNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ map[string]*model.BackupRoutine) error { return nil } diff --git a/internal/server/handlers/service.go b/internal/server/handlers/service.go index 3f8df245..037673b8 100644 --- a/internal/server/handlers/service.go +++ b/internal/server/handlers/service.go @@ -2,7 +2,6 @@ package handlers import ( "log/slog" - "sync" "github.com/aerospike/aerospike-backup-service/v3/internal/server/configuration" "github.com/aerospike/aerospike-backup-service/v3/pkg/model" @@ -12,7 +11,6 @@ import ( ) type Service struct { - sync.Mutex config *model.Config configApplier service.ConfigApplier scheduler quartz.Scheduler diff --git a/pkg/dto/aerospike_cluster.go b/pkg/dto/aerospike_cluster.go index bbe79ce6..14203ec0 100644 --- a/pkg/dto/aerospike_cluster.go +++ b/pkg/dto/aerospike_cluster.go @@ -63,7 +63,7 @@ func NewClusterFromReader(r io.Reader, format SerializationFormat) (*AerospikeCl return a, nil } -func NewClusterFromModel(m *model.AerospikeCluster, config *model.Config) *AerospikeCluster { +func NewClusterFromModel(m *model.AerospikeCluster, config *model.BackupConfig) *AerospikeCluster { if m == nil { return nil } @@ -73,7 +73,7 @@ func NewClusterFromModel(m *model.AerospikeCluster, config *model.Config) *Aeros return a } -func (a *AerospikeCluster) fromModel(m *model.AerospikeCluster, config *model.Config) { +func (a *AerospikeCluster) fromModel(m *model.AerospikeCluster, config *model.BackupConfig) { a.ClusterLabel = m.ClusterLabel a.SeedNodes = make([]SeedNode, len(m.SeedNodes)) for i, v := range m.SeedNodes { @@ -181,7 +181,7 @@ type Credentials struct { AuthMode *string `yaml:"auth-mode,omitempty" json:"auth-mode,omitempty" enums:"INTERNAL,EXTERNAL,PKI"` } -func (c *Credentials) fromModel(m *model.Credentials, config *model.Config) { +func (c *Credentials) fromModel(m *model.Credentials, config *model.BackupConfig) { c.User = m.User c.Password = m.Password c.PasswordPath = m.PasswordPath diff --git a/pkg/dto/backup_details.go b/pkg/dto/backup_details.go index d8010f25..c2019d99 100644 --- a/pkg/dto/backup_details.go +++ b/pkg/dto/backup_details.go @@ -35,7 +35,18 @@ type BackupMetadata struct { UDFCount uint64 `yaml:"udf-count" json:"udf-count" format:"int64" example:"2"` } -func (d *BackupDetails) fromModel(m *model.BackupDetails, config *model.Config) { +// NewBackupDetailsFromModel creates a new BackupDetails from a model.BackupDetails +func NewBackupDetailsFromModel(m *model.BackupDetails, config *model.BackupConfig) *BackupDetails { + if m == nil { + return nil + } + + var d BackupDetails + d.fromModel(m, config) + return &d +} + +func (d *BackupDetails) fromModel(m *model.BackupDetails, config *model.BackupConfig) { d.Key = m.Key d.Created = m.Created d.From = m.From @@ -48,19 +59,8 @@ func (d *BackupDetails) fromModel(m *model.BackupDetails, config *model.Config) d.Storage = NewStorageFromModel(m.Storage, config) } -// NewBackupDetailsFromModel creates a new BackupDetails from a model.BackupDetails -func NewBackupDetailsFromModel(m *model.BackupDetails, config *model.Config) *BackupDetails { - if m == nil { - return nil - } - - var d BackupDetails - d.fromModel(m, config) - return &d -} - func ConvertBackupDetailsMap( - modelMap map[string][]model.BackupDetails, config *model.Config, + modelMap map[string][]model.BackupDetails, config *model.BackupConfig, ) map[string][]BackupDetails { result := make(map[string][]BackupDetails, len(modelMap)) for key, modelSlice := range modelMap { diff --git a/pkg/dto/backup_routine.go b/pkg/dto/backup_routine.go index c90b7458..80e9099c 100644 --- a/pkg/dto/backup_routine.go +++ b/pkg/dto/backup_routine.go @@ -84,7 +84,7 @@ func (r *BackupRoutine) Validate() error { } func (r *BackupRoutine) ToModel( - config *model.Config, + config *model.BackupConfig, nsValidator aerospike.NamespaceValidator, ) (*model.BackupRoutine, error) { policy, found := config.BackupPolicies[r.BackupPolicy] @@ -158,11 +158,11 @@ func NewRoutineFromModel(m *model.BackupRoutine, config *model.Config) *BackupRo } b := &BackupRoutine{} - b.fromModel(m, config) + b.fromModel(m, config.BackupConfigCopy()) return b } -func (r *BackupRoutine) fromModel(m *model.BackupRoutine, config *model.Config) { +func (r *BackupRoutine) fromModel(m *model.BackupRoutine, config *model.BackupConfig) { r.BackupPolicy = findKeyByValue(config.BackupPolicies, m.BackupPolicy) r.SourceCluster = findKeyByValue(config.AerospikeClusters, m.SourceCluster) r.Storage = findStorageKey(config.Storage, m.Storage) diff --git a/pkg/dto/base_dto.go b/pkg/dto/base_dto.go index 128fd891..b92697d2 100644 --- a/pkg/dto/base_dto.go +++ b/pkg/dto/base_dto.go @@ -84,7 +84,7 @@ func ConvertModelMapToDTO[M any, D any](modelMap map[string]*M, dtoConstructor f } // ConvertStorageMapToDTO converts a map of models to a map of DTOs -func ConvertStorageMapToDTO(modelMap map[string]model.Storage, config *model.Config) map[string]*Storage { +func ConvertStorageMapToDTO(modelMap map[string]model.Storage, config *model.BackupConfig) map[string]*Storage { result := make(map[string]*Storage, len(modelMap)) for key, s := range modelMap { result[key] = NewStorageFromModel(s, config) diff --git a/pkg/dto/config.go b/pkg/dto/config.go index 81684896..570aaa85 100644 --- a/pkg/dto/config.go +++ b/pkg/dto/config.go @@ -33,29 +33,30 @@ func NewConfigFromModel(m *model.Config) *Config { func (c *Config) fromModel(m *model.Config) { c.ServiceConfig.fromModel(&m.ServiceConfig) + backupConfig := m.BackupConfigCopy() c.AerospikeClusters = make(map[string]*AerospikeCluster) - for name, a := range m.AerospikeClusters { - c.AerospikeClusters[name] = NewClusterFromModel(a, m) + for name, a := range backupConfig.AerospikeClusters { + c.AerospikeClusters[name] = NewClusterFromModel(a, backupConfig) } c.Storage = make(map[string]*Storage) - for name, s := range m.Storage { - c.Storage[name] = NewStorageFromModel(s, m) + for name, s := range backupConfig.Storage { + c.Storage[name] = NewStorageFromModel(s, backupConfig) } c.BackupPolicies = make(map[string]*BackupPolicy) - for name, p := range m.BackupPolicies { + for name, p := range backupConfig.BackupPolicies { c.BackupPolicies[name] = NewBackupPolicyFromModel(p) } c.BackupRoutines = make(map[string]*BackupRoutine) - for name, r := range m.BackupRoutines { + for name, r := range backupConfig.BackupRoutines { c.BackupRoutines[name] = NewRoutineFromModel(r, m) } c.SecretAgents = make(map[string]*SecretAgent) - for name, s := range m.SecretAgents { + for name, s := range backupConfig.SecretAgents { c.SecretAgents[name] = newSecretAgentFromModel(s) } } @@ -172,9 +173,10 @@ func (c *Config) ToModel(nsValidator aerospike.NamespaceValidator) (*model.Confi } } + backupConfig := modelConfig.BackupConfigCopy() // routines must be added after storage, secret agents and policies. for k, v := range c.BackupRoutines { - toModel, err := v.ToModel(modelConfig, nsValidator) + toModel, err := v.ToModel(backupConfig, nsValidator) if err != nil { return nil, fmt.Errorf("invalid backup routine %q: %w", k, err) } diff --git a/pkg/dto/config_test.go b/pkg/dto/config_test.go index 5f40a6c1..410f179b 100644 --- a/pkg/dto/config_test.go +++ b/pkg/dto/config_test.go @@ -64,7 +64,7 @@ func (m *MockNamespaceValidator) MissingNamespaces(_ *model.AerospikeCluster, _ return nil } -func (m *MockNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ *model.Config) error { +func (m *MockNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ map[string]*model.BackupRoutine) error { return nil } diff --git a/pkg/dto/restore_request.go b/pkg/dto/restore_request.go index 7d4baafc..b4ae6194 100644 --- a/pkg/dto/restore_request.go +++ b/pkg/dto/restore_request.go @@ -76,7 +76,7 @@ func (r *RestoreTimestampRequest) ToModel(config *model.Config) (*model.RestoreT if err != nil { return nil, fmt.Errorf("invalid cluster: %w", err) } - if _, ok := config.BackupRoutines[r.Routine]; !ok { + if _, ok := config.Routines()[r.Routine]; !ok { return nil, notFoundValidationError("routine", r.Routine) } diff --git a/pkg/dto/secret_agent.go b/pkg/dto/secret_agent.go index a9d827aa..88ee3379 100644 --- a/pkg/dto/secret_agent.go +++ b/pkg/dto/secret_agent.go @@ -66,7 +66,7 @@ func (s *SecretAgent) ToModel() *model.SecretAgent { } } -func ResolveSecretAgentFromModel(s *model.SecretAgent, config *model.Config) SecretAgentConfig { +func ResolveSecretAgentFromModel(s *model.SecretAgent, config *model.BackupConfig) SecretAgentConfig { secretAgentName := findKeyByValue(config.SecretAgents, s) if secretAgentName != "" { return SecretAgentConfig{ diff --git a/pkg/dto/storage.go b/pkg/dto/storage.go index d3a4488d..1d687218 100644 --- a/pkg/dto/storage.go +++ b/pkg/dto/storage.go @@ -75,7 +75,7 @@ func (s *Storage) ToModel(c *model.Config) (model.Storage, error) { } // NewStorageFromModel creates a new Storage DTO from the model. -func NewStorageFromModel(m model.Storage, config *model.Config) *Storage { +func NewStorageFromModel(m model.Storage, config *model.BackupConfig) *Storage { switch s := m.(type) { case *model.LocalStorage: return &Storage{ diff --git a/pkg/dto/storage_azure.go b/pkg/dto/storage_azure.go index c3b35cf1..06f28a1c 100644 --- a/pkg/dto/storage_azure.go +++ b/pkg/dto/storage_azure.go @@ -84,7 +84,7 @@ func getAzureAuth(a *AzureStorage) model.AzureAuth { return nil } -func newAzureStorageFromModel(s *model.AzureStorage, config *model.Config) *AzureStorage { +func newAzureStorageFromModel(s *model.AzureStorage, config *model.BackupConfig) *AzureStorage { azureStorage := &AzureStorage{ Endpoint: s.Endpoint, ContainerName: s.ContainerName, diff --git a/pkg/dto/storage_gcp.go b/pkg/dto/storage_gcp.go index 92f900f7..01dd1de5 100644 --- a/pkg/dto/storage_gcp.go +++ b/pkg/dto/storage_gcp.go @@ -50,7 +50,7 @@ func (s *GcpStorage) toModel(config *model.Config) (model.Storage, error) { }, nil } -func newGcpStorageFromModel(s *model.GcpStorage, config *model.Config) *GcpStorage { +func newGcpStorageFromModel(s *model.GcpStorage, config *model.BackupConfig) *GcpStorage { return &GcpStorage{ KeyFile: s.KeyFile, BucketName: s.BucketName, diff --git a/pkg/dto/storage_s3.go b/pkg/dto/storage_s3.go index c50605b4..d685dc95 100644 --- a/pkg/dto/storage_s3.go +++ b/pkg/dto/storage_s3.go @@ -83,7 +83,7 @@ func (s *S3Storage) toModel(config *model.Config) (*model.S3Storage, error) { }, nil } -func newS3StorageFromModel(s *model.S3Storage, config *model.Config) *S3Storage { +func newS3StorageFromModel(s *model.S3Storage, config *model.BackupConfig) *S3Storage { result := &S3Storage{ Bucket: s.Bucket, Path: s.Path, diff --git a/pkg/model/backup_details.go b/pkg/model/backup_details.go index 388930da..2c27c000 100644 --- a/pkg/model/backup_details.go +++ b/pkg/model/backup_details.go @@ -40,6 +40,9 @@ type BackupMetadata struct { // NewMetadataFromBytes creates a new Metadata object from a byte slice func NewMetadataFromBytes(data []byte) (*BackupMetadata, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty metadata file") + } var metadata BackupMetadata err := yaml.Unmarshal(data, &metadata) if err != nil { diff --git a/pkg/model/config.go b/pkg/model/config.go index ff475057..33c9ec12 100644 --- a/pkg/model/config.go +++ b/pkg/model/config.go @@ -2,13 +2,18 @@ package model import ( "fmt" + "maps" "sync" ) // Config represents the service configuration. type Config struct { - mu sync.Mutex - ServiceConfig BackupServiceConfig + mu sync.RWMutex + backupConfig BackupConfig + ServiceConfig BackupServiceConfig +} + +type BackupConfig struct { AerospikeClusters map[string]*AerospikeCluster Storage map[string]Storage // Storage is an interface BackupPolicies map[string]*BackupPolicy @@ -16,8 +21,30 @@ type Config struct { SecretAgents map[string]*SecretAgent } -func NewConfig() *Config { - return &Config{ +func (bc *BackupConfig) copy() *BackupConfig { + if bc == nil { + return nil + } + + newConfig := &BackupConfig{ + AerospikeClusters: make(map[string]*AerospikeCluster, len(bc.AerospikeClusters)), + Storage: make(map[string]Storage, len(bc.Storage)), + BackupPolicies: make(map[string]*BackupPolicy, len(bc.BackupPolicies)), + BackupRoutines: make(map[string]*BackupRoutine, len(bc.BackupRoutines)), + SecretAgents: make(map[string]*SecretAgent, len(bc.SecretAgents)), + } + + maps.Copy(newConfig.AerospikeClusters, bc.AerospikeClusters) + maps.Copy(newConfig.Storage, bc.Storage) + maps.Copy(newConfig.BackupRoutines, bc.BackupRoutines) + maps.Copy(newConfig.BackupPolicies, bc.BackupPolicies) + maps.Copy(newConfig.SecretAgents, bc.SecretAgents) + + return newConfig +} + +func newBackupConfig() *BackupConfig { + return &BackupConfig{ AerospikeClusters: make(map[string]*AerospikeCluster), Storage: make(map[string]Storage), BackupPolicies: make(map[string]*BackupPolicy), @@ -26,20 +53,32 @@ func NewConfig() *Config { } } +func NewConfig() *Config { + return &Config{ + backupConfig: *newBackupConfig(), + } +} + var ( ErrAlreadyExists = fmt.Errorf("item already exists") ErrNotFound = fmt.Errorf("item not found") ErrInUse = fmt.Errorf("item is in use") ) +func (c *Config) BackupConfigCopy() *BackupConfig { + c.mu.RLock() + defer c.mu.RUnlock() + return c.backupConfig.copy() +} + func (c *Config) AddStorage(name string, s Storage) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.Storage[name]; exists { + if _, exists := c.backupConfig.Storage[name]; exists { return fmt.Errorf("add storage %q: %w", name, ErrAlreadyExists) } - c.Storage[name] = s + c.backupConfig.Storage[name] = s return nil } @@ -47,14 +86,14 @@ func (c *Config) DeleteStorage(name string) error { c.mu.Lock() defer c.mu.Unlock() - s, exists := c.Storage[name] + s, exists := c.backupConfig.Storage[name] if !exists { return fmt.Errorf("delete storage %q: %w", name, ErrNotFound) } if routine := c.routineUsesStorage(s); routine != "" { return fmt.Errorf("delete storage %q: %w: it is used in routine %q", name, ErrInUse, routine) } - delete(c.Storage, name) + delete(c.backupConfig.Storage, name) return nil } @@ -62,24 +101,24 @@ func (c *Config) UpdateStorage(name string, s Storage) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.Storage[name]; !exists { + if _, exists := c.backupConfig.Storage[name]; !exists { return fmt.Errorf("update storage %q: %w", name, ErrNotFound) } - oldStorage := c.Storage[name] - for _, r := range c.BackupRoutines { + oldStorage := c.backupConfig.Storage[name] + for _, r := range c.backupConfig.BackupRoutines { if r.Storage == oldStorage { r.Storage = s } } - c.Storage[name] = s + c.backupConfig.Storage[name] = s return nil } func (c *Config) routineUsesStorage(s Storage) string { - for name, r := range c.BackupRoutines { + for name, r := range c.backupConfig.BackupRoutines { if r.Storage == s { return name } @@ -91,10 +130,10 @@ func (c *Config) AddPolicy(name string, p *BackupPolicy) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.BackupPolicies[name]; exists { + if _, exists := c.backupConfig.BackupPolicies[name]; exists { return fmt.Errorf("add backup policy %q: %w", name, ErrAlreadyExists) } - c.BackupPolicies[name] = p + c.backupConfig.BackupPolicies[name] = p return nil } @@ -102,14 +141,14 @@ func (c *Config) DeletePolicy(name string) error { c.mu.Lock() defer c.mu.Unlock() - p, exists := c.BackupPolicies[name] + p, exists := c.backupConfig.BackupPolicies[name] if !exists { return fmt.Errorf("delete backup policy %q: %w", name, ErrNotFound) } if routine := c.routineUsesPolicy(p); routine != "" { return fmt.Errorf("delete backup policy %q: %w: it is used in routine %q", name, ErrInUse, routine) } - delete(c.BackupPolicies, name) + delete(c.backupConfig.BackupPolicies, name) return nil } @@ -117,23 +156,23 @@ func (c *Config) UpdatePolicy(name string, p *BackupPolicy) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.BackupPolicies[name]; !exists { + if _, exists := c.backupConfig.BackupPolicies[name]; !exists { return fmt.Errorf("update backup policy %q: %w", name, ErrNotFound) } - oldPolicy := c.BackupPolicies[name] - for _, r := range c.BackupRoutines { + oldPolicy := c.backupConfig.BackupPolicies[name] + for _, r := range c.backupConfig.BackupRoutines { if r.BackupPolicy == oldPolicy { r.BackupPolicy = p } } - c.BackupPolicies[name] = p + c.backupConfig.BackupPolicies[name] = p return nil } func (c *Config) routineUsesPolicy(p *BackupPolicy) string { - for name, r := range c.BackupRoutines { + for name, r := range c.backupConfig.BackupRoutines { if r.BackupPolicy == p { return name } @@ -141,14 +180,24 @@ func (c *Config) routineUsesPolicy(p *BackupPolicy) string { return "" } +func (c *Config) Routines() map[string]*BackupRoutine { + c.mu.RLock() + defer c.mu.RUnlock() + + routines := make(map[string]*BackupRoutine, len(c.backupConfig.BackupRoutines)) + maps.Copy(routines, c.backupConfig.BackupRoutines) + + return routines +} + func (c *Config) AddRoutine(name string, r *BackupRoutine) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.BackupRoutines[name]; exists { + if _, exists := c.backupConfig.BackupRoutines[name]; exists { return fmt.Errorf("add backup routine %q: %w", name, ErrAlreadyExists) } - c.BackupRoutines[name] = r + c.backupConfig.BackupRoutines[name] = r return nil } @@ -156,10 +205,10 @@ func (c *Config) DeleteRoutine(name string) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.BackupRoutines[name]; !exists { + if _, exists := c.backupConfig.BackupRoutines[name]; !exists { return fmt.Errorf("delete backup routine %q: %w", name, ErrNotFound) } - delete(c.BackupRoutines, name) + delete(c.backupConfig.BackupRoutines, name) return nil } @@ -167,10 +216,10 @@ func (c *Config) UpdateRoutine(name string, r *BackupRoutine) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.BackupRoutines[name]; !exists { + if _, exists := c.backupConfig.BackupRoutines[name]; !exists { return fmt.Errorf("update backup routine %q: %w", name, ErrNotFound) } - c.BackupRoutines[name] = r + c.backupConfig.BackupRoutines[name] = r return nil } @@ -178,10 +227,10 @@ func (c *Config) AddCluster(name string, cluster *AerospikeCluster) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.AerospikeClusters[name]; exists { + if _, exists := c.backupConfig.AerospikeClusters[name]; exists { return fmt.Errorf("add Aerospike cluster %q: %w", name, ErrAlreadyExists) } - c.AerospikeClusters[name] = cluster + c.backupConfig.AerospikeClusters[name] = cluster return nil } @@ -189,14 +238,14 @@ func (c *Config) DeleteCluster(name string) error { c.mu.Lock() defer c.mu.Unlock() - cluster, exists := c.AerospikeClusters[name] + cluster, exists := c.backupConfig.AerospikeClusters[name] if !exists { return fmt.Errorf("delete Aerospike cluster %q: %w", name, ErrNotFound) } if routine := c.routineUsesCluster(cluster); routine != "" { return fmt.Errorf("delete Aerospike cluster %q: %w: it is used in routine %q", name, ErrInUse, routine) } - delete(c.AerospikeClusters, name) + delete(c.backupConfig.AerospikeClusters, name) return nil } @@ -204,24 +253,24 @@ func (c *Config) UpdateCluster(name string, cluster *AerospikeCluster) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.AerospikeClusters[name]; !exists { + if _, exists := c.backupConfig.AerospikeClusters[name]; !exists { return fmt.Errorf("update Aerospike cluster %q: %w", name, ErrNotFound) } - oldCluster := c.AerospikeClusters[name] - for _, r := range c.BackupRoutines { + oldCluster := c.backupConfig.AerospikeClusters[name] + for _, r := range c.backupConfig.BackupRoutines { if r.SourceCluster == oldCluster { r.SourceCluster = cluster } } - c.AerospikeClusters[name] = cluster + c.backupConfig.AerospikeClusters[name] = cluster return nil } func (c *Config) routineUsesCluster(cluster *AerospikeCluster) string { - for name, r := range c.BackupRoutines { + for name, r := range c.backupConfig.BackupRoutines { if r.SourceCluster == cluster { return name } @@ -233,29 +282,25 @@ func (c *Config) AddSecretAgent(name string, agent *SecretAgent) error { c.mu.Lock() defer c.mu.Unlock() - if _, exists := c.SecretAgents[name]; exists { + if _, exists := c.backupConfig.SecretAgents[name]; exists { return fmt.Errorf("add Secret agent %q: %w", name, ErrAlreadyExists) } - c.SecretAgents[name] = agent + c.backupConfig.SecretAgents[name] = agent return nil } -func (c *Config) CopyFrom(other *Config) { +func (c *Config) SetBackupConfig(other *BackupConfig) { c.mu.Lock() - other.mu.Lock() defer c.mu.Unlock() - defer other.mu.Unlock() - - c.AerospikeClusters = other.AerospikeClusters - c.Storage = other.Storage - c.BackupPolicies = other.BackupPolicies - c.BackupRoutines = other.BackupRoutines - c.SecretAgents = other.SecretAgents + c.backupConfig = *other } func (c *Config) ResolveSecretAgent(name *string, defaultAgent *SecretAgent) (*SecretAgent, error) { + c.mu.RLock() + defer c.mu.RUnlock() + if name != nil { - agent, ok := c.SecretAgents[*name] + agent, ok := c.backupConfig.SecretAgents[*name] if !ok { return nil, fmt.Errorf("unknown secret agent %q", *name) } @@ -271,11 +316,11 @@ func (c *Config) ToggleRoutineDisabled(name string, isDisabled bool) error { c.mu.Lock() defer c.mu.Unlock() - _, exists := c.BackupRoutines[name] + _, exists := c.backupConfig.BackupRoutines[name] if !exists { return fmt.Errorf("toggle disable for backup routine %q: %w", name, ErrNotFound) } - c.BackupRoutines[name].Disabled = isDisabled + c.backupConfig.BackupRoutines[name].Disabled = isDisabled return nil } diff --git a/pkg/model/restore_request.go b/pkg/model/restore_request.go index 8ba23237..e29c4aef 100644 --- a/pkg/model/restore_request.go +++ b/pkg/model/restore_request.go @@ -57,11 +57,13 @@ func NewRestoreRequest( policy *RestorePolicy, sourceStorage Storage, secretAgent *SecretAgent, + backupDataPath string, ) *RestoreRequest { return &RestoreRequest{ DestinationCluster: destinationCluster, Policy: policy, SourceStorage: sourceStorage, SecretAgent: secretAgent, + BackupDataPath: backupDataPath, } } diff --git a/pkg/service/aerospike/namespace_validator.go b/pkg/service/aerospike/namespace_validator.go index 6eabee0f..be63b6ff 100644 --- a/pkg/service/aerospike/namespace_validator.go +++ b/pkg/service/aerospike/namespace_validator.go @@ -20,7 +20,7 @@ type NamespaceValidator interface { MissingNamespaces(cluster *model.AerospikeCluster, namespaces []string) []string // ValidateRoutines verifies that all namespaces referenced in backup routines // exist in their respective clusters. - ValidateRoutines(cluster *model.AerospikeCluster, config *model.Config) error + ValidateRoutines(cluster *model.AerospikeCluster, routines map[string]*model.BackupRoutine) error } type defaultNamespaceValidator struct { @@ -56,10 +56,11 @@ func (nv *defaultNamespaceValidator) MissingNamespaces( return util.MissingElements(namespaces, namespacesInCluster) } -func (nv *defaultNamespaceValidator) ValidateRoutines(cluster *model.AerospikeCluster, config *model.Config) error { +func (nv *defaultNamespaceValidator) ValidateRoutines( + cluster *model.AerospikeCluster, routines map[string]*model.BackupRoutine, +) error { var err error - routines := filterRoutinesByCluster(config.BackupRoutines, cluster) - for routineName, routine := range routines { + for routineName, routine := range filterRoutinesByCluster(routines, cluster) { missingNamespaces := nv.MissingNamespaces(cluster, routine.Namespaces) if len(missingNamespaces) > 0 { err = errors.Join(err, fmt.Errorf("cluster is missing namespaces %v that are used in routine %v", @@ -118,6 +119,6 @@ func (n *NoopNamespaceValidator) MissingNamespaces(_ *model.AerospikeCluster, _ } // ValidateRoutines returns nil, indicating no error in validation. -func (n *NoopNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ *model.Config) error { +func (n *NoopNamespaceValidator) ValidateRoutines(_ *model.AerospikeCluster, _ map[string]*model.BackupRoutine) error { return nil } diff --git a/pkg/service/backup_backends_holder.go b/pkg/service/backup_backends_holder.go index 50aa80ff..3af2d646 100644 --- a/pkg/service/backup_backends_holder.go +++ b/pkg/service/backup_backends_holder.go @@ -10,7 +10,7 @@ import ( // We need it because same backends are used in API handlers and backup jobs. type BackendsHolder interface { // Init creates new backends from config. - Init(config *model.Config) + Init(routines map[string]*model.BackupRoutine) // GetReader returns BackupBackend for routine as BackupListReader. GetReader(routineName string) (BackupListReader, bool) // Get returns BackupBackend for routine. @@ -24,11 +24,10 @@ type BackendHolderImpl struct { data map[string]*BackupBackend } -func (b *BackendHolderImpl) Init(config *model.Config) { +func (b *BackendHolderImpl) Init(routines map[string]*model.BackupRoutine) { b.Lock() defer b.Unlock() - routines := config.BackupRoutines b.data = make(map[string]*BackupBackend, len(routines)) for routineName, routine := range routines { b.data[routineName] = newBackend(routineName, routine.Storage) diff --git a/pkg/service/backup_routine_handler.go b/pkg/service/backup_routine_handler.go index fc27d168..73f606a9 100644 --- a/pkg/service/backup_routine_handler.go +++ b/pkg/service/backup_routine_handler.go @@ -97,28 +97,27 @@ type BackupHandlerHolder map[string]backupRunner // newBackupRoutineHandler returns a new BackupRoutineHandler instance. func newBackupRoutineHandler( - config *model.Config, clientManager aerospike.ClientManager, backupService Backup, routineName string, + routine *model.BackupRoutine, backupBackend *BackupBackend, lastRun *model.LastBackupRun, ) *BackupRoutineHandler { - backupRoutine := config.BackupRoutines[routineName] - backupPolicy := backupRoutine.BackupPolicy - backupStorage := backupRoutine.Storage + backupPolicy := routine.BackupPolicy + backupStorage := routine.Storage logger := slog.Default().With(slog.String("routine", routineName)) return &BackupRoutineHandler{ backupService: backupService, metadataWriter: backupBackend, - backupRoutine: backupRoutine, + backupRoutine: routine, backupFullPolicy: backupPolicy, backupIncrPolicy: backupPolicy.CopySMDDisabled(), // incremental backups should not contain metadata routineName: routineName, - namespaces: backupRoutine.Namespaces, + namespaces: routine.Namespaces, storage: backupStorage, - secretAgent: backupRoutine.SecretAgent, + secretAgent: routine.SecretAgent, lastRun: lastRun, retry: newRetryExecutor( backupPolicy.GetRetryPolicyOrDefault(), diff --git a/pkg/service/backup_scheduler_test.go b/pkg/service/backup_scheduler_test.go index a46cf50e..2b670eef 100644 --- a/pkg/service/backup_scheduler_test.go +++ b/pkg/service/backup_scheduler_test.go @@ -27,19 +27,16 @@ func TestDisabledRoutine(t *testing.T) { mockScheduler := new(MockScheduler) mockScheduler.On("ScheduleJob", mock.Anything, mock.Anything).Return(nil) - config := &model.Config{ - BackupRoutines: map[string]*model.BackupRoutine{ - "routine1": {Disabled: true}, - "routine2": {Disabled: false, IntervalCron: "@daily"}, - }, - } + config := model.NewConfig() + _ = config.AddRoutine("routine1", &model.BackupRoutine{Disabled: true}) + _ = config.AddRoutine("routine2", &model.BackupRoutine{IntervalCron: "@daily"}) handlers := BackupHandlerHolder{ "routine1": &BackupRoutineHandler{}, "routine2": &BackupRoutineHandler{lastRun: model.NewLastBackupRun(util.Ptr(time.Now()), nil)}, } - err := scheduleRoutines(mockScheduler, config.BackupRoutines, handlers) + err := scheduleRoutines(mockScheduler, config.Routines(), handlers) require.NoError(t, err) mockScheduler.AssertNumberOfCalls(t, "ScheduleJob", 1) diff --git a/pkg/service/config_applier.go b/pkg/service/config_applier.go index 032cb296..f3415d0d 100644 --- a/pkg/service/config_applier.go +++ b/pkg/service/config_applier.go @@ -14,13 +14,11 @@ import ( // ConfigApplier is responsible for applying new configuration to the service. type ConfigApplier interface { - ApplyNewConfig(context.Context) error + ApplyNewRoutines(ctx context.Context, routines map[string]*model.BackupRoutine) error } type DefaultConfigApplier struct { - sync.Mutex scheduler quartz.Scheduler - config *model.Config backends BackendsHolder clientManager aerospike.ClientManager handlerHolder BackupHandlerHolder @@ -28,38 +26,33 @@ type DefaultConfigApplier struct { func NewDefaultConfigApplier( scheduler quartz.Scheduler, - config *model.Config, backends BackendsHolder, manager aerospike.ClientManager, handlerHolder BackupHandlerHolder, ) ConfigApplier { return &DefaultConfigApplier{ scheduler: scheduler, - config: config, backends: backends, clientManager: manager, handlerHolder: handlerHolder, } } -func (a *DefaultConfigApplier) ApplyNewConfig(ctx context.Context) error { - a.Lock() - defer a.Unlock() - +func (a *DefaultConfigApplier) ApplyNewRoutines(ctx context.Context, routines map[string]*model.BackupRoutine) error { err := a.clearPeriodicSchedulerJobs() if err != nil { return fmt.Errorf("failed to clear periodic jobs: %w", err) } - a.backends.Init(a.config) + a.backends.Init(routines) // Refill handlers - newHandlers := makeHandlers(ctx, a.clientManager, a.config, a.backends, a.handlerHolder) + newHandlers := makeHandlers(ctx, a.clientManager, routines, a.backends, a.handlerHolder) clear(a.handlerHolder) for k, v := range newHandlers { (a.handlerHolder)[k] = v } - err = scheduleRoutines(a.scheduler, a.config.BackupRoutines, a.handlerHolder) + err = scheduleRoutines(a.scheduler, routines, a.handlerHolder) if err != nil { return fmt.Errorf("failed to schedule periodic backups: %w", err) } @@ -88,7 +81,7 @@ func (a *DefaultConfigApplier) clearPeriodicSchedulerJobs() error { func makeHandlers( ctx context.Context, clientManager aerospike.ClientManager, - config *model.Config, + routines map[string]*model.BackupRoutine, backends BackendsHolder, oldHandlers BackupHandlerHolder, ) BackupHandlerHolder { @@ -96,11 +89,11 @@ func makeHandlers( var wg sync.WaitGroup var mu sync.Mutex - for routineName := range config.BackupRoutines { + for routineName, routine := range routines { wg.Add(1) go func() { defer wg.Done() - handler := makeHandler(ctx, clientManager, config, backends, oldHandlers, routineName) + handler := makeHandler(ctx, clientManager, backends, oldHandlers, routineName, routine) mu.Lock() handlers[routineName] = handler mu.Unlock() @@ -114,10 +107,10 @@ func makeHandlers( func makeHandler( ctx context.Context, clientManager aerospike.ClientManager, - config *model.Config, backends BackendsHolder, oldHandlers BackupHandlerHolder, routineName string, + routine *model.BackupRoutine, ) *BackupRoutineHandler { backupService := NewBackupGo() backend, _ := backends.Get(routineName) @@ -130,5 +123,5 @@ func makeHandler( lastRun = backend.findLastRun(ctx) // this scan can take some time. } - return newBackupRoutineHandler(config, clientManager, backupService, routineName, backend, lastRun) + return newBackupRoutineHandler(clientManager, backupService, routineName, routine, backend, lastRun) } diff --git a/pkg/service/restore_config.go b/pkg/service/restore_config.go index 4d62226d..2b59582a 100644 --- a/pkg/service/restore_config.go +++ b/pkg/service/restore_config.go @@ -8,6 +8,7 @@ import ( "github.com/aerospike/aerospike-backup-service/v3/pkg/util" ) +// configRetriever is used to read Aerospike configuration from backup. type configRetriever struct { backends BackendsHolder } diff --git a/pkg/service/restore_data.go b/pkg/service/restore_data.go index 017bf273..a333e7df 100644 --- a/pkg/service/restore_data.go +++ b/pkg/service/restore_data.go @@ -33,7 +33,6 @@ func NewErrJobNotFound(id model.RestoreJobID) *ErrJobNotFound { // Stores job information locally within a map. type dataRestorer struct { configRetriever - config *model.Config restoreJobs *RestoreJobsHolder restoreService Restore backends BackendsHolder @@ -45,7 +44,6 @@ var _ RestoreManager = (*dataRestorer)(nil) // NewRestoreManager returns a new dataRestorer instance. func NewRestoreManager(backends BackendsHolder, - config *model.Config, restoreService Restore, clientManager aerospike.ClientManager, restoreJobs *RestoreJobsHolder, @@ -58,7 +56,6 @@ func NewRestoreManager(backends BackendsHolder, restoreJobs: restoreJobs, restoreService: restoreService, backends: backends, - config: config, clientManager: clientManager, nsValidator: nsValidator, } @@ -205,7 +202,7 @@ func (r *dataRestorer) restoreNamespace( continue } - handler, err := r.restoreFromPath(ctx, client, request, b.Key) + handler, err := r.restoreFromPath(ctx, client, request, b.Key, fullBackup.Storage) if err != nil { return err } @@ -231,9 +228,15 @@ func (r *dataRestorer) restoreFromPath( client *backup.Client, request *model.RestoreTimestampRequest, backupPath string, + storage model.Storage, ) (RestoreHandler, error) { - restoreRequest := r.toRestoreRequest(request) - restoreRequest.BackupDataPath = backupPath + restoreRequest := model.NewRestoreRequest( + request.DestinationCluster, + request.Policy, + storage, + request.SecretAgent, + backupPath, + ) handler, err := r.restoreService.Run(ctx, client, restoreRequest) if err != nil { return nil, fmt.Errorf("could not start restore from backup at %s: %w", backupPath, err) @@ -242,16 +245,6 @@ func (r *dataRestorer) restoreFromPath( return handler, nil } -func (r *dataRestorer) toRestoreRequest(request *model.RestoreTimestampRequest) *model.RestoreRequest { - routine := r.config.BackupRoutines[request.RoutineName] - return model.NewRestoreRequest( - request.DestinationCluster, - request.Policy, - routine.Storage, - request.SecretAgent, - ) -} - // JobStatus returns the status of the job with the given id. func (r *dataRestorer) JobStatus(jobID model.RestoreJobID) (*model.RestoreJobStatus, error) { return r.restoreJobs.getStatus(jobID) diff --git a/pkg/service/restore_data_test.go b/pkg/service/restore_data_test.go index fe81b6e7..72cb5766 100644 --- a/pkg/service/restore_data_test.go +++ b/pkg/service/restore_data_test.go @@ -37,22 +37,10 @@ func (b *BackendHolderMock) GetAllReaders() map[string]BackupListReader { return nil } -func (b *BackendHolderMock) Init(_ *model.Config) { +func (b *BackendHolderMock) Init(_ map[string]*model.BackupRoutine) { } func makeTestRestoreService(wg *sync.WaitGroup) *dataRestorer { - storage := &model.LocalStorage{} - config := model.NewConfig() - _ = config.AddStorage("s", storage) - config.BackupRoutines = map[string]*model.BackupRoutine{ - "routine": { - Storage: storage, - }, - "routine_fail_restore": { - Storage: storage, - }, - } - return &dataRestorer{ configRetriever: configRetriever{ backends: &BackendHolderMock{}, @@ -60,7 +48,6 @@ func makeTestRestoreService(wg *sync.WaitGroup) *dataRestorer { restoreJobs: NewRestoreJobsHolder(), restoreService: NewRestoreMock(wg), backends: &BackendHolderMock{}, - config: config, clientManager: &MockClientManager{}, } }