| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | package xds
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"math/rand"
 | 
					
						
							|  |  |  | 	"net"
 | 
					
						
							|  |  |  | 	"sync"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 	xdspb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
 | 
					
						
							|  |  |  | 	corepb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | // SocketAddress holds a corepb2.SocketAddress and a health status
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | type SocketAddress struct {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 	*corepb2.SocketAddress
 | 
					
						
							|  |  |  | 	Health corepb2.HealthStatus
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 09:49:28 +01:00
										 |  |  | // Address returns the address from s.
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | func (s *SocketAddress) Address() net.IP { return net.ParseIP(s.GetAddress()) }
 | 
					
						
							| 
									
										
										
										
											2020-01-19 09:49:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Port returns the port from s.
 | 
					
						
							|  |  |  | func (s *SocketAddress) Port() uint16 { return uint16(s.GetPortValue()) }
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | type assignment struct {
 | 
					
						
							|  |  |  | 	mu  sync.RWMutex
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 	cla map[string]*xdspb2.ClusterLoadAssignment
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewAssignment returns a pointer to an assignment.
 | 
					
						
							|  |  |  | func NewAssignment() *assignment {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 	return &assignment{cla: make(map[string]*xdspb2.ClusterLoadAssignment)}
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetClusterLoadAssignment sets the assignment for the cluster to cla.
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | func (a *assignment) SetClusterLoadAssignment(cluster string, cla *xdspb2.ClusterLoadAssignment) {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	// If cla is nil we just found a cluster, check if we already know about it, or if we need to make a new entry.
 | 
					
						
							|  |  |  | 	a.mu.Lock()
 | 
					
						
							|  |  |  | 	defer a.mu.Unlock()
 | 
					
						
							|  |  |  | 	_, ok := a.cla[cluster]
 | 
					
						
							|  |  |  | 	if !ok {
 | 
					
						
							|  |  |  | 		a.cla[cluster] = cla
 | 
					
						
							|  |  |  | 		return
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	if cla == nil {
 | 
					
						
							|  |  |  | 		return
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	a.cla[cluster] = cla
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ClusterLoadAssignment returns the assignment for the cluster or nil if there is none.
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | func (a *assignment) ClusterLoadAssignment(cluster string) *xdspb2.ClusterLoadAssignment {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	a.mu.RLock()
 | 
					
						
							|  |  |  | 	cla, ok := a.cla[cluster]
 | 
					
						
							|  |  |  | 	a.mu.RUnlock()
 | 
					
						
							|  |  |  | 	if !ok {
 | 
					
						
							|  |  |  | 		return nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return cla
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *assignment) clusters() []string {
 | 
					
						
							|  |  |  | 	a.mu.RLock()
 | 
					
						
							|  |  |  | 	defer a.mu.RUnlock()
 | 
					
						
							|  |  |  | 	clusters := make([]string, len(a.cla))
 | 
					
						
							|  |  |  | 	i := 0
 | 
					
						
							|  |  |  | 	for k := range a.cla {
 | 
					
						
							|  |  |  | 		clusters[i] = k
 | 
					
						
							|  |  |  | 		i++
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return clusters
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | // Select selects a endpoint from cluster load assignments, using weighted random selection. It only selects endpoints that are reporting healthy.
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | func (a *assignment) Select(cluster string, healthy bool) (*SocketAddress, bool) {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	cla := a.ClusterLoadAssignment(cluster)
 | 
					
						
							|  |  |  | 	if cla == nil {
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 		return nil, false
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-05 17:33:35 +01:00
										 |  |  | 	weight := 0
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	health := 0
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	for _, ep := range cla.Endpoints {
 | 
					
						
							|  |  |  | 		for _, lb := range ep.GetLbEndpoints() {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 			if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 				continue
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-03-05 17:33:35 +01:00
										 |  |  | 			weight += int(lb.GetLoadBalancingWeight().GetValue())
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 			health++
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	if health == 0 {
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 		return nil, true
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-05 17:33:35 +01:00
										 |  |  | 	// all weights are 0, randomly select one of the endpoints,
 | 
					
						
							|  |  |  | 	if weight == 0 {
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 		r := rand.Intn(health)
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 		i := 0
 | 
					
						
							|  |  |  | 		for _, ep := range cla.Endpoints {
 | 
					
						
							|  |  |  | 			for _, lb := range ep.GetLbEndpoints() {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 				if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 					continue
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r == i {
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 					return &SocketAddress{lb.GetEndpoint().GetAddress().GetSocketAddress(), lb.GetHealthStatus()}, true
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 				i++
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 		return nil, true
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	r := rand.Intn(health) + 1
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	for _, ep := range cla.Endpoints {
 | 
					
						
							|  |  |  | 		for _, lb := range ep.GetLbEndpoints() {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 			if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 				continue
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 			r -= int(lb.GetLoadBalancingWeight().GetValue())
 | 
					
						
							|  |  |  | 			if r <= 0 {
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 				return &SocketAddress{lb.GetEndpoint().GetAddress().GetSocketAddress(), lb.GetHealthStatus()}, true
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return nil, true
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-24 10:33:52 +02:00
										 |  |  | // All returns all healthy endpoints, together with their weights.
 | 
					
						
							|  |  |  | func (a *assignment) All(cluster string, healthy bool) ([]*SocketAddress, []uint32, bool) {
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	cla := a.ClusterLoadAssignment(cluster)
 | 
					
						
							|  |  |  | 	if cla == nil {
 | 
					
						
							| 
									
										
										
										
											2020-07-24 10:33:52 +02:00
										 |  |  | 		return nil, nil, false
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sa := []*SocketAddress{}
 | 
					
						
							| 
									
										
										
										
											2020-07-24 10:33:52 +02:00
										 |  |  | 	we := []uint32{}
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	for _, ep := range cla.Endpoints {
 | 
					
						
							|  |  |  | 		for _, lb := range ep.GetLbEndpoints() {
 | 
					
						
							| 
									
										
										
										
											2020-06-04 13:36:27 +02:00
										 |  |  | 			if healthy && lb.GetHealthStatus() != corepb2.HealthStatus_HEALTHY {
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 				continue
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-07-24 10:33:52 +02:00
										 |  |  | 			weight := lb.GetLoadBalancingWeight().GetValue()
 | 
					
						
							|  |  |  | 			if weight > 2^16 {
 | 
					
						
							|  |  |  | 				log.Warning("Weight in cluster %q > %d, truncating to %d in SRV responses", cluster, weight, uint16(weight))
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 			we = append(we, weight)
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 			sa = append(sa, &SocketAddress{lb.GetEndpoint().GetAddress().GetSocketAddress(), lb.GetHealthStatus()})
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-07-24 10:33:52 +02:00
										 |  |  | 	return sa, we, true
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | }
 |