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  }