diff options
author | John Cai <jcai@gitlab.com> | 2020-02-07 23:32:46 +0300 |
---|---|---|
committer | John Cai <jcai@gitlab.com> | 2020-02-07 23:32:46 +0300 |
commit | 8bc9f82a3e2e1f9ab3b47172c8068c1bdbe19bbc (patch) | |
tree | f8b99177abd85655ad2340acd38f3d411786bf1f | |
parent | f4c653f110d5f25167906cb2fa1ee09519b32e87 (diff) |
Lock RepolicateRepository to 1 operation at a timejc-lock-concurrent-replicate-repository
-rw-r--r-- | internal/service/repository/replicate.go | 19 | ||||
-rw-r--r-- | internal/service/repository/replicate_test.go | 72 | ||||
-rw-r--r-- | internal/service/repository/testhelper_test.go | 4 |
3 files changed, 95 insertions, 0 deletions
diff --git a/internal/service/repository/replicate.go b/internal/service/repository/replicate.go index 9d0d23300..ef09b9792 100644 --- a/internal/service/repository/replicate.go +++ b/internal/service/repository/replicate.go @@ -23,6 +23,10 @@ import ( "google.golang.org/grpc" ) +const ( + replicationLockFileName = "replication.lock" +) + func (s *server) ReplicateRepository(ctx context.Context, in *gitalypb.ReplicateRepositoryRequest) (*gitalypb.ReplicateRepositoryResponse, error) { if err := validateReplicateRepository(in); err != nil { return nil, helper.ErrInvalidArgument(err) @@ -37,6 +41,21 @@ func (s *server) ReplicateRepository(ctx context.Context, in *gitalypb.Replicate return nil, helper.ErrInternal(err) } + // hold lockfile + lockfilePath := filepath.Join(repoPath, replicationLockFileName) + lockFile, err := os.OpenFile(lockfilePath, os.O_EXCL|os.O_CREATE, 0) + if err != nil { + if os.IsExist(err) { + return &gitalypb.ReplicateRepositoryResponse{}, nil + } + return nil, helper.ErrInternal(err) + } + + defer func() { + os.Remove(lockfilePath) + lockFile.Close() + }() + if helper.IsGitDirectory(repoPath) { syncFuncs = append(syncFuncs, s.syncRepository) } else { diff --git a/internal/service/repository/replicate_test.go b/internal/service/repository/replicate_test.go index 2824f3f68..2ae48c305 100644 --- a/internal/service/repository/replicate_test.go +++ b/internal/service/repository/replicate_test.go @@ -248,3 +248,75 @@ func TestReplicateRepository_BadRepository(t *testing.T) { testhelper.MustRunCommand(t, nil, "git", "-C", targetRepoPath, "fsck") } + +func TestReplicateRepositoryConcurrent(t *testing.T) { + tmpPath, cleanup := testhelper.TempDir(t, t.Name()) + defer cleanup() + + replicaPath := filepath.Join(tmpPath, "replica") + require.NoError(t, os.MkdirAll(replicaPath, 0755)) + + defer func(storages []config.Storage) { + config.Config.Storages = storages + }(config.Config.Storages) + + config.Config.Storages = []config.Storage{ + config.Storage{ + Name: "default", + Path: testhelper.GitlabTestStoragePath(), + }, + config.Storage{ + Name: "replica", + Path: replicaPath, + }, + } + + server, serverSocketPath := runFullServer(t) + defer server.Stop() + + testRepo, _, cleanupRepo := testhelper.NewTestRepo(t) + defer cleanupRepo() + + config.Config.SocketPath = serverSocketPath + + repoClient, conn := repository.NewRepositoryClient(t, serverSocketPath) + defer conn.Close() + + targetRepo := *testRepo + targetRepo.StorageName = "replica" + + targetRepoPath, err := helper.GetPath(&targetRepo) + require.NoError(t, err) + + require.NoError(t, os.MkdirAll(targetRepoPath, 0755)) + + lockfilePath := filepath.Join(targetRepoPath, repository.ReplicationLockFileName) + + // write a lockfile in the repository directory + f, err := os.OpenFile(lockfilePath, os.O_EXCL|os.O_CREATE, 0) + require.NoError(t, err) + + ctx, cancel := testhelper.Context() + defer cancel() + md := testhelper.GitalyServersMetadata(t, serverSocketPath) + injectedCtx := metadata.NewOutgoingContext(ctx, md) + + _, err = repoClient.ReplicateRepository(injectedCtx, &gitalypb.ReplicateRepositoryRequest{ + Repository: &targetRepo, + Source: testRepo, + }) + require.NoError(t, err) + + require.False(t, helper.IsGitDirectory(targetRepoPath), "replication should not have happened") + + // remove lockfile + require.NoError(t, f.Close()) + require.NoError(t, os.Remove(lockfilePath)) + + _, err = repoClient.ReplicateRepository(injectedCtx, &gitalypb.ReplicateRepositoryRequest{ + Repository: &targetRepo, + Source: testRepo, + }) + require.NoError(t, err) + require.True(t, helper.IsGitDirectory(targetRepoPath), "replication should have happened") +} diff --git a/internal/service/repository/testhelper_test.go b/internal/service/repository/testhelper_test.go index 37c5e6d89..ea5839c39 100644 --- a/internal/service/repository/testhelper_test.go +++ b/internal/service/repository/testhelper_test.go @@ -153,3 +153,7 @@ func testMain(m *testing.M) int { return m.Run() } + +const ( + ReplicationLockFileName = replicationLockFileName +) |