github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "github.com/hxx258456/ccgo/grpc" 34 "github.com/hxx258456/ccgo/grpc/admin" 35 "github.com/hxx258456/ccgo/grpc/credentials/insecure" 36 "github.com/hxx258456/ccgo/grpc/credentials/xds" 37 "github.com/hxx258456/ccgo/grpc/grpclog" 38 "github.com/hxx258456/ccgo/grpc/metadata" 39 "github.com/hxx258456/ccgo/grpc/peer" 40 "github.com/hxx258456/ccgo/grpc/reflection" 41 "github.com/hxx258456/ccgo/grpc/status" 42 _ "github.com/hxx258456/ccgo/grpc/xds" 43 44 testgrpc "github.com/hxx258456/ccgo/grpc/interop/grpc_testing" 45 testpb "github.com/hxx258456/ccgo/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 // {typ: emptyCall, md: {key1:value1}}. 344 func parseRPCMetadata(rpcMetadataStr string, rpcs []string) []*rpcConfig { 345 rpcMetadataSplit := strings.Split(rpcMetadataStr, ",") 346 rpcsToMD := make(map[string][]string) 347 for _, rm := range rpcMetadataSplit { 348 rmSplit := strings.Split(rm, ":") 349 if len(rmSplit)%2 != 1 { 350 log.Fatalf("invalid metadata config %v, want EmptyCall:key1:value1", rm) 351 } 352 rpcsToMD[rmSplit[0]] = append(rpcsToMD[rmSplit[0]], rmSplit[1:]...) 353 } 354 ret := make([]*rpcConfig, 0, len(rpcs)) 355 for _, rpcT := range rpcs { 356 rpcC := &rpcConfig{ 357 typ: rpcT, 358 } 359 if md := rpcsToMD[string(rpcT)]; len(md) > 0 { 360 rpcC.md = metadata.Pairs(md...) 361 } 362 ret = append(ret, rpcC) 363 } 364 return ret 365 } 366 367 func main() { 368 flag.Parse() 369 rpcCfgs.Store(parseRPCMetadata(*rpcMetadata, parseRPCTypes(*rpc))) 370 371 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *statsPort)) 372 if err != nil { 373 logger.Fatalf("failed to listen: %v", err) 374 } 375 s := grpc.NewServer() 376 defer s.Stop() 377 testgrpc.RegisterLoadBalancerStatsServiceServer(s, &statsService{}) 378 testgrpc.RegisterXdsUpdateClientConfigureServiceServer(s, &configureService{}) 379 reflection.Register(s) 380 cleanup, err := admin.Register(s) 381 if err != nil { 382 logger.Fatalf("Failed to register admin: %v", err) 383 } 384 defer cleanup() 385 go s.Serve(lis) 386 387 creds := insecure.NewCredentials() 388 if *secureMode { 389 var err error 390 creds, err = xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) 391 if err != nil { 392 logger.Fatalf("Failed to create xDS credentials: %v", err) 393 } 394 } 395 396 clients := make([]testgrpc.TestServiceClient, *numChannels) 397 for i := 0; i < *numChannels; i++ { 398 conn, err := grpc.Dial(*server, grpc.WithTransportCredentials(creds)) 399 if err != nil { 400 logger.Fatalf("Fail to dial: %v", err) 401 } 402 defer conn.Close() 403 clients[i] = testgrpc.NewTestServiceClient(conn) 404 } 405 ticker := time.NewTicker(time.Second / time.Duration(*qps**numChannels)) 406 defer ticker.Stop() 407 sendRPCs(clients, ticker) 408 } 409 410 func makeOneRPC(c testgrpc.TestServiceClient, cfg *rpcConfig) (*peer.Peer, *rpcInfo, error) { 411 timeout := *rpcTimeout 412 if cfg.timeout != 0 { 413 timeout = time.Duration(cfg.timeout) * time.Second 414 } 415 ctx, cancel := context.WithTimeout(context.Background(), timeout) 416 defer cancel() 417 418 if len(cfg.md) != 0 { 419 ctx = metadata.NewOutgoingContext(ctx, cfg.md) 420 } 421 info := rpcInfo{typ: cfg.typ} 422 423 var ( 424 p peer.Peer 425 header metadata.MD 426 err error 427 ) 428 accStats.startRPC(cfg.typ) 429 switch cfg.typ { 430 case unaryCall: 431 var resp *testpb.SimpleResponse 432 resp, err = c.UnaryCall(ctx, &testpb.SimpleRequest{FillServerId: true}, grpc.Peer(&p), grpc.Header(&header)) 433 // For UnaryCall, also read hostname from response, in case the server 434 // isn't updated to send headers. 435 if resp != nil { 436 info.hostname = resp.Hostname 437 } 438 case emptyCall: 439 _, err = c.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&p), grpc.Header(&header)) 440 } 441 accStats.finishRPC(cfg.typ, err) 442 if err != nil { 443 return nil, nil, err 444 } 445 446 hosts := header["hostname"] 447 if len(hosts) > 0 { 448 info.hostname = hosts[0] 449 } 450 return &p, &info, err 451 } 452 453 func sendRPCs(clients []testgrpc.TestServiceClient, ticker *time.Ticker) { 454 var i int 455 for range ticker.C { 456 // Get and increment request ID, and save a list of watchers that are 457 // interested in this RPC. 458 mu.Lock() 459 savedRequestID := currentRequestID 460 currentRequestID++ 461 savedWatchers := []*statsWatcher{} 462 for key, value := range watchers { 463 if key.startID <= savedRequestID && savedRequestID < key.endID { 464 savedWatchers = append(savedWatchers, value) 465 } 466 } 467 mu.Unlock() 468 469 // Get the RPC metadata configurations from the Configure RPC. 470 cfgs := rpcCfgs.Load().([]*rpcConfig) 471 472 c := clients[i] 473 for _, cfg := range cfgs { 474 go func(cfg *rpcConfig) { 475 p, info, err := makeOneRPC(c, cfg) 476 477 for _, watcher := range savedWatchers { 478 // This sends an empty string if the RPC failed. 479 watcher.chanHosts <- info 480 } 481 if err != nil && *failOnFailedRPC && hasRPCSucceeded() { 482 logger.Fatalf("RPC failed: %v", err) 483 } 484 if err == nil { 485 setRPCSucceeded() 486 } 487 if *printResponse { 488 if err == nil { 489 if cfg.typ == unaryCall { 490 // Need to keep this format, because some tests are 491 // relying on stdout. 492 fmt.Printf("Greeting: Hello world, this is %s, from %v\n", info.hostname, p.Addr) 493 } else { 494 fmt.Printf("RPC %q, from host %s, addr %v\n", cfg.typ, info.hostname, p.Addr) 495 } 496 } else { 497 fmt.Printf("RPC %q, failed with %v\n", cfg.typ, err) 498 } 499 } 500 }(cfg) 501 } 502 i = (i + 1) % len(clients) 503 } 504 }