istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/service.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 // This file describes the abstract model of services (and their instances) as 16 // represented in Istio. This model is independent of the underlying platform 17 // (Kubernetes, Mesos, etc.). Platform specific adapters found populate the 18 // model object with various fields, from the metadata found in the platform. 19 // The platform independent proxy code uses the representation in the model to 20 // generate the configuration files for the Layer 7 proxy sidecar. The proxy 21 // code is specific to individual proxy implementations 22 23 package model 24 25 import ( 26 "fmt" 27 "net/netip" 28 "sort" 29 "strconv" 30 "strings" 31 "time" 32 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/go-cmp/cmp/cmpopts" 35 "github.com/mitchellh/copystructure" 36 "google.golang.org/protobuf/proto" 37 "k8s.io/apimachinery/pkg/types" 38 39 "istio.io/api/label" 40 "istio.io/istio/pilot/pkg/features" 41 "istio.io/istio/pilot/pkg/serviceregistry/provider" 42 "istio.io/istio/pkg/cluster" 43 "istio.io/istio/pkg/config/constants" 44 "istio.io/istio/pkg/config/host" 45 "istio.io/istio/pkg/config/labels" 46 "istio.io/istio/pkg/config/protocol" 47 "istio.io/istio/pkg/config/schema/kind" 48 "istio.io/istio/pkg/config/visibility" 49 "istio.io/istio/pkg/maps" 50 pm "istio.io/istio/pkg/model" 51 "istio.io/istio/pkg/network" 52 "istio.io/istio/pkg/slices" 53 "istio.io/istio/pkg/util/sets" 54 "istio.io/istio/pkg/workloadapi" 55 "istio.io/istio/pkg/workloadapi/security" 56 ) 57 58 // Service describes an Istio service (e.g., catalog.mystore.com:8080) 59 // Each service has a fully qualified domain name (FQDN) and one or more 60 // ports where the service is listening for connections. *Optionally*, a 61 // service can have a single load balancer/virtual IP address associated 62 // with it, such that the DNS queries for the FQDN resolves to the virtual 63 // IP address (a load balancer IP). 64 // 65 // E.g., in kubernetes, a service foo is associated with 66 // foo.default.svc.cluster.local hostname, has a virtual IP of 10.0.1.1 and 67 // listens on ports 80, 8080 68 type Service struct { 69 // Attributes contains additional attributes associated with the service 70 // used mostly by RBAC for policy enforcement purposes. 71 Attributes ServiceAttributes 72 73 // Ports is the set of network ports where the service is listening for 74 // connections 75 Ports PortList `json:"ports,omitempty"` 76 77 // ServiceAccounts specifies the service accounts that run the service. 78 ServiceAccounts []string `json:"serviceAccounts,omitempty"` 79 80 // CreationTime records the time this service was created, if available. 81 CreationTime time.Time `json:"creationTime,omitempty"` 82 83 // Name of the service, e.g. "catalog.mystore.com" 84 Hostname host.Name `json:"hostname"` 85 86 // ClusterVIPs specifies the service address of the load balancer 87 // in each of the clusters where the service resides 88 ClusterVIPs AddressMap `json:"clusterVIPs,omitempty"` 89 90 // DefaultAddress specifies the default service IP of the load balancer. 91 // Do not access directly. Use GetAddressForProxy 92 DefaultAddress string `json:"defaultAddress,omitempty"` 93 94 // AutoAllocatedIPv4Address and AutoAllocatedIPv6Address specifies 95 // the automatically allocated IPv4/IPv6 address out of the reserved 96 // Class E subnet (240.240.0.0/16) or reserved Benchmarking IP range 97 // (2001:2::/48) in RFC5180.for service entries with non-wildcard 98 // hostnames. The IPs assigned to services are not 99 // synchronized across istiod replicas as the DNS resolution 100 // for these service entries happens completely inside a pod 101 // whose proxy is managed by one istiod. That said, the algorithm 102 // to allocate IPs is pretty deterministic that at stable state, two 103 // istiods will allocate the exact same set of IPs for a given set of 104 // service entries. 105 AutoAllocatedIPv4Address string `json:"autoAllocatedIPv4Address,omitempty"` 106 AutoAllocatedIPv6Address string `json:"autoAllocatedIPv6Address,omitempty"` 107 108 // Resolution indicates how the service instances need to be resolved before routing 109 // traffic. Most services in the service registry will use static load balancing wherein 110 // the proxy will decide the service instance that will receive the traffic. Service entries 111 // could either use DNS load balancing (i.e. proxy will query DNS server for the IP of the service) 112 // or use the passthrough model (i.e. proxy will forward the traffic to the network endpoint requested 113 // by the caller) 114 Resolution Resolution 115 116 // MeshExternal (if true) indicates that the service is external to the mesh. 117 // These services are defined using Istio's ServiceEntry spec. 118 MeshExternal bool 119 120 // ResourceVersion represents the internal version of this object. 121 ResourceVersion string 122 } 123 124 func (s *Service) NamespacedName() types.NamespacedName { 125 return types.NamespacedName{Name: s.Attributes.Name, Namespace: s.Attributes.Namespace} 126 } 127 128 func (s *Service) Key() string { 129 if s == nil { 130 return "" 131 } 132 133 return s.Attributes.Namespace + "/" + string(s.Hostname) 134 } 135 136 var serviceCmpOpts = []cmp.Option{cmpopts.IgnoreFields(AddressMap{}, "mutex")} 137 138 func (s *Service) CmpOpts() []cmp.Option { 139 return serviceCmpOpts 140 } 141 142 // Resolution indicates how the service instances need to be resolved before routing traffic. 143 type Resolution int 144 145 const ( 146 // ClientSideLB implies that the proxy will decide the endpoint from its local lb pool 147 ClientSideLB Resolution = iota 148 // DNSLB implies that the proxy will resolve a DNS address and forward to the resolved address 149 DNSLB 150 // Passthrough implies that the proxy should forward traffic to the destination IP requested by the caller 151 Passthrough 152 // DNSRoundRobinLB implies that the proxy will resolve a DNS address and forward to the resolved address 153 DNSRoundRobinLB 154 // Alias defines a Service that is an alias for another. 155 Alias 156 ) 157 158 // String converts Resolution in to String. 159 func (resolution Resolution) String() string { 160 switch resolution { 161 case ClientSideLB: 162 return "ClientSide" 163 case DNSLB: 164 return "DNS" 165 case DNSRoundRobinLB: 166 return "DNSRoundRobin" 167 case Passthrough: 168 return "Passthrough" 169 default: 170 return fmt.Sprintf("%d", int(resolution)) 171 } 172 } 173 174 const ( 175 // LocalityLabel indicates the region/zone/subzone of an instance. It is used to override the native 176 // registry's value. 177 // 178 // Note: because k8s labels does not support `/`, so we use `.` instead in k8s. 179 LocalityLabel = pm.LocalityLabel 180 ) 181 182 const ( 183 // TunnelLabel defines the label workloads describe to indicate that they support tunneling. 184 // Values are expected to be a CSV list, sorted by preference, of protocols supported. 185 // Currently supported values: 186 // * "http": indicates tunneling over HTTP over TCP. HTTP/2 vs HTTP/1.1 may be supported by ALPN negotiation. 187 // Planned future values: 188 // * "http3": indicates tunneling over HTTP over QUIC. This is distinct from "http", since we cannot do ALPN 189 // negotiation for QUIC vs TCP. 190 // Users should appropriately parse the full list rather than doing a string literal check to 191 // ensure future-proofing against new protocols being added. 192 TunnelLabel = "networking.istio.io/tunnel" 193 // TunnelLabelShortName is a short name for TunnelLabel to be used in optimized scenarios. 194 TunnelLabelShortName = "tunnel" 195 // TunnelHTTP indicates tunneling over HTTP over TCP. HTTP/2 vs HTTP/1.1 may be supported by ALPN 196 // negotiation. Note: ALPN negotiation is not currently implemented; HTTP/2 will always be used. 197 // This is future-proofed, however, because only the `h2` ALPN is exposed. 198 TunnelHTTP = "http" 199 ) 200 201 const ( 202 // TLSModeLabelShortname name used for determining endpoint level tls transport socket configuration 203 TLSModeLabelShortname = "tlsMode" 204 205 // DisabledTLSModeLabel implies that this endpoint should receive traffic as is (mostly plaintext) 206 DisabledTLSModeLabel = "disabled" 207 208 // IstioMutualTLSModeLabel implies that the endpoint is ready to receive Istio mTLS connections. 209 IstioMutualTLSModeLabel = "istio" 210 211 // IstioCanonicalServiceLabelName is the name of label for the Istio Canonical Service for a workload instance. 212 IstioCanonicalServiceLabelName = pm.IstioCanonicalServiceLabelName 213 214 // IstioCanonicalServiceRevisionLabelName is the name of label for the Istio Canonical Service revision for a workload instance. 215 IstioCanonicalServiceRevisionLabelName = pm.IstioCanonicalServiceRevisionLabelName 216 ) 217 218 func SupportsTunnel(labels map[string]string, tunnelType string) bool { 219 return sets.New(strings.Split(labels[TunnelLabel], ",")...).Contains(tunnelType) 220 } 221 222 // Port represents a network port where a service is listening for 223 // connections. The port should be annotated with the type of protocol 224 // used by the port. 225 type Port struct { 226 // Name ascribes a human readable name for the port object. When a 227 // service has multiple ports, the name field is mandatory 228 Name string `json:"name,omitempty"` 229 230 // Port number where the service can be reached. Does not necessarily 231 // map to the corresponding port numbers for the instances behind the 232 // service. 233 Port int `json:"port"` 234 235 // Protocol to be used for the port. 236 Protocol protocol.Instance `json:"protocol,omitempty"` 237 } 238 239 func (p Port) String() string { 240 return fmt.Sprintf("Name:%s Port:%d Protocol:%v", p.Name, p.Port, p.Protocol) 241 } 242 243 // PortList is a set of ports 244 type PortList []*Port 245 246 // TrafficDirection defines whether traffic exists a service instance or enters a service instance 247 type TrafficDirection string 248 249 const ( 250 // TrafficDirectionInbound indicates inbound traffic 251 TrafficDirectionInbound TrafficDirection = "inbound" 252 // TrafficDirectionInboundVIP indicates inbound traffic for vip 253 TrafficDirectionInboundVIP TrafficDirection = "inbound-vip" 254 // TrafficDirectionOutbound indicates outbound traffic 255 TrafficDirectionOutbound TrafficDirection = "outbound" 256 257 // trafficDirectionOutboundSrvPrefix the prefix for a DNS SRV type subset key 258 trafficDirectionOutboundSrvPrefix = string(TrafficDirectionOutbound) + "_" 259 // trafficDirectionInboundSrvPrefix the prefix for a DNS SRV type subset key 260 trafficDirectionInboundSrvPrefix = string(TrafficDirectionInbound) + "_" 261 ) 262 263 // ServiceInstance represents an individual instance of a specific version 264 // of a service. It binds a network endpoint (ip:port), the service 265 // description (which is oblivious to various versions) and a set of labels 266 // that describe the service version associated with this instance. 267 // 268 // Since a ServiceInstance has a single IstioEndpoint, which has a single port, 269 // multiple ServiceInstances are required to represent a workload that listens 270 // on multiple ports. 271 // 272 // The labels associated with a service instance are unique per a network endpoint. 273 // There is one well defined set of labels for each service instance network endpoint. 274 // 275 // For example, the set of service instances associated with catalog.mystore.com 276 // are modeled like this 277 // 278 // --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar) 279 // --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar) 280 // --> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat) 281 // --> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat) 282 type ServiceInstance struct { 283 Service *Service `json:"service,omitempty"` 284 ServicePort *Port `json:"servicePort,omitempty"` 285 Endpoint *IstioEndpoint `json:"endpoint,omitempty"` 286 } 287 288 func (instance *ServiceInstance) CmpOpts() []cmp.Option { 289 res := []cmp.Option{} 290 res = append(res, istioEndpointCmpOpts...) 291 res = append(res, serviceCmpOpts...) 292 return res 293 } 294 295 // ServiceTarget includes a Service object, along with a specific service port 296 // and target port. This is basically a smaller version of ServiceInstance, 297 // intended to avoid the need to have the full object when only port information 298 // is needed. 299 type ServiceTarget struct { 300 Service *Service 301 Port ServiceInstancePort 302 } 303 304 func (st ServiceTarget) NamespacedName() types.NamespacedName { 305 return st.Service.NamespacedName() 306 } 307 308 type ( 309 ServicePort = *Port 310 // ServiceInstancePort defines a port that has both a port and targetPort (which distinguishes it from model.Port) 311 // Note: ServiceInstancePort only makes sense in the context of a specific ServiceInstance, because TargetPort depends on a specific instance. 312 ServiceInstancePort struct { 313 ServicePort 314 TargetPort uint32 315 } 316 ) 317 318 func ServiceInstanceToTarget(e *ServiceInstance) ServiceTarget { 319 return ServiceTarget{ 320 Service: e.Service, 321 Port: ServiceInstancePort{ 322 ServicePort: e.ServicePort, 323 TargetPort: e.Endpoint.EndpointPort, 324 }, 325 } 326 } 327 328 // DeepCopy creates a copy of ServiceInstance. 329 func (instance *ServiceInstance) DeepCopy() *ServiceInstance { 330 return &ServiceInstance{ 331 Service: instance.Service.DeepCopy(), 332 Endpoint: instance.Endpoint.DeepCopy(), 333 ServicePort: &Port{ 334 Name: instance.ServicePort.Name, 335 Port: instance.ServicePort.Port, 336 Protocol: instance.ServicePort.Protocol, 337 }, 338 } 339 } 340 341 type workloadKind int 342 343 const ( 344 // PodKind indicates the workload is from pod 345 PodKind workloadKind = iota 346 // WorkloadEntryKind indicates the workload is from workloadentry 347 WorkloadEntryKind 348 ) 349 350 func (k workloadKind) String() string { 351 if k == PodKind { 352 return "Pod" 353 } 354 355 if k == WorkloadEntryKind { 356 return "WorkloadEntry" 357 } 358 return "" 359 } 360 361 type WorkloadInstance struct { 362 Name string `json:"name,omitempty"` 363 Namespace string `json:"namespace,omitempty"` 364 // Where the workloadInstance come from, valid values are`Pod` or `WorkloadEntry` 365 Kind workloadKind `json:"kind"` 366 Endpoint *IstioEndpoint `json:"endpoint,omitempty"` 367 PortMap map[string]uint32 `json:"portMap,omitempty"` 368 // Can only be selected by service entry of DNS type. 369 DNSServiceEntryOnly bool `json:"dnsServiceEntryOnly,omitempty"` 370 } 371 372 func (instance *WorkloadInstance) CmpOpts() []cmp.Option { 373 return istioEndpointCmpOpts 374 } 375 376 // DeepCopy creates a copy of WorkloadInstance. 377 func (instance *WorkloadInstance) DeepCopy() *WorkloadInstance { 378 pmap := map[string]uint32{} 379 for k, v := range instance.PortMap { 380 pmap[k] = v 381 } 382 return &WorkloadInstance{ 383 Name: instance.Name, 384 Namespace: instance.Namespace, 385 Kind: instance.Kind, 386 PortMap: pmap, 387 Endpoint: instance.Endpoint.DeepCopy(), 388 } 389 } 390 391 // WorkloadInstancesEqual is a custom comparison of workload instances based on the fields that we need. 392 // Returns true if equal, false otherwise. 393 func WorkloadInstancesEqual(first, second *WorkloadInstance) bool { 394 if first.Endpoint == nil || second.Endpoint == nil { 395 return first.Endpoint == second.Endpoint 396 } 397 if first.Endpoint.Address != second.Endpoint.Address { 398 return false 399 } 400 if first.Endpoint.Network != second.Endpoint.Network { 401 return false 402 } 403 if first.Endpoint.TLSMode != second.Endpoint.TLSMode { 404 return false 405 } 406 if !first.Endpoint.Labels.Equals(second.Endpoint.Labels) { 407 return false 408 } 409 if first.Endpoint.ServiceAccount != second.Endpoint.ServiceAccount { 410 return false 411 } 412 if first.Endpoint.Locality != second.Endpoint.Locality { 413 return false 414 } 415 if first.Endpoint.GetLoadBalancingWeight() != second.Endpoint.GetLoadBalancingWeight() { 416 return false 417 } 418 if first.Namespace != second.Namespace { 419 return false 420 } 421 if first.Name != second.Name { 422 return false 423 } 424 if first.Kind != second.Kind { 425 return false 426 } 427 if !maps.Equal(first.PortMap, second.PortMap) { 428 return false 429 } 430 return true 431 } 432 433 // GetLocalityLabel returns the locality from the supplied label. Because Kubernetes 434 // labels don't support `/`, we replace "." with "/" in the supplied label as a workaround. 435 func GetLocalityLabel(label string) string { 436 return pm.GetLocalityLabel(label) 437 } 438 439 // Locality information for an IstioEndpoint 440 type Locality struct { 441 // Label for locality on the endpoint. This is a "/" separated string. 442 Label string 443 444 // ClusterID where the endpoint is located 445 ClusterID cluster.ID 446 } 447 448 // Endpoint health status. 449 type HealthStatus int32 450 451 const ( 452 // Healthy. 453 Healthy HealthStatus = 1 454 // Unhealthy. 455 UnHealthy HealthStatus = 2 456 // Draining - the constant matches envoy 457 Draining HealthStatus = 3 458 ) 459 460 // IstioEndpoint defines a network address (IP:port) associated with an instance of the 461 // service. A service has one or more instances each running in a 462 // container/VM/pod. If a service has multiple ports, then the same 463 // instance IP is expected to be listening on multiple ports (one per each 464 // service port). Note that the port associated with an instance does not 465 // have to be the same as the port associated with the service. Depending 466 // on the network setup (NAT, overlays), this could vary. 467 // 468 // For e.g., if catalog.mystore.com is accessible through port 80 and 8080, 469 // and it maps to an instance with IP 172.16.0.1, such that connections to 470 // port 80 are forwarded to port 55446, and connections to port 8080 are 471 // forwarded to port 33333, 472 // 473 // then internally, we have two endpoint structs for the 474 // service catalog.mystore.com 475 // 476 // --> 172.16.0.1:55446 (with ServicePort pointing to 80) and 477 // --> 172.16.0.1:33333 (with ServicePort pointing to 8080) 478 // 479 // TODO: Investigate removing ServiceInstance entirely. 480 type IstioEndpoint struct { 481 // Labels points to the workload or deployment labels. 482 Labels labels.Instance 483 484 // Address is the address of the endpoint, using envoy proto. 485 Address string 486 487 // ServicePortName tracks the name of the port, this is used to select the IstioEndpoint by service port. 488 ServicePortName string 489 // LegacyClusterPortKey provides an alternative key from ServicePortName to support legacy quirks in the API. 490 // Basically, EDS merges by port name, but CDS historically ignored port name and matched on number. 491 // Note that for Kubernetes Service, this is identical - its only ServiceEntry where these checks can differ 492 LegacyClusterPortKey int 493 494 // ServiceAccount holds the associated service account. 495 ServiceAccount string 496 497 // Network holds the network where this endpoint is present 498 Network network.ID 499 500 // The locality where the endpoint is present. 501 Locality Locality 502 503 // EndpointPort is the port where the workload is listening, can be different 504 // from the service port. 505 EndpointPort uint32 506 507 // The load balancing weight associated with this endpoint. 508 LbWeight uint32 509 510 // TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS 511 TLSMode string 512 513 // Namespace that this endpoint belongs to. This is for telemetry purpose. 514 Namespace string 515 516 // Name of the workload that this endpoint belongs to. This is for telemetry purpose. 517 WorkloadName string 518 519 // Specifies the hostname of the Pod, empty for vm workload. 520 HostName string 521 522 // If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>". 523 SubDomain string 524 525 // Determines the discoverability of this endpoint throughout the mesh. 526 DiscoverabilityPolicy EndpointDiscoverabilityPolicy `json:"-"` 527 528 // Indicates the endpoint health status. 529 HealthStatus HealthStatus 530 531 // If in k8s, the node where the pod resides 532 NodeName string 533 } 534 535 func (ep *IstioEndpoint) SupportsTunnel(tunnelType string) bool { 536 return SupportsTunnel(ep.Labels, tunnelType) 537 } 538 539 // GetLoadBalancingWeight returns the weight for this endpoint, normalized to always be > 0. 540 func (ep *IstioEndpoint) GetLoadBalancingWeight() uint32 { 541 if ep.LbWeight > 0 { 542 return ep.LbWeight 543 } 544 return 1 545 } 546 547 // IsDiscoverableFromProxy indicates whether this endpoint is discoverable from the given Proxy. 548 func (ep *IstioEndpoint) IsDiscoverableFromProxy(p *Proxy) bool { 549 if ep == nil || ep.DiscoverabilityPolicy == nil { 550 // If no policy was assigned, default to discoverable mesh-wide. 551 // TODO(nmittler): Will need to re-think this default when cluster.local is actually cluster-local. 552 return true 553 } 554 return ep.DiscoverabilityPolicy.IsDiscoverableFromProxy(ep, p) 555 } 556 557 // MetadataClone returns the cloned endpoint metadata used for telemetry purposes. 558 // This should be used when the endpoint labels should be updated. 559 func (ep *IstioEndpoint) MetadataClone() *EndpointMetadata { 560 return &EndpointMetadata{ 561 Network: ep.Network, 562 TLSMode: ep.TLSMode, 563 WorkloadName: ep.WorkloadName, 564 Namespace: ep.Namespace, 565 Labels: maps.Clone(ep.Labels), 566 ClusterID: ep.Locality.ClusterID, 567 } 568 } 569 570 // Metadata returns the endpoint metadata used for telemetry purposes. 571 func (ep *IstioEndpoint) Metadata() *EndpointMetadata { 572 return &EndpointMetadata{ 573 Network: ep.Network, 574 TLSMode: ep.TLSMode, 575 WorkloadName: ep.WorkloadName, 576 Namespace: ep.Namespace, 577 Labels: ep.Labels, 578 ClusterID: ep.Locality.ClusterID, 579 } 580 } 581 582 var istioEndpointCmpOpts = []cmp.Option{cmpopts.IgnoreUnexported(IstioEndpoint{}), endpointDiscoverabilityPolicyImplCmpOpt, cmp.AllowUnexported()} 583 584 func (ep *IstioEndpoint) CmpOpts() []cmp.Option { 585 return istioEndpointCmpOpts 586 } 587 588 // EndpointMetadata represents metadata set on Envoy LbEndpoint used for telemetry purposes. 589 type EndpointMetadata struct { 590 // Network holds the network where this endpoint is present 591 Network network.ID 592 593 // TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS 594 TLSMode string 595 596 // Name of the workload that this endpoint belongs to. This is for telemetry purpose. 597 WorkloadName string 598 599 // Namespace that this endpoint belongs to. This is for telemetry purpose. 600 Namespace string 601 602 // Labels points to the workload or deployment labels. 603 Labels labels.Instance 604 605 // ClusterID where the endpoint is located 606 ClusterID cluster.ID 607 } 608 609 // EndpointDiscoverabilityPolicy determines the discoverability of an endpoint throughout the mesh. 610 type EndpointDiscoverabilityPolicy interface { 611 // IsDiscoverableFromProxy indicates whether an endpoint is discoverable from the given Proxy. 612 IsDiscoverableFromProxy(*IstioEndpoint, *Proxy) bool 613 614 // String returns name of this policy. 615 String() string 616 } 617 618 type endpointDiscoverabilityPolicyImpl struct { 619 name string 620 f func(*IstioEndpoint, *Proxy) bool 621 } 622 623 func (p *endpointDiscoverabilityPolicyImpl) IsDiscoverableFromProxy(ep *IstioEndpoint, proxy *Proxy) bool { 624 return p.f(ep, proxy) 625 } 626 627 func (p *endpointDiscoverabilityPolicyImpl) String() string { 628 return p.name 629 } 630 631 var endpointDiscoverabilityPolicyImplCmpOpt = cmp.Comparer(func(x, y endpointDiscoverabilityPolicyImpl) bool { 632 return x.String() == y.String() 633 }) 634 635 func (p *endpointDiscoverabilityPolicyImpl) CmpOpts() []cmp.Option { 636 return []cmp.Option{endpointDiscoverabilityPolicyImplCmpOpt} 637 } 638 639 // AlwaysDiscoverable is an EndpointDiscoverabilityPolicy that allows an endpoint to be discoverable throughout the mesh. 640 var AlwaysDiscoverable EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{ 641 name: "AlwaysDiscoverable", 642 f: func(*IstioEndpoint, *Proxy) bool { 643 return true 644 }, 645 } 646 647 // DiscoverableFromSameCluster is an EndpointDiscoverabilityPolicy that only allows an endpoint to be discoverable 648 // from proxies within the same cluster. 649 var DiscoverableFromSameCluster EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{ 650 name: "DiscoverableFromSameCluster", 651 f: func(ep *IstioEndpoint, p *Proxy) bool { 652 return p.InCluster(ep.Locality.ClusterID) 653 }, 654 } 655 656 // ServiceAttributes represents a group of custom attributes of the service. 657 type ServiceAttributes struct { 658 // ServiceRegistry indicates the backing service registry system where this service 659 // was sourced from. 660 // TODO: move the ServiceRegistry type from platform.go to model 661 ServiceRegistry provider.ID 662 // Name is "destination.service.name" attribute 663 Name string 664 // Namespace is "destination.service.namespace" attribute 665 Namespace string 666 // Labels applied to the service 667 Labels map[string]string 668 // ExportTo defines the visibility of Service in 669 // a namespace when the namespace is imported. 670 ExportTo sets.Set[visibility.Instance] 671 672 // LabelSelectors are the labels used by the service to select workloads. 673 // Applicable to both Kubernetes and ServiceEntries. 674 LabelSelectors map[string]string 675 676 // Aliases is the resolved set of aliases for this service. This is computed based on a global view of all Service's `AliasFor` 677 // fields. 678 // For example, if I had two Services with `externalName: foo`, "a" and "b", then the "foo" service would have Aliases=[a,b]. 679 Aliases []NamespacedHostname 680 681 // For Kubernetes platform 682 683 // ClusterExternalAddresses is a mapping between a cluster name and the external 684 // address(es) to access the service from outside the cluster. 685 // Used by the aggregator to aggregate the Attributes.ClusterExternalAddresses 686 // for clusters where the service resides 687 ClusterExternalAddresses *AddressMap 688 689 // ClusterExternalPorts is a mapping between a cluster name and the service port 690 // to node port mappings for a given service. When accessing the service via 691 // node port IPs, we need to use the kubernetes assigned node ports of the service 692 // The port that the user provides in the meshNetworks config is the service port. 693 // We translate that to the appropriate node port here. 694 ClusterExternalPorts map[cluster.ID]map[uint32]uint32 695 696 PassthroughTargetPorts map[uint32]uint32 697 698 K8sAttributes 699 } 700 701 type NamespacedHostname struct { 702 Hostname host.Name 703 Namespace string 704 } 705 706 type K8sAttributes struct { 707 // Type holds the value of the corev1.Type of the Kubernetes service 708 // spec.Type 709 Type string 710 711 // spec.ExternalName 712 ExternalName string 713 714 // NodeLocal means the proxy will only forward traffic to node local endpoints 715 // spec.InternalTrafficPolicy == Local 716 NodeLocal bool 717 } 718 719 // DeepCopy creates a deep copy of ServiceAttributes, but skips internal mutexes. 720 func (s *ServiceAttributes) DeepCopy() ServiceAttributes { 721 // AddressMap contains a mutex, which is safe to copy in this case. 722 // nolint: govet 723 out := *s 724 725 if s.Labels != nil { 726 out.Labels = make(map[string]string, len(s.Labels)) 727 for k, v := range s.Labels { 728 out.Labels[k] = v 729 } 730 } 731 732 if s.ExportTo != nil { 733 out.ExportTo = s.ExportTo.Copy() 734 } 735 736 if s.LabelSelectors != nil { 737 out.LabelSelectors = make(map[string]string, len(s.LabelSelectors)) 738 for k, v := range s.LabelSelectors { 739 out.LabelSelectors[k] = v 740 } 741 } 742 743 out.ClusterExternalAddresses = s.ClusterExternalAddresses.DeepCopy() 744 745 if s.ClusterExternalPorts != nil { 746 out.ClusterExternalPorts = make(map[cluster.ID]map[uint32]uint32, len(s.ClusterExternalPorts)) 747 for k, m := range s.ClusterExternalPorts { 748 if m == nil { 749 out.ClusterExternalPorts[k] = nil 750 continue 751 } 752 753 out.ClusterExternalPorts[k] = make(map[uint32]uint32, len(m)) 754 for sp, np := range m { 755 out.ClusterExternalPorts[k][sp] = np 756 } 757 } 758 } 759 760 out.Aliases = slices.Clone(s.Aliases) 761 762 // AddressMap contains a mutex, which is safe to return a copy in this case. 763 // nolint: govet 764 return out 765 } 766 767 // Equals checks whether the attributes are equal from the passed in service. 768 func (s *ServiceAttributes) Equals(other *ServiceAttributes) bool { 769 if s == nil { 770 return other == nil 771 } 772 if other == nil { 773 return s == nil 774 } 775 776 if !maps.Equal(s.Labels, other.Labels) { 777 return false 778 } 779 780 if !maps.Equal(s.LabelSelectors, other.LabelSelectors) { 781 return false 782 } 783 784 if !maps.Equal(s.ExportTo, other.ExportTo) { 785 return false 786 } 787 788 if !slices.Equal(s.Aliases, other.Aliases) { 789 return false 790 } 791 792 if s.ClusterExternalAddresses.Len() != other.ClusterExternalAddresses.Len() { 793 return false 794 } 795 796 for k, v1 := range s.ClusterExternalAddresses.GetAddresses() { 797 if v2, ok := other.ClusterExternalAddresses.Addresses[k]; !ok || !slices.Equal(v1, v2) { 798 return false 799 } 800 } 801 802 if len(s.ClusterExternalPorts) != len(other.ClusterExternalPorts) { 803 return false 804 } 805 806 for k, v1 := range s.ClusterExternalPorts { 807 if v2, ok := s.ClusterExternalPorts[k]; !ok || !maps.Equal(v1, v2) { 808 return false 809 } 810 } 811 return s.Name == other.Name && s.Namespace == other.Namespace && 812 s.ServiceRegistry == other.ServiceRegistry && s.K8sAttributes == other.K8sAttributes 813 } 814 815 // ServiceDiscovery enumerates Istio service instances. 816 // nolint: lll 817 type ServiceDiscovery interface { 818 NetworkGatewaysWatcher 819 820 // Services list declarations of all services in the system 821 Services() []*Service 822 823 // GetService retrieves a service by host name if it exists 824 GetService(hostname host.Name) *Service 825 826 // GetProxyServiceTargets returns the service targets that co-located with a given Proxy 827 // 828 // Co-located generally means running in the same network namespace and security context. 829 // 830 // A Proxy operating as a Sidecar will return a non-empty slice. A stand-alone Proxy 831 // will return an empty slice. 832 // 833 // There are two reasons why this returns multiple ServiceTargets instead of one: 834 // - A ServiceTargets has a single Port. But a Service 835 // may have many ports. So a workload implementing such a Service would need 836 // multiple ServiceTargets, one for each port. 837 // - A single workload may implement multiple logical Services. 838 // 839 // In the second case, multiple services may be implemented by the same physical port number, 840 // though with a different ServicePort and IstioEndpoint for each. If any of these overlapping 841 // services are not HTTP or H2-based, behavior is undefined, since the listener may not be able to 842 // determine the intended destination of a connection without a Host header on the request. 843 GetProxyServiceTargets(*Proxy) []ServiceTarget 844 GetProxyWorkloadLabels(*Proxy) labels.Instance 845 846 // MCSServices returns information about the services that have been exported/imported via the 847 // Kubernetes Multi-Cluster Services (MCS) ServiceExport API. Only applies to services in 848 // Kubernetes clusters. 849 MCSServices() []MCSServiceInfo 850 AmbientIndexes 851 } 852 853 type AmbientIndexes interface { 854 AddressInformation(addresses sets.String) ([]AddressInfo, sets.String) 855 AdditionalPodSubscriptions( 856 proxy *Proxy, 857 allAddresses sets.String, 858 currentSubs sets.String, 859 ) sets.String 860 Policies(requested sets.Set[ConfigKey]) []WorkloadAuthorization 861 ServicesForWaypoint(WaypointKey) []ServiceInfo 862 WorkloadsForWaypoint(WaypointKey) []WorkloadInfo 863 } 864 865 // WaypointKey is a multi-address extension of NetworkAddress which is commonly used for lookups in AmbientIndex 866 // We likely need to consider alternative keying options internally such as hostname as we look to expand beyong istio-waypoint 867 // This extension can ideally support that type of lookup in the interface without introducing scope creep into things 868 // like NetworkAddress 869 type WaypointKey struct { 870 Network string 871 Addresses []string 872 } 873 874 // WaypointKey contains all of the VIPs that the Proxy serves. 875 func WaypointKeyForProxy(node *Proxy) WaypointKey { 876 // TODO IP based lookup should switch to looking up services by name/ns 877 key := WaypointKey{ 878 Network: node.Metadata.Network.String(), 879 } 880 for _, svct := range node.ServiceTargets { 881 ips := svct.Service.ClusterVIPs.GetAddressesFor(node.GetClusterID()) 882 key.Addresses = append(key.Addresses, ips...) 883 } 884 return key 885 } 886 887 // NoopAmbientIndexes provides an implementation of AmbientIndexes that always returns nil, to easily "skip" it. 888 type NoopAmbientIndexes struct{} 889 890 func (u NoopAmbientIndexes) AddressInformation(sets.String) ([]AddressInfo, sets.String) { 891 return nil, nil 892 } 893 894 func (u NoopAmbientIndexes) AdditionalPodSubscriptions( 895 *Proxy, 896 sets.String, 897 sets.String, 898 ) sets.String { 899 return nil 900 } 901 902 func (u NoopAmbientIndexes) Policies(sets.Set[ConfigKey]) []WorkloadAuthorization { 903 return nil 904 } 905 906 func (u NoopAmbientIndexes) ServicesForWaypoint(WaypointKey) []ServiceInfo { 907 return nil 908 } 909 910 func (u NoopAmbientIndexes) Waypoint(string, string) []netip.Addr { 911 return nil 912 } 913 914 func (u NoopAmbientIndexes) WorkloadsForWaypoint(WaypointKey) []WorkloadInfo { 915 return nil 916 } 917 918 var _ AmbientIndexes = NoopAmbientIndexes{} 919 920 type AddressInfo struct { 921 *workloadapi.Address 922 } 923 924 func (i AddressInfo) Aliases() []string { 925 switch addr := i.Type.(type) { 926 case *workloadapi.Address_Workload: 927 aliases := make([]string, 0, len(addr.Workload.Addresses)) 928 network := addr.Workload.Network 929 for _, workloadAddr := range addr.Workload.Addresses { 930 ip, _ := netip.AddrFromSlice(workloadAddr) 931 aliases = append(aliases, network+"/"+ip.String()) 932 } 933 return aliases 934 case *workloadapi.Address_Service: 935 aliases := make([]string, 0, len(addr.Service.Addresses)) 936 for _, networkAddr := range addr.Service.Addresses { 937 ip, _ := netip.AddrFromSlice(networkAddr.Address) 938 aliases = append(aliases, networkAddr.Network+"/"+ip.String()) 939 } 940 return aliases 941 } 942 return nil 943 } 944 945 func (i AddressInfo) ResourceName() string { 946 var name string 947 switch addr := i.Type.(type) { 948 case *workloadapi.Address_Workload: 949 name = workloadResourceName(addr.Workload) 950 case *workloadapi.Address_Service: 951 name = serviceResourceName(addr.Service) 952 } 953 return name 954 } 955 956 type ServicePortName struct { 957 PortName string 958 TargetPortName string 959 } 960 961 type ServiceInfo struct { 962 *workloadapi.Service 963 // LabelSelectors for the Service. Note these are only used internally, not sent over XDS 964 LabelSelector 965 // PortNames provides a mapping of ServicePort -> port names. Note these are only used internally, not sent over XDS 966 PortNames map[int32]ServicePortName 967 // Source is the type that introduced this service. 968 Source kind.Kind 969 // Waypoint that clients should use when addressing traffic to this Service. 970 Waypoint string 971 } 972 973 func (i ServiceInfo) NamespacedName() types.NamespacedName { 974 return types.NamespacedName{Name: i.Name, Namespace: i.Namespace} 975 } 976 977 func (i ServiceInfo) Equals(other ServiceInfo) bool { 978 return proto.Equal(i.Service, other.Service) && 979 maps.Equal(i.LabelSelector.Labels, other.LabelSelector.Labels) && 980 maps.Equal(i.PortNames, other.PortNames) && 981 i.Source == other.Source 982 } 983 984 func (i ServiceInfo) ResourceName() string { 985 return serviceResourceName(i.Service) 986 } 987 988 func serviceResourceName(s *workloadapi.Service) string { 989 return s.Namespace + "/" + s.Hostname 990 } 991 992 type WorkloadSource string 993 994 type WorkloadInfo struct { 995 *workloadapi.Workload 996 // Labels for the workload. Note these are only used internally, not sent over XDS 997 Labels map[string]string 998 // Source is the type that introduced this workload. 999 Source kind.Kind 1000 // CreationTime is the time when the workload was created. Note this is used internally only. 1001 CreationTime time.Time 1002 } 1003 1004 func (i WorkloadInfo) Equals(other WorkloadInfo) bool { 1005 return proto.Equal(i.Workload, other.Workload) && 1006 maps.Equal(i.Labels, other.Labels) && 1007 i.Source == other.Source && 1008 i.CreationTime == other.CreationTime 1009 } 1010 1011 func workloadResourceName(w *workloadapi.Workload) string { 1012 return w.Uid 1013 } 1014 1015 func (i *WorkloadInfo) Clone() *WorkloadInfo { 1016 return &WorkloadInfo{ 1017 Workload: proto.Clone(i).(*workloadapi.Workload), 1018 Labels: maps.Clone(i.Labels), 1019 Source: i.Source, 1020 CreationTime: i.CreationTime, 1021 } 1022 } 1023 1024 func (i WorkloadInfo) ResourceName() string { 1025 return workloadResourceName(i.Workload) 1026 } 1027 1028 type WorkloadAuthorization struct { 1029 // LabelSelectors for the workload. Note these are only used internally, not sent over XDS 1030 LabelSelector 1031 Authorization *security.Authorization 1032 } 1033 1034 func (i WorkloadAuthorization) Equals(other WorkloadAuthorization) bool { 1035 return maps.Equal(i.LabelSelector.Labels, other.LabelSelector.Labels) && 1036 proto.Equal(i.Authorization, other.Authorization) 1037 } 1038 1039 func (i WorkloadAuthorization) ResourceName() string { 1040 return i.Authorization.GetNamespace() + "/" + i.Authorization.GetName() 1041 } 1042 1043 type LabelSelector struct { 1044 Labels map[string]string 1045 } 1046 1047 func NewSelector(l map[string]string) LabelSelector { 1048 return LabelSelector{l} 1049 } 1050 1051 func (l LabelSelector) GetLabelSelector() map[string]string { 1052 return l.Labels 1053 } 1054 1055 func ExtractWorkloadsFromAddresses(addrs []AddressInfo) []WorkloadInfo { 1056 return slices.MapFilter(addrs, func(a AddressInfo) *WorkloadInfo { 1057 switch addr := a.Type.(type) { 1058 case *workloadapi.Address_Workload: 1059 return &WorkloadInfo{Workload: addr.Workload} 1060 default: 1061 return nil 1062 } 1063 }) 1064 } 1065 1066 func SortWorkloadsByCreationTime(workloads []WorkloadInfo) []WorkloadInfo { 1067 sort.SliceStable(workloads, func(i, j int) bool { 1068 if workloads[i].CreationTime.Equal(workloads[j].CreationTime) { 1069 return workloads[i].Uid < workloads[j].Uid 1070 } 1071 return workloads[i].CreationTime.Before(workloads[j].CreationTime) 1072 }) 1073 return workloads 1074 } 1075 1076 // MCSServiceInfo combines the name of a service with a particular Kubernetes cluster. This 1077 // is used for debug information regarding the state of Kubernetes Multi-Cluster Services (MCS). 1078 type MCSServiceInfo struct { 1079 Cluster cluster.ID 1080 Name string 1081 Namespace string 1082 Exported bool 1083 Imported bool 1084 ClusterSetVIP string 1085 Discoverability map[host.Name]string 1086 } 1087 1088 // GetNames returns port names 1089 func (ports PortList) GetNames() []string { 1090 names := make([]string, 0, len(ports)) 1091 for _, port := range ports { 1092 names = append(names, port.Name) 1093 } 1094 return names 1095 } 1096 1097 // Get retrieves a port declaration by name 1098 func (ports PortList) Get(name string) (*Port, bool) { 1099 for _, port := range ports { 1100 if port.Name == name { 1101 return port, true 1102 } 1103 } 1104 return nil, false 1105 } 1106 1107 // GetByPort retrieves a port declaration by port value 1108 func (ports PortList) GetByPort(num int) (*Port, bool) { 1109 for _, port := range ports { 1110 if port.Port == num && port.Protocol != protocol.UDP { 1111 return port, true 1112 } 1113 } 1114 return nil, false 1115 } 1116 1117 func (p *Port) Equals(other *Port) bool { 1118 if p == nil { 1119 return other == nil 1120 } 1121 if other == nil { 1122 return p == nil 1123 } 1124 return p.Name == other.Name && p.Port == other.Port && p.Protocol == other.Protocol 1125 } 1126 1127 func (ports PortList) Equals(other PortList) bool { 1128 return slices.EqualFunc(ports, other, func(a, b *Port) bool { 1129 return a.Equals(b) 1130 }) 1131 } 1132 1133 func (ports PortList) String() string { 1134 sp := make([]string, 0, len(ports)) 1135 for _, p := range ports { 1136 sp = append(sp, p.String()) 1137 } 1138 return strings.Join(sp, ", ") 1139 } 1140 1141 // External predicate checks whether the service is external 1142 func (s *Service) External() bool { 1143 return s.MeshExternal 1144 } 1145 1146 // BuildSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port. 1147 // The proxy queries Pilot with this key to obtain the list of instances in a subset. 1148 func BuildSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string { 1149 return string(direction) + "|" + strconv.Itoa(port) + "|" + subsetName + "|" + string(hostname) 1150 } 1151 1152 // BuildInboundSubsetKey generates a unique string referencing service instances with port. 1153 func BuildInboundSubsetKey(port int) string { 1154 return BuildSubsetKey(TrafficDirectionInbound, "", "", port) 1155 } 1156 1157 // BuildDNSSrvSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port. 1158 // The proxy queries Pilot with this key to obtain the list of instances in a subset. 1159 // This is used only for the SNI-DNAT router. Do not use for other purposes. 1160 // The DNS Srv format of the cluster is also used as the default SNI string for Istio mTLS connections 1161 func BuildDNSSrvSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string { 1162 return string(direction) + "_." + strconv.Itoa(port) + "_." + subsetName + "_." + string(hostname) 1163 } 1164 1165 // IsValidSubsetKey checks if a string is valid for subset key parsing. 1166 func IsValidSubsetKey(s string) bool { 1167 return strings.Count(s, "|") == 3 1168 } 1169 1170 // IsDNSSrvSubsetKey checks whether the given key is a DNSSrv key (built by BuildDNSSrvSubsetKey). 1171 func IsDNSSrvSubsetKey(s string) bool { 1172 if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) || 1173 strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) { 1174 return true 1175 } 1176 return false 1177 } 1178 1179 // ParseSubsetKeyHostname is an optimized specialization of ParseSubsetKey that only returns the hostname. 1180 // This is created as this is used in some hot paths and is about 2x faster than ParseSubsetKey; for typical use ParseSubsetKey is sufficient (and zero-alloc). 1181 func ParseSubsetKeyHostname(s string) (hostname string) { 1182 idx := strings.LastIndex(s, "|") 1183 if idx == -1 { 1184 // Could be DNS SRV format. 1185 // Do not do LastIndex("_."), as those are valid characters in the hostname (unlike |) 1186 // Fallback to the full parser. 1187 _, _, hostname, _ := ParseSubsetKey(s) 1188 return string(hostname) 1189 } 1190 return s[idx+1:] 1191 } 1192 1193 // ParseSubsetKey is the inverse of the BuildSubsetKey method 1194 func ParseSubsetKey(s string) (direction TrafficDirection, subsetName string, hostname host.Name, port int) { 1195 sep := "|" 1196 // This could be the DNS srv form of the cluster that uses outbound_.port_.subset_.hostname 1197 // Since we do not want every callsite to implement the logic to differentiate between the two forms 1198 // we add an alternate parser here. 1199 if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) || 1200 strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) { 1201 sep = "_." 1202 } 1203 1204 // Format: dir|port|subset|hostname 1205 dir, s, ok := strings.Cut(s, sep) 1206 if !ok { 1207 return 1208 } 1209 direction = TrafficDirection(dir) 1210 1211 p, s, ok := strings.Cut(s, sep) 1212 if !ok { 1213 return 1214 } 1215 port, _ = strconv.Atoi(p) 1216 1217 ss, s, ok := strings.Cut(s, sep) 1218 if !ok { 1219 return 1220 } 1221 subsetName = ss 1222 1223 // last part. No | remains -- verify this 1224 if strings.Contains(s, sep) { 1225 return 1226 } 1227 hostname = host.Name(s) 1228 return 1229 } 1230 1231 // GetAddresses returns a Service's addresses. 1232 // This method returns all the VIPs of a service if the ClusterID is explicitly set to "", otherwise only return the VIP 1233 // specific to the cluster where the node resides 1234 func (s *Service) GetAddresses(node *Proxy) []string { 1235 if node.Metadata != nil && node.Metadata.ClusterID == "" { 1236 return s.getAllAddresses() 1237 } 1238 1239 return []string{s.GetAddressForProxy(node)} 1240 } 1241 1242 // GetAddressForProxy returns a Service's address specific to the cluster where the node resides 1243 func (s *Service) GetAddressForProxy(node *Proxy) string { 1244 if node.Metadata != nil { 1245 if node.Metadata.ClusterID != "" { 1246 addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID) 1247 if len(addresses) > 0 { 1248 return addresses[0] 1249 } 1250 } 1251 1252 if node.Metadata.DNSCapture && node.Metadata.DNSAutoAllocate && s.DefaultAddress == constants.UnspecifiedIP { 1253 if node.SupportsIPv4() && s.AutoAllocatedIPv4Address != "" { 1254 return s.AutoAllocatedIPv4Address 1255 } 1256 if node.SupportsIPv6() && s.AutoAllocatedIPv6Address != "" { 1257 return s.AutoAllocatedIPv6Address 1258 } 1259 } 1260 } 1261 1262 return s.DefaultAddress 1263 } 1264 1265 // GetExtraAddressesForProxy returns a k8s service's extra addresses to the cluster where the node resides. 1266 // Especially for dual stack k8s service to get other IP family addresses. 1267 func (s *Service) GetExtraAddressesForProxy(node *Proxy) []string { 1268 if features.EnableDualStack && node.Metadata != nil { 1269 if node.Metadata.ClusterID != "" { 1270 addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID) 1271 if len(addresses) > 1 { 1272 return addresses[1:] 1273 } 1274 } 1275 } 1276 return nil 1277 } 1278 1279 // getAllAddresses returns a Service's all addresses. 1280 func (s *Service) getAllAddresses() []string { 1281 var addresses []string 1282 addressMap := s.ClusterVIPs.GetAddresses() 1283 for _, clusterAddresses := range addressMap { 1284 addresses = append(addresses, clusterAddresses...) 1285 } 1286 1287 return addresses 1288 } 1289 1290 // GetTLSModeFromEndpointLabels returns the value of the label 1291 // security.istio.io/tlsMode if set. Do not return Enums or constants 1292 // from this function as users could provide values other than istio/disabled 1293 // and apply custom transport socket matchers here. 1294 func GetTLSModeFromEndpointLabels(labels map[string]string) string { 1295 if labels != nil { 1296 if val, exists := labels[label.SecurityTlsMode.Name]; exists { 1297 return val 1298 } 1299 } 1300 return DisabledTLSModeLabel 1301 } 1302 1303 // DeepCopy creates a clone of Service. 1304 func (s *Service) DeepCopy() *Service { 1305 // nolint: govet 1306 out := *s 1307 out.Attributes = s.Attributes.DeepCopy() 1308 if s.Ports != nil { 1309 out.Ports = make(PortList, len(s.Ports)) 1310 for i, port := range s.Ports { 1311 if port != nil { 1312 out.Ports[i] = &Port{ 1313 Name: port.Name, 1314 Port: port.Port, 1315 Protocol: port.Protocol, 1316 } 1317 } else { 1318 out.Ports[i] = nil 1319 } 1320 } 1321 } 1322 1323 if s.ServiceAccounts != nil { 1324 out.ServiceAccounts = make([]string, len(s.ServiceAccounts)) 1325 copy(out.ServiceAccounts, s.ServiceAccounts) 1326 } 1327 out.ClusterVIPs = *s.ClusterVIPs.DeepCopy() 1328 return &out 1329 } 1330 1331 // Equals compares two service objects. 1332 func (s *Service) Equals(other *Service) bool { 1333 if s == nil { 1334 return other == nil 1335 } 1336 if other == nil { 1337 return s == nil 1338 } 1339 1340 if !s.Attributes.Equals(&other.Attributes) { 1341 return false 1342 } 1343 1344 if !s.Ports.Equals(other.Ports) { 1345 return false 1346 } 1347 if !slices.Equal(s.ServiceAccounts, other.ServiceAccounts) { 1348 return false 1349 } 1350 1351 if len(s.ClusterVIPs.Addresses) != len(other.ClusterVIPs.Addresses) { 1352 return false 1353 } 1354 for k, v1 := range s.ClusterVIPs.Addresses { 1355 if v2, ok := other.ClusterVIPs.Addresses[k]; !ok || !slices.Equal(v1, v2) { 1356 return false 1357 } 1358 } 1359 1360 return s.DefaultAddress == other.DefaultAddress && s.AutoAllocatedIPv4Address == other.AutoAllocatedIPv4Address && 1361 s.AutoAllocatedIPv6Address == other.AutoAllocatedIPv6Address && s.Hostname == other.Hostname && 1362 s.Resolution == other.Resolution && s.MeshExternal == other.MeshExternal 1363 } 1364 1365 // DeepCopy creates a clone of IstioEndpoint. 1366 func (ep *IstioEndpoint) DeepCopy() *IstioEndpoint { 1367 return copyInternal(ep).(*IstioEndpoint) 1368 } 1369 1370 // ShallowCopy creates a shallow clone of IstioEndpoint. 1371 func (ep *IstioEndpoint) ShallowCopy() *IstioEndpoint { 1372 // nolint: govet 1373 cpy := *ep 1374 return &cpy 1375 } 1376 1377 // Equals checks whether the attributes are equal from the passed in service. 1378 func (ep *IstioEndpoint) Equals(other *IstioEndpoint) bool { 1379 if ep == nil { 1380 return other == nil 1381 } 1382 if other == nil { 1383 return ep == nil 1384 } 1385 1386 // Check things we can directly compare... 1387 eq := ep.Address == other.Address && 1388 ep.ServicePortName == other.ServicePortName && 1389 ep.LegacyClusterPortKey == other.LegacyClusterPortKey && 1390 ep.ServiceAccount == other.ServiceAccount && 1391 ep.Network == other.Network && 1392 ep.Locality == other.Locality && 1393 ep.EndpointPort == other.EndpointPort && 1394 ep.LbWeight == other.LbWeight && 1395 ep.TLSMode == other.TLSMode && 1396 ep.Namespace == other.Namespace && 1397 ep.WorkloadName == other.WorkloadName && 1398 ep.HostName == other.HostName && 1399 ep.SubDomain == other.SubDomain && 1400 ep.HealthStatus == other.HealthStatus && 1401 ep.NodeName == other.NodeName 1402 if !eq { 1403 return false 1404 } 1405 1406 // check everything else 1407 if !maps.Equal(ep.Labels, other.Labels) { 1408 return false 1409 } 1410 1411 // Compare discoverability by name 1412 var epp string 1413 if ep.DiscoverabilityPolicy != nil { 1414 epp = ep.DiscoverabilityPolicy.String() 1415 } 1416 var op string 1417 if other.DiscoverabilityPolicy != nil { 1418 op = other.DiscoverabilityPolicy.String() 1419 } 1420 if epp != op { 1421 return false 1422 } 1423 1424 return true 1425 } 1426 1427 func copyInternal(v any) any { 1428 copied, err := copystructure.Copy(v) 1429 if err != nil { 1430 // There are 2 locations where errors are generated in copystructure.Copy: 1431 // * The reflection walk over the structure fails, which should never happen 1432 // * A configurable copy function returns an error. This is only used for copying times, which never returns an error. 1433 // Therefore, this should never happen 1434 panic(err) 1435 } 1436 return copied 1437 }