@@ -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"
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+
5671func getHostname () string {
5772 if * hostNameOverride != "" {
5873 return * hostNameOverride
@@ -78,8 +93,101 @@ func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*test
7893}
7994
8095func (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