Skip to content

Commit fd9ef72

Browse files
authored
interop: implement rpc-behavior for UnaryCall() (#6575)
1 parent c6264a9 commit fd9ef72

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

interop/xds/custom_lb_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (s) TestCustomLB(t *testing.T) {
6666
}
6767
rpcBMD := md.Get("rpc-behavior")
6868
if len(rpcBMD) != 1 {
69-
errCh.Send(fmt.Errorf("received %d values for metadata key rpc-behavior, want 1", len(rpcBMD)))
69+
errCh.Send(fmt.Errorf("received %d values for metadata key \"rpc-behavior\", want 1", len(rpcBMD)))
7070
return &testpb.SimpleResponse{}, nil
7171
}
7272
wantVal := "error-code-0"

interop/xds/server/server.go

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ import (
2626
"log"
2727
"net"
2828
"os"
29+
"strconv"
30+
"strings"
31+
"time"
2932

3033
"google.golang.org/grpc"
3134
"google.golang.org/grpc/admin"
35+
"google.golang.org/grpc/codes"
3236
"google.golang.org/grpc/credentials/insecure"
3337
"google.golang.org/grpc/grpclog"
3438
"google.golang.org/grpc/health"
39+
"google.golang.org/grpc/internal/status"
3540
"google.golang.org/grpc/metadata"
3641
"google.golang.org/grpc/reflection"
3742
"google.golang.org/grpc/xds"
@@ -53,6 +58,16 @@ var (
5358
logger = grpclog.Component("interop")
5459
)
5560

61+
const (
62+
rpcBehaviorMDKey = "rpc-behavior"
63+
grpcPreviousRPCAttemptsMDKey = "grpc-previous-rpc-attempts"
64+
sleepPfx = "sleep-"
65+
keepOpenVal = "keep-open"
66+
errorCodePfx = "error-code-"
67+
succeedOnRetryPfx = "succeed-on-retry-attempt-"
68+
hostnamePfx = "hostname="
69+
)
70+
5671
func getHostname() string {
5772
if *hostNameOverride != "" {
5873
return *hostNameOverride
@@ -78,8 +93,101 @@ func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*test
7893
}
7994

8095
func (s *testServiceImpl) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
96+
response := &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}
97+
98+
forLoop:
99+
for _, headerVal := range getRPCBehaviorMetadata(ctx) {
100+
// A value can have a prefix "hostname=<string>" followed by a space.
101+
// In that case, the rest of the value should only be applied
102+
// if the specified hostname matches the server's hostname.
103+
if strings.HasPrefix(headerVal, hostnamePfx) {
104+
splitVal := strings.Split(headerVal, " ")
105+
if len(splitVal) <= 1 {
106+
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'hostname=<string> <header>=<value>' instead", headerVal)
107+
}
108+
109+
if s.hostname != splitVal[0][len(hostnamePfx):] {
110+
continue forLoop
111+
}
112+
headerVal = splitVal[1]
113+
}
114+
115+
switch {
116+
// If the value matches "sleep-<int>", the server should wait
117+
// the specified number of seconds before resuming
118+
// behavior matching and RPC processing.
119+
case strings.HasPrefix(headerVal, sleepPfx):
120+
sleep, err := strconv.Atoi(headerVal[len(sleepPfx):])
121+
if err != nil {
122+
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'sleep-<int>' instead", headerVal)
123+
}
124+
time.Sleep(time.Duration(sleep) * time.Second)
125+
126+
// If the value matches "keep-open", the server should
127+
// never respond to the request and behavior matching ends.
128+
case strings.HasPrefix(headerVal, keepOpenVal):
129+
<-ctx.Done()
130+
return nil, nil
131+
132+
// If the value matches "error-code-<int>", the server should
133+
// respond with the specified status code and behavior matching ends.
134+
case strings.HasPrefix(headerVal, errorCodePfx):
135+
code, err := strconv.Atoi(headerVal[len(errorCodePfx):])
136+
if err != nil {
137+
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'error-code-<int>' instead", headerVal)
138+
}
139+
return nil, status.Errorf(codes.Code(code), "rpc failed as per the rpc-behavior header value: %v", headerVal)
140+
141+
// If the value matches "success-on-retry-attempt-<int>", and the
142+
// value of the "grpc-previous-rpc-attempts" metadata field is equal to
143+
// the specified number, the normal RPC processing should resume
144+
// and behavior matching ends.
145+
case strings.HasPrefix(headerVal, succeedOnRetryPfx):
146+
wantRetry, err := strconv.Atoi(headerVal[len(succeedOnRetryPfx):])
147+
if err != nil {
148+
return nil, status.Errorf(codes.InvalidArgument, "invalid format for rpc-behavior header %v, must be 'success-on-retry-attempt-<int>' instead", headerVal)
149+
}
150+
151+
mdRetry := getMetadataValues(ctx, grpcPreviousRPCAttemptsMDKey)
152+
curRetry, err := strconv.Atoi(mdRetry[0])
153+
if err != nil {
154+
return nil, status.Errorf(codes.InvalidArgument, "invalid format for grpc-previous-rpc-attempts header: %v", mdRetry[0])
155+
}
156+
157+
if curRetry == wantRetry {
158+
break forLoop
159+
}
160+
}
161+
}
162+
81163
grpc.SetHeader(ctx, metadata.Pairs("hostname", s.hostname))
82-
return &testpb.SimpleResponse{ServerId: s.serverID, Hostname: s.hostname}, nil
164+
return response, status.Err(codes.OK, "")
165+
}
166+
167+
func getRPCBehaviorMetadata(ctx context.Context) []string {
168+
mdRPCBehavior := getMetadataValues(ctx, rpcBehaviorMDKey)
169+
var rpcBehaviorMetadata []string
170+
for _, mdVal := range mdRPCBehavior {
171+
splitVals := strings.Split(mdVal, ",")
172+
173+
for _, val := range splitVals {
174+
headerVal := strings.TrimSpace(val)
175+
if headerVal == "" {
176+
continue
177+
}
178+
rpcBehaviorMetadata = append(rpcBehaviorMetadata, headerVal)
179+
}
180+
}
181+
return rpcBehaviorMetadata
182+
}
183+
184+
func getMetadataValues(ctx context.Context, metadataKey string) []string {
185+
md, ok := metadata.FromIncomingContext(ctx)
186+
if !ok {
187+
logger.Error("Failed to retrieve metadata from incoming RPC context")
188+
return nil
189+
}
190+
return md.Get(metadataKey)
83191
}
84192

85193
// xdsUpdateHealthServiceImpl provides an implementation of the

0 commit comments

Comments
 (0)