gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/interop/xds/client/client.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Binary client for xDS interop tests. 20 package main 21 22 import ( 23 "context" 24 "flag" 25 "fmt" 26 "log" 27 "net" 28 "strings" 29 "sync" 30 "sync/atomic" 31 "time" 32 33 "gitee.com/zhaochuninhefei/gmgo/grpc" 34 "gitee.com/zhaochuninhefei/gmgo/grpc/admin" 35 "gitee.com/zhaochuninhefei/gmgo/grpc/credentials/insecure" 36 "gitee.com/zhaochuninhefei/gmgo/grpc/credentials/xds" 37 "gitee.com/zhaochuninhefei/gmgo/grpc/grpclog" 38 "gitee.com/zhaochuninhefei/gmgo/grpc/metadata" 39 "gitee.com/zhaochuninhefei/gmgo/grpc/peer" 40 "gitee.com/zhaochuninhefei/gmgo/grpc/reflection" 41 "gitee.com/zhaochuninhefei/gmgo/grpc/status" 42 _ "gitee.com/zhaochuninhefei/gmgo/grpc/xds" 43 44 testgrpc "gitee.com/zhaochuninhefei/gmgo/grpc/interop/grpc_testing" 45 testpb "gitee.com/zhaochuninhefei/gmgo/grpc/interop/grpc_testing" 46 ) 47 48 func init() { 49 rpcCfgs.Store([]*rpcConfig{{typ: unaryCall}}) 50 } 51 52 type statsWatcherKey struct { 53 startID int32 54 endID int32 55 } 56 57 // rpcInfo contains the rpc type and the hostname where the response is received 58 // from. 59 type rpcInfo struct { 60 typ string 61 hostname string 62 } 63 64 type statsWatcher struct { 65 rpcsByPeer map[string]int32 66 rpcsByType map[string]map[string]int32 67 numFailures int32 68 remainingRPCs int32 69 chanHosts chan *rpcInfo 70 } 71 72 func (watcher *statsWatcher) buildResp() *testpb.LoadBalancerStatsResponse { 73 rpcsByType := make(map[string]*testpb.LoadBalancerStatsResponse_RpcsByPeer, len(watcher.rpcsByType)) 74 for t, rpcsByPeer := range watcher.rpcsByType { 75 rpcsByType[t] = &testpb.LoadBalancerStatsResponse_RpcsByPeer{ 76 RpcsByPeer: rpcsByPeer, 77 } 78 } 79 80 return &testpb.LoadBalancerStatsResponse{ 81 NumFailures: watcher.numFailures + watcher.remainingRPCs, 82 RpcsByPeer: watcher.rpcsByPeer, 83 RpcsByMethod: rpcsByType, 84 } 85 } 86 87 type accumulatedStats struct { 88 mu sync.Mutex 89 numRPCsStartedByMethod map[string]int32 90 numRPCsSucceededByMethod map[string]int32 91 numRPCsFailedByMethod map[string]int32 92 rpcStatusByMethod map[string]map[int32]int32 93 } 94 95 func convertRPCName(in string) string { 96 switch in { 97 case unaryCall: 98 return testpb.ClientConfigureRequest_UNARY_CALL.String() 99 case emptyCall: 100 return testpb.ClientConfigureRequest_EMPTY_CALL.String() 101 } 102 logger.Warningf("unrecognized rpc type: %s", in) 103 return in 104 } 105 106 // copyStatsMap makes a copy of the map. 107 func copyStatsMap(originalMap map[string]int32) map[string]int32 { 108 newMap := make(map[string]int32, len(originalMap)) 109 for k, v := range originalMap { 110 newMap[k] = v 111 } 112 return newMap 113 } 114 115 // copyStatsIntMap makes a copy of the map. 116 func copyStatsIntMap(originalMap map[int32]int32) map[int32]int32 { 117 newMap := make(map[int32]int32, len(originalMap)) 118 for k, v := range originalMap { 119 newMap[k] = v 120 } 121 return newMap 122 } 123 124 func (as *accumulatedStats) makeStatsMap() map[string]*testpb.LoadBalancerAccumulatedStatsResponse_MethodStats { 125 m := make(map[string]*testpb.LoadBalancerAccumulatedStatsResponse_MethodStats) 126 for k, v := range as.numRPCsStartedByMethod { 127 m[k] = &testpb.LoadBalancerAccumulatedStatsResponse_MethodStats{RpcsStarted: v} 128 } 129 for k, v := range as.rpcStatusByMethod { 130 if m[k] == nil { 131 m[k] = &testpb.LoadBalancerAccumulatedStatsResponse_MethodStats{} 132 } 133 m[k].Result = copyStatsIntMap(v) 134 } 135 return m 136 } 137 138 func (as *accumulatedStats) buildResp() *testpb.LoadBalancerAccumulatedStatsResponse { 139 as.mu.Lock() 140 defer as.mu.Unlock() 141 //goland:noinspection GoDeprecation 142 return &testpb.LoadBalancerAccumulatedStatsResponse{ 143 NumRpcsStartedByMethod: copyStatsMap(as.numRPCsStartedByMethod), 144 NumRpcsSucceededByMethod: copyStatsMap(as.numRPCsSucceededByMethod), 145 NumRpcsFailedByMethod: copyStatsMap(as.numRPCsFailedByMethod), 146 StatsPerMethod: as.makeStatsMap(), 147 } 148 } 149 150 func (as *accumulatedStats) startRPC(rpcType string) { 151 as.mu.Lock() 152 defer as.mu.Unlock() 153 as.numRPCsStartedByMethod[convertRPCName(rpcType)]++ 154 } 155 156 func (as *accumulatedStats) finishRPC(rpcType string, err error) { 157 as.mu.Lock() 158 defer as.mu.Unlock() 159 name := convertRPCName(rpcType) 160 if as.rpcStatusByMethod[name] == nil { 161 as.rpcStatusByMethod[name] = make(map[int32]int32) 162 } 163 as.rpcStatusByMethod[name][int32(status.Convert(err).Code())]++ 164 if err != nil { 165 as.numRPCsFailedByMethod[name]++ 166 return 167 } 168 as.numRPCsSucceededByMethod[name]++ 169 } 170 171 var ( 172 failOnFailedRPC = flag.Bool("fail_on_failed_rpc", false, "Fail client if any RPCs fail after first success") 173 numChannels = flag.Int("num_channels", 1, "Num of channels") 174 printResponse = flag.Bool("print_response", false, "Write RPC response to stdout") 175 qps = flag.Int("qps", 1, "QPS per channel, for each type of RPC") 176 rpc = flag.String("rpc", "UnaryCall", "Types of RPCs to make, ',' separated string. RPCs can be EmptyCall or UnaryCall. Deprecated: Use Configure RPC to XdsUpdateClientConfigureServiceServer instead.") 177 rpcMetadata = flag.String("metadata", "", "The metadata to send with RPC, in format EmptyCall:key1:value1,UnaryCall:key2:value2. Deprecated: Use Configure RPC to XdsUpdateClientConfigureServiceServer instead.") 178 rpcTimeout = flag.Duration("rpc_timeout", 20*time.Second, "Per RPC timeout") 179 server = flag.String("server", "localhost:8080", "Address of server to connect to") 180 statsPort = flag.Int("stats_port", 8081, "Port to expose peer distribution stats service") 181 secureMode = flag.Bool("secure_mode", false, "If true, retrieve security configuration from the management server. Else, use insecure credentials.") 182 183 rpcCfgs atomic.Value 184 185 mu sync.Mutex 186 currentRequestID int32 187 watchers = make(map[statsWatcherKey]*statsWatcher) 188 189 accStats = accumulatedStats{ 190 numRPCsStartedByMethod: make(map[string]int32), 191 numRPCsSucceededByMethod: make(map[string]int32), 192 numRPCsFailedByMethod: make(map[string]int32), 193 rpcStatusByMethod: make(map[string]map[int32]int32), 194 } 195 196 // 0 or 1 representing an RPC has succeeded. Use hasRPCSucceeded and 197 // setRPCSucceeded to access in a safe manner. 198 rpcSucceeded uint32 199 200 logger = grpclog.Component("interop") 201 ) 202 203 type statsService struct { 204 testgrpc.UnimplementedLoadBalancerStatsServiceServer 205 } 206 207 func hasRPCSucceeded() bool { 208 return atomic.LoadUint32(&rpcSucceeded) > 0 209 } 210 211 func setRPCSucceeded() { 212 atomic.StoreUint32(&rpcSucceeded, 1) 213 } 214 215 // GetClientStats Wait for the next LoadBalancerStatsRequest.GetNumRpcs to start and complete, 216 // and return the distribution of remote peers. This is essentially a clientside 217 // LB reporting mechanism that is designed to be queried by an external test 218 // driver when verifying that the client is distributing RPCs as expected. 219 func (s *statsService) GetClientStats(ctx context.Context, in *testpb.LoadBalancerStatsRequest) (*testpb.LoadBalancerStatsResponse, error) { 220 mu.Lock() 221 watcherKey := statsWatcherKey{currentRequestID, currentRequestID + in.GetNumRpcs()} 222 watcher, ok := watchers[watcherKey] 223 if !ok { 224 watcher = &statsWatcher{ 225 rpcsByPeer: make(map[string]int32), 226 rpcsByType: make(map[string]map[string]int32), 227 numFailures: 0, 228 remainingRPCs: in.GetNumRpcs(), 229 chanHosts: make(chan *rpcInfo), 230 } 231 watchers[watcherKey] = watcher 232 } 233 mu.Unlock() 234 235 ctx, cancel := context.WithTimeout(ctx, time.Duration(in.GetTimeoutSec())*time.Second) 236 defer cancel() 237 238 defer func() { 239 mu.Lock() 240 delete(watchers, watcherKey) 241 mu.Unlock() 242 }() 243 244 // Wait until the requested RPCs have all been recorded or timeout occurs. 245 for { 246 select { 247 case info := <-watcher.chanHosts: 248 if info != nil { 249 watcher.rpcsByPeer[info.hostname]++ 250 251 rpcsByPeerForType := watcher.rpcsByType[info.typ] 252 if rpcsByPeerForType == nil { 253 rpcsByPeerForType = make(map[string]int32) 254 watcher.rpcsByType[info.typ] = rpcsByPeerForType 255 } 256 rpcsByPeerForType[info.hostname]++ 257 } else { 258 watcher.numFailures++ 259 } 260 watcher.remainingRPCs-- 261 if watcher.remainingRPCs == 0 { 262 return watcher.buildResp(), nil 263 } 264 case <-ctx.Done(): 265 logger.Info("Timed out, returning partial stats") 266 return watcher.buildResp(), nil 267 } 268 } 269 } 270 271 //goland:noinspection GoUnusedParameter 272 func (s *statsService) GetClientAccumulatedStats(ctx context.Context, in *testpb.LoadBalancerAccumulatedStatsRequest) (*testpb.LoadBalancerAccumulatedStatsResponse, error) { 273 return accStats.buildResp(), nil 274 } 275 276 type configureService struct { 277 testgrpc.UnimplementedXdsUpdateClientConfigureServiceServer 278 } 279 280 //goland:noinspection GoUnusedParameter 281 func (s *configureService) Configure(ctx context.Context, in *testpb.ClientConfigureRequest) (*testpb.ClientConfigureResponse, error) { 282 rpcsToMD := make(map[testpb.ClientConfigureRequest_RpcType][]string) 283 for _, typ := range in.GetTypes() { 284 rpcsToMD[typ] = nil 285 } 286 for _, md := range in.GetMetadata() { 287 typ := md.GetType() 288 strs, ok := rpcsToMD[typ] 289 if !ok { 290 continue 291 } 292 rpcsToMD[typ] = append(strs, md.GetKey(), md.GetValue()) 293 } 294 cfgs := make([]*rpcConfig, 0, len(rpcsToMD)) 295 for typ, md := range rpcsToMD { 296 var rpcType string 297 switch typ { 298 case testpb.ClientConfigureRequest_UNARY_CALL: 299 rpcType = unaryCall 300 case testpb.ClientConfigureRequest_EMPTY_CALL: 301 rpcType = emptyCall 302 default: 303 return nil, fmt.Errorf("unsupported RPC type: %v", typ) 304 } 305 cfgs = append(cfgs, &rpcConfig{ 306 typ: rpcType, 307 md: metadata.Pairs(md...), 308 timeout: in.GetTimeoutSec(), 309 }) 310 } 311 rpcCfgs.Store(cfgs) 312 return &testpb.ClientConfigureResponse{}, nil 313 } 314 315 const ( 316 unaryCall string = "UnaryCall" 317 emptyCall string = "EmptyCall" 318 ) 319 320 func parseRPCTypes(rpcStr string) []string { 321 if len(rpcStr) == 0 { 322 return []string{unaryCall} 323 } 324 325 rpcs := strings.Split(rpcStr, ",") 326 ret := make([]string, 0, len(rpcStr)) 327 for _, r := range rpcs { 328 switch r { 329 case unaryCall, emptyCall: 330 ret = append(ret, r) 331 default: 332 flag.PrintDefaults() 333 log.Fatalf("unsupported RPC type: %v", r) 334 } 335 } 336 return ret 337 } 338 339 type rpcConfig struct { 340 typ string 341 md metadata.MD 342 timeout int32 343 } 344 345 // parseRPCMetadata turns EmptyCall:key1:value1 into 346 // {typ: emptyCall, md: {key1:value1}}. 347 func parseRPCMetadata(rpcMetadataStr string, rpcs []string) []*rpcConfig { 348 rpcMetadataSplit := strings.Split(rpcMetadataStr, ",") 349 rpcsToMD := make(map[string][]string) 350 for _, rm := range rpcMetadataSplit { 351 rmSplit := strings.Split(rm, ":") 352 if len(rmSplit)%2 != 1 { 353 log.Fatalf("invalid metadata config %v, want EmptyCall:key1:value1", rm) 354 } 355 rpcsToMD[rmSplit[0]] = append(rpcsToMD[rmSplit[0]], rmSplit[1:]...) 356 } 357 ret := make([]*rpcConfig, 0, len(rpcs)) 358 for _, rpcT := range rpcs { 359 rpcC := &rpcConfig{ 360 typ: rpcT, 361 } 362 if md := rpcsToMD[rpcT]; len(md) > 0 { 363 rpcC.md = metadata.Pairs(md...) 364 } 365 ret = append(ret, rpcC) 366 } 367 return ret 368 } 369 370 func main() { 371 flag.Parse() 372 rpcCfgs.Store(parseRPCMetadata(*rpcMetadata, parseRPCTypes(*rpc))) 373 374 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *statsPort)) 375 if err != nil { 376 logger.Fatalf("failed to listen: %v", err) 377 } 378 s := grpc.NewServer() 379 defer s.Stop() 380 testgrpc.RegisterLoadBalancerStatsServiceServer(s, &statsService{}) 381 testgrpc.RegisterXdsUpdateClientConfigureServiceServer(s, &configureService{}) 382 reflection.Register(s) 383 cleanup, err := admin.Register(s) 384 if err != nil { 385 logger.Fatalf("Failed to register admin: %v", err) 386 } 387 defer cleanup() 388 go func() { 389 _ = s.Serve(lis) 390 }() 391 392 creds := insecure.NewCredentials() 393 if *secureMode { 394 var err error 395 creds, err = xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) 396 if err != nil { 397 logger.Fatalf("Failed to create xDS credentials: %v", err) 398 } 399 } 400 401 clients := make([]testgrpc.TestServiceClient, *numChannels) 402 for i := 0; i < *numChannels; i++ { 403 conn, err := grpc.Dial(*server, grpc.WithTransportCredentials(creds)) 404 if err != nil { 405 logger.Fatalf("Fail to dial: %v", err) 406 } 407 //goland:noinspection GoDeferInLoop 408 defer func(conn *grpc.ClientConn) { 409 _ = conn.Close() 410 }(conn) 411 clients[i] = testgrpc.NewTestServiceClient(conn) 412 } 413 ticker := time.NewTicker(time.Second / time.Duration(*qps**numChannels)) 414 defer ticker.Stop() 415 sendRPCs(clients, ticker) 416 } 417 418 func makeOneRPC(c testgrpc.TestServiceClient, cfg *rpcConfig) (*peer.Peer, *rpcInfo, error) { 419 timeout := *rpcTimeout 420 if cfg.timeout != 0 { 421 timeout = time.Duration(cfg.timeout) * time.Second 422 } 423 ctx, cancel := context.WithTimeout(context.Background(), timeout) 424 defer cancel() 425 426 if len(cfg.md) != 0 { 427 ctx = metadata.NewOutgoingContext(ctx, cfg.md) 428 } 429 info := rpcInfo{typ: cfg.typ} 430 431 var ( 432 p peer.Peer 433 header metadata.MD 434 err error 435 ) 436 accStats.startRPC(cfg.typ) 437 switch cfg.typ { 438 case unaryCall: 439 var resp *testpb.SimpleResponse 440 resp, err = c.UnaryCall(ctx, &testpb.SimpleRequest{FillServerId: true}, grpc.Peer(&p), grpc.Header(&header)) 441 // For UnaryCall, also read hostname from response, in case the server 442 // isn't updated to send headers. 443 if resp != nil { 444 info.hostname = resp.Hostname 445 } 446 case emptyCall: 447 _, err = c.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p), grpc.Header(&header)) 448 } 449 accStats.finishRPC(cfg.typ, err) 450 if err != nil { 451 return nil, nil, err 452 } 453 454 hosts := header["hostname"] 455 if len(hosts) > 0 { 456 info.hostname = hosts[0] 457 } 458 return &p, &info, err 459 } 460 461 func sendRPCs(clients []testgrpc.TestServiceClient, ticker *time.Ticker) { 462 var i int 463 for range ticker.C { 464 // Get and increment request ID, and save a list of watchers that are 465 // interested in this RPC. 466 mu.Lock() 467 savedRequestID := currentRequestID 468 currentRequestID++ 469 var savedWatchers []*statsWatcher 470 for key, value := range watchers { 471 if key.startID <= savedRequestID && savedRequestID < key.endID { 472 savedWatchers = append(savedWatchers, value) 473 } 474 } 475 mu.Unlock() 476 477 // Get the RPC metadata configurations from the Configure RPC. 478 cfgs := rpcCfgs.Load().([]*rpcConfig) 479 480 c := clients[i] 481 for _, cfg := range cfgs { 482 go func(cfg *rpcConfig) { 483 p, info, err := makeOneRPC(c, cfg) 484 485 for _, watcher := range savedWatchers { 486 // This sends an empty string if the RPC failed. 487 watcher.chanHosts <- info 488 } 489 if err != nil && *failOnFailedRPC && hasRPCSucceeded() { 490 logger.Fatalf("RPC failed: %v", err) 491 } 492 if err == nil { 493 setRPCSucceeded() 494 } 495 if *printResponse { 496 if err == nil { 497 if cfg.typ == unaryCall { 498 // Need to keep this format, because some tests are 499 // relying on stdout. 500 fmt.Printf("Greeting: Hello world, this is %s, from %v\n", info.hostname, p.Addr) 501 } else { 502 fmt.Printf("RPC %q, from host %s, addr %v\n", cfg.typ, info.hostname, p.Addr) 503 } 504 } else { 505 fmt.Printf("RPC %q, failed with %v\n", cfg.typ, err) 506 } 507 } 508 }(cfg) 509 } 510 i = (i + 1) % len(clients) 511 } 512 }