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:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2021-12-13 10:43:15 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2021-12-13 10:43:15 +0300
commit3e8831389d6c15793b6f640af4572531af37ca53 (patch)
treee06302d78fc93f3065e50fbc2e7c4a1f34004089
parent27dddad834d99e9901b4a9b137748b850e71849a (diff)
parentcfd0fd048b46f4d783880f0774ffbdb7abcc8cb4 (diff)
Merge branch 'pks-tx-voting-phases' into 'master'
proto: Introduce transactional voting phases See merge request gitlab-org/gitaly!4180
-rw-r--r--internal/git/housekeeping/housekeeping_test.go13
-rw-r--r--internal/git/localrepo/config_test.go24
-rw-r--r--internal/git/objectpool/link_test.go13
-rw-r--r--internal/gitaly/hook/referencetransaction.go21
-rw-r--r--internal/gitaly/hook/transactions.go9
-rw-r--r--internal/gitaly/hook/transactions_test.go2
-rw-r--r--internal/gitaly/service/operations/apply_patch_test.go16
-rw-r--r--internal/gitaly/service/operations/rebase_test.go14
-rw-r--r--internal/gitaly/service/ref/delete_refs.go11
-rw-r--r--internal/gitaly/service/ref/delete_refs_test.go13
-rw-r--r--internal/gitaly/service/repository/apply_gitattributes.go10
-rw-r--r--internal/gitaly/service/repository/create_repository.go2
-rw-r--r--internal/gitaly/service/repository/create_repository_from_bundle_test.go56
-rw-r--r--internal/gitaly/service/repository/create_repository_test.go31
-rw-r--r--internal/gitaly/service/repository/fetch_remote.go2
-rw-r--r--internal/gitaly/service/repository/fetch_remote_test.go18
-rw-r--r--internal/gitaly/service/repository/midx_test.go11
-rw-r--r--internal/gitaly/service/repository/remove.go27
-rw-r--r--internal/gitaly/service/repository/util.go4
-rw-r--r--internal/gitaly/service/repository/util_test.go23
-rw-r--r--internal/gitaly/service/ssh/receive_pack.go2
-rw-r--r--internal/gitaly/service/ssh/receive_pack_test.go18
-rw-r--r--internal/gitaly/transaction/manager.go10
-rw-r--r--internal/gitaly/transaction/manager_test.go49
-rw-r--r--internal/gitaly/transaction/mock.go54
-rw-r--r--internal/gitaly/transaction/voting.go8
-rw-r--r--internal/gitaly/transaction/voting_test.go33
-rw-r--r--internal/transaction/voting/phase.go36
-rw-r--r--internal/transaction/voting/vote.go10
-rw-r--r--internal/transaction/voting/vote_test.go27
-rw-r--r--proto/go/gitalypb/transaction.pb.go196
-rw-r--r--proto/transaction.proto16
-rw-r--r--ruby/proto/gitaly/transaction_pb.rb7
33 files changed, 487 insertions, 299 deletions
diff --git a/internal/git/housekeeping/housekeeping_test.go b/internal/git/housekeeping/housekeeping_test.go
index c70143080..2daae8b96 100644
--- a/internal/git/housekeeping/housekeeping_test.go
+++ b/internal/git/housekeeping/housekeeping_test.go
@@ -2,7 +2,6 @@ package housekeeping
import (
"bytes"
- "context"
"fmt"
"os"
"path/filepath"
@@ -20,7 +19,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"google.golang.org/grpc/peer"
)
@@ -706,13 +704,7 @@ func TestPerform_UnsetConfiguration_transactional(t *testing.T) {
gittest.Exec(t, cfg, "-C", repoPath, "config", "http.some.extraHeader", "value")
- votes := 0
- txManager := &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
ctx, err := txinfo.InjectTransaction(ctx, 1, "node", true)
require.NoError(t, err)
@@ -721,8 +713,7 @@ func TestPerform_UnsetConfiguration_transactional(t *testing.T) {
})
require.NoError(t, Perform(ctx, repo, txManager))
-
- require.Equal(t, 2, votes)
+ require.Equal(t, 2, len(txManager.Votes()))
configKeys := gittest.Exec(t, cfg, "-C", repoPath, "config", "--list", "--local", "--name-only")
diff --git a/internal/git/localrepo/config_test.go b/internal/git/localrepo/config_test.go
index e0d188cf4..2a31977a7 100644
--- a/internal/git/localrepo/config_test.go
+++ b/internal/git/localrepo/config_test.go
@@ -1,7 +1,6 @@
package localrepo
import (
- "context"
"fmt"
"path/filepath"
"runtime"
@@ -18,7 +17,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"google.golang.org/grpc/peer"
)
@@ -121,17 +119,12 @@ func TestRepo_SetConfig(t *testing.T) {
ctx, err := txinfo.InjectTransaction(ctx, 1, "node", true)
ctx = peer.NewContext(ctx, backchannelPeer)
- votes := 0
+ txManager := transaction.NewTrackingManager()
require.NoError(t, err)
- require.NoError(t, repo.SetConfig(ctx, "some.key", "value", &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }))
+ require.NoError(t, repo.SetConfig(ctx, "some.key", "value", txManager))
- require.Equal(t, 2, votes)
+ require.Equal(t, 2, len(txManager.Votes()))
})
}
@@ -262,16 +255,11 @@ func TestRepo_UnsetMatchingConfig(t *testing.T) {
ctx, err := txinfo.InjectTransaction(ctx, 1, "node", true)
ctx = peer.NewContext(ctx, backchannelPeer)
- votes := 0
+ txManager := transaction.NewTrackingManager()
require.NoError(t, err)
- require.NoError(t, repo.UnsetMatchingConfig(ctx, "some.key", &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }))
+ require.NoError(t, repo.UnsetMatchingConfig(ctx, "some.key", txManager))
- require.Equal(t, 2, votes)
+ require.Equal(t, 2, len(txManager.Votes()))
})
}
diff --git a/internal/git/objectpool/link_test.go b/internal/git/objectpool/link_test.go
index 3a45adb37..c960f48db 100644
--- a/internal/git/objectpool/link_test.go
+++ b/internal/git/objectpool/link_test.go
@@ -1,7 +1,6 @@
package objectpool
import (
- "context"
"os"
"path/filepath"
"strings"
@@ -13,7 +12,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/transaction"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"google.golang.org/grpc/peer"
)
@@ -54,13 +52,8 @@ func TestLink_transactional(t *testing.T) {
pool, poolMember := setupObjectPool(t)
require.NoError(t, pool.Create(ctx, poolMember))
- votes := 0
- pool.txManager = &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
+ pool.txManager = txManager
alternatesPath, err := pool.locator.InfoAlternatesPath(poolMember)
require.NoError(t, err)
@@ -74,7 +67,7 @@ func TestLink_transactional(t *testing.T) {
require.NoError(t, pool.Link(ctx, poolMember))
- require.Equal(t, 2, votes)
+ require.Equal(t, 2, len(txManager.Votes()))
}
func TestLinkRemoveBitmap(t *testing.T) {
diff --git a/internal/gitaly/hook/referencetransaction.go b/internal/gitaly/hook/referencetransaction.go
index f391876e0..bba8eaabe 100644
--- a/internal/gitaly/hook/referencetransaction.go
+++ b/internal/gitaly/hook/referencetransaction.go
@@ -9,6 +9,7 @@ import (
"io"
"gitlab.com/gitlab-org/gitaly/v14/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
)
// forceDeletionPrefix is the prefix of a queued reference transaction which deletes a
@@ -27,13 +28,19 @@ func (m *GitLabHookManager) ReferenceTransactionHook(ctx context.Context, state
return fmt.Errorf("reading stdin from request: %w", err)
}
+ var phase voting.Phase
+ switch state {
// We're voting in prepared state as this is the only stage in Git's reference transaction
- // which allows us to abort the transaction. We're also voting in committed state to tell
- // Praefect we've actually persisted the changes. This is necessary as some RPCs fail return
- // errors in the response body rather than as an error code. Praefect can't tell if these RPCs
- // have failed. Voting on committed ensure Praefect sees either a missing vote or that the RPC did
- // commit the changes.
- if state != ReferenceTransactionPrepared && state != ReferenceTransactionCommitted {
+ // which allows us to abort the transaction.
+ case ReferenceTransactionPrepared:
+ phase = voting.Prepared
+ // We're also voting in committed state to tell Praefect we've actually persisted the
+ // changes. This is necessary as some RPCs fail return errors in the response body rather
+ // than as an error code. Praefect can't tell if these RPCs have failed. Voting on committed
+ // ensure Praefect sees either a missing vote or that the RPC did commit the changes.
+ case ReferenceTransactionCommitted:
+ phase = voting.Committed
+ default:
return nil
}
@@ -61,7 +68,7 @@ func (m *GitLabHookManager) ReferenceTransactionHook(ctx context.Context, state
hash := sha1.Sum(changes)
- if err := m.voteOnTransaction(ctx, hash, payload); err != nil {
+ if err := m.voteOnTransaction(ctx, hash, phase, payload); err != nil {
return fmt.Errorf("error voting on transaction: %w", err)
}
diff --git a/internal/gitaly/hook/transactions.go b/internal/gitaly/hook/transactions.go
index 78cd82c4c..33097b551 100644
--- a/internal/gitaly/hook/transactions.go
+++ b/internal/gitaly/hook/transactions.go
@@ -32,9 +32,14 @@ func (m *GitLabHookManager) runWithTransaction(ctx context.Context, payload git.
return nil
}
-func (m *GitLabHookManager) voteOnTransaction(ctx context.Context, vote voting.Vote, payload git.HooksPayload) error {
+func (m *GitLabHookManager) voteOnTransaction(
+ ctx context.Context,
+ vote voting.Vote,
+ phase voting.Phase,
+ payload git.HooksPayload,
+) error {
return m.runWithTransaction(ctx, payload, func(ctx context.Context, tx txinfo.Transaction) error {
- return m.txManager.Vote(ctx, tx, vote)
+ return m.txManager.Vote(ctx, tx, vote, phase)
})
}
diff --git a/internal/gitaly/hook/transactions_test.go b/internal/gitaly/hook/transactions_test.go
index 0f1759b95..de9f7d7cb 100644
--- a/internal/gitaly/hook/transactions_test.go
+++ b/internal/gitaly/hook/transactions_test.go
@@ -124,7 +124,7 @@ func TestHookManager_contextCancellationCancelsVote(t *testing.T) {
cfg, repo, _ := testcfg.BuildWithRepo(t)
mockTxMgr := transaction.MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+ VoteFn: func(ctx context.Context, _ txinfo.Transaction, _ voting.Vote, _ voting.Phase) error {
<-ctx.Done()
return fmt.Errorf("mock error: %s", ctx.Err())
},
diff --git a/internal/gitaly/service/operations/apply_patch_test.go b/internal/gitaly/service/operations/apply_patch_test.go
index 89b50f4f0..b0676804d 100644
--- a/internal/gitaly/service/operations/apply_patch_test.go
+++ b/internal/gitaly/service/operations/apply_patch_test.go
@@ -1,12 +1,10 @@
package operations
import (
- "context"
"fmt"
"io"
"os"
"strings"
- "sync/atomic"
"testing"
"testing/iotest"
"time"
@@ -26,7 +24,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"gitlab.com/gitlab-org/gitaly/v14/streamio"
"google.golang.org/grpc/codes"
@@ -621,16 +618,7 @@ func TestUserApplyPatchTransactional(t *testing.T) {
ctx, cancel := testhelper.Context()
defer cancel()
- var votes int32
- txManager := &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- // We can see a race here between adding the worktree and changing its
- // config and updating refs via the reference-transaction hook. Given that
- // both come in via different threads, Go perceives it as a potential race.
- atomic.AddInt32(&votes, 1)
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
ctx, cfg, repoProto, repoPath, client := setupOperationsService(t, ctx, testserver.WithTransactionManager(txManager))
@@ -665,7 +653,7 @@ func TestUserApplyPatchTransactional(t *testing.T) {
require.True(t, response.BranchUpdate.BranchCreated)
- require.Equal(t, int32(14), votes)
+ require.Equal(t, 14, len(txManager.Votes()))
splitIndex := gittest.Exec(t, cfg, "-C", repoPath, "config", "core.splitIndex")
require.Equal(t, "false", text.ChompBytes(splitIndex))
diff --git a/internal/gitaly/service/operations/rebase_test.go b/internal/gitaly/service/operations/rebase_test.go
index 555c7048c..5c05880b3 100644
--- a/internal/gitaly/service/operations/rebase_test.go
+++ b/internal/gitaly/service/operations/rebase_test.go
@@ -1,7 +1,6 @@
package operations
import (
- "context"
"fmt"
"io"
"strings"
@@ -18,7 +17,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
@@ -93,13 +91,7 @@ func TestUserRebaseConfirmableTransaction(t *testing.T) {
ctx, cancel := testhelper.Context()
defer cancel()
- var voteCount int
- txManager := &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- voteCount++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
ctx, cfg, repoProto, repoPath, client := setupOperationsService(
t, ctx,
@@ -141,7 +133,7 @@ func TestUserRebaseConfirmableTransaction(t *testing.T) {
t.Run(tc.desc, func(t *testing.T) {
preReceiveHookOutputPath := gittest.WriteEnvToCustomHook(t, repoPath, "pre-receive")
- voteCount = 0
+ txManager.Reset()
ctx := ctx
if tc.withTransaction {
@@ -173,7 +165,7 @@ func TestUserRebaseConfirmableTransaction(t *testing.T) {
require.Nil(t, response)
require.Equal(t, io.EOF, err)
- require.Equal(t, tc.expectedVotes, voteCount)
+ require.Equal(t, tc.expectedVotes, len(txManager.Votes()))
if tc.expectPreReceiveHook {
require.FileExists(t, preReceiveHookOutputPath)
} else {
diff --git a/internal/gitaly/service/ref/delete_refs.go b/internal/gitaly/service/ref/delete_refs.go
index 26e6d2929..222432ed0 100644
--- a/internal/gitaly/service/ref/delete_refs.go
+++ b/internal/gitaly/service/ref/delete_refs.go
@@ -60,11 +60,18 @@ func (s *server) DeleteRefs(ctx context.Context, in *gitalypb.DeleteRefsRequest)
return nil, helper.ErrInternalf("could not compute vote: %v", err)
}
+ // We don't use proper two-phase voting when the feature flag is disabled, so we use
+ // `UnknownPhase` in that case.
+ preparedPhase := voting.UnknownPhase
+ if featureflag.TxTwoPhaseDeleteRefs.IsEnabled(ctx) {
+ preparedPhase = voting.Prepared
+ }
+
// All deletes we're doing in this RPC are force deletions. Because we're required to filter
// out transactions which only consist of force deletions, we never do any voting via the
// reference-transaction hook here. Instead, we need to resort to a manual vote which is
// simply the concatenation of all reference we're about to delete.
- if err := transaction.VoteOnContext(ctx, s.txManager, vote); err != nil {
+ if err := transaction.VoteOnContext(ctx, s.txManager, vote, preparedPhase); err != nil {
return nil, helper.ErrInternalf("preparatory vote: %w", err)
}
@@ -77,7 +84,7 @@ func (s *server) DeleteRefs(ctx context.Context, in *gitalypb.DeleteRefsRequest)
}
if featureflag.TxTwoPhaseDeleteRefs.IsEnabled(ctx) {
- if err := transaction.VoteOnContext(ctx, s.txManager, vote); err != nil {
+ if err := transaction.VoteOnContext(ctx, s.txManager, vote, voting.Committed); err != nil {
return nil, helper.ErrInternalf("committing vote: %w", err)
}
}
diff --git a/internal/gitaly/service/ref/delete_refs_test.go b/internal/gitaly/service/ref/delete_refs_test.go
index b52c3f74c..b508e4308 100644
--- a/internal/gitaly/service/ref/delete_refs_test.go
+++ b/internal/gitaly/service/ref/delete_refs_test.go
@@ -17,7 +17,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -92,13 +91,7 @@ func testDeleteRefsTransaction(t *testing.T, ctx context.Context) {
testcfg.BuildGitalyHooks(t, cfg)
- var votes int
- txManager := &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
addr := testserver.RunGitalyServer(t, cfg, nil, func(srv *grpc.Server, deps *service.Dependencies) {
gitalypb.RegisterRefServiceServer(srv, NewServer(
@@ -139,7 +132,7 @@ func testDeleteRefsTransaction(t *testing.T, ctx context.Context) {
},
} {
t.Run(tc.desc, func(t *testing.T) {
- votes = 0
+ txManager.Reset()
repo, _ := gittest.CloneRepo(t, cfg, cfg.Storages[0])
tc.request.Repository = repo
@@ -153,7 +146,7 @@ func testDeleteRefsTransaction(t *testing.T, ctx context.Context) {
expectedVotes *= 2
}
- require.Equal(t, expectedVotes, votes)
+ require.Equal(t, expectedVotes, len(txManager.Votes()))
})
}
}
diff --git a/internal/gitaly/service/repository/apply_gitattributes.go b/internal/gitaly/service/repository/apply_gitattributes.go
index 82a08d1a1..8f188183a 100644
--- a/internal/gitaly/service/repository/apply_gitattributes.go
+++ b/internal/gitaly/service/repository/apply_gitattributes.go
@@ -66,7 +66,7 @@ func (s *server) applyGitattributes(ctx context.Context, repo *localrepo.Repo, o
// We use the zero OID as placeholder to vote on removal of the
// gitattributes file.
- if err := s.vote(ctx, git.ZeroOID); err != nil {
+ if err := s.vote(ctx, git.ZeroOID, voting.Prepared); err != nil {
return fmt.Errorf("preimage vote: %w", err)
}
@@ -74,7 +74,7 @@ func (s *server) applyGitattributes(ctx context.Context, repo *localrepo.Repo, o
return err
}
- if err := s.vote(ctx, git.ZeroOID); err != nil {
+ if err := s.vote(ctx, git.ZeroOID, voting.Committed); err != nil {
return fmt.Errorf("postimage vote: %w", err)
}
@@ -83,7 +83,7 @@ func (s *server) applyGitattributes(ctx context.Context, repo *localrepo.Repo, o
// If there is no gitattributes file, we simply use the ZeroOID
// as a placeholder to vote on the removal.
- if err := s.vote(ctx, git.ZeroOID); err != nil {
+ if err := s.vote(ctx, git.ZeroOID, voting.UnknownPhase); err != nil {
return fmt.Errorf("could not remove gitattributes: %w", err)
}
@@ -114,7 +114,7 @@ func (s *server) applyGitattributes(ctx context.Context, repo *localrepo.Repo, o
return nil
}
-func (s *server) vote(ctx context.Context, oid git.ObjectID) error {
+func (s *server) vote(ctx context.Context, oid git.ObjectID, phase voting.Phase) error {
tx, err := txinfo.TransactionFromContext(ctx)
if errors.Is(err, txinfo.ErrTransactionNotFound) {
return nil
@@ -130,7 +130,7 @@ func (s *server) vote(ctx context.Context, oid git.ObjectID) error {
return fmt.Errorf("cannot convert OID to vote: %w", err)
}
- if err := s.txManager.Vote(ctx, tx, vote); err != nil {
+ if err := s.txManager.Vote(ctx, tx, vote, phase); err != nil {
return fmt.Errorf("vote failed: %w", err)
}
diff --git a/internal/gitaly/service/repository/create_repository.go b/internal/gitaly/service/repository/create_repository.go
index 334d8b79b..7d3243eb9 100644
--- a/internal/gitaly/service/repository/create_repository.go
+++ b/internal/gitaly/service/repository/create_repository.go
@@ -84,7 +84,7 @@ func (s *server) CreateRepository(ctx context.Context, req *gitalypb.CreateRepos
return err
}
- if err := s.txManager.Vote(ctx, tx, vote); err != nil {
+ if err := s.txManager.Vote(ctx, tx, vote, voting.UnknownPhase); err != nil {
return fmt.Errorf("casting vote: %w", err)
}
diff --git a/internal/gitaly/service/repository/create_repository_from_bundle_test.go b/internal/gitaly/service/repository/create_repository_from_bundle_test.go
index 90ab876d9..668b135f5 100644
--- a/internal/gitaly/service/repository/create_repository_from_bundle_test.go
+++ b/internal/gitaly/service/repository/create_repository_from_bundle_test.go
@@ -104,16 +104,9 @@ func TestCreateRepositoryFromBundle_transactional(t *testing.T) {
}
func testCreateRepositoryFromBundleTransactional(t *testing.T, ctx context.Context) {
- var votes []voting.Vote
- txManager := &transaction.MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
- votes = append(votes, vote)
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
- cfg, repoProto, repoPath, client := setupRepositoryService(t,
- testserver.WithTransactionManager(txManager))
+ cfg, repoProto, repoPath, client := setupRepositoryService(t, testserver.WithTransactionManager(txManager))
masterOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "refs/heads/master"))
featureOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "refs/heads/feature"))
@@ -155,50 +148,43 @@ func testCreateRepositoryFromBundleTransactional(t *testing.T, ctx context.Conte
require.NoError(t, err)
if featureflag.TxAtomicRepositoryCreation.IsEnabled(ctx) {
- var actualVotes []string
- for _, vote := range votes {
- actualVotes = append(actualVotes, vote.String())
+ createVote := func(hash string, phase voting.Phase) transaction.PhasedVote {
+ vote, err := voting.VoteFromString(hash)
+ require.NoError(t, err)
+ return transaction.PhasedVote{Vote: vote, Phase: phase}
}
// While the following votes are opaque to us, this doesn't really matter. All we do
// care about is that they're stable.
- require.Equal(t, []string{
+ require.Equal(t, []transaction.PhasedVote{
// These are the votes created by git-fetch(1).
- "47553c06f575f757ad56ef3216c59804b72aa4a6",
- "47553c06f575f757ad56ef3216c59804b72aa4a6",
+ createVote("47553c06f575f757ad56ef3216c59804b72aa4a6", voting.Prepared),
+ createVote("47553c06f575f757ad56ef3216c59804b72aa4a6", voting.Committed),
// And this is the manual votes we compute by walking the repository.
- "da39a3ee5e6b4b0d3255bfef95601890afd80709",
- "da39a3ee5e6b4b0d3255bfef95601890afd80709",
- }, actualVotes)
+ createVote("da39a3ee5e6b4b0d3255bfef95601890afd80709", voting.Prepared),
+ createVote("da39a3ee5e6b4b0d3255bfef95601890afd80709", voting.Committed),
+ }, txManager.Votes())
return
}
- var votingInput []string
-
// This accounts for the first two votes via git-clone(1). Given that git-clone(1) creates
// all references in a single reference transaction, the hook is executed twice for all
// fetched references (once for "prepare", once for "commit").
- votingInput = append(votingInput,
- fmt.Sprintf("%s %s refs/heads/feature\n%s %s refs/heads/master\n", git.ZeroOID, featureOID, git.ZeroOID, masterOID),
- fmt.Sprintf("%s %s refs/heads/feature\n%s %s refs/heads/master\n", git.ZeroOID, featureOID, git.ZeroOID, masterOID),
- )
+ voteFromCloning := []byte(fmt.Sprintf("%s %s refs/heads/feature\n%s %s refs/heads/master\n", git.ZeroOID, featureOID, git.ZeroOID, masterOID))
// Keep-around references are not fetched via git-clone(1) because non-mirror clones only
// fetch branches and tags. These additional references are thus obtained via git-fetch(1).
// Given that we use the `--atomic` flag for git-fetch(1), all reference updates will be in
// a single transaction and we thus expect exactly two votes (once for "prepare", once for
// "commit").
- votingInput = append(votingInput,
- fmt.Sprintf("%s %s refs/keep-around/2\n%s %s refs/keep-around/1\n", git.ZeroOID, masterOID, git.ZeroOID, masterOID),
- fmt.Sprintf("%s %s refs/keep-around/2\n%s %s refs/keep-around/1\n", git.ZeroOID, masterOID, git.ZeroOID, masterOID),
- )
-
- var expectedVotes []voting.Vote
- for _, expectedVote := range votingInput {
- expectedVotes = append(expectedVotes, voting.VoteFromData([]byte(expectedVote)))
- }
-
- require.Equal(t, votes, expectedVotes)
+ voteFromFetching := []byte(fmt.Sprintf("%s %s refs/keep-around/2\n%s %s refs/keep-around/1\n", git.ZeroOID, masterOID, git.ZeroOID, masterOID))
+
+ require.Equal(t, []transaction.PhasedVote{
+ {Vote: voting.VoteFromData(voteFromCloning), Phase: voting.Prepared},
+ {Vote: voting.VoteFromData(voteFromCloning), Phase: voting.Committed},
+ {Vote: voting.VoteFromData(voteFromFetching), Phase: voting.Prepared},
+ {Vote: voting.VoteFromData(voteFromFetching), Phase: voting.Committed},
+ }, txManager.Votes())
}
func TestCreateRepositoryFromBundle_invalidBundle(t *testing.T) {
diff --git a/internal/gitaly/service/repository/create_repository_test.go b/internal/gitaly/service/repository/create_repository_test.go
index 813259a4f..6e2a6df7c 100644
--- a/internal/gitaly/service/repository/create_repository_test.go
+++ b/internal/gitaly/service/repository/create_repository_test.go
@@ -150,26 +150,16 @@ func TestCreateRepository_transactional(t *testing.T) {
}
func testCreateRepositoryTransactional(t *testing.T, ctx context.Context) {
- var actualVote voting.Vote
- var called int
-
- mockTxManager := transaction.MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, v voting.Vote) error {
- actualVote = v
- called++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
- cfg, client := setupRepositoryServiceWithoutRepo(t, testserver.WithTransactionManager(&mockTxManager))
+ cfg, client := setupRepositoryServiceWithoutRepo(t, testserver.WithTransactionManager(txManager))
ctx, err := txinfo.InjectTransaction(ctx, 1, "node", true)
require.NoError(t, err)
ctx = metadata.IncomingToOutgoing(ctx)
t.Run("initial creation without refs", func(t *testing.T) {
- called = 0
- actualVote = voting.Vote{}
+ txManager.Reset()
repo := &gitalypb.Repository{
StorageName: cfg.Storages[0].Name,
@@ -180,16 +170,16 @@ func testCreateRepositoryTransactional(t *testing.T, ctx context.Context) {
require.DirExists(t, filepath.Join(cfg.Storages[0].Path, getReplicaPath(ctx, t, client, repo)))
if featureflag.TxAtomicRepositoryCreation.IsEnabled(ctx) {
- require.Equal(t, 2, called, "expected transactional vote")
+ require.Equal(t, 2, len(txManager.Votes()), "expected transactional vote")
} else {
- require.Equal(t, 1, called, "expected transactional vote")
- require.Equal(t, voting.VoteFromData([]byte{}), actualVote)
+ require.Equal(t, []transaction.PhasedVote{
+ {Vote: voting.VoteFromData([]byte{}), Phase: voting.UnknownPhase},
+ }, txManager.Votes())
}
})
t.Run("idempotent creation with preexisting refs", func(t *testing.T) {
- called = 0
- actualVote = voting.Vote{}
+ txManager.Reset()
// The above test creates the second repository on the server. As this test can run with Praefect in front of it,
// we'll use the next replica path Praefect will assign in order to ensure this repository creation conflicts even
@@ -212,8 +202,9 @@ func testCreateRepositoryTransactional(t *testing.T, ctx context.Context) {
refs := gittest.Exec(t, cfg, "-C", repoPath, "for-each-ref")
require.NotEmpty(t, refs)
- require.Equal(t, 1, called, "expected transactional vote")
- require.Equal(t, voting.VoteFromData(refs), actualVote)
+ require.Equal(t, []transaction.PhasedVote{
+ {Vote: voting.VoteFromData(refs), Phase: voting.UnknownPhase},
+ }, txManager.Votes())
})
}
diff --git a/internal/gitaly/service/repository/fetch_remote.go b/internal/gitaly/service/repository/fetch_remote.go
index 81845a363..f7849d838 100644
--- a/internal/gitaly/service/repository/fetch_remote.go
+++ b/internal/gitaly/service/repository/fetch_remote.go
@@ -114,7 +114,7 @@ func (s *server) FetchRemote(ctx context.Context, req *gitalypb.FetchRemoteReque
return err
}
- return s.txManager.Vote(ctx, tx, vote)
+ return s.txManager.Vote(ctx, tx, vote, voting.UnknownPhase)
}); err != nil {
return nil, status.Errorf(codes.Aborted, "failed vote on refs: %v", err)
}
diff --git a/internal/gitaly/service/repository/fetch_remote_test.go b/internal/gitaly/service/repository/fetch_remote_test.go
index 56355d45e..8f86bcac7 100644
--- a/internal/gitaly/service/repository/fetch_remote_test.go
+++ b/internal/gitaly/service/repository/fetch_remote_test.go
@@ -1,7 +1,6 @@
package repository
import (
- "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -24,7 +23,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -184,21 +182,11 @@ func TestFetchRemote_withDefaultRefmaps(t *testing.T) {
require.Equal(t, sourceRefs, targetRefs)
}
-type mockTxManager struct {
- transaction.Manager
- votes int
-}
-
-func (m *mockTxManager) Vote(context.Context, txinfo.Transaction, voting.Vote) error {
- m.votes++
- return nil
-}
-
func TestFetchRemote_transaction(t *testing.T) {
t.Parallel()
sourceCfg, _, sourceRepoPath := testcfg.BuildWithRepo(t)
- txManager := &mockTxManager{}
+ txManager := transaction.NewTrackingManager()
addr := testserver.RunGitalyServer(t, sourceCfg, nil, func(srv *grpc.Server, deps *service.Dependencies) {
gitalypb.RegisterRepositoryServiceServer(srv, NewServer(
deps.GetCfg(),
@@ -223,7 +211,7 @@ func TestFetchRemote_transaction(t *testing.T) {
require.NoError(t, err)
ctx = metadata.IncomingToOutgoing(ctx)
- require.Equal(t, 0, txManager.votes)
+ require.Equal(t, 0, len(txManager.Votes()))
_, err = client.FetchRemote(ctx, &gitalypb.FetchRemoteRequest{
Repository: targetRepoProto,
@@ -233,7 +221,7 @@ func TestFetchRemote_transaction(t *testing.T) {
})
require.NoError(t, err)
- require.Equal(t, 1, txManager.votes)
+ require.Equal(t, 1, len(txManager.Votes()))
}
func TestFetchRemote_prune(t *testing.T) {
diff --git a/internal/gitaly/service/repository/midx_test.go b/internal/gitaly/service/repository/midx_test.go
index 03db17ff1..b160f02d1 100644
--- a/internal/gitaly/service/repository/midx_test.go
+++ b/internal/gitaly/service/repository/midx_test.go
@@ -20,7 +20,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"google.golang.org/grpc/peer"
)
@@ -118,13 +117,7 @@ func TestMidxRepack_transactional(t *testing.T) {
ctx, cancel := testhelper.Context()
defer cancel()
- votes := 0
- txManager := &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- }
+ txManager := transaction.NewTrackingManager()
cfg, repo, repoPath, client := setupRepositoryService(t, testserver.WithTransactionManager(txManager))
@@ -140,7 +133,7 @@ func TestMidxRepack_transactional(t *testing.T) {
})
require.NoError(t, err)
- require.Equal(t, 2, votes)
+ require.Equal(t, 2, len(txManager.Votes()))
multiPackIndex := gittest.Exec(t, cfg, "-C", repoPath, "config", "core.multiPackIndex")
require.Equal(t, "true", text.ChompBytes(multiPackIndex))
diff --git a/internal/gitaly/service/repository/remove.go b/internal/gitaly/service/repository/remove.go
index c7befa398..e8a00efd7 100644
--- a/internal/gitaly/service/repository/remove.go
+++ b/internal/gitaly/service/repository/remove.go
@@ -84,7 +84,7 @@ func (s *server) RemoveRepository(ctx context.Context, in *gitalypb.RemoveReposi
return nil, helper.ErrInternalf("re-statting repository: %w", err)
}
- if err := s.voteOnAction(ctx, repo, preRemove); err != nil {
+ if err := s.voteOnAction(ctx, repo, voting.Prepared); err != nil {
return nil, helper.ErrInternalf("vote on rename: %v", err)
}
@@ -99,11 +99,11 @@ func (s *server) RemoveRepository(ctx context.Context, in *gitalypb.RemoveReposi
return nil, helper.ErrInternalf("removing repository: %w", err)
}
- if err := s.voteOnAction(ctx, repo, postRemove); err != nil {
+ if err := s.voteOnAction(ctx, repo, voting.Committed); err != nil {
return nil, helper.ErrInternalf("vote on finalizing: %v", err)
}
} else {
- if err := s.voteOnAction(ctx, repo, preRemove); err != nil {
+ if err := s.voteOnAction(ctx, repo, voting.Prepared); err != nil {
return nil, helper.ErrInternalf("vote on rename: %v", err)
}
@@ -121,7 +121,7 @@ func (s *server) RemoveRepository(ctx context.Context, in *gitalypb.RemoveReposi
return nil, helper.ErrInternal(err)
}
- if err := s.voteOnAction(ctx, repo, postRemove); err != nil {
+ if err := s.voteOnAction(ctx, repo, voting.Committed); err != nil {
return nil, helper.ErrInternalf("vote on finalizing: %v", err)
}
}
@@ -129,26 +129,19 @@ func (s *server) RemoveRepository(ctx context.Context, in *gitalypb.RemoveReposi
return &gitalypb.RemoveRepositoryResponse{}, nil
}
-type removalStep int
-
-const (
- preRemove = removalStep(iota)
- postRemove
-)
-
-func (s *server) voteOnAction(ctx context.Context, repo *gitalypb.Repository, step removalStep) error {
+func (s *server) voteOnAction(ctx context.Context, repo *gitalypb.Repository, phase voting.Phase) error {
return transaction.RunOnContext(ctx, func(tx txinfo.Transaction) error {
var voteStep string
- switch step {
- case preRemove:
+ switch phase {
+ case voting.Prepared:
voteStep = "pre-remove"
- case postRemove:
+ case voting.Committed:
voteStep = "post-remove"
default:
- return fmt.Errorf("invalid removal step: %d", step)
+ return fmt.Errorf("invalid removal step: %d", phase)
}
vote := fmt.Sprintf("%s %s", voteStep, repo.GetRelativePath())
- return s.txManager.Vote(ctx, tx, voting.VoteFromData([]byte(vote)))
+ return s.txManager.Vote(ctx, tx, voting.VoteFromData([]byte(vote)), phase)
})
}
diff --git a/internal/gitaly/service/repository/util.go b/internal/gitaly/service/repository/util.go
index 50a1182b3..dda214eb1 100644
--- a/internal/gitaly/service/repository/util.go
+++ b/internal/gitaly/service/repository/util.go
@@ -177,7 +177,7 @@ func (s *server) createRepository(
return helper.ErrAlreadyExistsf("repository exists already")
}
- if err := transaction.VoteOnContext(ctx, s.txManager, vote); err != nil {
+ if err := transaction.VoteOnContext(ctx, s.txManager, vote, voting.Prepared); err != nil {
return helper.ErrFailedPreconditionf("preparatory vote: %w", err)
}
@@ -187,7 +187,7 @@ func (s *server) createRepository(
return fmt.Errorf("moving repository into place: %w", err)
}
- if err := transaction.VoteOnContext(ctx, s.txManager, vote); err != nil {
+ if err := transaction.VoteOnContext(ctx, s.txManager, vote, voting.Committed); err != nil {
return helper.ErrFailedPreconditionf("committing vote: %w", err)
}
diff --git a/internal/gitaly/service/repository/util_test.go b/internal/gitaly/service/repository/util_test.go
index 2e4602a63..48ccebd15 100644
--- a/internal/gitaly/service/repository/util_test.go
+++ b/internal/gitaly/service/repository/util_test.go
@@ -44,7 +44,7 @@ func TestCreateRepository(t *testing.T) {
gitCmdFactory: gitCmdFactory,
}
- votes := 0
+ var votesByPhase map[voting.Phase]int
for _, tc := range []struct {
desc string
@@ -136,21 +136,24 @@ func TestCreateRepository(t *testing.T) {
desc: "successful transaction",
transactional: true,
setup: func(t *testing.T, repo *gitalypb.Repository, repoPath string) {
- votes = 0
- txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
+ votesByPhase = map[voting.Phase]int{}
+ txManager.VoteFn = func(_ context.Context, _ txinfo.Transaction, _ voting.Vote, phase voting.Phase) error {
+ votesByPhase[phase]++
return nil
}
},
verify: func(t *testing.T, tempRepo *gitalypb.Repository, tempRepoPath string, realRepo *gitalypb.Repository, realRepoPath string) {
- require.Equal(t, 2, votes)
+ require.Equal(t, map[voting.Phase]int{
+ voting.Prepared: 1,
+ voting.Committed: 1,
+ }, votesByPhase)
},
},
{
desc: "failing preparatory vote",
transactional: true,
setup: func(t *testing.T, repo *gitalypb.Repository, repoPath string) {
- txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote) error {
+ txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error {
return errors.New("vote failed")
}
},
@@ -164,10 +167,8 @@ func TestCreateRepository(t *testing.T) {
desc: "failing post-commit vote",
transactional: true,
setup: func(t *testing.T, repo *gitalypb.Repository, repoPath string) {
- votes = 0
- txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- if votes == 1 {
+ txManager.VoteFn = func(_ context.Context, _ txinfo.Transaction, _ voting.Vote, phase voting.Phase) error {
+ if phase == voting.Prepared {
return nil
}
return errors.New("vote failed")
@@ -196,7 +197,7 @@ func TestCreateRepository(t *testing.T) {
require.NoError(t, err)
require.NoError(t, lock.Close())
- txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote) error {
+ txManager.VoteFn = func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error {
require.FailNow(t, "no votes should have happened")
return nil
}
diff --git a/internal/gitaly/service/ssh/receive_pack.go b/internal/gitaly/service/ssh/receive_pack.go
index 4e82eddc4..80c359e03 100644
--- a/internal/gitaly/service/ssh/receive_pack.go
+++ b/internal/gitaly/service/ssh/receive_pack.go
@@ -100,7 +100,7 @@ func (s *server) sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer,
// ensure there's always at least one vote. In case there was diverging behaviour in
// git-receive-pack(1) which led to a different outcome across voters, then this final vote
// would fail because the sequence of votes would be different.
- if err := transaction.VoteOnContext(ctx, s.txManager, voting.Vote{}); err != nil {
+ if err := transaction.VoteOnContext(ctx, s.txManager, voting.Vote{}, voting.Committed); err != nil {
return status.Errorf(codes.Aborted, "final transactional vote: %v", err)
}
diff --git a/internal/gitaly/service/ssh/receive_pack_test.go b/internal/gitaly/service/ssh/receive_pack_test.go
index 6c27ae7d8..56d9ac5c3 100644
--- a/internal/gitaly/service/ssh/receive_pack_test.go
+++ b/internal/gitaly/service/ssh/receive_pack_test.go
@@ -2,7 +2,6 @@ package ssh
import (
"bytes"
- "context"
"fmt"
"io"
"os"
@@ -29,7 +28,6 @@ import (
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
- "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
"gitlab.com/gitlab-org/gitaly/v14/streamio"
"google.golang.org/grpc/codes"
@@ -282,15 +280,9 @@ func TestReceivePackTransactional(t *testing.T) {
testcfg.BuildGitalyHooks(t, cfg)
- var votes int
- serverSocketPath := runSSHServer(t, cfg, testserver.WithTransactionManager(
- &transaction.MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
- votes++
- return nil
- },
- },
- ))
+ txManager := transaction.NewTrackingManager()
+
+ serverSocketPath := runSSHServer(t, cfg, testserver.WithTransactionManager(txManager))
client, conn := newSSHClient(t, serverSocketPath)
defer conn.Close()
@@ -435,7 +427,7 @@ func TestReceivePackTransactional(t *testing.T) {
},
} {
t.Run(tc.desc, func(t *testing.T) {
- votes = 0
+ txManager.Reset()
var request bytes.Buffer
for i, command := range tc.commands {
@@ -480,7 +472,7 @@ func TestReceivePackTransactional(t *testing.T) {
require.Equal(t, expectedOID, actualOID.String())
}
}
- require.Equal(t, tc.expectedVotes, votes)
+ require.Equal(t, tc.expectedVotes, len(txManager.Votes()))
})
}
}
diff --git a/internal/gitaly/transaction/manager.go b/internal/gitaly/transaction/manager.go
index e53088115..d7e2a0993 100644
--- a/internal/gitaly/transaction/manager.go
+++ b/internal/gitaly/transaction/manager.go
@@ -47,7 +47,7 @@ var (
type Manager interface {
// Vote casts a vote on the given transaction which is hosted by the
// given Praefect server.
- Vote(context.Context, txinfo.Transaction, voting.Vote) error
+ Vote(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error
// Stop gracefully stops the given transaction which is hosted by the
// given Praefect server.
@@ -97,7 +97,12 @@ func (m *PoolManager) getTransactionClient(ctx context.Context, tx txinfo.Transa
}
// Vote connects to the given server and casts vote as a vote for the transaction identified by tx.
-func (m *PoolManager) Vote(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+func (m *PoolManager) Vote(
+ ctx context.Context,
+ tx txinfo.Transaction,
+ vote voting.Vote,
+ phase voting.Phase,
+) error {
client, err := m.getTransactionClient(ctx, tx)
if err != nil {
return err
@@ -118,6 +123,7 @@ func (m *PoolManager) Vote(ctx context.Context, tx txinfo.Transaction, vote voti
TransactionId: tx.ID,
Node: tx.Node,
ReferenceUpdatesHash: vote.Bytes(),
+ Phase: phase.ToProto(),
})
if err != nil {
// Add some additional context to cancellation errors so that
diff --git a/internal/gitaly/transaction/manager_test.go b/internal/gitaly/transaction/manager_test.go
index 6239f0ab2..eb67aa769 100644
--- a/internal/gitaly/transaction/manager_test.go
+++ b/internal/gitaly/transaction/manager_test.go
@@ -63,21 +63,64 @@ func TestPoolManager_Vote(t *testing.T) {
desc string
transaction txinfo.Transaction
vote voting.Vote
+ phase voting.Phase
voteFn func(*testing.T, *gitalypb.VoteTransactionRequest) (*gitalypb.VoteTransactionResponse, error)
expectedErr error
}{
{
- desc: "successful vote",
+ desc: "successful unknown vote",
transaction: txinfo.Transaction{
BackchannelID: backchannelID,
ID: 1,
Node: "node",
},
- vote: voting.VoteFromData([]byte("foobar")),
+ vote: voting.VoteFromData([]byte("foobar")),
+ phase: voting.UnknownPhase,
voteFn: func(t *testing.T, request *gitalypb.VoteTransactionRequest) (*gitalypb.VoteTransactionResponse, error) {
require.Equal(t, uint64(1), request.TransactionId)
require.Equal(t, "node", request.Node)
require.Equal(t, request.ReferenceUpdatesHash, voting.VoteFromData([]byte("foobar")).Bytes())
+ require.Equal(t, gitalypb.VoteTransactionRequest_UNKNOWN_PHASE, request.Phase)
+
+ return &gitalypb.VoteTransactionResponse{
+ State: gitalypb.VoteTransactionResponse_COMMIT,
+ }, nil
+ },
+ },
+ {
+ desc: "successful prepared vote",
+ transaction: txinfo.Transaction{
+ BackchannelID: backchannelID,
+ ID: 1,
+ Node: "node",
+ },
+ vote: voting.VoteFromData([]byte("foobar")),
+ phase: voting.Prepared,
+ voteFn: func(t *testing.T, request *gitalypb.VoteTransactionRequest) (*gitalypb.VoteTransactionResponse, error) {
+ require.Equal(t, uint64(1), request.TransactionId)
+ require.Equal(t, "node", request.Node)
+ require.Equal(t, request.ReferenceUpdatesHash, voting.VoteFromData([]byte("foobar")).Bytes())
+ require.Equal(t, gitalypb.VoteTransactionRequest_PREPARED_PHASE, request.Phase)
+
+ return &gitalypb.VoteTransactionResponse{
+ State: gitalypb.VoteTransactionResponse_COMMIT,
+ }, nil
+ },
+ },
+ {
+ desc: "successful committed vote",
+ transaction: txinfo.Transaction{
+ BackchannelID: backchannelID,
+ ID: 1,
+ Node: "node",
+ },
+ vote: voting.VoteFromData([]byte("foobar")),
+ phase: voting.Committed,
+ voteFn: func(t *testing.T, request *gitalypb.VoteTransactionRequest) (*gitalypb.VoteTransactionResponse, error) {
+ require.Equal(t, uint64(1), request.TransactionId)
+ require.Equal(t, "node", request.Node)
+ require.Equal(t, request.ReferenceUpdatesHash, voting.VoteFromData([]byte("foobar")).Bytes())
+ require.Equal(t, gitalypb.VoteTransactionRequest_COMMITTED_PHASE, request.Phase)
return &gitalypb.VoteTransactionResponse{
State: gitalypb.VoteTransactionResponse_COMMIT,
@@ -124,7 +167,7 @@ func TestPoolManager_Vote(t *testing.T) {
return tc.voteFn(t, request)
}
- err := manager.Vote(ctx, tc.transaction, tc.vote)
+ err := manager.Vote(ctx, tc.transaction, tc.vote, tc.phase)
testhelper.RequireGrpcError(t, tc.expectedErr, err)
})
}
diff --git a/internal/gitaly/transaction/mock.go b/internal/gitaly/transaction/mock.go
index 101d4b291..10ad6efdf 100644
--- a/internal/gitaly/transaction/mock.go
+++ b/internal/gitaly/transaction/mock.go
@@ -3,6 +3,7 @@ package transaction
import (
"context"
"errors"
+ "sync"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo"
"gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting"
@@ -10,16 +11,16 @@ import (
// MockManager is a mock Manager for use in tests.
type MockManager struct {
- VoteFn func(context.Context, txinfo.Transaction, voting.Vote) error
+ VoteFn func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error
StopFn func(context.Context, txinfo.Transaction) error
}
// Vote calls the MockManager's Vote function, if set. Otherwise, it returns an error.
-func (m *MockManager) Vote(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+func (m *MockManager) Vote(ctx context.Context, tx txinfo.Transaction, vote voting.Vote, phase voting.Phase) error {
if m.VoteFn == nil {
return errors.New("mock does not implement Vote function")
}
- return m.VoteFn(ctx, tx, vote)
+ return m.VoteFn(ctx, tx, vote, phase)
}
// Stop calls the MockManager's Stop function, if set. Otherwise, it returns an error.
@@ -29,3 +30,50 @@ func (m *MockManager) Stop(ctx context.Context, tx txinfo.Transaction) error {
}
return m.StopFn(ctx, tx)
}
+
+// PhasedVote is used to keep track of votes and the phase they were cast in.
+type PhasedVote struct {
+ Vote voting.Vote
+ Phase voting.Phase
+}
+
+// TrackingManager is a transaction manager which tracks all votes. Voting functions never return
+// an error.
+type TrackingManager struct {
+ MockManager
+
+ votesLock sync.Mutex
+ votes []PhasedVote
+}
+
+// NewTrackingManager creates a new TrackingManager which is ready for use.
+func NewTrackingManager() *TrackingManager {
+ manager := &TrackingManager{}
+
+ manager.VoteFn = func(_ context.Context, _ txinfo.Transaction, vote voting.Vote, phase voting.Phase) error {
+ manager.votesLock.Lock()
+ defer manager.votesLock.Unlock()
+ manager.votes = append(manager.votes, PhasedVote{Vote: vote, Phase: phase})
+ return nil
+ }
+
+ return manager
+}
+
+// Votes returns a copy of all votes which have been cast.
+func (m *TrackingManager) Votes() []PhasedVote {
+ m.votesLock.Lock()
+ defer m.votesLock.Unlock()
+
+ votes := make([]PhasedVote, len(m.votes))
+ copy(votes, m.votes)
+
+ return votes
+}
+
+// Reset resets all votes which have been recorded up to this point.
+func (m *TrackingManager) Reset() {
+ m.votesLock.Lock()
+ defer m.votesLock.Unlock()
+ m.votes = nil
+}
diff --git a/internal/gitaly/transaction/voting.go b/internal/gitaly/transaction/voting.go
index 071d944a8..d5ed0683f 100644
--- a/internal/gitaly/transaction/voting.go
+++ b/internal/gitaly/transaction/voting.go
@@ -25,9 +25,9 @@ func RunOnContext(ctx context.Context, fn func(txinfo.Transaction) error) error
}
// VoteOnContext casts the vote on a transaction identified by the context, if there is any.
-func VoteOnContext(ctx context.Context, m Manager, vote voting.Vote) error {
+func VoteOnContext(ctx context.Context, m Manager, vote voting.Vote, phase voting.Phase) error {
return RunOnContext(ctx, func(transaction txinfo.Transaction) error {
- return m.Vote(ctx, transaction, vote)
+ return m.Vote(ctx, transaction, vote, phase)
})
}
@@ -56,7 +56,7 @@ func CommitLockedFile(ctx context.Context, m Manager, writer *safe.LockingFileWr
return fmt.Errorf("computing vote for locked file: %w", err)
}
- if err := m.Vote(ctx, tx, vote); err != nil {
+ if err := m.Vote(ctx, tx, vote, voting.Prepared); err != nil {
return fmt.Errorf("preimage vote: %w", err)
}
@@ -69,7 +69,7 @@ func CommitLockedFile(ctx context.Context, m Manager, writer *safe.LockingFileWr
return fmt.Errorf("committing file: %w", err)
}
- if err := VoteOnContext(ctx, m, vote); err != nil {
+ if err := VoteOnContext(ctx, m, vote, voting.Committed); err != nil {
return fmt.Errorf("postimage vote: %w", err)
}
diff --git a/internal/gitaly/transaction/voting_test.go b/internal/gitaly/transaction/voting_test.go
index 434c9374e..dc195c9f4 100644
--- a/internal/gitaly/transaction/voting_test.go
+++ b/internal/gitaly/transaction/voting_test.go
@@ -76,10 +76,10 @@ func TestVoteOnContext(t *testing.T) {
AuthInfo: backchannel.WithID(nil, 1234),
}
- vote := voting.VoteFromData([]byte("1"))
+ expectedVote := voting.VoteFromData([]byte("1"))
t.Run("without transaction", func(t *testing.T) {
- require.NoError(t, VoteOnContext(ctx, &MockManager{}, voting.Vote{}))
+ require.NoError(t, VoteOnContext(ctx, &MockManager{}, voting.Vote{}, voting.Prepared))
})
t.Run("successful vote", func(t *testing.T) {
@@ -89,7 +89,7 @@ func TestVoteOnContext(t *testing.T) {
callbackExecuted := false
require.NoError(t, VoteOnContext(ctx, &MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+ VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote, phase voting.Phase) error {
require.Equal(t, txinfo.Transaction{
ID: 5678,
Node: "node",
@@ -97,9 +97,11 @@ func TestVoteOnContext(t *testing.T) {
BackchannelID: 1234,
}, tx)
callbackExecuted = true
+ require.Equal(t, expectedVote, vote)
+ require.Equal(t, voting.Prepared, phase)
return nil
},
- }, vote))
+ }, expectedVote, voting.Prepared))
require.True(t, callbackExecuted, "callback should have been executed")
})
@@ -110,10 +112,10 @@ func TestVoteOnContext(t *testing.T) {
expectedErr := fmt.Errorf("any error")
require.Equal(t, expectedErr, VoteOnContext(ctx, &MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+ VoteFn: func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error {
return expectedErr
},
- }, vote))
+ }, expectedVote, voting.Prepared))
})
}
@@ -151,7 +153,7 @@ func TestCommitLockedFile(t *testing.T) {
calls := 0
require.NoError(t, CommitLockedFile(ctx, &MockManager{
- VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error {
+ VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote, phase voting.Phase) error {
require.Equal(t, txinfo.Transaction{
ID: 5678,
Node: "node",
@@ -160,6 +162,16 @@ func TestCommitLockedFile(t *testing.T) {
}, tx)
require.Equal(t, voting.VoteFromData([]byte("contents")), vote)
calls++
+
+ switch calls {
+ case 1:
+ require.Equal(t, voting.Prepared, phase)
+ case 2:
+ require.Equal(t, voting.Committed, phase)
+ default:
+ require.FailNow(t, "unexpected voting phase %q", phase)
+ }
+
return nil
},
}, writer))
@@ -177,7 +189,7 @@ func TestCommitLockedFile(t *testing.T) {
require.NoError(t, err)
err = CommitLockedFile(ctx, &MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
+ VoteFn: func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error {
return fmt.Errorf("some error")
},
}, writer)
@@ -195,11 +207,10 @@ func TestCommitLockedFile(t *testing.T) {
require.NoError(t, err)
err = CommitLockedFile(ctx, &MockManager{
- VoteFn: func(context.Context, txinfo.Transaction, voting.Vote) error {
+ VoteFn: func(context.Context, txinfo.Transaction, voting.Vote, voting.Phase) error {
// This shouldn't typically happen given that the file is locked,
// but we concurrently update the file after our first vote.
- require.NoError(t, os.WriteFile(file, []byte("something"),
- 0o666))
+ require.NoError(t, os.WriteFile(file, []byte("something"), 0o666))
return nil
},
}, writer)
diff --git a/internal/transaction/voting/phase.go b/internal/transaction/voting/phase.go
new file mode 100644
index 000000000..fb3922a6c
--- /dev/null
+++ b/internal/transaction/voting/phase.go
@@ -0,0 +1,36 @@
+package voting
+
+import (
+ "fmt"
+
+ "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
+)
+
+// Phase is the transactional phase a given vote can be cast on.
+type Phase int
+
+const (
+ // UnknownPhase is the default value. It should not be used.
+ UnknownPhase = Phase(iota)
+ // Prepared is the prepratory phase. The data that is about to change is locked for
+ // concurrent modification, but changes have not yet been written to disk.
+ Prepared
+ // Committed is the committing phase. Data has been committed to disk and will be visible
+ // in all subsequent requests.
+ Committed
+)
+
+// ToProto converts the phase into its Protobuf enum. This function panics if called with an
+// invalid phase.
+func (p Phase) ToProto() gitalypb.VoteTransactionRequest_Phase {
+ switch p {
+ case UnknownPhase:
+ return gitalypb.VoteTransactionRequest_UNKNOWN_PHASE
+ case Prepared:
+ return gitalypb.VoteTransactionRequest_PREPARED_PHASE
+ case Committed:
+ return gitalypb.VoteTransactionRequest_COMMITTED_PHASE
+ default:
+ panic(fmt.Sprintf("unknown phase %q", p))
+ }
+}
diff --git a/internal/transaction/voting/vote.go b/internal/transaction/voting/vote.go
index dcb0fc32e..19412f742 100644
--- a/internal/transaction/voting/vote.go
+++ b/internal/transaction/voting/vote.go
@@ -37,6 +37,16 @@ func VoteFromHash(bytes []byte) (Vote, error) {
return vote, nil
}
+// VoteFromString converts the given string representation of the hash into a vote.
+func VoteFromString(s string) (Vote, error) {
+ bytes, err := hex.DecodeString(s)
+ if err != nil {
+ return Vote{}, fmt.Errorf("invalid vote string: %w", err)
+ }
+
+ return VoteFromHash(bytes)
+}
+
// VoteFromData hashes the given data and converts it to a vote.
func VoteFromData(data []byte) Vote {
return sha1.Sum(data)
diff --git a/internal/transaction/voting/vote_test.go b/internal/transaction/voting/vote_test.go
index 564eb2d84..98acfd509 100644
--- a/internal/transaction/voting/vote_test.go
+++ b/internal/transaction/voting/vote_test.go
@@ -2,7 +2,10 @@ package voting
import (
"bytes"
+ "encoding/hex"
+ "errors"
"fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/require"
@@ -23,6 +26,30 @@ func TestVoteFromHash(t *testing.T) {
require.Equal(t, bytes.Repeat([]byte{1}, voteSize), vote.Bytes())
}
+func TestVoteFromString(t *testing.T) {
+ _, err := VoteFromString("")
+ require.Equal(t, fmt.Errorf("invalid vote length 0"), err)
+
+ _, err = VoteFromString("x")
+ require.Error(t, err)
+ var invalidByteError hex.InvalidByteError
+ require.True(t, errors.As(err, &invalidByteError))
+ require.Equal(t, hex.InvalidByteError('x'), invalidByteError)
+
+ _, err = VoteFromString("1234")
+ require.Equal(t, fmt.Errorf("invalid vote length 2"), err)
+
+ _, err = VoteFromString(strings.Repeat("1", (voteSize+1)*2))
+ require.Equal(t, fmt.Errorf("invalid vote length 21"), err)
+
+ vote, err := VoteFromString(strings.Repeat("1", voteSize*2))
+ require.Equal(t, Vote{
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ }, vote)
+ require.NoError(t, err)
+}
+
func TestVoteFromData(t *testing.T) {
require.Equal(t, Vote{
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55,
diff --git a/proto/go/gitalypb/transaction.pb.go b/proto/go/gitalypb/transaction.pb.go
index 24e8aaab8..7910a01d1 100644
--- a/proto/go/gitalypb/transaction.pb.go
+++ b/proto/go/gitalypb/transaction.pb.go
@@ -20,6 +20,63 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
+type VoteTransactionRequest_Phase int32
+
+const (
+ // UNKNOWN_PHASE is the unknown voting phase. This value has been the
+ // default because phases have been introduced. Eventually, using this
+ // phase will become unsupported.
+ VoteTransactionRequest_UNKNOWN_PHASE VoteTransactionRequest_Phase = 0
+ // PREPARED_PHASE is the prepratory phase. The data that is about to change
+ // is locked for concurrent modification, but changes have not yet been
+ // written to disk.
+ VoteTransactionRequest_PREPARED_PHASE VoteTransactionRequest_Phase = 1
+ // COMMITTED_PHASE is the committing phase. Data has been committed to disk
+ // and will be visible in all subsequent requests.
+ VoteTransactionRequest_COMMITTED_PHASE VoteTransactionRequest_Phase = 2
+)
+
+// Enum value maps for VoteTransactionRequest_Phase.
+var (
+ VoteTransactionRequest_Phase_name = map[int32]string{
+ 0: "UNKNOWN_PHASE",
+ 1: "PREPARED_PHASE",
+ 2: "COMMITTED_PHASE",
+ }
+ VoteTransactionRequest_Phase_value = map[string]int32{
+ "UNKNOWN_PHASE": 0,
+ "PREPARED_PHASE": 1,
+ "COMMITTED_PHASE": 2,
+ }
+)
+
+func (x VoteTransactionRequest_Phase) Enum() *VoteTransactionRequest_Phase {
+ p := new(VoteTransactionRequest_Phase)
+ *p = x
+ return p
+}
+
+func (x VoteTransactionRequest_Phase) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (VoteTransactionRequest_Phase) Descriptor() protoreflect.EnumDescriptor {
+ return file_transaction_proto_enumTypes[0].Descriptor()
+}
+
+func (VoteTransactionRequest_Phase) Type() protoreflect.EnumType {
+ return &file_transaction_proto_enumTypes[0]
+}
+
+func (x VoteTransactionRequest_Phase) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use VoteTransactionRequest_Phase.Descriptor instead.
+func (VoteTransactionRequest_Phase) EnumDescriptor() ([]byte, []int) {
+ return file_transaction_proto_rawDescGZIP(), []int{0, 0}
+}
+
// The outcome of the given transaction telling the client whether the
// transaction should be committed or rolled back.
type VoteTransactionResponse_TransactionState int32
@@ -55,11 +112,11 @@ func (x VoteTransactionResponse_TransactionState) String() string {
}
func (VoteTransactionResponse_TransactionState) Descriptor() protoreflect.EnumDescriptor {
- return file_transaction_proto_enumTypes[0].Descriptor()
+ return file_transaction_proto_enumTypes[1].Descriptor()
}
func (VoteTransactionResponse_TransactionState) Type() protoreflect.EnumType {
- return &file_transaction_proto_enumTypes[0]
+ return &file_transaction_proto_enumTypes[1]
}
func (x VoteTransactionResponse_TransactionState) Number() protoreflect.EnumNumber {
@@ -83,6 +140,8 @@ type VoteTransactionRequest struct {
Node string `protobuf:"bytes,3,opt,name=node,proto3" json:"node,omitempty"`
// SHA1 of the references that are to be updated
ReferenceUpdatesHash []byte `protobuf:"bytes,4,opt,name=reference_updates_hash,json=referenceUpdatesHash,proto3" json:"reference_updates_hash,omitempty"`
+ // Phase is the voting phase.
+ Phase VoteTransactionRequest_Phase `protobuf:"varint,5,opt,name=phase,proto3,enum=gitaly.VoteTransactionRequest_Phase" json:"phase,omitempty"`
}
func (x *VoteTransactionRequest) Reset() {
@@ -145,6 +204,13 @@ func (x *VoteTransactionRequest) GetReferenceUpdatesHash() []byte {
return nil
}
+func (x *VoteTransactionRequest) GetPhase() VoteTransactionRequest_Phase {
+ if x != nil {
+ return x.Phase
+ }
+ return VoteTransactionRequest_UNKNOWN_PHASE
+}
+
type VoteTransactionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -292,7 +358,7 @@ var file_transaction_proto_rawDesc = []byte{
0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x06, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x1a, 0x0a, 0x6c, 0x69, 0x6e,
0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e,
- 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, 0x16, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc4, 0x02, 0x0a, 0x16, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x38, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65,
@@ -304,43 +370,51 @@ var file_transaction_proto_rawDesc = []byte{
0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
0x63, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
- 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x22, 0x96, 0x01, 0x0a, 0x17,
- 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
- 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e,
- 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
- 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22,
- 0x33, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
- 0x61, 0x74, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x00, 0x12,
- 0x09, 0x0a, 0x05, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54,
- 0x4f, 0x50, 0x10, 0x02, 0x22, 0x79, 0x0a, 0x16, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e,
- 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38,
- 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x70, 0x6f,
- 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x42, 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, 0x0a, 0x72, 0x65,
- 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e,
- 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
- 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22,
- 0x19, 0x0a, 0x17, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
- 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc8, 0x01, 0x0a, 0x0e, 0x52,
- 0x65, 0x66, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5a, 0x0a,
- 0x0f, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
- 0x12, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72,
- 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
- 0x1a, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72,
- 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x5a, 0x0a, 0x0f, 0x53, 0x74, 0x6f,
- 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67,
- 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
- 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67,
- 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
- 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0xfa,
- 0x97, 0x28, 0x02, 0x08, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e,
- 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2d, 0x6f, 0x72, 0x67, 0x2f, 0x67,
- 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2f, 0x76, 0x31, 0x34, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
- 0x67, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
- 0x74, 0x6f, 0x33,
+ 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3a, 0x0a, 0x05, 0x70,
+ 0x68, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x69, 0x74,
+ 0x61, 0x6c, 0x79, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
+ 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65,
+ 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x05, 0x50, 0x68, 0x61, 0x73, 0x65,
+ 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x48, 0x41, 0x53,
+ 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x44, 0x5f,
+ 0x50, 0x48, 0x41, 0x53, 0x45, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4d, 0x4d, 0x49,
+ 0x54, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41, 0x53, 0x45, 0x10, 0x02, 0x22, 0x96, 0x01, 0x0a,
+ 0x17, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79,
+ 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
+ 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
+ 0x22, 0x33, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53,
+ 0x74, 0x61, 0x74, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x00,
+ 0x12, 0x09, 0x0a, 0x05, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53,
+ 0x54, 0x4f, 0x50, 0x10, 0x02, 0x22, 0x79, 0x0a, 0x16, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61,
+ 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x38, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x42, 0x04, 0x98, 0xc6, 0x2c, 0x01, 0x52, 0x0a, 0x72,
+ 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61,
+ 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x04, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,
+ 0x22, 0x19, 0x0a, 0x17, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
+ 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc8, 0x01, 0x0a, 0x0e,
+ 0x52, 0x65, 0x66, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5a,
+ 0x0a, 0x0f, 0x56, 0x6f, 0x74, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
+ 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54,
+ 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x54,
+ 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x22, 0x06, 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x12, 0x5a, 0x0a, 0x0f, 0x53, 0x74,
+ 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e,
+ 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73,
+ 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
+ 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x72, 0x61, 0x6e, 0x73,
+ 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06,
+ 0xfa, 0x97, 0x28, 0x02, 0x08, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2d, 0x6f, 0x72, 0x67, 0x2f,
+ 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x2f, 0x76, 0x31, 0x34, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x79, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -355,29 +429,31 @@ func file_transaction_proto_rawDescGZIP() []byte {
return file_transaction_proto_rawDescData
}
-var file_transaction_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_transaction_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_transaction_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_transaction_proto_goTypes = []interface{}{
- (VoteTransactionResponse_TransactionState)(0), // 0: gitaly.VoteTransactionResponse.TransactionState
- (*VoteTransactionRequest)(nil), // 1: gitaly.VoteTransactionRequest
- (*VoteTransactionResponse)(nil), // 2: gitaly.VoteTransactionResponse
- (*StopTransactionRequest)(nil), // 3: gitaly.StopTransactionRequest
- (*StopTransactionResponse)(nil), // 4: gitaly.StopTransactionResponse
- (*Repository)(nil), // 5: gitaly.Repository
+ (VoteTransactionRequest_Phase)(0), // 0: gitaly.VoteTransactionRequest.Phase
+ (VoteTransactionResponse_TransactionState)(0), // 1: gitaly.VoteTransactionResponse.TransactionState
+ (*VoteTransactionRequest)(nil), // 2: gitaly.VoteTransactionRequest
+ (*VoteTransactionResponse)(nil), // 3: gitaly.VoteTransactionResponse
+ (*StopTransactionRequest)(nil), // 4: gitaly.StopTransactionRequest
+ (*StopTransactionResponse)(nil), // 5: gitaly.StopTransactionResponse
+ (*Repository)(nil), // 6: gitaly.Repository
}
var file_transaction_proto_depIdxs = []int32{
- 5, // 0: gitaly.VoteTransactionRequest.repository:type_name -> gitaly.Repository
- 0, // 1: gitaly.VoteTransactionResponse.state:type_name -> gitaly.VoteTransactionResponse.TransactionState
- 5, // 2: gitaly.StopTransactionRequest.repository:type_name -> gitaly.Repository
- 1, // 3: gitaly.RefTransaction.VoteTransaction:input_type -> gitaly.VoteTransactionRequest
- 3, // 4: gitaly.RefTransaction.StopTransaction:input_type -> gitaly.StopTransactionRequest
- 2, // 5: gitaly.RefTransaction.VoteTransaction:output_type -> gitaly.VoteTransactionResponse
- 4, // 6: gitaly.RefTransaction.StopTransaction:output_type -> gitaly.StopTransactionResponse
- 5, // [5:7] is the sub-list for method output_type
- 3, // [3:5] is the sub-list for method input_type
- 3, // [3:3] is the sub-list for extension type_name
- 3, // [3:3] is the sub-list for extension extendee
- 0, // [0:3] is the sub-list for field type_name
+ 6, // 0: gitaly.VoteTransactionRequest.repository:type_name -> gitaly.Repository
+ 0, // 1: gitaly.VoteTransactionRequest.phase:type_name -> gitaly.VoteTransactionRequest.Phase
+ 1, // 2: gitaly.VoteTransactionResponse.state:type_name -> gitaly.VoteTransactionResponse.TransactionState
+ 6, // 3: gitaly.StopTransactionRequest.repository:type_name -> gitaly.Repository
+ 2, // 4: gitaly.RefTransaction.VoteTransaction:input_type -> gitaly.VoteTransactionRequest
+ 4, // 5: gitaly.RefTransaction.StopTransaction:input_type -> gitaly.StopTransactionRequest
+ 3, // 6: gitaly.RefTransaction.VoteTransaction:output_type -> gitaly.VoteTransactionResponse
+ 5, // 7: gitaly.RefTransaction.StopTransaction:output_type -> gitaly.StopTransactionResponse
+ 6, // [6:8] is the sub-list for method output_type
+ 4, // [4:6] is the sub-list for method input_type
+ 4, // [4:4] is the sub-list for extension type_name
+ 4, // [4:4] is the sub-list for extension extendee
+ 0, // [0:4] is the sub-list for field type_name
}
func init() { file_transaction_proto_init() }
@@ -442,7 +518,7 @@ func file_transaction_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_transaction_proto_rawDesc,
- NumEnums: 1,
+ NumEnums: 2,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
diff --git a/proto/transaction.proto b/proto/transaction.proto
index 745a764a5..3d3e48de5 100644
--- a/proto/transaction.proto
+++ b/proto/transaction.proto
@@ -26,6 +26,20 @@ service RefTransaction {
}
message VoteTransactionRequest {
+ enum Phase {
+ // UNKNOWN_PHASE is the unknown voting phase. This value has been the
+ // default because phases have been introduced. Eventually, using this
+ // phase will become unsupported.
+ UNKNOWN_PHASE = 0;
+ // PREPARED_PHASE is the prepratory phase. The data that is about to change
+ // is locked for concurrent modification, but changes have not yet been
+ // written to disk.
+ PREPARED_PHASE = 1;
+ // COMMITTED_PHASE is the committing phase. Data has been committed to disk
+ // and will be visible in all subsequent requests.
+ COMMITTED_PHASE = 2;
+ };
+
Repository repository = 1[(target_repository)=true];
// ID of the transaction we're processing
uint64 transaction_id = 2;
@@ -33,6 +47,8 @@ message VoteTransactionRequest {
string node = 3;
// SHA1 of the references that are to be updated
bytes reference_updates_hash = 4;
+ // Phase is the voting phase.
+ Phase phase = 5;
}
message VoteTransactionResponse {
diff --git a/ruby/proto/gitaly/transaction_pb.rb b/ruby/proto/gitaly/transaction_pb.rb
index 59c7f5626..01a04bf3c 100644
--- a/ruby/proto/gitaly/transaction_pb.rb
+++ b/ruby/proto/gitaly/transaction_pb.rb
@@ -12,6 +12,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :transaction_id, :uint64, 2
optional :node, :string, 3
optional :reference_updates_hash, :bytes, 4
+ optional :phase, :enum, 5, "gitaly.VoteTransactionRequest.Phase"
+ end
+ add_enum "gitaly.VoteTransactionRequest.Phase" do
+ value :UNKNOWN_PHASE, 0
+ value :PREPARED_PHASE, 1
+ value :COMMITTED_PHASE, 2
end
add_message "gitaly.VoteTransactionResponse" do
optional :state, :enum, 1, "gitaly.VoteTransactionResponse.TransactionState"
@@ -32,6 +38,7 @@ end
module Gitaly
VoteTransactionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.VoteTransactionRequest").msgclass
+ VoteTransactionRequest::Phase = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.VoteTransactionRequest.Phase").enummodule
VoteTransactionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.VoteTransactionResponse").msgclass
VoteTransactionResponse::TransactionState = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.VoteTransactionResponse.TransactionState").enummodule
StopTransactionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.StopTransactionRequest").msgclass