diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2022-07-27 17:42:09 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2022-07-27 17:42:09 +0300 |
commit | f301b48eb56ad3f89702755de92792fbb6807e92 (patch) | |
tree | 6da04cdae522d96d322d4480cf0ed7d00f80cdfc | |
parent | 75d80735ef12c4572a8219cd725084961a8a7893 (diff) | |
parent | 2d958aba0c67cc9887e667ee9b60f0bf165b355a (diff) |
Merge branch 'wc-remove-db-only' into 'master'
praefect: Add -db-only flag to remove repository
Closes #4279
See merge request gitlab-org/gitaly!4715
-rw-r--r-- | cmd/praefect/subcmd_remove_repository.go | 30 | ||||
-rw-r--r-- | cmd/praefect/subcmd_remove_repository_test.go | 53 |
2 files changed, 72 insertions, 11 deletions
diff --git a/cmd/praefect/subcmd_remove_repository.go b/cmd/praefect/subcmd_remove_repository.go index fe991082d..9c966de6d 100644 --- a/cmd/praefect/subcmd_remove_repository.go +++ b/cmd/praefect/subcmd_remove_repository.go @@ -23,6 +23,7 @@ import ( const ( removeRepositoryCmdName = "remove-repository" paramApply = "apply" + paramDBOnly = "db-only" ) type writer struct { @@ -41,6 +42,7 @@ type removeRepository struct { virtualStorage string relativePath string apply bool + dbOnly bool dialTimeout time.Duration w io.Writer } @@ -53,16 +55,19 @@ func (cmd *removeRepository) FlagSet() *flag.FlagSet { fs := flag.NewFlagSet(removeRepositoryCmdName, flag.ExitOnError) fs.StringVar(&cmd.virtualStorage, paramVirtualStorage, "", "name of the repository's virtual storage") fs.BoolVar(&cmd.apply, paramApply, false, "physically remove the repository from disk and the database") + fs.BoolVar(&cmd.dbOnly, paramDBOnly, false, "remove the repository records from the database only") fs.StringVar(&cmd.relativePath, paramRelativePath, "", "relative path to the repository") fs.Usage = func() { printfErr("Description:\n" + " This command removes all state associated with a given repository from the Gitaly Cluster.\n" + " This includes both on-disk repositories on all relevant Gitaly nodes as well as any potential\n" + - " database state as tracked by Praefect.\n" + + " database state as tracked by Praefect, or optionally only database state.\n" + " Runs in dry-run mode by default checks whether the repository exists" + " without actually removing it from the database and disk.\n" + " When -apply is used, the repository will be removed from the database as well as\n" + - " the individual gitaly nodes on which they exist.\n") + " the individual gitaly nodes on which they exist.\n" + + " When -apply and -db-only are used, the repository will be removed from the\n" + + " database but left in-place on the gitaly nodes.") fs.PrintDefaults() } return fs @@ -115,7 +120,24 @@ func (cmd *removeRepository) exec(ctx context.Context, logger logrus.FieldLogger } if !cmd.apply { - fmt.Fprintln(cmd.w, "Re-run the command with -apply to remove repositories from the database and disk.") + fmt.Fprintln(cmd.w, "Re-run the command with -apply to remove repositories from the database"+ + " and disk or -apply and -db-only to remove from database only.") + return nil + } + + ticker := helper.NewTimerTicker(time.Second) + defer ticker.Stop() + + if cmd.dbOnly { + fmt.Fprintf(cmd.w, "Attempting to remove %s from the database...\n", cmd.relativePath) + if _, _, err := rs.DeleteRepository(ctx, cmd.virtualStorage, cmd.relativePath); err != nil { + return fmt.Errorf("remove repository from database: %w", err) + } + fmt.Fprintln(cmd.w, "Repository removal from database completed.") + + if err := cmd.removeReplicationEvents(ctx, logger, db, ticker); err != nil { + return fmt.Errorf("remove scheduled replication events: %w", err) + } return nil } @@ -137,8 +159,6 @@ func (cmd *removeRepository) exec(ctx context.Context, logger logrus.FieldLogger fmt.Fprintln(cmd.w, "Repository removal completed.") fmt.Fprintln(cmd.w, "Removing replication events...") - ticker := helper.NewTimerTicker(time.Second) - defer ticker.Stop() if err := cmd.removeReplicationEvents(ctx, logger, db, ticker); err != nil { return fmt.Errorf("remove scheduled replication events: %w", err) } diff --git a/cmd/praefect/subcmd_remove_repository_test.go b/cmd/praefect/subcmd_remove_repository_test.go index 2f6bbae2a..19e53a231 100644 --- a/cmd/praefect/subcmd_remove_repository_test.go +++ b/cmd/praefect/subcmd_remove_repository_test.go @@ -19,6 +19,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" gitalycfg "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/service/setup" + "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v15/internal/helper" "gitlab.com/gitlab-org/gitaly/v15/internal/praefect/config" "gitlab.com/gitlab-org/gitaly/v15/internal/praefect/datastore" @@ -127,6 +128,7 @@ func TestRemoveRepository_Exec(t *testing.T) { relativePath: repo.RelativePath, dialTimeout: time.Second, apply: false, + dbOnly: false, w: &writer{w: &out}, } require.NoError(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf)) @@ -134,36 +136,72 @@ func TestRemoveRepository_Exec(t *testing.T) { require.DirExists(t, filepath.Join(g1Cfg.Storages[0].Path, replicaPath)) require.DirExists(t, filepath.Join(g2Cfg.Storages[0].Path, replicaPath)) assert.Contains(t, out.String(), "Repository found in the database.\n") - assert.Contains(t, out.String(), "Re-run the command with -apply to remove repositories from the database and disk.\n") + assert.Contains(t, out.String(), "Re-run the command with -apply to remove repositories from the database and disk or -apply and -db-only to remove from database only.") require.True(t, repositoryExists(t, repo)) }) t.Run("ok", func(t *testing.T) { var out bytes.Buffer repo := createRepo(t, ctx, repoClient, praefectStorage, t.Name()) + replicaPath := gittest.GetReplicaPath(ctx, t, gitalycfg.Cfg{SocketPath: praefectServer.Address()}, repo) + require.DirExists(t, filepath.Join(g1Cfg.Storages[0].Path, replicaPath)) + require.DirExists(t, filepath.Join(g2Cfg.Storages[0].Path, replicaPath)) + cmd := &removeRepository{ logger: testhelper.NewDiscardingLogger(t), virtualStorage: repo.StorageName, relativePath: repo.RelativePath, dialTimeout: time.Second, apply: true, + dbOnly: false, w: &writer{w: &out}, } require.NoError(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf)) - require.NoDirExists(t, filepath.Join(g1Cfg.Storages[0].Path, repo.RelativePath)) - require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, repo.RelativePath)) + require.NoDirExists(t, filepath.Join(g1Cfg.Storages[0].Path, replicaPath)) + require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, replicaPath)) assert.Contains(t, out.String(), "Repository found in the database.\n") assert.Contains(t, out.String(), fmt.Sprintf("Attempting to remove %s from the database, and delete it from all gitaly nodes...\n", repo.RelativePath)) assert.Contains(t, out.String(), "Repository removal completed.") require.False(t, repositoryExists(t, repo)) }) + t.Run("db only", func(t *testing.T) { + var out bytes.Buffer + repo := createRepo(t, ctx, repoClient, praefectStorage, t.Name()) + replicaPath := gittest.GetReplicaPath(ctx, t, gitalycfg.Cfg{SocketPath: praefectServer.Address()}, repo) + + cmd := &removeRepository{ + logger: testhelper.NewDiscardingLogger(t), + virtualStorage: repo.StorageName, + relativePath: repo.RelativePath, + dialTimeout: time.Second, + apply: true, + dbOnly: true, + w: &writer{w: &out}, + } + require.NoError(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf)) + + // Repo is no longer present in the Praefect DB. + require.False(t, repositoryExists(t, repo)) + + // Repo is still present on-disk on the Gitaly nodes. + require.True(t, storage.IsGitDirectory(filepath.Join(g1Cfg.Storages[0].Path, replicaPath))) + require.True(t, storage.IsGitDirectory(filepath.Join(g2Cfg.Storages[0].Path, replicaPath))) + + assert.Contains(t, out.String(), "Repository found in the database.\n") + assert.Contains(t, out.String(), fmt.Sprintf("Attempting to remove %s from the database...\n", repo.RelativePath)) + assert.Contains(t, out.String(), "Repository removal from database completed.") + }) + t.Run("repository doesnt exist on one gitaly", func(t *testing.T) { var out bytes.Buffer repo := createRepo(t, ctx, repoClient, praefectStorage, t.Name()) + replicaPath := gittest.GetReplicaPath(ctx, t, gitalycfg.Cfg{SocketPath: praefectServer.Address()}, repo) - require.NoError(t, os.RemoveAll(filepath.Join(g2Cfg.Storages[0].Path, repo.RelativePath))) + require.DirExists(t, filepath.Join(g2Cfg.Storages[0].Path, replicaPath)) + require.DirExists(t, filepath.Join(g1Cfg.Storages[0].Path, replicaPath)) + require.NoError(t, os.RemoveAll(filepath.Join(g2Cfg.Storages[0].Path, replicaPath))) cmd := &removeRepository{ logger: testhelper.NewDiscardingLogger(t), @@ -171,12 +209,13 @@ func TestRemoveRepository_Exec(t *testing.T) { relativePath: repo.RelativePath, dialTimeout: time.Second, apply: true, + dbOnly: false, w: &writer{w: &out}, } require.NoError(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf)) - require.NoDirExists(t, filepath.Join(g1Cfg.Storages[0].Path, repo.RelativePath)) - require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, repo.RelativePath)) + require.NoDirExists(t, filepath.Join(g1Cfg.Storages[0].Path, replicaPath)) + require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, replicaPath)) assert.Contains(t, out.String(), "Repository found in the database.\n") assert.Contains(t, out.String(), fmt.Sprintf("Attempting to remove %s from the database, and delete it from all gitaly nodes...\n", repo.RelativePath)) assert.Contains(t, out.String(), "Repository removal completed.") @@ -198,6 +237,7 @@ func TestRemoveRepository_Exec(t *testing.T) { relativePath: repo.RelativePath, dialTimeout: time.Second, apply: true, + dbOnly: false, w: &writer{w: &out}, } require.Error(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf), "repository is not being tracked in Praefect") @@ -221,6 +261,7 @@ func TestRemoveRepository_Exec(t *testing.T) { relativePath: repo.RelativePath, dialTimeout: 100 * time.Millisecond, apply: true, + dbOnly: false, w: &writer{w: &out}, } |