istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/endpoints/endpoint_builder.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package endpoints
    16  
    17  import (
    18  	"math"
    19  	"net"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  
    24  	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    25  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    26  	"google.golang.org/protobuf/types/known/structpb"
    27  	"google.golang.org/protobuf/types/known/wrapperspb"
    28  
    29  	"istio.io/api/networking/v1alpha3"
    30  	"istio.io/istio/pilot/pkg/features"
    31  	"istio.io/istio/pilot/pkg/model"
    32  	"istio.io/istio/pilot/pkg/networking/core/loadbalancer"
    33  	"istio.io/istio/pilot/pkg/networking/util"
    34  	"istio.io/istio/pkg/cluster"
    35  	"istio.io/istio/pkg/config/constants"
    36  	"istio.io/istio/pkg/config/host"
    37  	"istio.io/istio/pkg/config/labels"
    38  	"istio.io/istio/pkg/config/schema/kind"
    39  	istiolog "istio.io/istio/pkg/log"
    40  	"istio.io/istio/pkg/network"
    41  	"istio.io/istio/pkg/slices"
    42  	"istio.io/istio/pkg/util/hash"
    43  	netutil "istio.io/istio/pkg/util/net"
    44  )
    45  
    46  var (
    47  	Separator = []byte{'~'}
    48  	Slash     = []byte{'/'}
    49  
    50  	// same as the above "xds" package
    51  	log = istiolog.RegisterScope("ads", "ads debugging")
    52  )
    53  
    54  // ConnectOriginate is the name for the resources associated with the origination of HTTP CONNECT.
    55  // Duplicated from v1alpha3/waypoint.go to avoid import cycle
    56  const connectOriginate = "connect_originate"
    57  
    58  type EndpointBuilder struct {
    59  	// These fields define the primary key for an endpoint, and can be used as a cache key
    60  	clusterName            string
    61  	network                network.ID
    62  	proxyView              model.ProxyView
    63  	clusterID              cluster.ID
    64  	locality               *corev3.Locality
    65  	destinationRule        *model.ConsolidatedDestRule
    66  	service                *model.Service
    67  	clusterLocal           bool
    68  	nodeType               model.NodeType
    69  	failoverPriorityLabels []byte
    70  
    71  	// These fields are provided for convenience only
    72  	subsetName   string
    73  	subsetLabels labels.Instance
    74  	hostname     host.Name
    75  	port         int
    76  	push         *model.PushContext
    77  	proxy        *model.Proxy
    78  	dir          model.TrafficDirection
    79  
    80  	mtlsChecker *mtlsChecker
    81  }
    82  
    83  func NewEndpointBuilder(clusterName string, proxy *model.Proxy, push *model.PushContext) EndpointBuilder {
    84  	dir, subsetName, hostname, port := model.ParseSubsetKey(clusterName)
    85  
    86  	svc := push.ServiceForHostname(proxy, hostname)
    87  	var dr *model.ConsolidatedDestRule
    88  	if svc != nil {
    89  		dr = proxy.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, proxy, svc.Hostname)
    90  	}
    91  
    92  	return *NewCDSEndpointBuilder(
    93  		proxy, push, clusterName,
    94  		dir, subsetName, hostname, port,
    95  		svc, dr,
    96  	)
    97  }
    98  
    99  // NewCDSEndpointBuilder allows setting some fields directly when we already
   100  // have the Service and DestinationRule.
   101  func NewCDSEndpointBuilder(
   102  	proxy *model.Proxy, push *model.PushContext, clusterName string,
   103  	dir model.TrafficDirection, subsetName string, hostname host.Name, port int,
   104  	service *model.Service, dr *model.ConsolidatedDestRule,
   105  ) *EndpointBuilder {
   106  	b := EndpointBuilder{
   107  		clusterName:     clusterName,
   108  		network:         proxy.Metadata.Network,
   109  		proxyView:       proxy.GetView(),
   110  		clusterID:       proxy.Metadata.ClusterID,
   111  		locality:        proxy.Locality,
   112  		destinationRule: dr,
   113  		service:         service,
   114  		clusterLocal:    push.IsClusterLocal(service),
   115  		nodeType:        proxy.Type,
   116  
   117  		subsetName: subsetName,
   118  		hostname:   hostname,
   119  		port:       port,
   120  		push:       push,
   121  		proxy:      proxy,
   122  		dir:        dir,
   123  	}
   124  	b.populateSubsetInfo()
   125  	b.populateFailoverPriorityLabels()
   126  	return &b
   127  }
   128  
   129  func (b *EndpointBuilder) servicePort(port int) *model.Port {
   130  	if !b.ServiceFound() {
   131  		log.Debugf("can not find the service %s for cluster %s", b.hostname, b.clusterName)
   132  		return nil
   133  	}
   134  	svcPort, f := b.service.Ports.GetByPort(port)
   135  	if !f {
   136  		log.Debugf("can not find the service port %d for cluster %s", b.port, b.clusterName)
   137  		return nil
   138  	}
   139  	return svcPort
   140  }
   141  
   142  func (b *EndpointBuilder) WithSubset(subset string) *EndpointBuilder {
   143  	if b == nil {
   144  		return nil
   145  	}
   146  	subsetBuilder := *b
   147  	subsetBuilder.subsetName = subset
   148  	subsetBuilder.populateSubsetInfo()
   149  	return &subsetBuilder
   150  }
   151  
   152  func (b *EndpointBuilder) populateSubsetInfo() {
   153  	if b.dir == model.TrafficDirectionInboundVIP {
   154  		b.subsetName = strings.TrimPrefix(b.subsetName, "http/")
   155  		b.subsetName = strings.TrimPrefix(b.subsetName, "tcp/")
   156  	}
   157  	b.mtlsChecker = newMtlsChecker(b.push, b.port, b.destinationRule.GetRule(), b.subsetName)
   158  	b.subsetLabels = getSubSetLabels(b.DestinationRule(), b.subsetName)
   159  }
   160  
   161  func (b *EndpointBuilder) populateFailoverPriorityLabels() {
   162  	enableFailover, lb := getOutlierDetectionAndLoadBalancerSettings(b.DestinationRule(), b.port, b.subsetName)
   163  	if enableFailover {
   164  		lbSetting := loadbalancer.GetLocalityLbSetting(b.push.Mesh.GetLocalityLbSetting(), lb.GetLocalityLbSetting())
   165  		if lbSetting != nil && lbSetting.Distribute == nil &&
   166  			len(lbSetting.FailoverPriority) > 0 && (lbSetting.Enabled == nil || lbSetting.Enabled.Value) {
   167  			b.failoverPriorityLabels = util.GetFailoverPriorityLabels(b.proxy.Labels, lbSetting.FailoverPriority)
   168  		}
   169  	}
   170  }
   171  
   172  func (b *EndpointBuilder) DestinationRule() *v1alpha3.DestinationRule {
   173  	if dr := b.destinationRule.GetRule(); dr != nil {
   174  		dr, _ := dr.Spec.(*v1alpha3.DestinationRule)
   175  		return dr
   176  	}
   177  	return nil
   178  }
   179  
   180  func (b *EndpointBuilder) Type() string {
   181  	return model.EDSType
   182  }
   183  
   184  func (b *EndpointBuilder) ServiceFound() bool {
   185  	return b.service != nil
   186  }
   187  
   188  func (b *EndpointBuilder) IsDNSCluster() bool {
   189  	return b.service != nil && (b.service.Resolution == model.DNSLB || b.service.Resolution == model.DNSRoundRobinLB)
   190  }
   191  
   192  // Key provides the eds cache key and should include any information that could change the way endpoints are generated.
   193  func (b *EndpointBuilder) Key() any {
   194  	// nolint: gosec
   195  	// Not security sensitive code
   196  	h := hash.New()
   197  	b.WriteHash(h)
   198  	return h.Sum64()
   199  }
   200  
   201  func (b *EndpointBuilder) WriteHash(h hash.Hash) {
   202  	if b == nil {
   203  		return
   204  	}
   205  	h.WriteString(b.clusterName)
   206  	h.Write(Separator)
   207  	h.WriteString(string(b.network))
   208  	h.Write(Separator)
   209  	h.WriteString(string(b.clusterID))
   210  	h.Write(Separator)
   211  	h.WriteString(string(b.nodeType))
   212  	h.Write(Separator)
   213  	h.WriteString(strconv.FormatBool(b.clusterLocal))
   214  	h.Write(Separator)
   215  	if b.proxy != nil {
   216  		h.WriteString(strconv.FormatBool(b.proxy.IsProxylessGrpc()))
   217  		h.Write(Separator)
   218  		h.WriteString(strconv.FormatBool(bool(b.proxy.Metadata.DisableHBONESend)))
   219  		h.Write(Separator)
   220  	}
   221  	h.WriteString(util.LocalityToString(b.locality))
   222  	h.Write(Separator)
   223  	if len(b.failoverPriorityLabels) > 0 {
   224  		h.Write(b.failoverPriorityLabels)
   225  		h.Write(Separator)
   226  	}
   227  	if b.service.Attributes.NodeLocal {
   228  		h.WriteString(b.proxy.GetNodeName())
   229  		h.Write(Separator)
   230  	}
   231  
   232  	if b.push != nil && b.push.AuthnPolicies != nil {
   233  		h.WriteString(b.push.AuthnPolicies.GetVersion())
   234  	}
   235  	h.Write(Separator)
   236  
   237  	for _, dr := range b.destinationRule.GetFrom() {
   238  		h.WriteString(dr.Name)
   239  		h.Write(Slash)
   240  		h.WriteString(dr.Namespace)
   241  	}
   242  	h.Write(Separator)
   243  
   244  	if b.service != nil {
   245  		h.WriteString(string(b.service.Hostname))
   246  		h.Write(Slash)
   247  		h.WriteString(b.service.Attributes.Namespace)
   248  	}
   249  	h.Write(Separator)
   250  
   251  	if b.proxyView != nil {
   252  		h.WriteString(b.proxyView.String())
   253  	}
   254  	h.Write(Separator)
   255  }
   256  
   257  func (b *EndpointBuilder) Cacheable() bool {
   258  	// If service is not defined, we cannot do any caching as we will not have a way to
   259  	// invalidate the results.
   260  	// Service being nil means the EDS will be empty anyways, so not much lost here.
   261  	return b.service != nil
   262  }
   263  
   264  func (b *EndpointBuilder) DependentConfigs() []model.ConfigHash {
   265  	drs := b.destinationRule.GetFrom()
   266  	configs := make([]model.ConfigHash, 0, len(drs)+1)
   267  	if b.destinationRule != nil {
   268  		for _, dr := range drs {
   269  			configs = append(configs, model.ConfigKey{
   270  				Kind: kind.DestinationRule,
   271  				Name: dr.Name, Namespace: dr.Namespace,
   272  			}.HashCode())
   273  		}
   274  	}
   275  	if b.service != nil {
   276  		configs = append(configs, model.ConfigKey{
   277  			Kind: kind.ServiceEntry,
   278  			Name: string(b.service.Hostname), Namespace: b.service.Attributes.Namespace,
   279  		}.HashCode())
   280  	}
   281  
   282  	// For now, this matches clusterCache's DependentConfigs. If adding anything here, we may need to add them there.
   283  
   284  	return configs
   285  }
   286  
   287  type LocalityEndpoints struct {
   288  	istioEndpoints []*model.IstioEndpoint
   289  	// The protobuf message which contains LbEndpoint slice.
   290  	llbEndpoints endpoint.LocalityLbEndpoints
   291  }
   292  
   293  func (e *LocalityEndpoints) append(ep *model.IstioEndpoint, le *endpoint.LbEndpoint) {
   294  	e.istioEndpoints = append(e.istioEndpoints, ep)
   295  	e.llbEndpoints.LbEndpoints = append(e.llbEndpoints.LbEndpoints, le)
   296  }
   297  
   298  func (e *LocalityEndpoints) refreshWeight() {
   299  	var weight *wrapperspb.UInt32Value
   300  	if len(e.llbEndpoints.LbEndpoints) == 0 {
   301  		weight = nil
   302  	} else {
   303  		weight = &wrapperspb.UInt32Value{}
   304  		for _, lbEp := range e.llbEndpoints.LbEndpoints {
   305  			weight.Value += lbEp.GetLoadBalancingWeight().Value
   306  		}
   307  	}
   308  	e.llbEndpoints.LoadBalancingWeight = weight
   309  }
   310  
   311  func (e *LocalityEndpoints) AssertInvarianceInTest() {
   312  	if len(e.llbEndpoints.LbEndpoints) != len(e.istioEndpoints) {
   313  		panic(" len(e.llbEndpoints.LbEndpoints) != len(e.tunnelMetadata)")
   314  	}
   315  }
   316  
   317  // FromServiceEndpoints builds LocalityLbEndpoints from the PushContext's snapshotted ServiceIndex.
   318  // Used for CDS (ClusterLoadAssignment constructed elsewhere).
   319  func (b *EndpointBuilder) FromServiceEndpoints() []*endpoint.LocalityLbEndpoints {
   320  	if b == nil {
   321  		return nil
   322  	}
   323  	svcEps := b.push.ServiceEndpointsByPort(b.service, b.port, b.subsetLabels)
   324  	// don't use the pre-computed endpoints for CDS to preserve previous behavior
   325  	return ExtractEnvoyEndpoints(b.generate(svcEps))
   326  }
   327  
   328  // BuildClusterLoadAssignment converts the shards for this EndpointBuilder's Service
   329  // into a ClusterLoadAssignment. Used for EDS.
   330  func (b *EndpointBuilder) BuildClusterLoadAssignment(endpointIndex *model.EndpointIndex) *endpoint.ClusterLoadAssignment {
   331  	svcPort := b.servicePort(b.port)
   332  	if svcPort == nil {
   333  		return buildEmptyClusterLoadAssignment(b.clusterName)
   334  	}
   335  	svcEps := b.snapshotShards(endpointIndex)
   336  	svcEps = slices.FilterInPlace(svcEps, func(ep *model.IstioEndpoint) bool {
   337  		// filter out endpoints that don't match the service port
   338  		if svcPort.Name != ep.ServicePortName {
   339  			return false
   340  		}
   341  		// filter out endpoint that has invalid ip address, mostly domain name. Because this is generated from ServiceEntry.
   342  		// There are other two cases that should not be filtered out:
   343  		// 1. ep.Address can be empty since https://github.com/istio/istio/pull/45150, in this case we will replace it with gateway ip.
   344  		// 2. ep.Address can be uds when EndpointPort = 0
   345  		if ep.Address != "" && ep.EndpointPort != 0 && !netutil.IsValidIPAddress(ep.Address) {
   346  			return false
   347  		}
   348  		// filter out endpoints that don't match the subset
   349  		if !b.subsetLabels.SubsetOf(ep.Labels) {
   350  			return false
   351  		}
   352  		return true
   353  	})
   354  
   355  	localityLbEndpoints := b.generate(svcEps)
   356  	if len(localityLbEndpoints) == 0 {
   357  		return buildEmptyClusterLoadAssignment(b.clusterName)
   358  	}
   359  
   360  	l := b.createClusterLoadAssignment(localityLbEndpoints)
   361  
   362  	// If locality aware routing is enabled, prioritize endpoints or set their lb weight.
   363  	// Failover should only be enabled when there is an outlier detection, otherwise Envoy
   364  	// will never detect the hosts are unhealthy and redirect traffic.
   365  	enableFailover, lb := getOutlierDetectionAndLoadBalancerSettings(b.DestinationRule(), b.port, b.subsetName)
   366  	lbSetting := loadbalancer.GetLocalityLbSetting(b.push.Mesh.GetLocalityLbSetting(), lb.GetLocalityLbSetting())
   367  	if lbSetting != nil {
   368  		// Make a shallow copy of the cla as we are mutating the endpoints with priorities/weights relative to the calling proxy
   369  		l = util.CloneClusterLoadAssignment(l)
   370  		wrappedLocalityLbEndpoints := make([]*loadbalancer.WrappedLocalityLbEndpoints, len(localityLbEndpoints))
   371  		for i := range localityLbEndpoints {
   372  			wrappedLocalityLbEndpoints[i] = &loadbalancer.WrappedLocalityLbEndpoints{
   373  				IstioEndpoints:      localityLbEndpoints[i].istioEndpoints,
   374  				LocalityLbEndpoints: l.Endpoints[i],
   375  			}
   376  		}
   377  		loadbalancer.ApplyLocalityLoadBalancer(l, wrappedLocalityLbEndpoints, b.locality, b.proxy.Labels, lbSetting, enableFailover)
   378  	}
   379  	return l
   380  }
   381  
   382  // generate endpoints with applies weights, multi-network mapping and other filtering
   383  func (b *EndpointBuilder) generate(eps []*model.IstioEndpoint) []*LocalityEndpoints {
   384  	// shouldn't happen here
   385  	if !b.ServiceFound() {
   386  		return nil
   387  	}
   388  
   389  	eps = slices.Filter(eps, func(ep *model.IstioEndpoint) bool {
   390  		return b.filterIstioEndpoint(ep)
   391  	})
   392  
   393  	localityEpMap := make(map[string]*LocalityEndpoints)
   394  	for _, ep := range eps {
   395  		mtlsEnabled := b.mtlsChecker.checkMtlsEnabled(ep, b.proxy.IsWaypointProxy())
   396  		eep := buildEnvoyLbEndpoint(b, ep, mtlsEnabled)
   397  		if eep == nil {
   398  			continue
   399  		}
   400  		locLbEps, found := localityEpMap[ep.Locality.Label]
   401  		if !found {
   402  			locLbEps = &LocalityEndpoints{
   403  				llbEndpoints: endpoint.LocalityLbEndpoints{
   404  					Locality:    util.ConvertLocality(ep.Locality.Label),
   405  					LbEndpoints: make([]*endpoint.LbEndpoint, 0, len(eps)),
   406  				},
   407  			}
   408  			localityEpMap[ep.Locality.Label] = locLbEps
   409  		}
   410  		locLbEps.append(ep, eep)
   411  	}
   412  
   413  	locEps := make([]*LocalityEndpoints, 0, len(localityEpMap))
   414  	locs := make([]string, 0, len(localityEpMap))
   415  	for k := range localityEpMap {
   416  		locs = append(locs, k)
   417  	}
   418  	if len(locs) >= 2 {
   419  		sort.Strings(locs)
   420  	}
   421  	for _, locality := range locs {
   422  		locLbEps := localityEpMap[locality]
   423  		var weight uint32
   424  		var overflowStatus bool
   425  		for _, ep := range locLbEps.llbEndpoints.LbEndpoints {
   426  			weight, overflowStatus = addUint32(weight, ep.LoadBalancingWeight.GetValue())
   427  		}
   428  		locLbEps.llbEndpoints.LoadBalancingWeight = &wrapperspb.UInt32Value{
   429  			Value: weight,
   430  		}
   431  		if overflowStatus {
   432  			log.Warnf("Sum of localityLbEndpoints weight is overflow: service:%s, port: %d, locality:%s",
   433  				b.service.Hostname, b.port, locality)
   434  		}
   435  		locEps = append(locEps, locLbEps)
   436  	}
   437  
   438  	if len(locEps) == 0 {
   439  		b.push.AddMetric(model.ProxyStatusClusterNoInstances, b.clusterName, "", "")
   440  	}
   441  
   442  	// Apply the Split Horizon EDS filter, if applicable.
   443  	locEps = b.EndpointsByNetworkFilter(locEps)
   444  
   445  	if model.IsDNSSrvSubsetKey(b.clusterName) {
   446  		// For the SNI-DNAT clusters, we are using AUTO_PASSTHROUGH gateway. AUTO_PASSTHROUGH is intended
   447  		// to passthrough mTLS requests. However, at the gateway we do not actually have any way to tell if the
   448  		// request is a valid mTLS request or not, since its passthrough TLS.
   449  		// To ensure we allow traffic only to mTLS endpoints, we filter out non-mTLS endpoints for these cluster types.
   450  		locEps = b.EndpointsWithMTLSFilter(locEps)
   451  	}
   452  
   453  	return locEps
   454  }
   455  
   456  // addUint32AvoidOverflow returns sum of two uint32 and status. If sum overflows,
   457  // and returns MaxUint32 and status.
   458  func addUint32(left, right uint32) (uint32, bool) {
   459  	if math.MaxUint32-right < left {
   460  		return math.MaxUint32, true
   461  	}
   462  	return left + right, false
   463  }
   464  
   465  func (b *EndpointBuilder) filterIstioEndpoint(ep *model.IstioEndpoint) bool {
   466  	// for ServiceInternalTrafficPolicy
   467  	if b.service.Attributes.NodeLocal && ep.NodeName != b.proxy.GetNodeName() {
   468  		return false
   469  	}
   470  	// Only send endpoints from the networks in the network view requested by the proxy.
   471  	// The default network view assigned to the Proxy is nil, in that case match any network.
   472  	if !b.proxyView.IsVisible(ep) {
   473  		// Endpoint's network doesn't match the set of networks that the proxy wants to see.
   474  		return false
   475  	}
   476  	// If the downstream service is configured as cluster-local, only include endpoints that
   477  	// reside in the same cluster.
   478  	if b.clusterLocal && (b.clusterID != ep.Locality.ClusterID) {
   479  		return false
   480  	}
   481  	// TODO(nmittler): Consider merging discoverability policy with cluster-local
   482  	if !ep.IsDiscoverableFromProxy(b.proxy) {
   483  		return false
   484  	}
   485  	// If we don't know the address we must eventually use a gateway address
   486  	if ep.Address == "" && (!b.gateways().IsMultiNetworkEnabled() || b.proxy.InNetwork(ep.Network)) {
   487  		return false
   488  	}
   489  	// Filter out unhealthy endpoints
   490  	if !features.SendUnhealthyEndpoints.Load() && ep.HealthStatus == model.UnHealthy {
   491  		return false
   492  	}
   493  	// Draining endpoints are only sent to 'persistent session' clusters.
   494  	draining := ep.HealthStatus == model.Draining ||
   495  		features.DrainingLabel != "" && ep.Labels[features.DrainingLabel] != ""
   496  	if draining {
   497  		persistentSession := b.service.Attributes.Labels[features.PersistentSessionLabel] != ""
   498  		if !persistentSession {
   499  			return false
   500  		}
   501  	}
   502  	return true
   503  }
   504  
   505  // snapshotShards into a local slice to avoid lock contention
   506  func (b *EndpointBuilder) snapshotShards(endpointIndex *model.EndpointIndex) []*model.IstioEndpoint {
   507  	shards := b.findShards(endpointIndex)
   508  	if shards == nil {
   509  		return nil
   510  	}
   511  
   512  	// Determine whether or not the target service is considered local to the cluster
   513  	// and should, therefore, not be accessed from outside the cluster.
   514  	isClusterLocal := b.clusterLocal
   515  	var eps []*model.IstioEndpoint
   516  	shards.RLock()
   517  	defer shards.RUnlock()
   518  	// Extract shard keys so we can iterate in order. This ensures a stable EDS output.
   519  	keys := shards.Keys()
   520  	// The shards are updated independently, now need to filter and merge for this cluster
   521  	for _, shardKey := range keys {
   522  		if shardKey.Cluster != b.clusterID {
   523  			// If the downstream service is configured as cluster-local, only include endpoints that
   524  			// reside in the same cluster.
   525  			if isClusterLocal || b.service.Attributes.NodeLocal {
   526  				continue
   527  			}
   528  		}
   529  		eps = append(eps, shards.Shards[shardKey]...)
   530  	}
   531  	return eps
   532  }
   533  
   534  // findShards returns the endpoints for a cluster
   535  func (b *EndpointBuilder) findShards(endpointIndex *model.EndpointIndex) *model.EndpointShards {
   536  	if b.service == nil {
   537  		log.Debugf("can not find the service for cluster %s", b.clusterName)
   538  		return nil
   539  	}
   540  
   541  	// Service resolution type might have changed and Cluster may be still in the EDS cluster list of "Connection.Clusters".
   542  	// This can happen if a ServiceEntry's resolution is changed from STATIC to DNS which changes the Envoy cluster type from
   543  	// EDS to STRICT_DNS or LOGICAL_DNS. When pushEds is called before Envoy sends the updated cluster list via Endpoint request which in turn
   544  	// will update "Connection.Clusters", we might accidentally send EDS updates for STRICT_DNS cluster. This check guards
   545  	// against such behavior and returns nil. When the updated cluster warms up in Envoy, it would update with new endpoints
   546  	// automatically.
   547  	// Gateways use EDS for Passthrough cluster. So we should allow Passthrough here.
   548  	if b.IsDNSCluster() {
   549  		log.Infof("cluster %s in eds cluster, but its resolution now is updated to %v, skipping it.", b.clusterName, b.service.Resolution)
   550  		return nil
   551  	}
   552  
   553  	epShards, f := endpointIndex.ShardsForService(string(b.hostname), b.service.Attributes.Namespace)
   554  	if !f {
   555  		// Shouldn't happen here
   556  		log.Debugf("can not find the endpointShards for cluster %s", b.clusterName)
   557  		return nil
   558  	}
   559  	return epShards
   560  }
   561  
   562  // Create the CLusterLoadAssignment. At this moment the options must have been applied to the locality lb endpoints.
   563  func (b *EndpointBuilder) createClusterLoadAssignment(llbOpts []*LocalityEndpoints) *endpoint.ClusterLoadAssignment {
   564  	llbEndpoints := make([]*endpoint.LocalityLbEndpoints, 0, len(llbOpts))
   565  	for _, l := range llbOpts {
   566  		llbEndpoints = append(llbEndpoints, &l.llbEndpoints)
   567  	}
   568  	return &endpoint.ClusterLoadAssignment{
   569  		ClusterName: b.clusterName,
   570  		Endpoints:   llbEndpoints,
   571  	}
   572  }
   573  
   574  // cluster with no endpoints
   575  func buildEmptyClusterLoadAssignment(clusterName string) *endpoint.ClusterLoadAssignment {
   576  	return &endpoint.ClusterLoadAssignment{
   577  		ClusterName: clusterName,
   578  	}
   579  }
   580  
   581  func (b *EndpointBuilder) gateways() *model.NetworkGateways {
   582  	if b.IsDNSCluster() {
   583  		return b.push.NetworkManager().Unresolved
   584  	}
   585  	return b.push.NetworkManager().NetworkGateways
   586  }
   587  
   588  func ExtractEnvoyEndpoints(locEps []*LocalityEndpoints) []*endpoint.LocalityLbEndpoints {
   589  	var locLbEps []*endpoint.LocalityLbEndpoints
   590  	for _, eps := range locEps {
   591  		locLbEps = append(locLbEps, &eps.llbEndpoints)
   592  	}
   593  	return locLbEps
   594  }
   595  
   596  // buildEnvoyLbEndpoint packs the endpoint based on istio info.
   597  func buildEnvoyLbEndpoint(b *EndpointBuilder, e *model.IstioEndpoint, mtlsEnabled bool) *endpoint.LbEndpoint {
   598  	addr := util.BuildAddress(e.Address, e.EndpointPort)
   599  	healthStatus := e.HealthStatus
   600  	if features.DrainingLabel != "" && e.Labels[features.DrainingLabel] != "" {
   601  		healthStatus = model.Draining
   602  	}
   603  
   604  	ep := &endpoint.LbEndpoint{
   605  		HealthStatus: corev3.HealthStatus(healthStatus),
   606  		LoadBalancingWeight: &wrapperspb.UInt32Value{
   607  			Value: e.GetLoadBalancingWeight(),
   608  		},
   609  		HostIdentifier: &endpoint.LbEndpoint_Endpoint{
   610  			Endpoint: &endpoint.Endpoint{
   611  				Address: addr,
   612  			},
   613  		},
   614  		Metadata: &corev3.Metadata{},
   615  	}
   616  
   617  	// Istio telemetry depends on the metadata value being set for endpoints in the mesh.
   618  	// Istio endpoint level tls transport socket configuration depends on this logic
   619  	// Do not remove
   620  	var meta *model.EndpointMetadata
   621  	if features.CanonicalServiceForMeshExternalServiceEntry && b.service.MeshExternal {
   622  		svcLabels := b.service.Attributes.Labels
   623  		if _, ok := svcLabels[model.IstioCanonicalServiceLabelName]; ok {
   624  			meta = e.MetadataClone()
   625  			if meta.Labels == nil {
   626  				meta.Labels = make(map[string]string)
   627  			}
   628  			meta.Labels[model.IstioCanonicalServiceLabelName] = svcLabels[model.IstioCanonicalServiceLabelName]
   629  			meta.Labels[model.IstioCanonicalServiceRevisionLabelName] = svcLabels[model.IstioCanonicalServiceRevisionLabelName]
   630  		} else {
   631  			meta = e.Metadata()
   632  		}
   633  		meta.Namespace = b.service.Attributes.Namespace
   634  	} else {
   635  		meta = e.Metadata()
   636  	}
   637  
   638  	// detect if mTLS is possible for this endpoint, used later during ep filtering
   639  	// this must be done while converting IstioEndpoints because we still have workload labels
   640  	if !mtlsEnabled {
   641  		meta.TLSMode = ""
   642  	}
   643  	util.AppendLbEndpointMetadata(meta, ep.Metadata)
   644  
   645  	tunnel := supportTunnel(b, e)
   646  	if mtlsEnabled && !features.PreferHBONESend {
   647  		tunnel = false
   648  	}
   649  	if b.proxy.Metadata.DisableHBONESend {
   650  		tunnel = false
   651  	}
   652  	if tunnel {
   653  		address, port := e.Address, int(e.EndpointPort)
   654  		// We intentionally do not take into account waypoints here.
   655  		// 1. Workload waypoints: sidecar/ingress do not support sending traffic directly to workloads, only to services,
   656  		//    so these are not applicable.
   657  		// 2. Service waypoints: in ztunnel, we would defer handling service traffic if the service has a waypoint, and instead
   658  		//    send to the waypoint. However, with sidecars this is problematic. We don't know which service is the intended destination
   659  		//    until *after* we apply policies. If we then sent to a service waypoint, we apply service policies twice.
   660  		//    This can be problematic: double mirroring, fault injection, request manipulation, ....
   661  		//    Instead, we consider this to workload traffic. This gives the same behavior as if we were an application doing internal load balancing
   662  		//    with ztunnel.
   663  		//    Note: there is a pretty valid case for wanting to send to the service from ingress. This gives a two tier delegation.
   664  		//    However, it's not safe to do that by default; perhaps a future API could opt into this.
   665  		// Support connecting to server side waypoint proxy, if the destination has one. This is for sidecars and ingress.
   666  		// Setup tunnel metadata so requests will go through the tunnel
   667  		ep.HostIdentifier = &endpoint.LbEndpoint_Endpoint{Endpoint: &endpoint.Endpoint{
   668  			Address: util.BuildInternalAddressWithIdentifier(connectOriginate, net.JoinHostPort(address, strconv.Itoa(port))),
   669  		}}
   670  		ep.Metadata.FilterMetadata[util.OriginalDstMetadataKey] = util.BuildTunnelMetadataStruct(address, port)
   671  		if b.dir != model.TrafficDirectionInboundVIP {
   672  			// Add TLS metadata matcher to indicate we can use HBONE for this endpoint.
   673  			// We skip this for service waypoint, which doesn't need to dynamically match mTLS vs HBONE.
   674  			ep.Metadata.FilterMetadata[util.EnvoyTransportSocketMetadataKey] = &structpb.Struct{
   675  				Fields: map[string]*structpb.Value{
   676  					model.TunnelLabelShortName: {Kind: &structpb.Value_StringValue{StringValue: model.TunnelHTTP}},
   677  				},
   678  			}
   679  		}
   680  	}
   681  
   682  	return ep
   683  }
   684  
   685  func supportTunnel(b *EndpointBuilder, e *model.IstioEndpoint) bool {
   686  	if b.proxy.IsProxylessGrpc() {
   687  		// Proxyless client cannot handle tunneling, even if the server can
   688  		return false
   689  	}
   690  
   691  	// Other side is a waypoint proxy.
   692  	if al := e.Labels[constants.ManagedGatewayLabel]; al == constants.ManagedGatewayMeshControllerLabel {
   693  		return true
   694  	}
   695  
   696  	// Otherwise has ambient enabled. Note: this is a synthetic label, not existing in the real Pod.
   697  	if b.push.SupportsTunnel(e.Network, e.Address) {
   698  		return true
   699  	}
   700  	// Otherwise supports tunnel
   701  	// Currently we only support HTTP tunnel, so just check for that. If we support more, we will
   702  	// need to pick the right one based on our support overlap.
   703  	if e.SupportsTunnel(model.TunnelHTTP) {
   704  		return true
   705  	}
   706  
   707  	return false
   708  }
   709  
   710  func getOutlierDetectionAndLoadBalancerSettings(
   711  	destinationRule *v1alpha3.DestinationRule,
   712  	portNumber int,
   713  	subsetName string,
   714  ) (bool, *v1alpha3.LoadBalancerSettings) {
   715  	if destinationRule == nil {
   716  		return false, nil
   717  	}
   718  	outlierDetectionEnabled := false
   719  	var lbSettings *v1alpha3.LoadBalancerSettings
   720  
   721  	port := &model.Port{Port: portNumber}
   722  	policy := getSubsetTrafficPolicy(destinationRule, port, subsetName)
   723  	if policy != nil {
   724  		lbSettings = policy.LoadBalancer
   725  		if policy.OutlierDetection != nil {
   726  			outlierDetectionEnabled = true
   727  		}
   728  	}
   729  
   730  	return outlierDetectionEnabled, lbSettings
   731  }
   732  
   733  func getSubsetTrafficPolicy(destinationRule *v1alpha3.DestinationRule, port *model.Port, subsetName string) *v1alpha3.TrafficPolicy {
   734  	var subSetTrafficPolicy *v1alpha3.TrafficPolicy
   735  	for _, subset := range destinationRule.Subsets {
   736  		if subset.Name == subsetName {
   737  			subSetTrafficPolicy = subset.TrafficPolicy
   738  			break
   739  		}
   740  	}
   741  	return util.MergeSubsetTrafficPolicy(destinationRule.TrafficPolicy, subSetTrafficPolicy, port)
   742  }
   743  
   744  // getSubSetLabels returns the labels associated with a subset of a given service.
   745  func getSubSetLabels(dr *v1alpha3.DestinationRule, subsetName string) labels.Instance {
   746  	// empty subset
   747  	if subsetName == "" {
   748  		return nil
   749  	}
   750  
   751  	if dr == nil {
   752  		return nil
   753  	}
   754  
   755  	for _, subset := range dr.Subsets {
   756  		if subset.Name == subsetName {
   757  			if len(subset.Labels) == 0 {
   758  				return nil
   759  			}
   760  			return subset.Labels
   761  		}
   762  	}
   763  
   764  	return nil
   765  }