mirror of
https://github.com/cloudnativelabs/kube-router.git
synced 2026-05-04 22:26:16 +02:00
131 lines
4.6 KiB
Go
131 lines
4.6 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
v1core "k8s.io/api/core/v1"
|
|
discoveryv1 "k8s.io/api/discovery/v1"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
const (
|
|
IPInIPHeaderLength = 20
|
|
)
|
|
|
|
// ServiceNameforEndpointSlice returns the name of the service that created the EndpointSlice for a given EndpointSlice
|
|
//
|
|
// With endpoints, the name of the endpoint object always matches the service object, however when it comes to
|
|
// EndpointSlices, things work a bit different as k8s' controller will autogenerated it (something like: foo-kl29b)
|
|
//
|
|
// We can get service information from a number of spots:
|
|
// * From the ownerReferences in the metadata EndpointSlice -> metadata -> ownerReferences[0] -> name
|
|
// * We can also get this from the label: kubernetes.io/service-name
|
|
// * generateName will also contain the prefix for the autogenerated name which should align with our service name
|
|
//
|
|
// We'll all through all of these and do our best to identify the service's name, if we aren't able to find any of these
|
|
// or they disagree with each other we'll throw an error
|
|
func ServiceNameforEndpointSlice(es *discoveryv1.EndpointSlice) (string, error) {
|
|
const serviceNameLabel = "kubernetes.io/service-name"
|
|
var ownerRefName, labelSvcName, generateName, finalSvcName string
|
|
|
|
ownerRef := es.GetObjectMeta().GetOwnerReferences()
|
|
if len(ownerRef) == 1 {
|
|
ownerRefName = ownerRef[0].Name
|
|
}
|
|
|
|
labels := es.GetObjectMeta().GetLabels()
|
|
if svcLabel, ok := labels[serviceNameLabel]; ok {
|
|
labelSvcName = svcLabel
|
|
}
|
|
|
|
if es.GetObjectMeta().GetGenerateName() != "" {
|
|
generateName = strings.TrimRight(es.GetObjectMeta().GetGenerateName(), "-")
|
|
}
|
|
|
|
if ownerRefName == "" && labelSvcName == "" && generateName == "" {
|
|
return "", fmt.Errorf("all identifiers for service are empty on this EndpointSlice, unable to determine "+
|
|
"owning service for: %s/%s", es.Namespace, es.Name)
|
|
}
|
|
|
|
// Take things in an order of precedence here: generateName < ownerRefName < labelSvcName
|
|
finalSvcName = generateName
|
|
if ownerRefName != "" {
|
|
finalSvcName = ownerRefName
|
|
}
|
|
if labelSvcName != "" {
|
|
finalSvcName = labelSvcName
|
|
}
|
|
|
|
// At this point we do some checks to ensure that the final owning service name is sane. Specifically, we want to
|
|
// check it against labelSvcName and ownerRefName if they were not blank and log a debug log if they don't agree. We
|
|
// don't worry about generateName as that is less conclusive.
|
|
//
|
|
// See: https://github.com/cloudnativelabs/kube-router/issues/1957 for more information
|
|
if ownerRefName != "" && finalSvcName != ownerRefName {
|
|
klog.V(1).Infof("The metadata ownerReference name %s and the label service name (%s) %s don't appear to "+
|
|
"match for EndpointSlice %s/%s. In this case we prefer the label service name.",
|
|
ownerRefName, serviceNameLabel, labelSvcName, es.Namespace, es.Name)
|
|
}
|
|
|
|
return finalSvcName, nil
|
|
}
|
|
|
|
// ServiceForEndpoints given EndpointSlice object return Service API object if it exists
|
|
func ServiceForEndpointSlice(ci *cache.Indexer, es *discoveryv1.EndpointSlice) (any, bool, error) {
|
|
svcName, err := ServiceNameforEndpointSlice(es)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
// The key that we're looking for here is just <namespace>/<name>
|
|
key := fmt.Sprintf("%s/%s", es.Namespace, svcName)
|
|
klog.V(2).Infof("key for looking up service from EndpointSlice is: %s", key)
|
|
|
|
item, exists, err := (*ci).GetByKey(key)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if !exists {
|
|
return nil, false, nil
|
|
}
|
|
|
|
return item, true, nil
|
|
}
|
|
|
|
// ServiceHasNoClusterIP decides whether or not the this service is a headless service which is often useful to
|
|
// kube-router as there is no need to execute logic on most headless changes. Function takes a generic interface as its
|
|
// input parameter so that it can be used more easily in early processing if needed. If a non-service object is given,
|
|
// function will return false.
|
|
func ServiceHasNoClusterIP(obj any) bool {
|
|
if svc, _ := obj.(*v1core.Service); svc != nil {
|
|
if svc.Spec.Type == v1core.ServiceTypeClusterIP {
|
|
if ClusterIPIsNone(svc.Spec.ClusterIP) && containsOnlyNone(svc.Spec.ClusterIPs) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ClusterIPIsNone checks to see whether the ClusterIP contains "None" which would indicate that it is headless
|
|
func ClusterIPIsNone(clusterIP string) bool {
|
|
return strings.ToLower(clusterIP) == "none"
|
|
}
|
|
|
|
// ClusterIPIsNoneOrBlank checks to see whether the ClusterIP contains "None" or is blank
|
|
func ClusterIPIsNoneOrBlank(clusterIP string) bool {
|
|
return ClusterIPIsNone(clusterIP) || clusterIP == ""
|
|
}
|
|
|
|
func containsOnlyNone(clusterIPs []string) bool {
|
|
for _, clusterIP := range clusterIPs {
|
|
if !ClusterIPIsNone(clusterIP) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|