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 }