istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/util.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 controller
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"strings"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	klabels "k8s.io/apimachinery/pkg/labels"
    25  	"k8s.io/apimachinery/pkg/selection"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/apimachinery/pkg/util/intstr"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  
    30  	"istio.io/api/annotation"
    31  	"istio.io/istio/pilot/pkg/model"
    32  	"istio.io/istio/pkg/config"
    33  	"istio.io/istio/pkg/config/constants"
    34  	"istio.io/istio/pkg/config/host"
    35  	"istio.io/istio/pkg/config/labels"
    36  )
    37  
    38  func getLabelValue(metadata metav1.ObjectMeta, label string, fallBackLabel string) string {
    39  	metaLabels := metadata.GetLabels()
    40  	val := metaLabels[label]
    41  	if val != "" {
    42  		return val
    43  	}
    44  
    45  	return metaLabels[fallBackLabel]
    46  }
    47  
    48  // Forked from Kubernetes k8s.io/kubernetes/pkg/api/v1/pod
    49  // FindPort locates the container port for the given pod and portName.  If the
    50  // targetPort is a number, use that.  If the targetPort is a string, look that
    51  // string up in all named ports in all containers in the target pod.  If no
    52  // match is found, fail.
    53  func FindPort(pod *v1.Pod, svcPort *v1.ServicePort) (int, error) {
    54  	portName := svcPort.TargetPort
    55  	switch portName.Type {
    56  	case intstr.String:
    57  		name := portName.StrVal
    58  		for _, container := range pod.Spec.Containers {
    59  			for _, port := range container.Ports {
    60  				if port.Name == name && port.Protocol == svcPort.Protocol {
    61  					return int(port.ContainerPort), nil
    62  				}
    63  			}
    64  		}
    65  	case intstr.Int:
    66  		return portName.IntValue(), nil
    67  	}
    68  
    69  	return 0, fmt.Errorf("no suitable port for manifest: %s", pod.UID)
    70  }
    71  
    72  // findPortFromMetadata resolves the TargetPort of a Service Port, by reading the Pod spec.
    73  func findPortFromMetadata(svcPort v1.ServicePort, podPorts []model.PodPort) (int, error) {
    74  	target := svcPort.TargetPort
    75  
    76  	switch target.Type {
    77  	case intstr.String:
    78  		name := target.StrVal
    79  		for _, port := range podPorts {
    80  			if port.Name == name && port.Protocol == string(svcPort.Protocol) {
    81  				return port.ContainerPort, nil
    82  			}
    83  		}
    84  	case intstr.Int:
    85  		// For a direct reference we can just return the port number
    86  		return target.IntValue(), nil
    87  	}
    88  
    89  	return 0, fmt.Errorf("no matching port found for %+v", svcPort)
    90  }
    91  
    92  type serviceTargetPort struct {
    93  	// the mapped port number, or 0 if unspecified
    94  	num int
    95  	// the mapped port name
    96  	name string
    97  	// a bool indicating if the mapped port name was explicitly set on the TargetPort field, or inferred from k8s' port.Name
    98  	explicitName bool
    99  }
   100  
   101  func findServiceTargetPort(servicePort *model.Port, k8sService *v1.Service) serviceTargetPort {
   102  	for _, p := range k8sService.Spec.Ports {
   103  		// TODO(@hzxuzhonghu): check protocol as well as port
   104  		if p.Name == servicePort.Name || p.Port == int32(servicePort.Port) {
   105  			if p.TargetPort.Type == intstr.Int && p.TargetPort.IntVal > 0 {
   106  				return serviceTargetPort{num: int(p.TargetPort.IntVal), name: p.Name, explicitName: false}
   107  			}
   108  			return serviceTargetPort{num: 0, name: p.TargetPort.StrVal, explicitName: true}
   109  		}
   110  	}
   111  	// should never happen
   112  	log.Debugf("did not find matching target port for %v on service %s", servicePort, k8sService.Name)
   113  	return serviceTargetPort{num: 0, name: "", explicitName: false}
   114  }
   115  
   116  func getPodServices(allServices []*v1.Service, pod *v1.Pod) []*v1.Service {
   117  	var services []*v1.Service
   118  	for _, service := range allServices {
   119  		if labels.Instance(service.Spec.Selector).Match(pod.Labels) {
   120  			services = append(services, service)
   121  		}
   122  	}
   123  
   124  	return services
   125  }
   126  
   127  func getNodeSelectorsForService(svc *v1.Service) labels.Instance {
   128  	if nodeSelector := svc.Annotations[annotation.TrafficNodeSelector.Name]; nodeSelector != "" {
   129  		var nodeSelectorKV map[string]string
   130  		if err := json.Unmarshal([]byte(nodeSelector), &nodeSelectorKV); err != nil {
   131  			log.Debugf("failed to unmarshal node selector annotation value for service %s.%s: %v",
   132  				svc.Name, svc.Namespace, err)
   133  		}
   134  		return nodeSelectorKV
   135  	}
   136  	return nil
   137  }
   138  
   139  func nodeEquals(a, b kubernetesNode) bool {
   140  	return a.address == b.address && a.labels.Equals(b.labels)
   141  }
   142  
   143  func isNodePortGatewayService(svc *v1.Service) bool {
   144  	if svc == nil {
   145  		return false
   146  	}
   147  	_, ok := svc.Annotations[annotation.TrafficNodeSelector.Name]
   148  	return ok && svc.Spec.Type == v1.ServiceTypeNodePort
   149  }
   150  
   151  // Get the pod key of the proxy which can be used to get pod from the informer cache
   152  func podKeyByProxy(proxy *model.Proxy) types.NamespacedName {
   153  	parts := strings.Split(proxy.ID, ".")
   154  	if len(parts) == 2 && proxy.Metadata.Namespace == parts[1] {
   155  		return types.NamespacedName{Name: parts[0], Namespace: parts[1]}
   156  	}
   157  
   158  	return types.NamespacedName{}
   159  }
   160  
   161  func namespacedNameForService(svc *model.Service) types.NamespacedName {
   162  	return types.NamespacedName{
   163  		Namespace: svc.Attributes.Namespace,
   164  		Name:      svc.Attributes.Name,
   165  	}
   166  }
   167  
   168  // serviceClusterSetLocalHostname produces Kubernetes Multi-Cluster Services (MCS) ClusterSet FQDN for a k8s service
   169  func serviceClusterSetLocalHostname(nn types.NamespacedName) host.Name {
   170  	return host.Name(nn.Name + "." + nn.Namespace + "." + "svc" + "." + constants.DefaultClusterSetLocalDomain)
   171  }
   172  
   173  // serviceClusterSetLocalHostnameForKR calls serviceClusterSetLocalHostname with the name and namespace of the given kubernetes resource.
   174  func serviceClusterSetLocalHostnameForKR(obj metav1.Object) host.Name {
   175  	return serviceClusterSetLocalHostname(config.NamespacedName(obj))
   176  }
   177  
   178  func labelRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) *klabels.Requirement {
   179  	out, err := klabels.NewRequirement(key, op, vals, opts...)
   180  	if err != nil {
   181  		panic(fmt.Sprintf("failed creating requirements for Service: %v", err))
   182  	}
   183  	return out
   184  }