gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/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 grpc "gitee.com/ks-custle/core-gm/grpc" 34 "gitee.com/ks-custle/core-gm/grpc/admin" 35 "gitee.com/ks-custle/core-gm/grpc/credentials/insecure" 36 "gitee.com/ks-custle/core-gm/grpc/credentials/xds" 37 "gitee.com/ks-custle/core-gm/grpc/grpclog" 38 "gitee.com/ks-custle/core-gm/grpc/metadata" 39 "gitee.com/ks-custle/core-gm/grpc/peer" 40 "gitee.com/ks-custle/core-gm/grpc/reflection" 41 "gitee.com/ks-custle/core-gm/grpc/status" 42 _ "gitee.com/ks-custle/core-gm/grpc/xds" 43 44 testgrpc "gitee.com/ks-custle/core-gm/grpc/interop/grpc_testing" 45 testpb "gitee.com/ks-custle/core-gm/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 return &testpb.LoadBalancerAccumulatedStatsResponse{ 142 NumRpcsStartedByMethod: copyStatsMap(as.numRPCsStartedByMethod), 143 NumRpcsSucceededByMethod: copyStatsMap(as.numRPCsSucceededByMethod), 144 NumRpcsFailedByMethod: copyStatsMap(as.numRPCsFailedByMethod), 145 StatsPerMethod: as.makeStatsMap(), 146 } 147 } 148 149 func (as *accumulatedStats) startRPC(rpcType string) { 150 as.mu.Lock() 151 defer as.mu.Unlock() 152 as.numRPCsStartedByMethod[convertRPCName(rpcType)]++ 153 } 154 155 func (as *accumulatedStats) finishRPC(rpcType string, err error) { 156 as.mu.Lock() 157 defer as.mu.Unlock() 158 name := convertRPCName(rpcType) 159 if as.rpcStatusByMethod[name] == nil { 160 as.rpcStatusByMethod[name] = make(map[int32]int32) 161 } 162 as.rpcStatusByMethod[name][int32(status.Convert(err).Code())]++ 163 if err != nil { 164 as.numRPCsFailedByMethod[name]++ 165 return 166 } 167 as.numRPCsSucceededByMethod[name]++ 168 } 169 170 var ( 171 failOnFailedRPC = flag.Bool("fail_on_failed_rpc", false, "Fail client if any RPCs fail after first success") 172 numChannels = flag.Int("num_channels", 1, "Num of channels") 173 printResponse = flag.Bool("print_response", false, "Write RPC response to stdout") 174 qps = flag.Int("qps", 1, "QPS per channel, for each type of RPC") 175 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.") 176 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.") 177 rpcTimeout = flag.Duration("rpc_timeout", 20*time.Second, "Per RPC timeout") 178 server = flag.String("server", "localhost:8080", "Address of server to connect to") 179 statsPort = flag.Int("stats_port", 8081, "Port to expose peer distribution stats service") 180 secureMode = flag.Bool("secure_mode", false, "If true, retrieve security configuration from the management server. Else, use insecure credentials.") 181 182 rpcCfgs atomic.Value 183 184 mu sync.Mutex 185 currentRequestID int32 186 watchers = make(map[statsWatcherKey]*statsWatcher) 187 188 accStats = accumulatedStats{ 189 numRPCsStartedByMethod: make(map[string]int32), 190 numRPCsSucceededByMethod: make(map[string]int32), 191 numRPCsFailedByMethod: make(map[string]int32), 192 rpcStatusByMethod: make(map[string]map[int32]int32), 193 } 194 195 // 0 or 1 representing an RPC has succeeded. Use hasRPCSucceeded and 196 // setRPCSucceeded to access in a safe manner. 197 rpcSucceeded uint32 198 199 logger = grpclog.Component("interop") 200 ) 201 202 type statsService struct { 203 testgrpc.UnimplementedLoadBalancerStatsServiceServer 204 } 205 206 func hasRPCSucceeded() bool { 207 return atomic.LoadUint32(&rpcSucceeded) > 0 208 } 209 210 func setRPCSucceeded() { 211 atomic.StoreUint32(&rpcSucceeded, 1) 212 } 213 214 // Wait for the next LoadBalancerStatsRequest.GetNumRpcs to start and complete, 215 // and return the distribution of remote peers. This is essentially a clientside 216 // LB reporting mechanism that is designed to be queried by an external test 217 // driver when verifying that the client is distributing RPCs as expected. 218 func (s *statsService) GetClientStats(ctx context.Context, in *testpb.LoadBalancerStatsRequest) (*testpb.LoadBalancerStatsResponse, error) { 219 mu.Lock() 220 watcherKey := statsWatcherKey{currentRequestID, currentRequestID + in.GetNumRpcs()} 221 watcher, ok := watchers[watcherKey] 222 if !ok { 223 watcher = &statsWatcher{ 224 rpcsByPeer: make(map[string]int32), 225 rpcsByType: make(map[string]map[string]int32), 226 numFailures: 0, 227 remainingRPCs: in.GetNumRpcs(), 228 chanHosts: make(chan *rpcInfo), 229 } 230 watchers[watcherKey] = watcher 231 } 232 mu.Unlock() 233 234 ctx, cancel := context.WithTimeout(ctx, time.Duration(in.GetTimeoutSec())*time.Second) 235 defer cancel() 236 237 defer func() { 238 mu.Lock() 239 delete(watchers, watcherKey) 240 mu.Unlock() 241 }() 242 243 // Wait until the requested RPCs have all been recorded or timeout occurs. 244 for { 245 select { 246 case info := <-watcher.chanHosts: 247 if info != nil { 248 watcher.rpcsByPeer[info.hostname]++ 249 250 rpcsByPeerForType := watcher.rpcsByType[info.typ] 251 if rpcsByPeerForType == nil { 252 rpcsByPeerForType = make(map[string]int32) 253 watcher.rpcsByType[info.typ] = rpcsByPeerForType 254 } 255 rpcsByPeerForType[info.hostname]++ 256 } else { 257 watcher.numFailures++ 258 } 259 watcher.remainingRPCs-- 260 if watcher.remainingRPCs == 0 { 261 return watcher.buildResp(), nil 262 } 263 case <-ctx.Done(): 264 logger.Info("Timed out, returning partial stats") 265 return watcher.buildResp(), nil 266 } 267 } 268 } 269 270 func (s *statsService) GetClientAccumulatedStats(ctx context.Context, in *testpb.LoadBalancerAccumulatedStatsRequest) (*testpb.LoadBalancerAccumulatedStatsResponse, error) { 271 return accStats.buildResp(), nil 272 } 273 274 type configureService struct { 275 testgrpc.UnimplementedXdsUpdateClientConfigureServiceServer 276 } 277 278 func (s *configureService) Configure(ctx context.Context, in *testpb.ClientConfigureRequest) (*testpb.ClientConfigureResponse, error) { 279 rpcsToMD := make(map[testpb.ClientConfigureRequest_RpcType][]string) 280 for _, typ := range in.GetTypes() { 281 rpcsToMD[typ] = nil 282 } 283 for _, md := range in.GetMetadata() { 284 typ := md.GetType() 285 strs, ok := rpcsToMD[typ] 286 if !ok { 287 continue 288 } 289 rpcsToMD[typ] = append(strs, md.GetKey(), md.GetValue()) 290 } 291 cfgs := make([]*rpcConfig, 0, len(rpcsToMD)) 292 for typ, md := range rpcsToMD { 293 var rpcType string 294 switch typ { 295 case testpb.ClientConfigureRequest_UNARY_CALL: 296 rpcType = unaryCall 297 case testpb.ClientConfigureRequest_EMPTY_CALL: 298 rpcType = emptyCall 299 default: 300 return nil, fmt.Errorf("unsupported RPC type: %v", typ) 301 } 302 cfgs = append(cfgs, &rpcConfig{ 303 typ: rpcType, 304 md: metadata.Pairs(md...), 305 timeout: in.GetTimeoutSec(), 306 }) 307 } 308 rpcCfgs.Store(cfgs) 309 return &testpb.ClientConfigureResponse{}, nil 310 } 311 312 const ( 313 unaryCall string = "UnaryCall" 314 emptyCall string = "EmptyCall" 315 ) 316 317 func parseRPCTypes(rpcStr string) []string { 318 if len(rpcStr) == 0 { 319 return []string{unaryCall} 320 } 321 322 rpcs := strings.Split(rpcStr, ",") 323 ret := make([]string, 0, len(rpcStr)) 324 for _, r := range rpcs { 325 switch r { 326 case unaryCall, emptyCall: 327 ret = append(ret, r) 328 default: 329 flag.PrintDefaults() 330 log.Fatalf("unsupported RPC type: %v", r) 331 } 332 } 333 return ret 334 } 335 336 type rpcConfig struct { 337 typ string 338 md metadata.MD 339 timeout int32 340 } 341 342 // parseRPCMetadata turns EmptyCall:key1:value1 into 343 // 344 // {typ: emptyCall, md: {key1:value1}}. 345 func parseRPCMetadata(rpcMetadataStr string, rpcs []string) []*rpcConfig { 346 rpcMetadataSplit := strings.Split(rpcMetadataStr, ",") 347 rpcsToMD := make(map[string][]string) 348 for _, rm := range rpcMetadataSplit { 349 rmSplit := strings.Split(rm, ":") 350 if len(rmSplit)%2 != 1 { 351 log.Fatalf("invalid metadata config %v, want EmptyCall:key1:value1", rm) 352 } 353 rpcsToMD[rmSplit[0]] = append(rpcsToMD[rmSplit[0]], rmSplit[1:]...) 354 } 355 ret := make([]*rpcConfig, 0, len(rpcs)) 356 for _, rpcT := range rpcs { 357 rpcC := &rpcConfig{ 358 typ: rpcT, 359 } 360 if md := rpcsToMD[string(rpcT)]; len(md) > 0 { 361 rpcC.md = metadata.Pairs(md...) 362 } 363 ret = append(ret, rpcC) 364 } 365 return ret 366 } 367 368 func main() { 369 flag.Parse() 370 rpcCfgs.Store(parseRPCMetadata(*rpcMetadata, parseRPCTypes(*rpc))) 371 372 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *statsPort)) 373 if err != nil { 374 logger.Fatalf("failed to listen: %v", err) 375 } 376 s := grpc.NewServer() 377 defer s.Stop() 378 testgrpc.RegisterLoadBalancerStatsServiceServer(s, &statsService{}) 379 testgrpc.RegisterXdsUpdateClientConfigureServiceServer(s, &configureService{}) 380 reflection.Register(s) 381 cleanup, err := admin.Register(s) 382 if err != nil { 383 logger.Fatalf("Failed to register admin: %v", err) 384 } 385 defer cleanup() 386 go s.Serve(lis) 387 388 creds := insecure.NewCredentials() 389 if *secureMode { 390 var err error 391 creds, err = xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) 392 if err != nil { 393 logger.Fatalf("Failed to create xDS credentials: %v", err) 394 } 395 } 396 397 clients := make([]testgrpc.TestServiceClient, *numChannels) 398 for i := 0; i < *numChannels; i++ { 399 conn, err := grpc.Dial(*server, grpc.WithTransportCredentials(creds)) 400 if err != nil { 401 logger.Fatalf("Fail to dial: %v", err) 402 } 403 defer conn.Close() 404 clients[i] = testgrpc.NewTestServiceClient(conn) 405 } 406 ticker := time.NewTicker(time.Second / time.Duration(*qps**numChannels)) 407 defer ticker.Stop() 408 sendRPCs(clients, ticker) 409 } 410 411 func makeOneRPC(c testgrpc.TestServiceClient, cfg *rpcConfig) (*peer.Peer, *rpcInfo, error) { 412 timeout := *rpcTimeout 413 if cfg.timeout != 0 { 414 timeout = time.Duration(cfg.timeout) * time.Second 415 } 416 ctx, cancel := context.WithTimeout(context.Background(), timeout) 417 defer cancel() 418 419 if len(cfg.md) != 0 { 420 ctx = metadata.NewOutgoingContext(ctx, cfg.md) 421 } 422 info := rpcInfo{typ: cfg.typ} 423 424 var ( 425 p peer.Peer 426 header metadata.MD 427 err error 428 ) 429 accStats.startRPC(cfg.typ) 430 switch cfg.typ { 431 case unaryCall: 432 var resp *testpb.SimpleResponse 433 resp, err = c.UnaryCall(ctx, &testpb.SimpleRequest{FillServerId: true}, grpc.Peer(&p), grpc.Header(&header)) 434 // For UnaryCall, also read hostname from response, in case the server 435 // isn't updated to send headers. 436 if resp != nil { 437 info.hostname = resp.Hostname 438 } 439 case emptyCall: 440 _, err = c.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p), grpc.Header(&header)) 441 } 442 accStats.finishRPC(cfg.typ, err) 443 if err != nil { 444 return nil, nil, err 445 } 446 447 hosts := header["hostname"] 448 if len(hosts) > 0 { 449 info.hostname = hosts[0] 450 } 451 return &p, &info, err 452 } 453 454 func sendRPCs(clients []testgrpc.TestServiceClient, ticker *time.Ticker) { 455 var i int 456 for range ticker.C { 457 // Get and increment request ID, and save a list of watchers that are 458 // interested in this RPC. 459 mu.Lock() 460 savedRequestID := currentRequestID 461 currentRequestID++ 462 savedWatchers := []*statsWatcher{} 463 for key, value := range watchers { 464 if key.startID <= savedRequestID && savedRequestID < key.endID { 465 savedWatchers = append(savedWatchers, value) 466 } 467 } 468 mu.Unlock() 469 470 // Get the RPC metadata configurations from the Configure RPC. 471 cfgs := rpcCfgs.Load().([]*rpcConfig) 472 473 c := clients[i] 474 for _, cfg := range cfgs { 475 go func(cfg *rpcConfig) { 476 p, info, err := makeOneRPC(c, cfg) 477 478 for _, watcher := range savedWatchers { 479 // This sends an empty string if the RPC failed. 480 watcher.chanHosts <- info 481 } 482 if err != nil && *failOnFailedRPC && hasRPCSucceeded() { 483 logger.Fatalf("RPC failed: %v", err) 484 } 485 if err == nil { 486 setRPCSucceeded() 487 } 488 if *printResponse { 489 if err == nil { 490 if cfg.typ == unaryCall { 491 // Need to keep this format, because some tests are 492 // relying on stdout. 493 fmt.Printf("Greeting: Hello world, this is %s, from %v\n", info.hostname, p.Addr) 494 } else { 495 fmt.Printf("RPC %q, from host %s, addr %v\n", cfg.typ, info.hostname, p.Addr) 496 } 497 } else { 498 fmt.Printf("RPC %q, failed with %v\n", cfg.typ, err) 499 } 500 } 501 }(cfg) 502 } 503 i = (i + 1) % len(clients) 504 } 505 }