From ada54fc25380ad5b6844f46df793a62075b4bb31 Mon Sep 17 00:00:00 2001 From: James Liu Date: Tue, 12 Dec 2023 14:54:25 +1100 Subject: backup: Add RemoveRepository to the strategy Adds a new method to the Strategy interface used by regular and server-side backups for performing repository backups and restores. This new method removes a single repository from its storage, and will eventually replace the existing RemoveAllRepositories method. --- internal/backup/backup.go | 19 ++++++++++++++++ internal/backup/backup_test.go | 43 +++++++++++++++++++++++++++++++++++++ internal/backup/pipeline.go | 7 ++++++ internal/backup/pipeline_test.go | 8 +++++++ internal/backup/server_side.go | 19 ++++++++++++++++ internal/backup/server_side_test.go | 35 ++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+) diff --git a/internal/backup/backup.go b/internal/backup/backup.go index 5eed0724a..bb6bad6a4 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -230,6 +230,25 @@ func (mgr *Manager) RemoveAllRepositories(ctx context.Context, req *RemoveAllRep return nil } +// RemoveRepository removes the specified repository from its storage. +func (mgr *Manager) RemoveRepository(ctx context.Context, req *RemoveRepositoryRequest) error { + if err := setContextServerInfo(ctx, &req.Server, req.Repo.StorageName); err != nil { + return fmt.Errorf("remove repo: set context: %w", err) + } + + repoClient, err := mgr.newRepoClient(ctx, req.Server) + if err != nil { + return fmt.Errorf("remove repo: create client: %w", err) + } + + _, err = repoClient.RemoveRepository(ctx, &gitalypb.RemoveRepositoryRequest{Repository: req.Repo}) + if err != nil { + return fmt.Errorf("remove repo: remove: %w", err) + } + + return nil +} + // Create creates a repository backup. func (mgr *Manager) Create(ctx context.Context, req *CreateRequest) error { if req.VanityRepository == nil { diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go index 14341ec6a..ca92ad546 100644 --- a/internal/backup/backup_test.go +++ b/internal/backup/backup_test.go @@ -65,6 +65,49 @@ Issue: https://gitlab.com/gitlab-org/gitaly/-/issues/5269`) require.NoError(t, err) } +func TestManager_RemoveRepository(t *testing.T) { + if testhelper.IsPraefectEnabled() { + t.Skip("local backup manager expects to operate on the local filesystem so cannot operate through praefect") + } + + t.Parallel() + + cfg := testcfg.Build(t) + cfg.SocketPath = testserver.RunGitalyServer(t, cfg, setup.RegisterAll) + + ctx := testhelper.Context(t) + + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + commitID := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main")) + gittest.WriteTag(t, cfg, repoPath, "v1.0.0", commitID.Revision()) + + pool := client.NewPool() + defer testhelper.MustClose(t, pool) + + backupRoot := testhelper.TempDir(t) + sink := backup.NewFilesystemSink(backupRoot) + defer testhelper.MustClose(t, sink) + + locator, err := backup.ResolveLocator("pointer", sink) + require.NoError(t, err) + + fsBackup := backup.NewManager(sink, locator, pool) + err = fsBackup.RemoveRepository(ctx, &backup.RemoveRepositoryRequest{ + Server: storage.ServerInfo{Address: cfg.SocketPath, Token: cfg.Auth.Token}, + Repo: repo, + }) + require.NoError(t, err) + require.NoDirExists(t, repoPath) + + // With an invalid repository + err = fsBackup.RemoveRepository(ctx, &backup.RemoveRepositoryRequest{ + Server: storage.ServerInfo{Address: cfg.SocketPath, Token: cfg.Auth.Token}, + Repo: &gitalypb.Repository{StorageName: "nonexistent", RelativePath: "nonexistent"}, + }) + + require.EqualError(t, err, "remove repo: remove: rpc error: code = InvalidArgument desc = storage name not found") +} + func TestManager_Create(t *testing.T) { t.Parallel() diff --git a/internal/backup/pipeline.go b/internal/backup/pipeline.go index 8000f61af..1b969ccac 100644 --- a/internal/backup/pipeline.go +++ b/internal/backup/pipeline.go @@ -16,6 +16,7 @@ import ( type Strategy interface { Create(context.Context, *CreateRequest) error Restore(context.Context, *RestoreRequest) error + RemoveRepository(context.Context, *RemoveRepositoryRequest) error RemoveAllRepositories(context.Context, *RemoveAllRepositoriesRequest) error } @@ -52,6 +53,12 @@ type RestoreRequest struct { BackupID string } +// RemoveRepositoryRequest is a request to remove an individual repository from its storage. +type RemoveRepositoryRequest struct { + Server storage.ServerInfo + Repo *gitalypb.Repository +} + // RemoveAllRepositoriesRequest is the request to remove all repositories in the specified // storage name. type RemoveAllRepositoriesRequest struct { diff --git a/internal/backup/pipeline_test.go b/internal/backup/pipeline_test.go index 8545c905e..cd77fb5a7 100644 --- a/internal/backup/pipeline_test.go +++ b/internal/backup/pipeline_test.go @@ -125,6 +125,7 @@ type MockStrategy struct { CreateFunc func(context.Context, *CreateRequest) error RestoreFunc func(context.Context, *RestoreRequest) error RemoveAllRepositoriesFunc func(context.Context, *RemoveAllRepositoriesRequest) error + RemoveRepositoryFunc func(context.Context, *RemoveRepositoryRequest) error } func (s MockStrategy) Create(ctx context.Context, req *CreateRequest) error { @@ -148,6 +149,13 @@ func (s MockStrategy) RemoveAllRepositories(ctx context.Context, req *RemoveAllR return nil } +func (s MockStrategy) RemoveRepository(ctx context.Context, req *RemoveRepositoryRequest) error { + if s.RemoveRepositoryFunc != nil { + return s.RemoveRepositoryFunc(ctx, req) + } + return nil +} + func testPipeline(t *testing.T, init func() *Pipeline) { strategy := MockStrategy{ CreateFunc: func(_ context.Context, req *CreateRequest) error { diff --git a/internal/backup/server_side.go b/internal/backup/server_side.go index 35654f215..17382e66e 100644 --- a/internal/backup/server_side.go +++ b/internal/backup/server_side.go @@ -112,6 +112,25 @@ func (ss ServerSideAdapter) RemoveAllRepositories(ctx context.Context, req *Remo return nil } +// RemoveRepository removes the specified repository from its storage. +func (ss ServerSideAdapter) RemoveRepository(ctx context.Context, req *RemoveRepositoryRequest) error { + if err := setContextServerInfo(ctx, &req.Server, req.Repo.StorageName); err != nil { + return fmt.Errorf("server-side remove repo: set context: %w", err) + } + + repoClient, err := ss.newRepoClient(ctx, req.Server) + if err != nil { + return fmt.Errorf("server-side remove repo: create client: %w", err) + } + + _, err = repoClient.RemoveRepository(ctx, &gitalypb.RemoveRepositoryRequest{Repository: req.Repo}) + if err != nil { + return fmt.Errorf("server-side remove repo: remove: %w", err) + } + + return nil +} + func (ss ServerSideAdapter) newRepoClient(ctx context.Context, server storage.ServerInfo) (gitalypb.RepositoryServiceClient, error) { conn, err := ss.pool.Dial(ctx, server.Address, server.Token) if err != nil { diff --git a/internal/backup/server_side_test.go b/internal/backup/server_side_test.go index 2acc547c5..a7957bcd8 100644 --- a/internal/backup/server_side_test.go +++ b/internal/backup/server_side_test.go @@ -295,3 +295,38 @@ Issue: https://gitlab.com/gitlab-org/gitaly/-/issues/5269`) }) require.NoError(t, err) } + +func TestServerSideAdapter_RemoveRepository(t *testing.T) { + t.Parallel() + + db := testdb.New(t) + db.TruncateAll(t) + datastore.NewPostgresRepositoryStore(db, map[string][]string{"virtual-storage": {"default"}}) + + cfg := testcfg.Build(t) + cfg.SocketPath = testserver.RunGitalyServer(t, cfg, setup.RegisterAll) + + ctx := testhelper.Context(t) + + repo, repoPath := gittest.CreateRepository(t, ctx, cfg) + gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main")) + + pool := client.NewPool() + defer testhelper.MustClose(t, pool) + + adapter := backup.NewServerSideAdapter(pool) + err := adapter.RemoveRepository(ctx, &backup.RemoveRepositoryRequest{ + Server: storage.ServerInfo{Address: cfg.SocketPath, Token: cfg.Auth.Token}, + Repo: repo, + }) + require.NoError(t, err) + require.NoDirExists(t, repoPath) + + // With an invalid repository + err = adapter.RemoveRepository(ctx, &backup.RemoveRepositoryRequest{ + Server: storage.ServerInfo{Address: cfg.SocketPath, Token: cfg.Auth.Token}, + Repo: &gitalypb.Repository{StorageName: "default", RelativePath: "nonexistent"}, + }) + + require.EqualError(t, err, "server-side remove repo: remove: rpc error: code = NotFound desc = repository does not exist") +} -- cgit v1.2.3