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:
Roland Bracewell Shoemaker 2017-01-19 14:59:44 -08:00 committed by Jacob Hoffman-Andrews
parent cb64fee358
commit 7d7adabe44
3 changed files with 79 additions and 2 deletions

View File

@ -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
}

View File

@ -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))
}
}

50
grpc/errors_test.go Normal file
View File

@ -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)
}
}