diff options
author | Toon Claes <toon@gitlab.com> | 2021-09-20 11:21:57 +0300 |
---|---|---|
committer | Toon Claes <toon@gitlab.com> | 2021-09-20 11:21:57 +0300 |
commit | e38c2b3add636eadb6b62148d5086a72a2f9cd3f (patch) | |
tree | b20faa2c3d76b4a51d5e8f78169bc7b3540ce04e | |
parent | ec75cf8b08b74572068e2961ef212e5b97a97917 (diff) | |
parent | 8240393ca21cd48287cfcf54fc6a7c2c97394b12 (diff) |
Merge branch 'fix_restore_into_non_existent' into 'master'
Allow restoring into a non-existent repo on praefect
Closes #3775
See merge request gitlab-org/gitaly!3865
-rw-r--r-- | internal/backup/backup.go | 6 | ||||
-rw-r--r-- | internal/backup/backup_test.go | 144 | ||||
-rw-r--r-- | internal/git/gittest/repo.go | 6 |
3 files changed, 135 insertions, 21 deletions
diff --git a/internal/backup/backup.go b/internal/backup/backup.go index 33eba0d69..70a9b6d91 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -226,7 +226,11 @@ func (mgr *Manager) removeRepository(ctx context.Context, server storage.ServerI if err != nil { return fmt.Errorf("remove repository: %w", err) } - if _, err := repoClient.RemoveRepository(ctx, &gitalypb.RemoveRepositoryRequest{Repository: repo}); err != nil { + _, err = repoClient.RemoveRepository(ctx, &gitalypb.RemoveRepositoryRequest{Repository: repo}) + switch { + case status.Code(err) == codes.NotFound: + return nil + case err != nil: return fmt.Errorf("remove repository: %w", err) } return nil diff --git a/internal/backup/backup_test.go b/internal/backup/backup_test.go index 081d21f28..425656435 100644 --- a/internal/backup/backup_test.go +++ b/internal/backup/backup_test.go @@ -6,13 +6,21 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" "testing" + "time" + "github.com/pelletier/go-toml" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/client" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" + "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" + gitalylog "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config/log" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/service/setup" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/storage" + praefectConfig "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/config" + "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/datastore/glsql" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver" @@ -131,25 +139,119 @@ func TestManager_Restore(t *testing.T) { gitalyAddr := testserver.RunGitalyServer(t, cfg, nil, setup.RegisterAll) + testManagerRestore(t, cfg, gitalyAddr) +} + +func TestManager_Restore_praefect(t *testing.T) { + gitalyCfg := testcfg.Build(t, testcfg.WithStorages("gitaly-1")) + + testhelper.BuildPraefect(t, gitalyCfg) + testhelper.BuildGitalyHooks(t, gitalyCfg) + + gitalyAddr := testserver.RunGitalyServer(t, gitalyCfg, nil, setup.RegisterAll, testserver.WithDisablePraefect()) + + db := glsql.NewDB(t) + var database string + require.NoError(t, db.QueryRow(`SELECT current_database()`).Scan(&database)) + dbConf := glsql.GetDBConfig(t, database) + + conf := praefectConfig.Config{ + SocketPath: testhelper.GetTemporaryGitalySocketFileName(t), + VirtualStorages: []*praefectConfig.VirtualStorage{ + { + Name: "default", + Nodes: []*praefectConfig.Node{ + {Storage: gitalyCfg.Storages[0].Name, Address: gitalyAddr}, + }, + }, + }, + DB: dbConf, + Failover: praefectConfig.Failover{ + Enabled: true, + ElectionStrategy: praefectConfig.ElectionStrategyPerRepository, + }, + Replication: praefectConfig.DefaultReplicationConfig(), + Logging: gitalylog.Config{ + Format: "json", + Level: "panic", + }, + } + + tempDir := testhelper.TempDir(t) + configFilePath := filepath.Join(tempDir, "config.toml") + configFile, err := os.Create(configFilePath) + require.NoError(t, err) + defer testhelper.MustClose(t, configFile) + + require.NoError(t, toml.NewEncoder(configFile).Encode(&conf)) + require.NoError(t, configFile.Sync()) + + cmd := exec.Command(filepath.Join(gitalyCfg.BinDir, "praefect"), "-config", configFilePath) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + require.NoError(t, cmd.Start()) + + t.Cleanup(func() { _ = cmd.Wait() }) + t.Cleanup(func() { _ = cmd.Process.Kill() }) + + testManagerRestore(t, gitalyCfg, "unix://"+conf.SocketPath) +} + +func testManagerRestore(t *testing.T, cfg config.Cfg, gitalyAddr string) { + ctx, cancel := testhelper.Context() + defer cancel() + + cc, err := client.Dial(gitalyAddr, nil) + require.NoError(t, err) + defer func() { require.NoError(t, cc.Close()) }() + repoClient := gitalypb.NewRepositoryServiceClient(cc) + + createRepo := func(t testing.TB, relativePath string) *gitalypb.Repository { + t.Helper() + + repo := &gitalypb.Repository{ + StorageName: "default", + RelativePath: relativePath, + } + + for i := 0; true; i++ { + _, err := repoClient.CreateRepository(ctx, &gitalypb.CreateRepositoryRequest{Repository: repo}) + if err != nil { + require.Regexp(t, "(no healthy nodes)|(no such file or directory)|(connection refused)", err.Error()) + require.Less(t, i, 100, "praefect doesn't serve for too long") + time.Sleep(50 * time.Millisecond) + } else { + break + } + } + + return repo + } + path := testhelper.TempDir(t) - existingRepo, existRepoPath := gittest.CloneRepo(t, cfg, cfg.Storages[0], gittest.CloneRepoOpts{ - RelativePath: "existing_repo", - }) - existingRepoPath := filepath.Join(path, existingRepo.RelativePath) - existingRepoBundlePath := existingRepoPath + ".bundle" - existingRepoCustomHooksPath := filepath.Join(existingRepoPath, "custom_hooks.tar") - require.NoError(t, os.MkdirAll(existingRepoPath, os.ModePerm)) + existingRepo := createRepo(t, "existing") + require.NoError(t, os.MkdirAll(filepath.Join(path, existingRepo.RelativePath), os.ModePerm)) + existingRepoBundlePath := filepath.Join(path, existingRepo.RelativePath+".bundle") + gittest.BundleTestRepo(t, cfg, "gitlab-test.git", existingRepoBundlePath) - gittest.Exec(t, cfg, "-C", existRepoPath, "bundle", "create", existingRepoBundlePath, "--all") - testhelper.CopyFile(t, "../gitaly/service/repository/testdata/custom_hooks.tar", existingRepoCustomHooksPath) + existingRepoHooks := createRepo(t, "existing_hooks") + existingRepoHooksBundlePath := filepath.Join(path, existingRepoHooks.RelativePath+".bundle") + existingRepoHooksCustomHooksPath := filepath.Join(path, existingRepoHooks.RelativePath, "custom_hooks.tar") + require.NoError(t, os.MkdirAll(filepath.Join(path, existingRepoHooks.RelativePath), os.ModePerm)) + testhelper.CopyFile(t, existingRepoBundlePath, existingRepoHooksBundlePath) + testhelper.CopyFile(t, "../gitaly/service/repository/testdata/custom_hooks.tar", existingRepoHooksCustomHooksPath) - newRepo := gittest.InitRepoDir(t, cfg.Storages[0].Path, "new_repo") - newRepoBundlePath := filepath.Join(path, newRepo.RelativePath+".bundle") - testhelper.CopyFile(t, existingRepoBundlePath, newRepoBundlePath) + missingBundleRepo := createRepo(t, "missing_bundle") + missingBundleRepoAlwaysCreate := createRepo(t, "missing_bundle_always_create") - missingBundleRepo := gittest.InitRepoDir(t, cfg.Storages[0].Path, "missing_bundle") - missingBundleRepoAlwaysCreate := gittest.InitRepoDir(t, cfg.Storages[0].Path, "missing_bundle_always_create") + nonexistentRepo := &gitalypb.Repository{ + StorageName: "default", + RelativePath: "nonexistent", + } + nonexistentRepoBundlePath := filepath.Join(path, nonexistentRepo.RelativePath+".bundle") + testhelper.CopyFile(t, existingRepoBundlePath, nonexistentRepoBundlePath) for _, tc := range []struct { desc string @@ -160,13 +262,13 @@ func TestManager_Restore(t *testing.T) { expectVerify bool }{ { - desc: "new repo, without hooks", - repo: newRepo, + desc: "existing repo, without hooks", + repo: existingRepo, expectVerify: true, }, { desc: "existing repo, with hooks", - repo: existingRepo, + repo: existingRepoHooks, expectedPaths: []string{ "custom_hooks/pre-commit.sample", "custom_hooks/prepare-commit-msg.sample", @@ -184,14 +286,16 @@ func TestManager_Restore(t *testing.T) { repo: missingBundleRepoAlwaysCreate, alwaysCreate: true, }, + { + desc: "nonexistent repo", + repo: nonexistentRepo, + expectVerify: true, + }, } { t.Run(tc.desc, func(t *testing.T) { repoPath := filepath.Join(cfg.Storages[0].Path, tc.repo.RelativePath) bundlePath := filepath.Join(path, tc.repo.RelativePath+".bundle") - ctx, cancel := testhelper.Context() - defer cancel() - fsBackup := NewManager(NewFilesystemSink(path), LegacyLocator{}) err := fsBackup.Restore(ctx, &RestoreRequest{ Server: storage.ServerInfo{Address: gitalyAddr, Token: cfg.Auth.Token}, diff --git a/internal/git/gittest/repo.go b/internal/git/gittest/repo.go index c05e9ce9c..6821a38fb 100644 --- a/internal/git/gittest/repo.go +++ b/internal/git/gittest/repo.go @@ -155,6 +155,12 @@ func CloneRepo(t testing.TB, cfg config.Cfg, storage config.Storage, opts ...Clo return repo, absolutePath } +// BundleTestRepo creates a bundle of a local test repo. E.g. `gitlab-test.git` +func BundleTestRepo(t testing.TB, cfg config.Cfg, sourceRepo, bundlePath string) { + repoPath := testRepositoryPath(t, sourceRepo) + Exec(t, cfg, "-C", repoPath, "bundle", "create", bundlePath, "--all") +} + // testRepositoryPath returns the absolute path of local 'gitlab-org/gitlab-test.git' clone. // It is cloned under the path by the test preparing step of make. func testRepositoryPath(t testing.TB, repo string) string { |