Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSami Hiltunen <shiltunen@gitlab.com>2021-10-22 15:51:33 +0300
committerSami Hiltunen <shiltunen@gitlab.com>2022-05-16 16:22:01 +0300
commit6ef60ea1d66fed382059e2dd8b66c12d41bc5ca1 (patch)
treeaf606938e5088b0cb77e6f50e4d01ccc3ec3719b
parente9f2924a3db3b3f64bdc810a53432b3fa6afedcb (diff)
Add RenameRepositoryInPlace to RepositoryStore
This commit adds RenameRepositoryInPlace to RepositoryStore. The new method will replace RenameRepository which can be removed in a later release. Unlike RenameRepository, renames doesn't change the replica path stored in the database. As Praefect is now routing using the replica_path, this enables Praefect to rename repositories atomically in the database without necessiating any disk changes. The old RenameRepository is left in place so any in-flight rename jobs can still be processed by it.
-rw-r--r--internal/praefect/datastore/repository_store.go36
-rw-r--r--internal/praefect/datastore/repository_store_mock.go6
-rw-r--r--internal/praefect/datastore/repository_store_test.go44
3 files changed, 86 insertions, 0 deletions
diff --git a/internal/praefect/datastore/repository_store.go b/internal/praefect/datastore/repository_store.go
index 780073155..35a8a6a68 100644
--- a/internal/praefect/datastore/repository_store.go
+++ b/internal/praefect/datastore/repository_store.go
@@ -118,6 +118,9 @@ type RepositoryStore interface {
// as the storage's which is calling it. Returns RepositoryNotExistsError when trying to rename a repository
// which has no record in the virtual storage or the storage.
RenameRepository(ctx context.Context, virtualStorage, relativePath, storage, newRelativePath string) error
+ // RenameRepositoryInPlace renames the repository in the database without changing the replica path. This will replace
+ // RenameRepository which can be removed in a later release.
+ RenameRepositoryInPlace(ctx context.Context, virtualStorage, relativePath, newRelativePath string) error
// GetConsistentStoragesByRepositoryID returns the replica path and the set of up to date storages for the given repository keyed by repository ID.
GetConsistentStoragesByRepositoryID(ctx context.Context, repositoryID int64) (string, map[string]struct{}, error)
ConsistentStoragesGetter
@@ -535,6 +538,39 @@ AND storage = $2
return nil
}
+// RenameRepositoryInPlace renames the repository in the database without changing the replica path. This will replace
+// RenameRepository which can be removed in a later release.
+func (rs *PostgresRepositoryStore) RenameRepositoryInPlace(ctx context.Context, virtualStorage, relativePath, newRelativePath string) error {
+ result, err := rs.db.ExecContext(ctx, `
+WITH repository AS (
+ UPDATE repositories
+ SET relative_path = $3
+ WHERE virtual_storage = $1
+ AND relative_path = $2
+ RETURNING repository_id
+)
+
+UPDATE storage_repositories
+SET relative_path = $3
+WHERE repository_id = (SELECT repository_id FROM repository)
+ `, virtualStorage, relativePath, newRelativePath)
+ if err != nil {
+ if glsql.IsUniqueViolation(err, "repository_lookup_index") {
+ return commonerr.ErrRepositoryAlreadyExists
+ }
+
+ return fmt.Errorf("query: %w", err)
+ }
+
+ if rowsAffected, err := result.RowsAffected(); err != nil {
+ return fmt.Errorf("rows affected: %w", err)
+ } else if rowsAffected == 0 {
+ return commonerr.ErrRepositoryNotFound
+ }
+
+ return nil
+}
+
//nolint: revive,stylecheck // This is unintentionally missing documentation.
func (rs *PostgresRepositoryStore) RenameRepository(ctx context.Context, virtualStorage, relativePath, storage, newRelativePath string) error {
const q = `
diff --git a/internal/praefect/datastore/repository_store_mock.go b/internal/praefect/datastore/repository_store_mock.go
index 2a021661d..42acafb6e 100644
--- a/internal/praefect/datastore/repository_store_mock.go
+++ b/internal/praefect/datastore/repository_store_mock.go
@@ -14,6 +14,7 @@ type MockRepositoryStore struct {
SetAuthoritativeReplicaFunc func(ctx context.Context, virtualStorage, relativePath, storage string) error
DeleteRepositoryFunc func(ctx context.Context, virtualStorage, relativePath string) (string, []string, error)
DeleteReplicaFunc func(ctx context.Context, repositoryID int64, storage string) error
+ RenameRepositoryInPlaceFunc func(ctx context.Context, virtualStorage, relativePath, newRelativePath string) error
RenameRepositoryFunc func(ctx context.Context, virtualStorage, relativePath, storage, newRelativePath string) error
GetConsistentStoragesByRepositoryIDFunc func(ctx context.Context, repositoryID int64) (string, map[string]struct{}, error)
GetConsistentStoragesFunc func(ctx context.Context, virtualStorage, relativePath string) (string, map[string]struct{}, error)
@@ -99,6 +100,11 @@ func (m MockRepositoryStore) DeleteReplica(ctx context.Context, repositoryID int
return m.DeleteReplicaFunc(ctx, repositoryID, storage)
}
+// RenameRepositoryInPlace runs the mock's RenameRepositoryInPlaceFunc.
+func (m MockRepositoryStore) RenameRepositoryInPlace(ctx context.Context, virtualStorage, relativePath, newRelativePath string) error {
+ return m.RenameRepositoryInPlaceFunc(ctx, virtualStorage, relativePath, newRelativePath)
+}
+
//nolint: revive,stylecheck // This is unintentionally missing documentation.
func (m MockRepositoryStore) RenameRepository(ctx context.Context, virtualStorage, relativePath, storage, newRelativePath string) error {
if m.RenameRepositoryFunc == nil {
diff --git a/internal/praefect/datastore/repository_store_test.go b/internal/praefect/datastore/repository_store_test.go
index 9c980376c..afdfcdb14 100644
--- a/internal/praefect/datastore/repository_store_test.go
+++ b/internal/praefect/datastore/repository_store_test.go
@@ -765,6 +765,50 @@ func TestRepositoryStore_Postgres(t *testing.T) {
})
})
+ t.Run("RenameRepositoryInPlace", func(t *testing.T) {
+ t.Run("rename non-existing", func(t *testing.T) {
+ rs := newRepositoryStore(t, nil)
+
+ require.Equal(t,
+ commonerr.ErrRepositoryNotFound,
+ rs.RenameRepositoryInPlace(ctx, vs, repo, "new-relative-path"),
+ )
+ })
+
+ t.Run("destination exists", func(t *testing.T) {
+ rs := newRepositoryStore(t, nil)
+
+ require.NoError(t, rs.CreateRepository(ctx, 1, vs, "relative-path-1", "replica-path-1", "primary", nil, nil, true, false))
+ require.NoError(t, rs.CreateRepository(ctx, 2, vs, "relative-path-2", "replica-path-2", "primary", nil, nil, true, false))
+
+ require.Equal(t,
+ commonerr.ErrRepositoryAlreadyExists,
+ rs.RenameRepositoryInPlace(ctx, vs, "relative-path-1", "relative-path-2"),
+ )
+ })
+
+ t.Run("successfully renamed", func(t *testing.T) {
+ rs := newRepositoryStore(t, nil)
+
+ require.NoError(t, rs.CreateRepository(ctx, 1, vs, "original-relative-path", "original-replica-path", "primary", nil, nil, false, false))
+ require.NoError(t, rs.RenameRepositoryInPlace(ctx, vs, "original-relative-path", "renamed-relative-path"))
+ requireState(t, ctx, db,
+ virtualStorageState{
+ vs: {
+ "renamed-relative-path": {repositoryID: 1, replicaPath: "original-replica-path"},
+ },
+ },
+ storageState{
+ vs: {
+ "renamed-relative-path": {
+ "primary": {repositoryID: 1},
+ },
+ },
+ },
+ )
+ })
+ })
+
t.Run("RenameRepository", func(t *testing.T) {
t.Run("rename non-existing", func(t *testing.T) {
rs := newRepositoryStore(t, nil)