blob: 4e218dbaa42ca22db542588c1079742dc78d93c2 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file describes the abstract model of services (and their instances) as
// represented in Istio. This model is independent of the underlying platform
// (Kubernetes, Mesos, etc.). Platform specific adapters found populate the
// model object with various fields, from the metadata found in the platform.
// The platform independent proxy code uses the representation in the model to
// generate the configuration files for the Layer 7 proxy sidecar. The proxy
// code is specific to individual proxy implementations
package model
import (
"fmt"
"strconv"
"strings"
"time"
)
import (
endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
"github.com/mitchellh/copystructure"
"istio.io/api/label"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/serviceregistry/provider"
"github.com/apache/dubbo-go-pixiu/pkg/cluster"
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/config/visibility"
"github.com/apache/dubbo-go-pixiu/pkg/network"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
// Service describes an Istio service (e.g., catalog.mystore.com:8080)
// Each service has a fully qualified domain name (FQDN) and one or more
// ports where the service is listening for connections. *Optionally*, a
// service can have a single load balancer/virtual IP address associated
// with it, such that the DNS queries for the FQDN resolves to the virtual
// IP address (a load balancer IP).
//
// E.g., in kubernetes, a service foo is associated with
// foo.default.svc.cluster.local hostname, has a virtual IP of 10.0.1.1 and
// listens on ports 80, 8080
type Service struct {
// Attributes contains additional attributes associated with the service
// used mostly by RBAC for policy enforcement purposes.
Attributes ServiceAttributes
// Ports is the set of network ports where the service is listening for
// connections
Ports PortList `json:"ports,omitempty"`
// ServiceAccounts specifies the service accounts that run the service.
ServiceAccounts []string `json:"serviceAccounts,omitempty"`
// CreationTime records the time this service was created, if available.
CreationTime time.Time `json:"creationTime,omitempty"`
// Name of the service, e.g. "catalog.mystore.com"
Hostname host.Name `json:"hostname"`
// ClusterVIPs specifies the service address of the load balancer
// in each of the clusters where the service resides
ClusterVIPs AddressMap `json:"clusterVIPs,omitempty"`
// DefaultAddress specifies the default service IP of the load balancer.
// Do not access directly. Use GetAddressForProxy
DefaultAddress string `json:"defaultAddress,omitempty"`
// AutoAllocatedIPv4Address and AutoAllocatedIPv6Address specifies
// the automatically allocated IPv4/IPv6 address out of the reserved
// Class E subnet (240.240.0.0/16) or reserved Benchmarking IP range
// (2001:2::/48) in RFC5180.for service entries with non-wildcard
// hostnames. The IPs assigned to services are not
// synchronized across istiod replicas as the DNS resolution
// for these service entries happens completely inside a pod
// whose proxy is managed by one istiod. That said, the algorithm
// to allocate IPs is pretty deterministic that at stable state, two
// istiods will allocate the exact same set of IPs for a given set of
// service entries.
AutoAllocatedIPv4Address string `json:"autoAllocatedIPv4Address,omitempty"`
AutoAllocatedIPv6Address string `json:"autoAllocatedIPv6Address,omitempty"`
// Resolution indicates how the service instances need to be resolved before routing
// traffic. Most services in the service registry will use static load balancing wherein
// the proxy will decide the service instance that will receive the traffic. Service entries
// could either use DNS load balancing (i.e. proxy will query DNS server for the IP of the service)
// or use the passthrough model (i.e. proxy will forward the traffic to the network endpoint requested
// by the caller)
Resolution Resolution
// MeshExternal (if true) indicates that the service is external to the mesh.
// These services are defined using Istio's ServiceEntry spec.
MeshExternal bool
// ResourceVersion represents the internal version of this object.
ResourceVersion string
}
func (s *Service) Key() string {
if s == nil {
return ""
}
return s.Attributes.Namespace + "/" + string(s.Hostname)
}
// Resolution indicates how the service instances need to be resolved before routing traffic.
type Resolution int
const (
// ClientSideLB implies that the proxy will decide the endpoint from its local lb pool
ClientSideLB Resolution = iota
// DNSLB implies that the proxy will resolve a DNS address and forward to the resolved address
DNSLB
// Passthrough implies that the proxy should forward traffic to the destination IP requested by the caller
Passthrough
// DNSRoundRobinLB implies that the proxy will resolve a DNS address and forward to the resolved address
DNSRoundRobinLB
)
// String converts Resolution in to String.
func (resolution Resolution) String() string {
switch resolution {
case ClientSideLB:
return "ClientSide"
case DNSLB:
return "DNS"
case DNSRoundRobinLB:
return "DNSRoundRobin"
case Passthrough:
return "Passthrough"
default:
return fmt.Sprintf("%d", int(resolution))
}
}
const (
// IstioDefaultConfigNamespace constant for default namespace
IstioDefaultConfigNamespace = "default"
// LocalityLabel indicates the region/zone/subzone of an instance. It is used to override the native
// registry's value.
//
// Note: because k8s labels does not support `/`, so we use `.` instead in k8s.
LocalityLabel = "istio-locality"
// k8s istio-locality label separator
k8sSeparator = "."
)
const (
// TLSModeLabelShortname name used for determining endpoint level tls transport socket configuration
TLSModeLabelShortname = "tlsMode"
// DisabledTLSModeLabel implies that this endpoint should receive traffic as is (mostly plaintext)
DisabledTLSModeLabel = "disabled"
// IstioMutualTLSModeLabel implies that the endpoint is ready to receive Istio mTLS connections.
IstioMutualTLSModeLabel = "istio"
// IstioCanonicalServiceLabelName is the name of label for the Istio Canonical Service for a workload instance.
IstioCanonicalServiceLabelName = "service.istio.io/canonical-name"
// IstioCanonicalServiceRevisionLabelName is the name of label for the Istio Canonical Service revision for a workload instance.
IstioCanonicalServiceRevisionLabelName = "service.istio.io/canonical-revision"
)
// Port represents a network port where a service is listening for
// connections. The port should be annotated with the type of protocol
// used by the port.
type Port struct {
// Name ascribes a human readable name for the port object. When a
// service has multiple ports, the name field is mandatory
Name string `json:"name,omitempty"`
// Port number where the service can be reached. Does not necessarily
// map to the corresponding port numbers for the instances behind the
// service.
Port int `json:"port"`
// Protocol to be used for the port.
Protocol protocol.Instance `json:"protocol,omitempty"`
}
// PortList is a set of ports
type PortList []*Port
// TrafficDirection defines whether traffic exists a service instance or enters a service instance
type TrafficDirection string
const (
// TrafficDirectionInbound indicates inbound traffic
TrafficDirectionInbound TrafficDirection = "inbound"
// TrafficDirectionOutbound indicates outbound traffic
TrafficDirectionOutbound TrafficDirection = "outbound"
// trafficDirectionOutboundSrvPrefix the prefix for a DNS SRV type subset key
trafficDirectionOutboundSrvPrefix = string(TrafficDirectionOutbound) + "_"
// trafficDirectionInboundSrvPrefix the prefix for a DNS SRV type subset key
trafficDirectionInboundSrvPrefix = string(TrafficDirectionInbound) + "_"
)
// ServiceInstance represents an individual instance of a specific version
// of a service. It binds a network endpoint (ip:port), the service
// description (which is oblivious to various versions) and a set of labels
// that describe the service version associated with this instance.
//
// Since a ServiceInstance has a single IstioEndpoint, which has a single port,
// multiple ServiceInstances are required to represent a workload that listens
// on multiple ports.
//
// The labels associated with a service instance are unique per a network endpoint.
// There is one well defined set of labels for each service instance network endpoint.
//
// For example, the set of service instances associated with catalog.mystore.com
// are modeled like this
//
// --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
// --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
// --> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat)
// --> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat)
type ServiceInstance struct {
Service *Service `json:"service,omitempty"`
ServicePort *Port `json:"servicePort,omitempty"`
Endpoint *IstioEndpoint `json:"endpoint,omitempty"`
}
// DeepCopy creates a copy of ServiceInstance.
func (instance *ServiceInstance) DeepCopy() *ServiceInstance {
return &ServiceInstance{
Service: instance.Service.DeepCopy(),
Endpoint: instance.Endpoint.DeepCopy(),
ServicePort: &Port{
Name: instance.ServicePort.Name,
Port: instance.ServicePort.Port,
Protocol: instance.ServicePort.Protocol,
},
}
}
type workloadKind int
const (
// PodKind indicates the workload is from pod
PodKind workloadKind = iota
// WorkloadEntryKind indicates the workload is from workloadentry
WorkloadEntryKind
)
func (k workloadKind) String() string {
if k == PodKind {
return "Pod"
}
if k == WorkloadEntryKind {
return "WorkloadEntry"
}
return ""
}
type WorkloadInstance struct {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
// Where the workloadInstance come from, valid values are`Pod` or `WorkloadEntry`
Kind workloadKind `json:"kind"`
Endpoint *IstioEndpoint `json:"endpoint,omitempty"`
PortMap map[string]uint32 `json:"portMap,omitempty"`
// Can only be selected by service entry of DNS type.
DNSServiceEntryOnly bool `json:"dnsServiceEntryOnly,omitempty"`
}
// DeepCopy creates a copy of WorkloadInstance.
func (instance *WorkloadInstance) DeepCopy() *WorkloadInstance {
pmap := map[string]uint32{}
for k, v := range instance.PortMap {
pmap[k] = v
}
return &WorkloadInstance{
Name: instance.Name,
Namespace: instance.Namespace,
Kind: instance.Kind,
PortMap: pmap,
Endpoint: instance.Endpoint.DeepCopy(),
}
}
// WorkloadInstancesEqual is a custom comparison of workload instances based on the fields that we need
// i.e. excluding the ports. Returns true if equal, false otherwise.
func WorkloadInstancesEqual(first, second *WorkloadInstance) bool {
if first.Endpoint == nil || second.Endpoint == nil {
return first.Endpoint == second.Endpoint
}
if first.Endpoint.Address != second.Endpoint.Address {
return false
}
if first.Endpoint.Network != second.Endpoint.Network {
return false
}
if first.Endpoint.TLSMode != second.Endpoint.TLSMode {
return false
}
if !first.Endpoint.Labels.Equals(second.Endpoint.Labels) {
return false
}
if first.Endpoint.ServiceAccount != second.Endpoint.ServiceAccount {
return false
}
if first.Endpoint.Locality != second.Endpoint.Locality {
return false
}
if first.Endpoint.GetLoadBalancingWeight() != second.Endpoint.GetLoadBalancingWeight() {
return false
}
if first.Namespace != second.Namespace {
return false
}
if first.Name != second.Name {
return false
}
if first.Kind != second.Kind {
return false
}
if !portMapEquals(first.PortMap, second.PortMap) {
return false
}
return true
}
func portMapEquals(a, b map[string]uint32) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if b[k] != v {
return false
}
}
return true
}
// GetLocalityLabelOrDefault returns the locality from the supplied label, or falls back to
// the supplied default locality if the supplied label is empty. Because Kubernetes
// labels don't support `/`, we replace "." with "/" in the supplied label as a workaround.
func GetLocalityLabelOrDefault(label, defaultLabel string) string {
if len(label) > 0 {
// if there are /'s present we don't need to replace
if strings.Contains(label, "/") {
return label
}
// replace "." with "/"
return strings.Replace(label, k8sSeparator, "/", -1)
}
return defaultLabel
}
// SplitLocalityLabel splits a locality label into region, zone and subzone strings.
func SplitLocalityLabel(locality string) (region, zone, subzone string) {
items := strings.Split(locality, "/")
switch len(items) {
case 1:
return items[0], "", ""
case 2:
return items[0], items[1], ""
default:
return items[0], items[1], items[2]
}
}
// Locality information for an IstioEndpoint
type Locality struct {
// Label for locality on the endpoint. This is a "/" separated string.
Label string
// ClusterID where the endpoint is located
ClusterID cluster.ID
}
// Endpoint health status.
type HealthStatus int32
const (
// Healthy.
Healthy HealthStatus = 0
// Unhealthy.
UnHealthy HealthStatus = 1
)
// IstioEndpoint defines a network address (IP:port) associated with an instance of the
// service. A service has one or more instances each running in a
// container/VM/pod. If a service has multiple ports, then the same
// instance IP is expected to be listening on multiple ports (one per each
// service port). Note that the port associated with an instance does not
// have to be the same as the port associated with the service. Depending
// on the network setup (NAT, overlays), this could vary.
//
// For e.g., if catalog.mystore.com is accessible through port 80 and 8080,
// and it maps to an instance with IP 172.16.0.1, such that connections to
// port 80 are forwarded to port 55446, and connections to port 8080 are
// forwarded to port 33333,
//
// then internally, we have two endpoint structs for the
// service catalog.mystore.com
//
// --> 172.16.0.1:55446 (with ServicePort pointing to 80) and
// --> 172.16.0.1:33333 (with ServicePort pointing to 8080)
//
// TODO: Investigate removing ServiceInstance entirely.
type IstioEndpoint struct {
// Labels points to the workload or deployment labels.
Labels labels.Instance
// Address is the address of the endpoint, using envoy proto.
Address string
// ServicePortName tracks the name of the port, this is used to select the IstioEndpoint by service port.
ServicePortName string
// EnvoyEndpoint is a cached LbEndpoint, converted from the data, to
// avoid recomputation
EnvoyEndpoint *endpoint.LbEndpoint
// ServiceAccount holds the associated service account.
ServiceAccount string
// Network holds the network where this endpoint is present
Network network.ID
// The locality where the endpoint is present.
Locality Locality
// EndpointPort is the port where the workload is listening, can be different
// from the service port.
EndpointPort uint32
// The load balancing weight associated with this endpoint.
LbWeight uint32
// TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS
TLSMode string
// Namespace that this endpoint belongs to. This is for telemetry purpose.
Namespace string
// Name of the workload that this endpoint belongs to. This is for telemetry purpose.
WorkloadName string
// Specifies the hostname of the Pod, empty for vm workload.
HostName string
// If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>".
SubDomain string
// The ingress tunnel supportability of this endpoint.
// If this endpoint sidecar proxy does not support h2 tunnel, this endpoint will not show up in the EDS clusters
// which are generated for h2 tunnel.
TunnelAbility networking.TunnelAbility
// Determines the discoverability of this endpoint throughout the mesh.
DiscoverabilityPolicy EndpointDiscoverabilityPolicy `json:"-"`
// Indicatesthe endpoint health status.
HealthStatus HealthStatus
}
// GetLoadBalancingWeight returns the weight for this endpoint, normalized to always be > 0.
func (ep *IstioEndpoint) GetLoadBalancingWeight() uint32 {
if ep.LbWeight > 0 {
return ep.LbWeight
}
return 1
}
// IsDiscoverableFromProxy indicates whether this endpoint is discoverable from the given Proxy.
func (ep *IstioEndpoint) IsDiscoverableFromProxy(p *Proxy) bool {
if ep == nil || ep.DiscoverabilityPolicy == nil {
// If no policy was assigned, default to discoverable mesh-wide.
// TODO(nmittler): Will need to re-think this default when cluster.local is actually cluster-local.
return true
}
return ep.DiscoverabilityPolicy.IsDiscoverableFromProxy(ep, p)
}
// EndpointDiscoverabilityPolicy determines the discoverability of an endpoint throughout the mesh.
type EndpointDiscoverabilityPolicy interface {
// IsDiscoverableFromProxy indicates whether an endpoint is discoverable from the given Proxy.
IsDiscoverableFromProxy(*IstioEndpoint, *Proxy) bool
// String returns name of this policy.
String() string
}
type endpointDiscoverabilityPolicyImpl struct {
name string
f func(*IstioEndpoint, *Proxy) bool
}
func (p *endpointDiscoverabilityPolicyImpl) IsDiscoverableFromProxy(ep *IstioEndpoint, proxy *Proxy) bool {
return p.f(ep, proxy)
}
func (p *endpointDiscoverabilityPolicyImpl) String() string {
return p.name
}
// AlwaysDiscoverable is an EndpointDiscoverabilityPolicy that allows an endpoint to be discoverable throughout the mesh.
var AlwaysDiscoverable EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{
name: "AlwaysDiscoverable",
f: func(*IstioEndpoint, *Proxy) bool {
return true
},
}
// DiscoverableFromSameCluster is an EndpointDiscoverabilityPolicy that only allows an endpoint to be discoverable
// from proxies within the same cluster.
var DiscoverableFromSameCluster EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{
name: "DiscoverableFromSameCluster",
f: func(ep *IstioEndpoint, p *Proxy) bool {
return p.InCluster(ep.Locality.ClusterID)
},
}
// ServiceAttributes represents a group of custom attributes of the service.
type ServiceAttributes struct {
// ServiceRegistry indicates the backing service registry system where this service
// was sourced from.
// TODO: move the ServiceRegistry type from platform.go to model
ServiceRegistry provider.ID
// Name is "destination.service.name" attribute
Name string
// Namespace is "destination.service.namespace" attribute
Namespace string
// Labels applied to the service
Labels map[string]string
// ExportTo defines the visibility of Service in
// a namespace when the namespace is imported.
ExportTo map[visibility.Instance]bool
// LabelSelectors are the labels used by the service to select workloads.
// Applicable to both Kubernetes and ServiceEntries.
LabelSelectors map[string]string
// For Kubernetes platform
// ClusterExternalAddresses is a mapping between a cluster name and the external
// address(es) to access the service from outside the cluster.
// Used by the aggregator to aggregate the Attributes.ClusterExternalAddresses
// for clusters where the service resides
ClusterExternalAddresses AddressMap
// ClusterExternalPorts is a mapping between a cluster name and the service port
// to node port mappings for a given service. When accessing the service via
// node port IPs, we need to use the kubernetes assigned node ports of the service
// The port that the user provides in the meshNetworks config is the service port.
// We translate that to the appropriate node port here.
ClusterExternalPorts map[cluster.ID]map[uint32]uint32
}
// DeepCopy creates a deep copy of ServiceAttributes, but skips internal mutexes.
func (s *ServiceAttributes) DeepCopy() ServiceAttributes {
// Nested mutexes are configured to be ignored by copystructure.Copy.
// Disabling `go vet` warning since this is actually safe in this case.
// nolint: vet
return copyInternal(*s).(ServiceAttributes)
}
// ServiceDiscovery enumerates Istio service instances.
// nolint: lll
type ServiceDiscovery interface {
NetworkGatewaysWatcher
// Services list declarations of all services in the system
Services() []*Service
// GetService retrieves a service by host name if it exists
GetService(hostname host.Name) *Service
// InstancesByPort retrieves instances for a service on the given ports with labels that match
// any of the supplied labels. All instances match an empty tag list.
//
// For example, consider an example of catalog.mystore.com:
// Instances(catalog.myservice.com, 80) ->
// --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
// --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
// --> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat)
// --> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat)
//
// Calling Instances with specific labels returns a trimmed list.
// e.g., Instances(catalog.myservice.com, 80, foo=bar) ->
// --> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
// --> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
//
// Similar concepts apply for calling this function with a specific
// port, hostname and labels.
//
// Introduced in Istio 0.8. It is only called with 1 port.
// CDS (clusters.go) calls it for building 'dnslb' type clusters.
// EDS calls it for building the endpoints result.
// Consult istio-dev before using this for anything else (except debugging/tools)
InstancesByPort(svc *Service, servicePort int, labels labels.Instance) []*ServiceInstance
// GetProxyServiceInstances returns the service instances that co-located with a given Proxy
//
// Co-located generally means running in the same network namespace and security context.
//
// A Proxy operating as a Sidecar will return a non-empty slice. A stand-alone Proxy
// will return an empty slice.
//
// There are two reasons why this returns multiple ServiceInstances instead of one:
// - A ServiceInstance has a single IstioEndpoint which has a single Port. But a Service
// may have many ports. So a workload implementing such a Service would need
// multiple ServiceInstances, one for each port.
// - A single workload may implement multiple logical Services.
//
// In the second case, multiple services may be implemented by the same physical port number,
// though with a different ServicePort and IstioEndpoint for each. If any of these overlapping
// services are not HTTP or H2-based, behavior is undefined, since the listener may not be able to
// determine the intended destination of a connection without a Host header on the request.
GetProxyServiceInstances(*Proxy) []*ServiceInstance
GetProxyWorkloadLabels(*Proxy) labels.Instance
// GetIstioServiceAccounts returns a list of service accounts looked up from
// the specified service hostname and ports.
// Deprecated - service account tracking moved to XdsServer, incremental.
GetIstioServiceAccounts(svc *Service, ports []int) []string
// MCSServices returns information about the services that have been exported/imported via the
// Kubernetes Multi-Cluster Services (MCS) ServiceExport API. Only applies to services in
// Kubernetes clusters.
MCSServices() []MCSServiceInfo
}
// MCSServiceInfo combines the name of a service with a particular Kubernetes cluster. This
// is used for debug information regarding the state of Kubernetes Multi-Cluster Services (MCS).
type MCSServiceInfo struct {
Cluster cluster.ID
Name string
Namespace string
Exported bool
Imported bool
ClusterSetVIP string
Discoverability map[host.Name]string
}
// GetNames returns port names
func (ports PortList) GetNames() []string {
names := make([]string, 0, len(ports))
for _, port := range ports {
names = append(names, port.Name)
}
return names
}
// Get retrieves a port declaration by name
func (ports PortList) Get(name string) (*Port, bool) {
for _, port := range ports {
if port.Name == name {
return port, true
}
}
return nil, false
}
// GetByPort retrieves a port declaration by port value
func (ports PortList) GetByPort(num int) (*Port, bool) {
for _, port := range ports {
if port.Port == num && port.Protocol != protocol.UDP {
return port, true
}
}
return nil, false
}
// External predicate checks whether the service is external
func (s *Service) External() bool {
return s.MeshExternal
}
// BuildSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
// The proxy queries Pilot with this key to obtain the list of instances in a subset.
func BuildSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
return string(direction) + "|" + strconv.Itoa(port) + "|" + subsetName + "|" + string(hostname)
}
// BuildInboundSubsetKey generates a unique string referencing service instances with port.
func BuildInboundSubsetKey(port int) string {
return BuildSubsetKey(TrafficDirectionInbound, "", "", port)
}
// BuildDNSSrvSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
// The proxy queries Pilot with this key to obtain the list of instances in a subset.
// This is used only for the SNI-DNAT router. Do not use for other purposes.
// The DNS Srv format of the cluster is also used as the default SNI string for Istio mTLS connections
func BuildDNSSrvSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
return string(direction) + "_." + strconv.Itoa(port) + "_." + subsetName + "_." + string(hostname)
}
// IsValidSubsetKey checks if a string is valid for subset key parsing.
func IsValidSubsetKey(s string) bool {
return strings.Count(s, "|") == 3
}
// IsDNSSrvSubsetKey checks whether the given key is a DNSSrv key (built by BuildDNSSrvSubsetKey).
func IsDNSSrvSubsetKey(s string) bool {
if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) ||
strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) {
return true
}
return false
}
// ParseSubsetKey is the inverse of the BuildSubsetKey method
func ParseSubsetKey(s string) (direction TrafficDirection, subsetName string, hostname host.Name, port int) {
var parts []string
dnsSrvMode := false
// This could be the DNS srv form of the cluster that uses outbound_.port_.subset_.hostname
// Since we do not want every callsite to implement the logic to differentiate between the two forms
// we add an alternate parser here.
if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) ||
strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) {
parts = strings.SplitN(s, ".", 4)
dnsSrvMode = true
} else {
parts = strings.Split(s, "|")
}
if len(parts) < 4 {
return
}
direction = TrafficDirection(strings.TrimSuffix(parts[0], "_"))
port, _ = strconv.Atoi(strings.TrimSuffix(parts[1], "_"))
subsetName = parts[2]
if dnsSrvMode {
subsetName = strings.TrimSuffix(parts[2], "_")
}
hostname = host.Name(parts[3])
return
}
// GetAddresses returns a Service's addresses.
// This method returns all the VIPs of a service if the ClusterID is explicitly set to "", otherwise only return the VIP
// specific to the cluster where the node resides
func (s *Service) GetAddresses(node *Proxy) []string {
if node.Metadata != nil && node.Metadata.ClusterID == "" {
return s.getAllAddresses()
}
return []string{s.GetAddressForProxy(node)}
}
// GetAddressForProxy returns a Service's address specific to the cluster where the node resides
func (s *Service) GetAddressForProxy(node *Proxy) string {
if node.Metadata != nil {
if node.Metadata.ClusterID != "" {
addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID)
if len(addresses) > 0 {
return addresses[0]
}
}
if node.Metadata.DNSCapture && node.Metadata.DNSAutoAllocate && s.DefaultAddress == constants.UnspecifiedIP {
if node.SupportsIPv4() && s.AutoAllocatedIPv4Address != "" {
return s.AutoAllocatedIPv4Address
}
if node.SupportsIPv6() && s.AutoAllocatedIPv6Address != "" {
return s.AutoAllocatedIPv6Address
}
}
}
return s.DefaultAddress
}
// getAllAddresses returns a Service's all addresses.
func (s *Service) getAllAddresses() []string {
var addresses []string
addressMap := s.ClusterVIPs.GetAddresses()
for _, clusterAddresses := range addressMap {
addresses = append(addresses, clusterAddresses...)
}
return addresses
}
// GetTLSModeFromEndpointLabels returns the value of the label
// security.istio.io/tlsMode if set. Do not return Enums or constants
// from this function as users could provide values other than istio/disabled
// and apply custom transport socket matchers here.
func GetTLSModeFromEndpointLabels(labels map[string]string) string {
if labels != nil {
if val, exists := labels[label.SecurityTlsMode.Name]; exists {
return val
}
}
return DisabledTLSModeLabel
}
// GetServiceAccounts returns aggregated list of service accounts of Service plus its instances.
func GetServiceAccounts(svc *Service, ports []int, discovery ServiceDiscovery) []string {
sa := sets.Set{}
instances := make([]*ServiceInstance, 0)
// Get the service accounts running service within Kubernetes. This is reflected by the pods that
// the service is deployed on, and the service accounts of the pods.
for _, port := range ports {
svcInstances := discovery.InstancesByPort(svc, port, nil)
instances = append(instances, svcInstances...)
}
for _, si := range instances {
if si.Endpoint.ServiceAccount != "" {
sa.Insert(si.Endpoint.ServiceAccount)
}
}
sa.InsertAll(svc.ServiceAccounts...)
return sa.UnsortedList()
}
// DeepCopy creates a clone of Service.
func (s *Service) DeepCopy() *Service {
// nolint: govet
out := *s
out.Attributes = s.Attributes.DeepCopy()
if s.Ports != nil {
out.Ports = make(PortList, len(s.Ports))
for i, port := range s.Ports {
if port != nil {
out.Ports[i] = &Port{
Name: port.Name,
Port: port.Port,
Protocol: port.Protocol,
}
} else {
out.Ports[i] = nil
}
}
}
if s.ServiceAccounts != nil {
out.ServiceAccounts = make([]string, len(s.ServiceAccounts))
for i, sa := range s.ServiceAccounts {
out.ServiceAccounts[i] = sa
}
}
out.ClusterVIPs = s.ClusterVIPs.DeepCopy()
return &out
}
// DeepCopy creates a clone of IstioEndpoint.
func (ep *IstioEndpoint) DeepCopy() *IstioEndpoint {
return copyInternal(ep).(*IstioEndpoint)
}
func copyInternal(v interface{}) interface{} {
copied, err := copystructure.Copy(v)
if err != nil {
// There are 2 locations where errors are generated in copystructure.Copy:
// * The reflection walk over the structure fails, which should never happen
// * A configurable copy function returns an error. This is only used for copying times, which never returns an error.
// Therefore, this should never happen
panic(err)
}
return copied
}