Skip to content

Commit 59acf61

Browse files
Igor Drozdovikbenale
Igor Drozdov
andcommitted
Merge branch 'gitaly-limit-error' into 'main'
Improve error message for Gitaly `LimitError`s Closes #556 See merge request https://gitlab.com/gitlab-org/gitlab-shell/-/merge_requests/691 Merged-by: Igor Drozdov <[email protected]> Approved-by: John Cai <[email protected]> Approved-by: Igor Drozdov <[email protected]> Co-authored-by: Alejandro Rodríguez <[email protected]>
2 parents b42a398 + 9a58cbd commit 59acf61

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

internal/handler/exec.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ func NewGitalyCommand(cfg *config.Config, serviceName string, response *accessve
4141
return &GitalyCommand{Config: cfg, Response: response, Command: gc}
4242
}
4343

44+
// processGitalyError handles errors that come back from Gitaly that may be a
45+
// LimitError. A LimitError is returned by Gitaly when it is at its limit in
46+
// handling requests. Since this is a known error, we should print a sensible
47+
// error message to the end user.
48+
func processGitalyError(statusErr error) error {
49+
if st, ok := grpcstatus.FromError(statusErr); ok {
50+
details := st.Details()
51+
for _, detail := range details {
52+
switch detail.(type) {
53+
case *pb.LimitError:
54+
return grpcstatus.Error(grpccodes.Unavailable, "GitLab is currently unable to handle this request due to load.")
55+
}
56+
}
57+
}
58+
59+
return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
60+
}
61+
4462
// RunGitalyCommand provides a bootstrap for Gitaly commands executed
4563
// through GitLab-Shell. It ensures that logging, tracing and other
4664
// common concerns are configured before executing the `handler`.
@@ -61,7 +79,7 @@ func (gc *GitalyCommand) RunGitalyCommand(ctx context.Context, handler GitalyHan
6179
ctxlog.WithError(err).WithFields(log.Fields{"exit_status": exitStatus}).Error("Failed to execute Git command")
6280

6381
if grpcstatus.Code(err) == grpccodes.Unavailable {
64-
return grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator.")
82+
return processGitalyError(err)
6583
}
6684
}
6785

internal/handler/exec_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/config"
1717
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/gitlabnet/accessverifier"
1818
"gitlab.com/gitlab-org/gitlab-shell/v14/internal/sshenv"
19+
"google.golang.org/protobuf/proto"
20+
"google.golang.org/protobuf/types/known/anypb"
21+
"google.golang.org/protobuf/types/known/durationpb"
1922
)
2023

2124
func makeHandler(t *testing.T, err error) func(context.Context, *grpc.ClientConn) (int32, error) {
@@ -88,6 +91,21 @@ func TestUnavailableGitalyErr(t *testing.T) {
8891
require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "The git server, Gitaly, is not available at this time. Please contact your administrator."))
8992
}
9093

94+
func TestGitalyLimitErr(t *testing.T) {
95+
cmd := NewGitalyCommand(
96+
newConfig(),
97+
string(commandargs.UploadPack),
98+
&accessverifier.Response{
99+
Gitaly: accessverifier.Gitaly{Address: "tcp://localhost:9999"},
100+
},
101+
)
102+
limitErr := errWithDetail(t, &pb.LimitError{
103+
ErrorMessage: "concurrency queue wait time reached",
104+
RetryAfter: durationpb.New(0)})
105+
err := cmd.RunGitalyCommand(context.Background(), makeHandler(t, limitErr))
106+
require.Equal(t, err, grpcstatus.Error(grpccodes.Unavailable, "GitLab is currently unable to handle this request due to load."))
107+
}
108+
91109
func TestRunGitalyCommandMetadata(t *testing.T) {
92110
tests := []struct {
93111
name string
@@ -211,3 +229,16 @@ func newConfig() *config.Config {
211229
cfg.GitalyClient.InitSidechannelRegistry(context.Background())
212230
return cfg
213231
}
232+
233+
// errWithDetail adds the given details to the error if it is a gRPC status whose code is not OK.
234+
func errWithDetail(t *testing.T, detail proto.Message) error {
235+
st := grpcstatus.New(grpccodes.Unavailable, "too busy")
236+
237+
proto := st.Proto()
238+
marshaled, err := anypb.New(detail)
239+
require.NoError(t, err)
240+
241+
proto.Details = append(proto.Details, marshaled)
242+
243+
return grpcstatus.ErrorProto(proto)
244+
}

0 commit comments

Comments
 (0)