mirror of https://github.com/grpc/grpc-go.git
interop: implement rpc-behavior for UnaryCall() (#6575)
This commit is contained in:
parent
c6264a9f90
commit
fd9ef7263a
|
@ -66,7 +66,7 @@ func (s) TestCustomLB(t *testing.T) {
|
||||||
}
|
}
|
||||||
rpcBMD := md.Get("rpc-behavior")
|
rpcBMD := md.Get("rpc-behavior")
|
||||||
if len(rpcBMD) != 1 {
|
if len(rpcBMD) != 1 {
|
||||||
errCh.Send(fmt.Errorf("received %d values for metadata key rpc-behavior, want 1", len(rpcBMD)))
|
errCh.Send(fmt.Errorf("received %d values for metadata key \"rpc-behavior\", want 1", len(rpcBMD)))
|
||||||
return &testpb.SimpleResponse{}, nil
|
return &testpb.SimpleResponse{}, nil
|
||||||
}
|
}
|
||||||
wantVal := "error-code-0"
|
wantVal := "error-code-0"
|
||||||
|
|
|
@ -26,12 +26,17 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/admin"
|
"google.golang.org/grpc/admin"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
"google.golang.org/grpc/health"
|
"google.golang.org/grpc/health"
|
||||||
|
"google.golang.org/grpc/internal/status"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
"google.golang.org/grpc/xds"
|
"google.golang.org/grpc/xds"
|
||||||
|
@ -53,6 +58,16 @@ var (
|
||||||
logger = grpclog.Component("interop")
|
logger = grpclog.Component("interop")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rpcBehaviorMDKey = "rpc-behavior"
|
||||||
|
grpcPreviousRPCAttemptsMDKey = "grpc-previous-rpc-attempts"
|
||||||
|
sleepPfx = "sleep-"
|
||||||
|
keepOpenVal = "keep-open"
|
||||||
|
errorCodePfx = "error-code-"
|
||||||
|
succeedOnRetryPfx = "succeed-on-retry-attempt-"
|
||||||
|
hostnamePfx = "hostname="
|
||||||
|
)
|
||||||
|
|
||||||
func getHostname() string {
|
func getHostname() string {
|
||||||
if *hostNameOverride != "" {
|
if *hostNameOverride != "" {
|
||||||
return *hostNameOverride
|
return *hostNameOverride
|
||||||
|
@ -78,8 +93,101 @@ func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*test
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testServiceImpl) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
func (s *testServiceImpl) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||||
|
response := &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}
|
||||||
|
|
||||||
|
forLoop:
|
||||||
|
for _, headerVal := range getRPCBehaviorMetadata(ctx) {
|
||||||
|
// A value can have a prefix "hostname=<string>" followed by a space.
|
||||||
|
// In that case, the rest of the value should only be applied
|
||||||
|
// if the specified hostname matches the server's hostname.
|
||||||
|
if strings.HasPrefix(headerVal, hostnamePfx) {
|
||||||
|
splitVal := strings.Split(headerVal, " ")
|
||||||
|
if len(splitVal) <= 1 {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'hostname=<string> <header>=<value>' instead", headerVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.hostname != splitVal[0][len(hostnamePfx):] {
|
||||||
|
continue forLoop
|
||||||
|
}
|
||||||
|
headerVal = splitVal[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// If the value matches "sleep-<int>", the server should wait
|
||||||
|
// the specified number of seconds before resuming
|
||||||
|
// behavior matching and RPC processing.
|
||||||
|
case strings.HasPrefix(headerVal, sleepPfx):
|
||||||
|
sleep, err := strconv.Atoi(headerVal[len(sleepPfx):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'sleep-<int>' instead", headerVal)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(sleep) * time.Second)
|
||||||
|
|
||||||
|
// If the value matches "keep-open", the server should
|
||||||
|
// never respond to the request and behavior matching ends.
|
||||||
|
case strings.HasPrefix(headerVal, keepOpenVal):
|
||||||
|
<-ctx.Done()
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
// If the value matches "error-code-<int>", the server should
|
||||||
|
// respond with the specified status code and behavior matching ends.
|
||||||
|
case strings.HasPrefix(headerVal, errorCodePfx):
|
||||||
|
code, err := strconv.Atoi(headerVal[len(errorCodePfx):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'error-code-<int>' instead", headerVal)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Code(code), "rpc failed as per the rpc-behavior header value: %v", headerVal)
|
||||||
|
|
||||||
|
// If the value matches "success-on-retry-attempt-<int>", and the
|
||||||
|
// value of the "grpc-previous-rpc-attempts" metadata field is equal to
|
||||||
|
// the specified number, the normal RPC processing should resume
|
||||||
|
// and behavior matching ends.
|
||||||
|
case strings.HasPrefix(headerVal, succeedOnRetryPfx):
|
||||||
|
wantRetry, err := strconv.Atoi(headerVal[len(succeedOnRetryPfx):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'success-on-retry-attempt-<int>' instead", headerVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
mdRetry := getMetadataValues(ctx, grpcPreviousRPCAttemptsMDKey)
|
||||||
|
curRetry, err := strconv.Atoi(mdRetry[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid format for grpc-previous-rpc-attempts header: %v", mdRetry[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if curRetry == wantRetry {
|
||||||
|
break forLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
|
grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
|
||||||
return &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}, nil
|
return response, status.Err(codes.OK, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRPCBehaviorMetadata(ctx context.Context) []string {
|
||||||
|
mdRPCBehavior := getMetadataValues(ctx, rpcBehaviorMDKey)
|
||||||
|
var rpcBehaviorMetadata []string
|
||||||
|
for _, mdVal := range mdRPCBehavior {
|
||||||
|
splitVals := strings.Split(mdVal, ",")
|
||||||
|
|
||||||
|
for _, val := range splitVals {
|
||||||
|
headerVal := strings.TrimSpace(val)
|
||||||
|
if headerVal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpcBehaviorMetadata = append(rpcBehaviorMetadata, headerVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rpcBehaviorMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetadataValues(ctx context.Context, metadataKey string) []string {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("Failed to retrieve metadata from incoming RPC context")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return md.Get(metadataKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// xdsUpdateHealthServiceImpl provides an implementation of the
|
// xdsUpdateHealthServiceImpl provides an implementation of the
|
||||||
|
|
Loading…
Reference in New Issue