gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/balancer/clusterresolver/configbuilder.go (about) 1 /* 2 * 3 * Copyright 2021 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 package clusterresolver 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "sort" 25 26 "gitee.com/ks-custle/core-gm/grpc/balancer/roundrobin" 27 "gitee.com/ks-custle/core-gm/grpc/balancer/weightedroundrobin" 28 "gitee.com/ks-custle/core-gm/grpc/balancer/weightedtarget" 29 "gitee.com/ks-custle/core-gm/grpc/internal/hierarchy" 30 internalserviceconfig "gitee.com/ks-custle/core-gm/grpc/internal/serviceconfig" 31 "gitee.com/ks-custle/core-gm/grpc/resolver" 32 "gitee.com/ks-custle/core-gm/grpc/xds/internal" 33 "gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/clusterimpl" 34 "gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/priority" 35 "gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/ringhash" 36 "gitee.com/ks-custle/core-gm/grpc/xds/internal/xdsclient/xdsresource" 37 ) 38 39 const million = 1000000 40 41 // priorityConfig is config for one priority. For example, if there an EDS and a 42 // DNS, the priority list will be [priorityConfig{EDS}, priorityConfig{DNS}]. 43 // 44 // Each priorityConfig corresponds to one discovery mechanism from the LBConfig 45 // generated by the CDS balancer. The CDS balancer resolves the cluster name to 46 // an ordered list of discovery mechanisms (if the top cluster is an aggregated 47 // cluster), one for each underlying cluster. 48 type priorityConfig struct { 49 mechanism DiscoveryMechanism 50 // edsResp is set only if type is EDS. 51 edsResp xdsresource.EndpointsUpdate 52 // addresses is set only if type is DNS. 53 addresses []string 54 } 55 56 // buildPriorityConfigJSON builds balancer config for the passed in 57 // priorities. 58 // 59 // The built tree of balancers (see test for the output struct). 60 // 61 // If xds lb policy is ROUND_ROBIN, the children will be weighted_target for 62 // locality picking, and round_robin for endpoint picking. 63 // 64 // ┌────────┐ 65 // │priority│ 66 // └┬──────┬┘ 67 // │ │ 68 // ┌───────────▼┐ ┌▼───────────┐ 69 // │cluster_impl│ │cluster_impl│ 70 // └─┬──────────┘ └──────────┬─┘ 71 // │ │ 72 // ┌──────────────▼─┐ ┌─▼──────────────┐ 73 // │locality_picking│ │locality_picking│ 74 // └┬──────────────┬┘ └┬──────────────┬┘ 75 // │ │ │ │ 76 // ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ ┌─▼─┐ 77 // │LRS│ │LRS│ │LRS│ │LRS│ 78 // └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ 79 // │ │ │ │ 80 // 81 // ┌──────────▼─────┐ ┌─────▼──────────┐ ┌──────────▼─────┐ ┌─────▼──────────┐ 82 // │endpoint_picking│ │endpoint_picking│ │endpoint_picking│ │endpoint_picking│ 83 // └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ 84 // 85 // If xds lb policy is RING_HASH, the children will be just a ring_hash policy. 86 // The endpoints from all localities will be flattened to one addresses list, 87 // and the ring_hash policy will pick endpoints from it. 88 // 89 // ┌────────┐ 90 // │priority│ 91 // └┬──────┬┘ 92 // │ │ 93 // 94 // ┌──────────▼─┐ ┌─▼──────────┐ 95 // │cluster_impl│ │cluster_impl│ 96 // └──────┬─────┘ └─────┬──────┘ 97 // 98 // │ │ 99 // 100 // ┌──────▼─────┐ ┌─────▼──────┐ 101 // │ ring_hash │ │ ring_hash │ 102 // └────────────┘ └────────────┘ 103 // 104 // If endpointPickingPolicy is nil, roundrobin will be used. 105 // 106 // Custom locality picking policy isn't support, and weighted_target is always 107 // used. 108 func buildPriorityConfigJSON(priorities []priorityConfig, xdsLBPolicy *internalserviceconfig.BalancerConfig) ([]byte, []resolver.Address, error) { 109 pc, addrs, err := buildPriorityConfig(priorities, xdsLBPolicy) 110 if err != nil { 111 return nil, nil, fmt.Errorf("failed to build priority config: %v", err) 112 } 113 ret, err := json.Marshal(pc) 114 if err != nil { 115 return nil, nil, fmt.Errorf("failed to marshal built priority config struct into json: %v", err) 116 } 117 return ret, addrs, nil 118 } 119 120 func buildPriorityConfig(priorities []priorityConfig, xdsLBPolicy *internalserviceconfig.BalancerConfig) (*priority.LBConfig, []resolver.Address, error) { 121 var ( 122 retConfig = &priority.LBConfig{Children: make(map[string]*priority.Child)} 123 retAddrs []resolver.Address 124 ) 125 for i, p := range priorities { 126 switch p.mechanism.Type { 127 case DiscoveryMechanismTypeEDS: 128 names, configs, addrs, err := buildClusterImplConfigForEDS(i, p.edsResp, p.mechanism, xdsLBPolicy) 129 if err != nil { 130 return nil, nil, err 131 } 132 retConfig.Priorities = append(retConfig.Priorities, names...) 133 for n, c := range configs { 134 retConfig.Children[n] = &priority.Child{ 135 Config: &internalserviceconfig.BalancerConfig{Name: clusterimpl.Name, Config: c}, 136 // Ignore all re-resolution from EDS children. 137 IgnoreReresolutionRequests: true, 138 } 139 } 140 retAddrs = append(retAddrs, addrs...) 141 case DiscoveryMechanismTypeLogicalDNS: 142 name, config, addrs := buildClusterImplConfigForDNS(i, p.addresses) 143 retConfig.Priorities = append(retConfig.Priorities, name) 144 retConfig.Children[name] = &priority.Child{ 145 Config: &internalserviceconfig.BalancerConfig{Name: clusterimpl.Name, Config: config}, 146 // Not ignore re-resolution from DNS children, they will trigger 147 // DNS to re-resolve. 148 IgnoreReresolutionRequests: false, 149 } 150 retAddrs = append(retAddrs, addrs...) 151 } 152 } 153 return retConfig, retAddrs, nil 154 } 155 156 func buildClusterImplConfigForDNS(parentPriority int, addrStrs []string) (string, *clusterimpl.LBConfig, []resolver.Address) { 157 // Endpoint picking policy for DNS is hardcoded to pick_first. 158 const childPolicy = "pick_first" 159 retAddrs := make([]resolver.Address, 0, len(addrStrs)) 160 pName := fmt.Sprintf("priority-%v", parentPriority) 161 for _, addrStr := range addrStrs { 162 retAddrs = append(retAddrs, hierarchy.Set(resolver.Address{Addr: addrStr}, []string{pName})) 163 } 164 return pName, &clusterimpl.LBConfig{ChildPolicy: &internalserviceconfig.BalancerConfig{Name: childPolicy}}, retAddrs 165 } 166 167 // buildClusterImplConfigForEDS returns a list of cluster_impl configs, one for 168 // each priority, sorted by priority, and the addresses for each priority (with 169 // hierarchy attributes set). 170 // 171 // For example, if there are two priorities, the returned values will be 172 // - ["p0", "p1"] 173 // - map{"p0":p0_config, "p1":p1_config} 174 // - [p0_address_0, p0_address_1, p1_address_0, p1_address_1] 175 // - p0 addresses' hierarchy attributes are set to p0 176 func buildClusterImplConfigForEDS(parentPriority int, edsResp xdsresource.EndpointsUpdate, mechanism DiscoveryMechanism, xdsLBPolicy *internalserviceconfig.BalancerConfig) ([]string, map[string]*clusterimpl.LBConfig, []resolver.Address, error) { 177 drops := make([]clusterimpl.DropConfig, 0, len(edsResp.Drops)) 178 for _, d := range edsResp.Drops { 179 drops = append(drops, clusterimpl.DropConfig{ 180 Category: d.Category, 181 RequestsPerMillion: d.Numerator * million / d.Denominator, 182 }) 183 } 184 185 priorityChildNames, priorities := groupLocalitiesByPriority(edsResp.Localities) 186 retNames := make([]string, 0, len(priorityChildNames)) 187 retAddrs := make([]resolver.Address, 0, len(priorityChildNames)) 188 retConfigs := make(map[string]*clusterimpl.LBConfig, len(priorityChildNames)) 189 for _, priorityName := range priorityChildNames { 190 priorityLocalities := priorities[priorityName] 191 // Prepend parent priority to the priority names, to avoid duplicates. 192 pName := fmt.Sprintf("priority-%v-%v", parentPriority, priorityName) 193 retNames = append(retNames, pName) 194 cfg, addrs, err := priorityLocalitiesToClusterImpl(priorityLocalities, pName, mechanism, drops, xdsLBPolicy) 195 if err != nil { 196 return nil, nil, nil, err 197 } 198 retConfigs[pName] = cfg 199 retAddrs = append(retAddrs, addrs...) 200 } 201 return retNames, retConfigs, retAddrs, nil 202 } 203 204 // groupLocalitiesByPriority returns the localities grouped by priority. 205 // 206 // It also returns a list of strings where each string represents a priority, 207 // and the list is sorted from higher priority to lower priority. 208 // 209 // For example, for L0-p0, L1-p0, L2-p1, results will be 210 // - ["p0", "p1"] 211 // - map{"p0":[L0, L1], "p1":[L2]} 212 func groupLocalitiesByPriority(localities []xdsresource.Locality) ([]string, map[string][]xdsresource.Locality) { 213 var priorityIntSlice []int 214 priorities := make(map[string][]xdsresource.Locality) 215 for _, locality := range localities { 216 if locality.Weight == 0 { 217 continue 218 } 219 priorityName := fmt.Sprintf("%v", locality.Priority) 220 priorities[priorityName] = append(priorities[priorityName], locality) 221 priorityIntSlice = append(priorityIntSlice, int(locality.Priority)) 222 } 223 // Sort the priorities based on the int value, deduplicate, and then turn 224 // the sorted list into a string list. This will be child names, in priority 225 // order. 226 sort.Ints(priorityIntSlice) 227 priorityIntSliceDeduped := dedupSortedIntSlice(priorityIntSlice) 228 priorityNameSlice := make([]string, 0, len(priorityIntSliceDeduped)) 229 for _, p := range priorityIntSliceDeduped { 230 priorityNameSlice = append(priorityNameSlice, fmt.Sprintf("%v", p)) 231 } 232 return priorityNameSlice, priorities 233 } 234 235 func dedupSortedIntSlice(a []int) []int { 236 if len(a) == 0 { 237 return a 238 } 239 i, j := 0, 1 240 for ; j < len(a); j++ { 241 if a[i] == a[j] { 242 continue 243 } 244 i++ 245 if i != j { 246 a[i] = a[j] 247 } 248 } 249 return a[:i+1] 250 } 251 252 // rrBalancerConfig is a const roundrobin config, used as child of 253 // weighted-roundrobin. To avoid allocating memory everytime. 254 var rrBalancerConfig = &internalserviceconfig.BalancerConfig{Name: roundrobin.Name} 255 256 // priorityLocalitiesToClusterImpl takes a list of localities (with the same 257 // priority), and generates a cluster impl policy config, and a list of 258 // addresses. 259 func priorityLocalitiesToClusterImpl(localities []xdsresource.Locality, priorityName string, mechanism DiscoveryMechanism, drops []clusterimpl.DropConfig, xdsLBPolicy *internalserviceconfig.BalancerConfig) (*clusterimpl.LBConfig, []resolver.Address, error) { 260 clusterImplCfg := &clusterimpl.LBConfig{ 261 Cluster: mechanism.Cluster, 262 EDSServiceName: mechanism.EDSServiceName, 263 LoadReportingServerName: mechanism.LoadReportingServerName, 264 MaxConcurrentRequests: mechanism.MaxConcurrentRequests, 265 DropCategories: drops, 266 // ChildPolicy is not set. Will be set based on xdsLBPolicy 267 } 268 269 if xdsLBPolicy == nil || xdsLBPolicy.Name == rrName { 270 // If lb policy is ROUND_ROBIN: 271 // - locality-picking policy is weighted_target 272 // - endpoint-picking policy is round_robin 273 logger.Infof("xds lb policy is %q, building config with weighted_target + round_robin", rrName) 274 // Child of weighted_target is hardcoded to round_robin. 275 wtConfig, addrs := localitiesToWeightedTarget(localities, priorityName, rrBalancerConfig) 276 clusterImplCfg.ChildPolicy = &internalserviceconfig.BalancerConfig{Name: weightedtarget.Name, Config: wtConfig} 277 return clusterImplCfg, addrs, nil 278 } 279 280 if xdsLBPolicy.Name == rhName { 281 // If lb policy is RIHG_HASH, will build one ring_hash policy as child. 282 // The endpoints from all localities will be flattened to one addresses 283 // list, and the ring_hash policy will pick endpoints from it. 284 logger.Infof("xds lb policy is %q, building config with ring_hash", rhName) 285 addrs := localitiesToRingHash(localities, priorityName) 286 // Set child to ring_hash, note that the ring_hash config is from 287 // xdsLBPolicy. 288 clusterImplCfg.ChildPolicy = &internalserviceconfig.BalancerConfig{Name: ringhash.Name, Config: xdsLBPolicy.Config} 289 return clusterImplCfg, addrs, nil 290 } 291 292 return nil, nil, fmt.Errorf("unsupported xds LB policy %q, not one of {%q,%q}", xdsLBPolicy.Name, rrName, rhName) 293 } 294 295 // localitiesToRingHash takes a list of localities (with the same priority), and 296 // generates a list of addresses. 297 // 298 // The addresses have path hierarchy set to [priority-name], so priority knows 299 // which child policy they are for. 300 func localitiesToRingHash(localities []xdsresource.Locality, priorityName string) []resolver.Address { 301 var addrs []resolver.Address 302 for _, locality := range localities { 303 var lw uint32 = 1 304 if locality.Weight != 0 { 305 lw = locality.Weight 306 } 307 localityStr, err := locality.ID.ToString() 308 if err != nil { 309 localityStr = fmt.Sprintf("%+v", locality.ID) 310 } 311 for _, endpoint := range locality.Endpoints { 312 // Filter out all "unhealthy" endpoints (unknown and healthy are 313 // both considered to be healthy: 314 // https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/health_check.proto#envoy-api-enum-core-healthstatus). 315 if endpoint.HealthStatus != xdsresource.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsresource.EndpointHealthStatusUnknown { 316 continue 317 } 318 319 var ew uint32 = 1 320 if endpoint.Weight != 0 { 321 ew = endpoint.Weight 322 } 323 324 // The weight of each endpoint is locality_weight * endpoint_weight. 325 ai := weightedroundrobin.AddrInfo{Weight: lw * ew} 326 addr := weightedroundrobin.SetAddrInfo(resolver.Address{Addr: endpoint.Address}, ai) 327 addr = hierarchy.Set(addr, []string{priorityName, localityStr}) 328 addr = internal.SetLocalityID(addr, locality.ID) 329 addrs = append(addrs, addr) 330 } 331 } 332 return addrs 333 } 334 335 // localitiesToWeightedTarget takes a list of localities (with the same 336 // priority), and generates a weighted target config, and list of addresses. 337 // 338 // The addresses have path hierarchy set to [priority-name, locality-name], so 339 // priority and weighted target know which child policy they are for. 340 func localitiesToWeightedTarget(localities []xdsresource.Locality, priorityName string, childPolicy *internalserviceconfig.BalancerConfig) (*weightedtarget.LBConfig, []resolver.Address) { 341 weightedTargets := make(map[string]weightedtarget.Target) 342 var addrs []resolver.Address 343 for _, locality := range localities { 344 localityStr, err := locality.ID.ToString() 345 if err != nil { 346 localityStr = fmt.Sprintf("%+v", locality.ID) 347 } 348 weightedTargets[localityStr] = weightedtarget.Target{Weight: locality.Weight, ChildPolicy: childPolicy} 349 for _, endpoint := range locality.Endpoints { 350 // Filter out all "unhealthy" endpoints (unknown and healthy are 351 // both considered to be healthy: 352 // https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/health_check.proto#envoy-api-enum-core-healthstatus). 353 if endpoint.HealthStatus != xdsresource.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsresource.EndpointHealthStatusUnknown { 354 continue 355 } 356 357 addr := resolver.Address{Addr: endpoint.Address} 358 if childPolicy.Name == weightedroundrobin.Name && endpoint.Weight != 0 { 359 ai := weightedroundrobin.AddrInfo{Weight: endpoint.Weight} 360 addr = weightedroundrobin.SetAddrInfo(addr, ai) 361 } 362 addr = hierarchy.Set(addr, []string{priorityName, localityStr}) 363 addr = internal.SetLocalityID(addr, locality.ID) 364 addrs = append(addrs, addr) 365 } 366 } 367 return &weightedtarget.LBConfig{Targets: weightedTargets}, addrs 368 }