@@ -26,12 +26,17 @@ import (
26
26
"log"
27
27
"net"
28
28
"os"
29
+ "strconv"
30
+ "strings"
31
+ "time"
29
32
30
33
"google.golang.org/grpc"
31
34
"google.golang.org/grpc/admin"
35
+ "google.golang.org/grpc/codes"
32
36
"google.golang.org/grpc/credentials/insecure"
33
37
"google.golang.org/grpc/grpclog"
34
38
"google.golang.org/grpc/health"
39
+ "google.golang.org/grpc/internal/status"
35
40
"google.golang.org/grpc/metadata"
36
41
"google.golang.org/grpc/reflection"
37
42
"google.golang.org/grpc/xds"
53
58
logger = grpclog .Component ("interop" )
54
59
)
55
60
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
+
56
71
func getHostname () string {
57
72
if * hostNameOverride != "" {
58
73
return * hostNameOverride
@@ -78,8 +93,101 @@ func (s *testServiceImpl) EmptyCall(ctx context.Context, _ *testpb.Empty) (*test
78
93
}
79
94
80
95
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
+
81
163
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 )
83
191
}
84
192
85
193
// xdsUpdateHealthServiceImpl provides an implementation of the
0 commit comments