package utils import ( "fmt" "strings" v1core "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" ) const ( IPInIPHeaderLength = 20 ) // ServiceForEndpoints given Endpoint object return Service API object if it exists func ServiceForEndpoints(ci *cache.Indexer, ep *v1core.Endpoints) (interface{}, bool, error) { key, err := cache.MetaNamespaceKeyFunc(ep) if err != nil { return nil, false, err } klog.V(2).Infof("key for looking up service from Endpoint 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 } // 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 *discovery.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 return error if they don't agree. We // don't worry about generateName as that is less conclusive. // // From above, we already know that if labelSvcName was not blank then it is equal to finalSvcName, so we only need // to worry about ownerRefName if ownerRefName != "" && finalSvcName != ownerRefName { return "", fmt.Errorf("the ownerReferences field on EndpointSlice (%s) doesn't agree with with the %s label "+ "(%s) for %s/%s EndpointSlice", 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 *discovery.EndpointSlice) (interface{}, bool, error) { svcName, err := ServiceNameforEndpointSlice(es) if err != nil { return nil, false, err } // The key that we're looking for here is just / 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 } // ServiceIsHeadless 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 ServiceIsHeadless(obj interface{}) 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 }