diff options
author | Sami Hiltunen <shiltunen@gitlab.com> | 2021-10-22 15:51:33 +0300 |
---|---|---|
committer | Sami Hiltunen <shiltunen@gitlab.com> | 2022-05-16 16:22:01 +0300 |
commit | 6ef60ea1d66fed382059e2dd8b66c12d41bc5ca1 (patch) | |
tree | af606938e5088b0cb77e6f50e4d01ccc3ec3719b | |
parent | e9f2924a3db3b3f64bdc810a53432b3fa6afedcb (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.go | 36 | ||||
-rw-r--r-- | internal/praefect/datastore/repository_store_mock.go | 6 | ||||
-rw-r--r-- | internal/praefect/datastore/repository_store_test.go | 44 |
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) |