Allow probs.ProblemDetails to be passed across gRPC layer (#2506)
Currently services will pass both `core.XXXError` and `probs.XXX` type errors across the gRPC layer. In the future (#2505) we intend to stop passing `probs.XXX` type errors across this layer but for now we need to support them until that change is landed. This patch takes the easiest path to allow this by encoding the `probs.ProblemDetails` to JSON and storing it in the gRPC error body so that it can be passed around. Fixes #2497.
This commit is contained in:
parent
cb64fee358
commit
7d7adabe44
|
@ -1,12 +1,14 @@
|
|||
package grpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
)
|
||||
|
||||
// gRPC error codes used by Boulder. While the gRPC codes
|
||||
|
@ -23,6 +25,7 @@ const (
|
|||
BadNonceError
|
||||
NoSuchRegistrationError
|
||||
InternalServerError
|
||||
ProblemDetails
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -52,13 +55,29 @@ func errorToCode(err error) codes.Code {
|
|||
return NoSuchRegistrationError
|
||||
case core.InternalServerError:
|
||||
return InternalServerError
|
||||
case *probs.ProblemDetails:
|
||||
return ProblemDetails
|
||||
default:
|
||||
return codes.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
return grpc.Errorf(errorToCode(err), err.Error())
|
||||
code := errorToCode(err)
|
||||
var body string
|
||||
if code == ProblemDetails {
|
||||
pd := err.(*probs.ProblemDetails)
|
||||
bodyBytes, jsonErr := json.Marshal(pd)
|
||||
if jsonErr != nil {
|
||||
// Since gRPC will wrap this itself using grpc.Errorf(codes.Unknown, ...)
|
||||
// we just pass the original error back to the caller
|
||||
return err
|
||||
}
|
||||
body = string(bodyBytes)
|
||||
} else {
|
||||
body = err.Error()
|
||||
}
|
||||
return grpc.Errorf(code, body)
|
||||
}
|
||||
|
||||
func unwrapError(err error) error {
|
||||
|
@ -85,6 +104,12 @@ func unwrapError(err error) error {
|
|||
return core.LengthRequiredError(errBody)
|
||||
case BadNonceError:
|
||||
return core.BadNonceError(errBody)
|
||||
case ProblemDetails:
|
||||
pd := probs.ProblemDetails{}
|
||||
if json.Unmarshal([]byte(errBody), &pd) != nil {
|
||||
return err
|
||||
}
|
||||
return &pd
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"google.golang.org/grpc/codes"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
@ -25,11 +26,12 @@ func TestErrors(t *testing.T) {
|
|||
{core.BadNonceError("test 8"), BadNonceError},
|
||||
{core.NoSuchRegistrationError("test 9"), NoSuchRegistrationError},
|
||||
{core.InternalServerError("test 10"), InternalServerError},
|
||||
{&probs.ProblemDetails{Type: probs.ConnectionProblem, Detail: "testing..."}, ProblemDetails},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
wrappedErr := wrapError(tc.err)
|
||||
test.AssertEquals(t, grpc.Code(wrappedErr), tc.expectedCode)
|
||||
test.AssertEquals(t, tc.err, unwrapError(wrappedErr))
|
||||
test.AssertDeepEquals(t, tc.err, unwrapError(wrappedErr))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package grpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
testproto "github.com/letsencrypt/boulder/grpc/test_proto"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
type errorServer struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *errorServer) Chill(_ context.Context, _ *testproto.Time) (*testproto.Time, error) {
|
||||
return nil, wrapError(s.err)
|
||||
}
|
||||
|
||||
func TestErrorWrapping(t *testing.T) {
|
||||
srv := grpc.NewServer()
|
||||
es := &errorServer{}
|
||||
testproto.RegisterChillerServer(srv, es)
|
||||
lis, err := net.Listen("tcp", ":")
|
||||
test.AssertNotError(t, err, "Failed to create listener")
|
||||
go func() { _ = srv.Serve(lis) }()
|
||||
defer srv.Stop()
|
||||
|
||||
conn, err := grpc.Dial(
|
||||
lis.Addr().String(),
|
||||
grpc.WithInsecure(),
|
||||
)
|
||||
test.AssertNotError(t, err, "Failed to dial grpc test server")
|
||||
client := testproto.NewChillerClient(conn)
|
||||
|
||||
for _, tc := range []error{
|
||||
core.MalformedRequestError("yup"),
|
||||
&probs.ProblemDetails{Type: probs.MalformedProblem, Detail: "yup"},
|
||||
} {
|
||||
es.err = tc
|
||||
_, err := client.Chill(context.Background(), &testproto.Time{})
|
||||
test.Assert(t, err != nil, fmt.Sprintf("nil error returned, expected: %s", err))
|
||||
test.AssertDeepEquals(t, unwrapError(err), tc)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue