github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/balancer/clusterresolver/testutil_test.go (about)

     1  /*
     2   * Copyright 2020 gRPC authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package clusterresolver
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"reflect"
    23  	"strconv"
    24  	"time"
    25  
    26  	xdspb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2"
    27  	corepb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2/core"
    28  	endpointpb "github.com/hxx258456/ccgo/go-control-plane/envoy/api/v2/endpoint"
    29  	typepb "github.com/hxx258456/ccgo/go-control-plane/envoy/type"
    30  	"github.com/hxx258456/ccgo/grpc/balancer"
    31  	"github.com/hxx258456/ccgo/grpc/internal/testutils"
    32  	"github.com/hxx258456/ccgo/grpc/xds/internal"
    33  	"github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource"
    34  )
    35  
    36  // parseEDSRespProtoForTesting parses EDS response, and panic if parsing fails.
    37  //
    38  // TODO: delete this. The EDS balancer tests should build an EndpointsUpdate
    39  // directly, instead of building and parsing a proto message.
    40  func parseEDSRespProtoForTesting(m *xdspb.ClusterLoadAssignment) xdsresource.EndpointsUpdate {
    41  	u, err := parseEDSRespProto(m)
    42  	if err != nil {
    43  		panic(err.Error())
    44  	}
    45  	return u
    46  }
    47  
    48  // parseEDSRespProto turns EDS response proto message to EndpointsUpdate.
    49  func parseEDSRespProto(m *xdspb.ClusterLoadAssignment) (xdsresource.EndpointsUpdate, error) {
    50  	ret := xdsresource.EndpointsUpdate{}
    51  	for _, dropPolicy := range m.GetPolicy().GetDropOverloads() {
    52  		ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy))
    53  	}
    54  	priorities := make(map[uint32]struct{})
    55  	for _, locality := range m.Endpoints {
    56  		l := locality.GetLocality()
    57  		if l == nil {
    58  			return xdsresource.EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality)
    59  		}
    60  		lid := internal.LocalityID{
    61  			Region:  l.Region,
    62  			Zone:    l.Zone,
    63  			SubZone: l.SubZone,
    64  		}
    65  		priority := locality.GetPriority()
    66  		priorities[priority] = struct{}{}
    67  		ret.Localities = append(ret.Localities, xdsresource.Locality{
    68  			ID:        lid,
    69  			Endpoints: parseEndpoints(locality.GetLbEndpoints()),
    70  			Weight:    locality.GetLoadBalancingWeight().GetValue(),
    71  			Priority:  priority,
    72  		})
    73  	}
    74  	for i := 0; i < len(priorities); i++ {
    75  		if _, ok := priorities[uint32(i)]; !ok {
    76  			return xdsresource.EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities)
    77  		}
    78  	}
    79  	return ret, nil
    80  }
    81  
    82  func parseAddress(socketAddress *corepb.SocketAddress) string {
    83  	return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue())))
    84  }
    85  
    86  func parseDropPolicy(dropPolicy *xdspb.ClusterLoadAssignment_Policy_DropOverload) xdsresource.OverloadDropConfig {
    87  	percentage := dropPolicy.GetDropPercentage()
    88  	var (
    89  		numerator   = percentage.GetNumerator()
    90  		denominator uint32
    91  	)
    92  	switch percentage.GetDenominator() {
    93  	case typepb.FractionalPercent_HUNDRED:
    94  		denominator = 100
    95  	case typepb.FractionalPercent_TEN_THOUSAND:
    96  		denominator = 10000
    97  	case typepb.FractionalPercent_MILLION:
    98  		denominator = 1000000
    99  	}
   100  	return xdsresource.OverloadDropConfig{
   101  		Category:    dropPolicy.GetCategory(),
   102  		Numerator:   numerator,
   103  		Denominator: denominator,
   104  	}
   105  }
   106  
   107  func parseEndpoints(lbEndpoints []*endpointpb.LbEndpoint) []xdsresource.Endpoint {
   108  	endpoints := make([]xdsresource.Endpoint, 0, len(lbEndpoints))
   109  	for _, lbEndpoint := range lbEndpoints {
   110  		endpoints = append(endpoints, xdsresource.Endpoint{
   111  			HealthStatus: xdsresource.EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
   112  			Address:      parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()),
   113  			Weight:       lbEndpoint.GetLoadBalancingWeight().GetValue(),
   114  		})
   115  	}
   116  	return endpoints
   117  }
   118  
   119  // testPickerFromCh receives pickers from the channel, and check if their
   120  // behaviors are as expected (that the given function returns nil err).
   121  //
   122  // It returns nil if one picker has the correct behavior.
   123  //
   124  // It returns error when there's no picker from channel after 1 second timeout,
   125  // and the error returned is the mismatch error from the previous picker.
   126  func testPickerFromCh(ch chan balancer.Picker, f func(balancer.Picker) error) error {
   127  	var (
   128  		p   balancer.Picker
   129  		err error
   130  	)
   131  	for {
   132  		select {
   133  		case p = <-ch:
   134  		case <-time.After(defaultTestTimeout):
   135  			// TODO: this function should take a context, and use the context
   136  			// here, instead of making a new timer.
   137  			return fmt.Errorf("timeout waiting for picker with expected behavior, error from previous picker: %v", err)
   138  		}
   139  
   140  		err = f(p)
   141  		if err == nil {
   142  			return nil
   143  		}
   144  	}
   145  }
   146  
   147  func subConnFromPicker(p balancer.Picker) func() balancer.SubConn {
   148  	return func() balancer.SubConn {
   149  		scst, _ := p.Pick(balancer.PickInfo{})
   150  		return scst.SubConn
   151  	}
   152  }
   153  
   154  // testRoundRobinPickerFromCh receives pickers from the channel, and check if
   155  // their behaviors are round-robin of want.
   156  //
   157  // It returns nil if one picker has the correct behavior.
   158  //
   159  // It returns error when there's no picker from channel after 1 second timeout,
   160  // and the error returned is the mismatch error from the previous picker.
   161  func testRoundRobinPickerFromCh(ch chan balancer.Picker, want []balancer.SubConn) error {
   162  	return testPickerFromCh(ch, func(p balancer.Picker) error {
   163  		return testutils.IsRoundRobin(want, subConnFromPicker(p))
   164  	})
   165  }
   166  
   167  // testErrPickerFromCh receives pickers from the channel, and check if they
   168  // return the wanted error.
   169  //
   170  // It returns nil if one picker has the correct behavior.
   171  //
   172  // It returns error when there's no picker from channel after 1 second timeout,
   173  // and the error returned is the mismatch error from the previous picker.
   174  func testErrPickerFromCh(ch chan balancer.Picker, want error) error {
   175  	return testPickerFromCh(ch, func(p balancer.Picker) error {
   176  		for i := 0; i < 5; i++ {
   177  			_, err := p.Pick(balancer.PickInfo{})
   178  			if !reflect.DeepEqual(err, want) {
   179  				return fmt.Errorf("picker.Pick, got err %q, want err %q", err, want)
   180  			}
   181  		}
   182  		return nil
   183  	})
   184  }