RemoveHintsFromSlices

// RemoveHintsFromSlices removes topology hints on EndpointSlices and returns
// updated lists of EndpointSlices to create and update.
func RemoveHintsFromSlices(si *SliceInfo) ([]*discovery.EndpointSlice, []*discovery.EndpointSlice) {
	// Remove hints on all EndpointSlices we were already going to change.
	slices := append(si.ToCreate, si.ToUpdate...)
	for _, slice := range slices {
		for i := range slice.Endpoints {
			slice.Endpoints[i].Hints = nil
		}
	}

	// Remove hints on all unchanged EndpointSlices and mark them for update
	// if any already had hints. We use j to track the number/index of slices
	// that are still unchanged.
	j := 0
	for _, slice := range si.Unchanged {
		changed := false
		for i, endpoint := range slice.Endpoints {
			if endpoint.Hints != nil {
				// Unchanged slices are still direct copies from informer cache.
				// Need to deep copy before we make any modifications to avoid
				// accidentally changing informer cache.
				slice = slice.DeepCopy()
				slice.Endpoints[i].Hints = nil
				changed = true
			}
		}
		if changed {
			si.ToUpdate = append(si.ToUpdate, slice)
		} else {
			si.Unchanged[j] = slice
			j++
		}
	}

	// truncate si.Unchanged so it only includes slices that are still
	// unchanged.
	si.Unchanged = si.Unchanged[:j]

	return si.ToCreate, si.ToUpdate
}


FormatWithAddressType

// FormatWithAddressType foramts a given string by adding an addressType to the end of it.
func FormatWithAddressType(s string, addressType discovery.AddressType) string {
	return fmt.Sprintf("%s, addressType: %s", s, addressType)
}


redistributeHints

// redistributeHints redistributes hints based in the provided EndpointSlices.
// It allocates endpoints from the provided givingZones to the provided
// receivingZones. This returns a map that represents the changes in allocated
// endpoints by zone.
func redistributeHints(slices []*discovery.EndpointSlice, givingZones, receivingZones map[string]int) map[string]int {
	redistributions := map[string]int{}

	for _, slice := range slices {
		for i, endpoint := range slice.Endpoints {
			if !endpointsliceutil.EndpointReady(endpoint) {
				endpoint.Hints = nil
				continue
			}
			if len(givingZones) == 0 || len(receivingZones) == 0 {
				return redistributions
			}
			if endpoint.Zone == nil || *endpoint.Zone == "" {
				// This should always be caught earlier in AddHints()
				klog.Warningf("Endpoint found without zone specified")
				continue
			}

			givingZone := *endpoint.Zone
			numToGive, ok := givingZones[givingZone]
			if ok && numToGive > 0 {
				for receivingZone, numToReceive := range receivingZones {
					if numToReceive > 0 {
						slice.Endpoints[i].Hints = &discovery.EndpointHints{ForZones: []discovery.ForZone{{Name: receivingZone}}}
						if numToGive == 1 {
							delete(givingZones, givingZone)
						} else {
							givingZones[givingZone]--
						}
						if numToReceive == 1 {
							delete(receivingZones, receivingZone)
						} else {
							receivingZones[receivingZone]--
						}

						redistributions[receivingZone]++
						redistributions[givingZone]--

						break
					}
				}
			}
		}
	}
	return redistributions
}


getGivingAndReceivingZones

// getGivingAndReceivingZones returns the number of endpoints each zone should
// give to other zones along with the number of endpoints each zone should
// receive from other zones. This is calculated with the provided allocations
// (desired state) and allocatedHintsByZone (current state).
func getGivingAndReceivingZones(allocations map[string]Allocation, allocatedHintsByZone map[string]int) (map[string]int, map[string]int) {
	// 1. Determine the precise number of additional endpoints each zone has
	//    (giving) or needs (receiving).
	givingZonesDesired := map[string]float64{}
	receivingZonesDesired := map[string]float64{}

	for zone, allocation := range allocations {
		allocatedHints, _ := allocatedHintsByZone[zone]
		target := allocation.Desired
		if float64(allocatedHints) > target {
			givingZonesDesired[zone] = float64(allocatedHints) - target
		} else if float64(allocatedHints) < target {
			receivingZonesDesired[zone] = target - float64(allocatedHints)
		}
	}

	// 2. Convert the precise numbers needed into ints representing real
	//    endpoints given from one zone to another.
	givingZones := map[string]int{}
	receivingZones := map[string]int{}

	for {
		givingZone, numToGive := getMost(givingZonesDesired)
		receivingZone, numToReceive := getMost(receivingZonesDesired)

		// return early if any of the following are true:
		// - giving OR receiving zone are unspecified
		// - giving AND receiving zones have less than 1 endpoint left to give or receive
		// - giving OR receiving zones have less than 0.5 endpoints left to give or receive
		if givingZone == "" || receivingZone == "" || (numToGive < 1.0 && numToReceive < 1.0) || numToGive < 0.5 || numToReceive < 0.5 {
			break
		}

		givingZones[givingZone]++
		givingZonesDesired[givingZone]--
		receivingZones[receivingZone]++
		receivingZonesDesired[receivingZone]--
	}

	return givingZones, receivingZones
}


getMost

// getMost accepts a map[string]float64 and returns the string and float64 that
// represent the greatest value in this provided map. This function is not very
// efficient but it is expected that len() will rarely be greater than 2.
func getMost(zones map[string]float64) (string, float64) {
	zone := ""
	num := 0.0
	for z, n := range zones {
		if n > num {
			zone = z
			num = n
		}
	}

	return zone, num
}


getHintsByZone

// getHintsByZone returns the number of hints allocated to each zone by the
// provided EndpointSlice. This function returns nil to indicate that the
// current allocations are invalid and that the EndpointSlice needs to be
// updated. This could be caused by:
// - A hint for a zone that no longer requires any allocations.
// - An endpoint with no hints.
// - Hints that would make minimum allocations impossible.
func getHintsByZone(slice *discovery.EndpointSlice, allocatedHintsByZone EndpointZoneInfo, allocations map[string]Allocation) map[string]int {
	hintsByZone := map[string]int{}
	for _, endpoint := range slice.Endpoints {
		if !endpointsliceutil.EndpointReady(endpoint) {
			continue
		}
		if endpoint.Hints == nil || len(endpoint.Hints.ForZones) == 0 {
			return nil
		}

		for i := range endpoint.Hints.ForZones {
			zone := endpoint.Hints.ForZones[i].Name
			if _, ok := allocations[zone]; !ok {
				return nil
			}
			hintsByZone[zone]++
		}
	}

	for zone, numHints := range hintsByZone {
		alreadyAllocated, _ := allocatedHintsByZone[zone]
		allocation, ok := allocations[zone]
		if !ok || (numHints+alreadyAllocated) > allocation.Maximum {
			return nil
		}
	}

	return hintsByZone
}


serviceOverloaded

// serviceOverloaded returns true if the Service has an insufficient amount of
// endpoints for any zone.
func serviceOverloaded(ezi EndpointZoneInfo, zoneRatios map[string]float64) bool {
	if len(ezi) == 0 {
		return false
	}
	if len(zoneRatios) == 0 {
		return true
	}

	totalEndpoints := 0.0
	for _, numEndpoints := range ezi {
		totalEndpoints += float64(numEndpoints)
	}

	for zone, ratio := range zoneRatios {
		svcEndpoints, ok := ezi[zone]
		if !ok {
			return true
		}
		minEndpoints := math.Ceil(totalEndpoints * ratio * (1 / (1 + OverloadThreshold)))
		if svcEndpoints < int(minEndpoints) {
			return true
		}
	}

	return false
}


NodeReady

// NodeReady returns true if the Node has a status condition of type "NodeReady"
// with a status of "True".
func NodeReady(nodeStatus v1.NodeStatus) bool {
	for _, cond := range nodeStatus.Conditions {
		if cond.Type == v1.NodeReady {
			return cond.Status == v1.ConditionTrue
		}
	}
	return false
}


numReadyEndpoints

// numReadyEndpoints returns the number of Endpoints that are ready from the
// specified list.
func numReadyEndpoints(endpoints []discovery.Endpoint) int {
	var total int
	for _, ep := range endpoints {
		if ep.Conditions.Ready != nil && *ep.Conditions.Ready {
			total++
		}
	}
	return total
}


podToEndpoint

// podToEndpoint returns an Endpoint object generated from a Pod, a Node, and a Service for a particular addressType.
func podToEndpoint(pod *v1.Pod, node *v1.Node, service *v1.Service, addressType discovery.AddressType) discovery.Endpoint {
	serving := podutil.IsPodReady(pod)
	terminating := pod.DeletionTimestamp != nil
	// For compatibility reasons, "ready" should never be "true" if a pod is terminatng, unless
	// publishNotReadyAddresses was set.
	ready := service.Spec.PublishNotReadyAddresses || (serving && !terminating)
	ep := discovery.Endpoint{
		Addresses: getEndpointAddresses(pod.Status, service, addressType),
		Conditions: discovery.EndpointConditions{
			Ready:       &ready,
			Serving:     &serving,
			Terminating: &terminating,
		},
		TargetRef: &v1.ObjectReference{
			Kind:      "Pod",
			Namespace: pod.ObjectMeta.Namespace,
			Name:      pod.ObjectMeta.Name,
			UID:       pod.ObjectMeta.UID,
		},
	}

	if pod.Spec.NodeName != "" {
		ep.NodeName = &pod.Spec.NodeName
	}

	if node != nil && node.Labels[v1.LabelTopologyZone] != "" {
		zone := node.Labels[v1.LabelTopologyZone]
		ep.Zone = &zone
	}

	if endpointutil.ShouldSetHostname(pod, service) {
		ep.Hostname = &pod.Spec.Hostname
	}

	return ep
}


getEndpointPorts

// getEndpointPorts returns a list of EndpointPorts generated from a Service
// and Pod.
func getEndpointPorts(service *v1.Service, pod *v1.Pod) []discovery.EndpointPort {
	endpointPorts := []discovery.EndpointPort{}

	// Allow headless service not to have ports.
	if len(service.Spec.Ports) == 0 && service.Spec.ClusterIP == api.ClusterIPNone {
		return endpointPorts
	}

	for i := range service.Spec.Ports {
		servicePort := &service.Spec.Ports[i]

		portName := servicePort.Name
		portProto := servicePort.Protocol
		portNum, err := podutil.FindPort(pod, servicePort)
		if err != nil {
			klog.V(4).Infof("Failed to find port for service %s/%s: %v", service.Namespace, service.Name, err)
			continue
		}

		i32PortNum := int32(portNum)
		endpointPorts = append(endpointPorts, discovery.EndpointPort{
			Name:        &portName,
			Port:        &i32PortNum,
			Protocol:    &portProto,
			AppProtocol: servicePort.AppProtocol,
		})
	}

	return endpointPorts
}


getEndpointAddresses

// getEndpointAddresses returns a list of addresses generated from a pod status.
func getEndpointAddresses(podStatus v1.PodStatus, service *v1.Service, addressType discovery.AddressType) []string {
	addresses := []string{}

	for _, podIP := range podStatus.PodIPs {
		isIPv6PodIP := utilnet.IsIPv6String(podIP.IP)
		if isIPv6PodIP && addressType == discovery.AddressTypeIPv6 {
			addresses = append(addresses, podIP.IP)
		}

		if !isIPv6PodIP && addressType == discovery.AddressTypeIPv4 {
			addresses = append(addresses, podIP.IP)
		}
	}

	return addresses
}


newEndpointSlice

// newEndpointSlice returns an EndpointSlice generated from a service and
// endpointMeta.
func newEndpointSlice(service *v1.Service, endpointMeta *endpointMeta) *discovery.EndpointSlice {
	gvk := schema.GroupVersionKind{Version: "v1", Kind: "Service"}
	ownerRef := metav1.NewControllerRef(service, gvk)
	epSlice := &discovery.EndpointSlice{
		ObjectMeta: metav1.ObjectMeta{
			Labels:          map[string]string{},
			GenerateName:    getEndpointSlicePrefix(service.Name),
			OwnerReferences: []metav1.OwnerReference{*ownerRef},
			Namespace:       service.Namespace,
		},
		Ports:       endpointMeta.Ports,
		AddressType: endpointMeta.AddressType,
		Endpoints:   []discovery.Endpoint{},
	}
	// add parent service labels
	epSlice.Labels, _ = setEndpointSliceLabels(epSlice, service)

	return epSlice
}


getEndpointSlicePrefix

// getEndpointSlicePrefix returns a suitable prefix for an EndpointSlice name.
func getEndpointSlicePrefix(serviceName string) string {
	// use the dash (if the name isn't too long) to make the pod name a bit prettier
	prefix := fmt.Sprintf("%s-", serviceName)
	if len(validation.ValidateEndpointSliceName(prefix, true)) != 0 {
		prefix = serviceName
	}
	return prefix
}


ownedBy

// ownedBy returns true if the provided EndpointSlice is owned by the provided
// Service.
func ownedBy(endpointSlice *discovery.EndpointSlice, svc *v1.Service) bool {
	for _, o := range endpointSlice.OwnerReferences {
		if o.UID == svc.UID && o.Kind == "Service" && o.APIVersion == "v1" {
			return true
		}
	}
	return false
}


getSliceToFill

// getSliceToFill will return the EndpointSlice that will be closest to full
// when numEndpoints are added. If no EndpointSlice can be found, a nil pointer
// will be returned.
func getSliceToFill(endpointSlices []*discovery.EndpointSlice, numEndpoints, maxEndpoints int) (slice *discovery.EndpointSlice) {
	closestDiff := maxEndpoints
	var closestSlice *discovery.EndpointSlice
	for _, endpointSlice := range endpointSlices {
		currentDiff := maxEndpoints - (numEndpoints + len(endpointSlice.Endpoints))
		if currentDiff >= 0 && currentDiff < closestDiff {
			closestDiff = currentDiff
			closestSlice = endpointSlice
			if closestDiff == 0 {
				return closestSlice
			}
		}
	}
	return closestSlice
}


getEndpointSliceFromDeleteAction

// getEndpointSliceFromDeleteAction parses an EndpointSlice from a delete action.
func getEndpointSliceFromDeleteAction(obj interface{}) *discovery.EndpointSlice {
	if endpointSlice, ok := obj.(*discovery.EndpointSlice); ok {
		// Enqueue all the services that the pod used to be a member of.
		// This is the same thing we do when we add a pod.
		return endpointSlice
	}
	// If we reached here it means the pod was deleted but its final state is unrecorded.
	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
		return nil
	}
	endpointSlice, ok := tombstone.Obj.(*discovery.EndpointSlice)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a EndpointSlice: %#v", obj))
		return nil
	}
	return endpointSlice
}


addTriggerTimeAnnotation

// addTriggerTimeAnnotation adds a triggerTime annotation to an EndpointSlice
func addTriggerTimeAnnotation(endpointSlice *discovery.EndpointSlice, triggerTime time.Time) {
	if endpointSlice.Annotations == nil {
		endpointSlice.Annotations = make(map[string]string)
	}

	if !triggerTime.IsZero() {
		endpointSlice.Annotations[v1.EndpointsLastChangeTriggerTime] = triggerTime.UTC().Format(time.RFC3339Nano)
	} else { // No new trigger time, clear the annotation.
		delete(endpointSlice.Annotations, v1.EndpointsLastChangeTriggerTime)
	}
}


serviceControllerKey

// serviceControllerKey returns a controller key for a Service but derived from
// an EndpointSlice.
func serviceControllerKey(endpointSlice *discovery.EndpointSlice) (string, error) {
	if endpointSlice == nil {
		return "", fmt.Errorf("nil EndpointSlice passed to serviceControllerKey()")
	}
	serviceName, ok := endpointSlice.Labels[discovery.LabelServiceName]
	if !ok || serviceName == "" {
		return "", fmt.Errorf("EndpointSlice missing %s label", discovery.LabelServiceName)
	}
	return fmt.Sprintf("%s/%s", endpointSlice.Namespace, serviceName), nil
}


setEndpointSliceLabels

// setEndpointSliceLabels returns a map with the new endpoint slices labels and true if there was an update.
// Slices labels must be equivalent to the Service labels except for the reserved IsHeadlessService, LabelServiceName and LabelManagedBy labels
// Changes to IsHeadlessService, LabelServiceName and LabelManagedBy labels on the Service do not result in updates to EndpointSlice labels.
func setEndpointSliceLabels(epSlice *discovery.EndpointSlice, service *v1.Service) (map[string]string, bool) {
	updated := false
	epLabels := make(map[string]string)
	svcLabels := make(map[string]string)

	// check if the endpoint slice and the service have the same labels
	// clone current slice labels except the reserved labels
	for key, value := range epSlice.Labels {
		if IsReservedLabelKey(key) {
			continue
		}
		// copy endpoint slice labels
		epLabels[key] = value
	}

	for key, value := range service.Labels {
		if IsReservedLabelKey(key) {
			klog.Warningf("Service %s/%s using reserved endpoint slices label, skipping label %s: %s", service.Namespace, service.Name, key, value)
			continue
		}
		// copy service labels
		svcLabels[key] = value
	}

	// if the labels are not identical update the slice with the corresponding service labels
	if !apiequality.Semantic.DeepEqual(epLabels, svcLabels) {
		updated = true
	}

	// add or remove headless label depending on the service Type
	if !helper.IsServiceIPSet(service) {
		svcLabels[v1.IsHeadlessService] = ""
	} else {
		delete(svcLabels, v1.IsHeadlessService)
	}

	// override endpoint slices reserved labels
	svcLabels[discovery.LabelServiceName] = service.Name
	svcLabels[discovery.LabelManagedBy] = controllerName

	return svcLabels, updated
}


IsReservedLabelKey

// IsReservedLabelKey return true if the label is one of the reserved label for slices
func IsReservedLabelKey(label string) bool {
	if label == discovery.LabelServiceName ||
		label == discovery.LabelManagedBy ||
		label == v1.IsHeadlessService {
		return true
	}
	return false
}


Len

func (sl endpointSliceEndpointLen) Len() int      { return len(sl) }


Swap

func (sl endpointSliceEndpointLen) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] }


Less

func (sl endpointSliceEndpointLen) Less(i, j int) bool {
	return len(sl[i].Endpoints) > len(sl[j].Endpoints)
}


getAddressTypesForService

// returns a map of address types used by a service
func getAddressTypesForService(service *v1.Service) map[discovery.AddressType]struct{} {
	serviceSupportedAddresses := make(map[discovery.AddressType]struct{})
	// TODO: (khenidak) when address types are removed in favor of
	// v1.IPFamily this will need to be removed, and work directly with
	// v1.IPFamily types

	// IMPORTANT: we assume that IP of (discovery.AddressType enum) is never in use
	// as it gets deprecated
	for _, family := range service.Spec.IPFamilies {
		if family == v1.IPv4Protocol {
			serviceSupportedAddresses[discovery.AddressTypeIPv4] = struct{}{}
		}

		if family == v1.IPv6Protocol {
			serviceSupportedAddresses[discovery.AddressTypeIPv6] = struct{}{}
		}
	}

	if len(serviceSupportedAddresses) > 0 {
		return serviceSupportedAddresses // we have found families for this service
	}

	// TODO (khenidak) remove when (1) dual stack becomes
	// enabled by default (2) v1.19 falls off supported versions

	// Why do we need this:
	// a cluster being upgraded to the new apis
	// will have service.spec.IPFamilies: nil
	// if the controller manager connected to old api
	// server. This will have the nasty side effect of
	// removing all slices already created for this service.
	// this will disable all routing to service vip (ClusterIP)
	// this ensures that this does not happen. Same for headless services
	// we assume it is dual stack, until they get defaulted by *new* api-server
	// this ensures that traffic is not disrupted  until then. But *may*
	// include undesired families for headless services until then.

	if len(service.Spec.ClusterIP) > 0 && service.Spec.ClusterIP != v1.ClusterIPNone { // headfull
		addrType := discovery.AddressTypeIPv4
		if utilnet.IsIPv6String(service.Spec.ClusterIP) {
			addrType = discovery.AddressTypeIPv6
		}
		serviceSupportedAddresses[addrType] = struct{}{}
		klog.V(2).Infof("couldn't find ipfamilies for service: %v/%v. This could happen if controller manager is connected to an old apiserver that does not support ip families yet. EndpointSlices for this Service will use %s as the IP Family based on familyOf(ClusterIP:%v).", service.Namespace, service.Name, addrType, service.Spec.ClusterIP)
		return serviceSupportedAddresses
	}

	// headless
	// for now we assume two families. This should have minimal side effect
	// if the service is headless with no selector, then this will remain the case
	// if the service is headless with selector then chances are pods are still using single family
	// since kubelet will need to restart in order to start patching pod status with multiple ips
	serviceSupportedAddresses[discovery.AddressTypeIPv4] = struct{}{}
	serviceSupportedAddresses[discovery.AddressTypeIPv6] = struct{}{}
	klog.V(2).Infof("couldn't find ipfamilies for headless service: %v/%v likely because controller manager is likely connected to an old apiserver that does not support ip families yet. The service endpoint slice will use dual stack families until api-server default it correctly", service.Namespace, service.Name)
	return serviceSupportedAddresses
}


unchangedSlices

func unchangedSlices(existingSlices, slicesToUpdate, slicesToDelete []*discovery.EndpointSlice) []*discovery.EndpointSlice {
	changedSliceNames := sets.String{}
	for _, slice := range slicesToUpdate {
		changedSliceNames.Insert(slice.Name)
	}
	for _, slice := range slicesToDelete {
		changedSliceNames.Insert(slice.Name)
	}
	unchangedSlices := []*discovery.EndpointSlice{}
	for _, slice := range existingSlices {
		if !changedSliceNames.Has(slice.Name) {
			unchangedSlices = append(unchangedSlices, slice)
		}
	}

	return unchangedSlices
}


hintsEnabled

// hintsEnabled returns true if the provided annotations include either
// v1.AnnotationTopologyMode or v1.DeprecatedAnnotationTopologyAwareHints key
// with a value set to "Auto" or "auto". When both are set,
// v1.DeprecatedAnnotationTopologyAwareHints has precedence.
func hintsEnabled(annotations map[string]string) bool {
	val, ok := annotations[v1.DeprecatedAnnotationTopologyAwareHints]
	if !ok {
		val, ok = annotations[v1.AnnotationTopologyMode]
		if !ok {
			return false
		}
	}
	return val == "Auto" || val == "auto"
}


managedByChanged

// managedByChanged returns true if one of the provided EndpointSlices is
// managed by the EndpointSlice controller while the other is not.
func managedByChanged(endpointSlice1, endpointSlice2 *discovery.EndpointSlice) bool {
	return managedByController(endpointSlice1) != managedByController(endpointSlice2)
}


managedByController

// managedByController returns true if the controller of the provided
// EndpointSlices is the EndpointSlice controller.
func managedByController(endpointSlice *discovery.EndpointSlice) bool {
	managedBy, _ := endpointSlice.Labels[discovery.LabelManagedBy]
	return managedBy == controllerName
}


// Convert_v1alpha1_EndpointSliceMirroringControllerConfiguration_To_config_EndpointSliceMirroringControllerConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_EndpointSliceMirroringControllerConfiguration_To_config_EndpointSliceMirroringControllerConfiguration(in *v1alpha1.EndpointSliceMirroringControllerConfiguration, out *endpointslicemirroringconfig.EndpointSliceMirroringControllerConfiguration, s conversion.Scope) error {
	return autoConvert_v1alpha1_EndpointSliceMirroringControllerConfiguration_To_config_EndpointSliceMirroringControllerConfiguration(in, out, s)
}


// Convert_config_EndpointSliceMirroringControllerConfiguration_To_v1alpha1_EndpointSliceMirroringControllerConfiguration is an autogenerated conversion function.
func Convert_config_EndpointSliceMirroringControllerConfiguration_To_v1alpha1_EndpointSliceMirroringControllerConfiguration(in *endpointslicemirroringconfig.EndpointSliceMirroringControllerConfiguration, out *v1alpha1.EndpointSliceMirroringControllerConfiguration, s conversion.Scope) error {
	return autoConvert_config_EndpointSliceMirroringControllerConfiguration_To_v1alpha1_EndpointSliceMirroringControllerConfiguration(in, out, s)
}


// RecommendedDefaultEndpointSliceMirroringControllerConfiguration defaults a
// pointer to a EndpointSliceMirroringControllerConfiguration struct. This will
// set the recommended default values, but they may be subject to change between
// API versions. This function is intentionally not registered in the scheme as
// a "normal" `SetDefaults_Foo` function to allow consumers of this type to set
// whatever defaults for their embedded configs. Forcing consumers to use these
// defaults would be problematic as defaulting in the scheme is done as part of
// the conversion, and there would be no easy way to opt-out. Instead, if you
// want to use this defaulting method run it in your wrapper struct of this type
// in its `SetDefaults_` method.
func RecommendedDefaultEndpointSliceMirroringControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.EndpointSliceMirroringControllerConfiguration) {
	if obj.MirroringConcurrentServiceEndpointSyncs == 0 {
		obj.MirroringConcurrentServiceEndpointSyncs = 5
	}

	if obj.MirroringMaxEndpointsPerSubset == 0 {
		obj.MirroringMaxEndpointsPerSubset = 1000
	}
}


NewController

// NewController creates and initializes a new Controller
func NewController(endpointsInformer coreinformers.EndpointsInformer,
	endpointSliceInformer discoveryinformers.EndpointSliceInformer,
	serviceInformer coreinformers.ServiceInformer,
	maxEndpointsPerSubset int32,
	client clientset.Interface,
	endpointUpdatesBatchPeriod time.Duration,
) *Controller {
	broadcaster := record.NewBroadcaster()
	recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "endpoint-slice-mirroring-controller"})

	metrics.RegisterMetrics()

	c := &Controller{
		client: client,
		// This is similar to the DefaultControllerRateLimiter, just with a
		// significantly higher default backoff (1s vs 5ms). This controller
		// processes events that can require significant EndpointSlice changes.
		// A more significant rate limit back off here helps ensure that the
		// Controller does not overwhelm the API Server.
		queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewMaxOfRateLimiter(
			workqueue.NewItemExponentialFailureRateLimiter(defaultSyncBackOff, maxSyncBackOff),
			// 10 qps, 100 bucket size. This is only for retry speed and its
			// only the overall factor (not per item).
			&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
		), "endpoint_slice_mirroring"),
		workerLoopPeriod: time.Second,
	}

	endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.onEndpointsAdd,
		UpdateFunc: c.onEndpointsUpdate,
		DeleteFunc: c.onEndpointsDelete,
	})
	c.endpointsLister = endpointsInformer.Lister()
	c.endpointsSynced = endpointsInformer.Informer().HasSynced

	endpointSliceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.onEndpointSliceAdd,
		UpdateFunc: c.onEndpointSliceUpdate,
		DeleteFunc: c.onEndpointSliceDelete,
	})

	c.endpointSliceLister = endpointSliceInformer.Lister()
	c.endpointSlicesSynced = endpointSliceInformer.Informer().HasSynced
	c.endpointSliceTracker = endpointsliceutil.NewEndpointSliceTracker()

	c.serviceLister = serviceInformer.Lister()
	c.servicesSynced = serviceInformer.Informer().HasSynced
	serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.onServiceAdd,
		UpdateFunc: c.onServiceUpdate,
		DeleteFunc: c.onServiceDelete,
	})

	c.maxEndpointsPerSubset = maxEndpointsPerSubset

	c.reconciler = &reconciler{
		client:                c.client,
		maxEndpointsPerSubset: c.maxEndpointsPerSubset,
		endpointSliceTracker:  c.endpointSliceTracker,
		metricsCache:          metrics.NewCache(maxEndpointsPerSubset),
		eventRecorder:         recorder,
	}

	c.eventBroadcaster = broadcaster
	c.eventRecorder = recorder

	c.endpointUpdatesBatchPeriod = endpointUpdatesBatchPeriod

	return c
}


Run

// Run will not return until stopCh is closed.
func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	// Start events processing pipeline.
	c.eventBroadcaster.StartLogging(klog.Infof)
	c.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.client.CoreV1().Events("")})
	defer c.eventBroadcaster.Shutdown()

	defer c.queue.ShutDown()

	klog.Infof("Starting EndpointSliceMirroring controller")
	defer klog.Infof("Shutting down EndpointSliceMirroring controller")

	if !cache.WaitForNamedCacheSync("endpoint_slice_mirroring", stopCh, c.endpointsSynced, c.endpointSlicesSynced, c.servicesSynced) {
		return
	}

	klog.V(2).Infof("Starting %d worker threads", workers)
	for i := 0; i < workers; i++ {
		go wait.Until(c.worker, c.workerLoopPeriod, stopCh)
	}

	<-stopCh
}


worker

// worker runs a worker thread that just dequeues items, processes them, and
// marks them done. You may run as many of these in parallel as you wish; the
// workqueue guarantees that they will not end up processing the same service
// at the same time
func (c *Controller) worker() {
	for c.processNextWorkItem() {
	}
}


processNextWorkItem

func (c *Controller) processNextWorkItem() bool {
	cKey, quit := c.queue.Get()
	if quit {
		return false
	}
	defer c.queue.Done(cKey)

	err := c.syncEndpoints(cKey.(string))
	c.handleErr(err, cKey)

	return true
}


handleErr

func (c *Controller) handleErr(err error, key interface{}) {
	if err == nil {
		c.queue.Forget(key)
		return
	}

	if c.queue.NumRequeues(key) < maxRetries {
		klog.Warningf("Error mirroring EndpointSlices for %q Endpoints, retrying. Error: %v", key, err)
		c.queue.AddRateLimited(key)
		return
	}

	klog.Warningf("Retry budget exceeded, dropping %q Endpoints out of the queue: %v", key, err)
	c.queue.Forget(key)
	utilruntime.HandleError(err)
}


syncEndpoints

func (c *Controller) syncEndpoints(key string) error {
	startTime := time.Now()
	defer func() {
		syncDuration := float64(time.Since(startTime).Milliseconds()) / 1000
		metrics.EndpointsSyncDuration.WithLabelValues().Observe(syncDuration)
		klog.V(4).Infof("Finished syncing EndpointSlices for %q Endpoints. (%v)", key, time.Since(startTime))
	}()

	klog.V(4).Infof("syncEndpoints(%q)", key)

	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		return err
	}

	endpoints, err := c.endpointsLister.Endpoints(namespace).Get(name)
	if err != nil {
		if apierrors.IsNotFound(err) {
			klog.V(4).Infof("%s/%s Endpoints not found, cleaning up any mirrored EndpointSlices", namespace, name)
			c.endpointSliceTracker.DeleteService(namespace, name)
			return c.deleteMirroredSlices(namespace, name)
		}
		return err
	}

	if !c.shouldMirror(endpoints) {
		klog.V(4).Infof("%s/%s Endpoints should not be mirrored, cleaning up any mirrored EndpointSlices", namespace, name)
		c.endpointSliceTracker.DeleteService(namespace, name)
		return c.deleteMirroredSlices(namespace, name)
	}

	svc, err := c.serviceLister.Services(namespace).Get(name)
	if err != nil {
		if apierrors.IsNotFound(err) {
			klog.V(4).Infof("%s/%s Service not found, cleaning up any mirrored EndpointSlices", namespace, name)
			c.endpointSliceTracker.DeleteService(namespace, name)
			return c.deleteMirroredSlices(namespace, name)
		}
		return err
	}

	// If a selector is specified, clean up any mirrored slices.
	if svc.Spec.Selector != nil {
		klog.V(4).Infof("%s/%s Service now has selector, cleaning up any mirrored EndpointSlices", namespace, name)
		c.endpointSliceTracker.DeleteService(namespace, name)
		return c.deleteMirroredSlices(namespace, name)
	}

	endpointSlices, err := endpointSlicesMirroredForService(c.endpointSliceLister, namespace, name)
	if err != nil {
		return err
	}

	if c.endpointSliceTracker.StaleSlices(svc, endpointSlices) {
		return endpointsliceutil.NewStaleInformerCache("EndpointSlice informer cache is out of date")
	}

	err = c.reconciler.reconcile(endpoints, endpointSlices)
	if err != nil {
		return err
	}

	return nil
}


queueEndpoints

// queueEndpoints queues the Endpoints resource for processing.
func (c *Controller) queueEndpoints(obj interface{}) {
	key, err := controller.KeyFunc(obj)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v (type %T): %v", obj, obj, err))
		return
	}

	c.queue.Add(key)
}


shouldMirror

// shouldMirror returns true if an Endpoints resource should be mirrored by this
// controller. This will be false if:
// - the Endpoints resource is nil.
// - the Endpoints resource has a skip-mirror label.
// - the Endpoints resource has a leader election annotation.
// This does not ensure that a corresponding Service exists with a nil selector.
// That check should be performed separately.
func (c *Controller) shouldMirror(endpoints *v1.Endpoints) bool {
	if endpoints == nil || skipMirror(endpoints.Labels) || hasLeaderElection(endpoints.Annotations) {
		return false
	}

	return true
}


onServiceAdd

// onServiceAdd queues a sync for the relevant Endpoints resource.
func (c *Controller) onServiceAdd(obj interface{}) {
	service := obj.(*v1.Service)
	if service == nil {
		utilruntime.HandleError(fmt.Errorf("onServiceAdd() expected type v1.Service, got %T", obj))
		return
	}
	if service.Spec.Selector == nil {
		c.queueEndpoints(obj)
	}
}


onServiceUpdate

// onServiceUpdate queues a sync for the relevant Endpoints resource.
func (c *Controller) onServiceUpdate(prevObj, obj interface{}) {
	service := obj.(*v1.Service)
	prevService := prevObj.(*v1.Service)
	if service == nil || prevService == nil {
		utilruntime.HandleError(fmt.Errorf("onServiceUpdate() expected type v1.Service, got %T, %T", prevObj, obj))
		return
	}
	if (service.Spec.Selector == nil) != (prevService.Spec.Selector == nil) {
		c.queueEndpoints(obj)
	}
}


onServiceDelete

// onServiceDelete queues a sync for the relevant Endpoints resource.
func (c *Controller) onServiceDelete(obj interface{}) {
	service := getServiceFromDeleteAction(obj)
	if service == nil {
		utilruntime.HandleError(fmt.Errorf("onServiceDelete() expected type v1.Service, got %T", obj))
		return
	}
	if service.Spec.Selector == nil {
		c.queueEndpoints(obj)
	}
}


onEndpointsAdd

// onEndpointsAdd queues a sync for the relevant Endpoints resource.
func (c *Controller) onEndpointsAdd(obj interface{}) {
	endpoints := obj.(*v1.Endpoints)
	if endpoints == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointsAdd() expected type v1.Endpoints, got %T", obj))
		return
	}
	if !c.shouldMirror(endpoints) {
		klog.V(5).Infof("Skipping mirroring for %s/%s", endpoints.Namespace, endpoints.Name)
		return
	}
	c.queueEndpoints(obj)
}


onEndpointsUpdate

// onEndpointsUpdate queues a sync for the relevant Endpoints resource.
func (c *Controller) onEndpointsUpdate(prevObj, obj interface{}) {
	endpoints := obj.(*v1.Endpoints)
	prevEndpoints := prevObj.(*v1.Endpoints)
	if endpoints == nil || prevEndpoints == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointsUpdate() expected type v1.Endpoints, got %T, %T", prevObj, obj))
		return
	}
	if !c.shouldMirror(endpoints) && !c.shouldMirror(prevEndpoints) {
		klog.V(5).Infof("Skipping mirroring for %s/%s", endpoints.Namespace, endpoints.Name)
		return
	}
	c.queueEndpoints(obj)
}


onEndpointsDelete

// onEndpointsDelete queues a sync for the relevant Endpoints resource.
func (c *Controller) onEndpointsDelete(obj interface{}) {
	endpoints := getEndpointsFromDeleteAction(obj)
	if endpoints == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointsDelete() expected type v1.Endpoints, got %T", obj))
		return
	}
	if !c.shouldMirror(endpoints) {
		klog.V(5).Infof("Skipping mirroring for %s/%s", endpoints.Namespace, endpoints.Name)
		return
	}
	c.queueEndpoints(obj)
}


onEndpointSliceAdd

// onEndpointSliceAdd queues a sync for the relevant Endpoints resource for a
// sync if the EndpointSlice resource version does not match the expected
// version in the endpointSliceTracker.
func (c *Controller) onEndpointSliceAdd(obj interface{}) {
	endpointSlice := obj.(*discovery.EndpointSlice)
	if endpointSlice == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointSliceAdd() expected type discovery.EndpointSlice, got %T", obj))
		return
	}
	if managedByController(endpointSlice) && c.endpointSliceTracker.ShouldSync(endpointSlice) {
		c.queueEndpointsForEndpointSlice(endpointSlice)
	}
}


onEndpointSliceUpdate

// onEndpointSliceUpdate queues a sync for the relevant Endpoints resource for a
// sync if the EndpointSlice resource version does not match the expected
// version in the endpointSliceTracker or the managed-by value of the
// EndpointSlice has changed from or to this controller.
func (c *Controller) onEndpointSliceUpdate(prevObj, obj interface{}) {
	prevEndpointSlice := obj.(*discovery.EndpointSlice)
	endpointSlice := prevObj.(*discovery.EndpointSlice)
	if endpointSlice == nil || prevEndpointSlice == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointSliceUpdated() expected type discovery.EndpointSlice, got %T, %T", prevObj, obj))
		return
	}
	// EndpointSlice generation does not change when labels change. Although the
	// controller will never change LabelServiceName, users might. This check
	// ensures that we handle changes to this label.
	svcName := endpointSlice.Labels[discovery.LabelServiceName]
	prevSvcName := prevEndpointSlice.Labels[discovery.LabelServiceName]
	if svcName != prevSvcName {
		klog.Warningf("%s label changed from %s  to %s for %s", discovery.LabelServiceName, prevSvcName, svcName, endpointSlice.Name)
		c.queueEndpointsForEndpointSlice(endpointSlice)
		c.queueEndpointsForEndpointSlice(prevEndpointSlice)
		return
	}
	if managedByChanged(prevEndpointSlice, endpointSlice) || (managedByController(endpointSlice) && c.endpointSliceTracker.ShouldSync(endpointSlice)) {
		c.queueEndpointsForEndpointSlice(endpointSlice)
	}
}


onEndpointSliceDelete

// onEndpointSliceDelete queues a sync for the relevant Endpoints resource for a
// sync if the EndpointSlice resource version does not match the expected
// version in the endpointSliceTracker.
func (c *Controller) onEndpointSliceDelete(obj interface{}) {
	endpointSlice := getEndpointSliceFromDeleteAction(obj)
	if endpointSlice == nil {
		utilruntime.HandleError(fmt.Errorf("onEndpointSliceDelete() expected type discovery.EndpointSlice, got %T", obj))
		return
	}
	if managedByController(endpointSlice) && c.endpointSliceTracker.Has(endpointSlice) {
		// This returns false if we didn't expect the EndpointSlice to be
		// deleted. If that is the case, we queue the Service for another sync.
		if !c.endpointSliceTracker.HandleDeletion(endpointSlice) {
			c.queueEndpointsForEndpointSlice(endpointSlice)
		}
	}
}


queueEndpointsForEndpointSlice

// queueEndpointsForEndpointSlice attempts to queue the corresponding Endpoints
// resource for the provided EndpointSlice.
func (c *Controller) queueEndpointsForEndpointSlice(endpointSlice *discovery.EndpointSlice) {
	key, err := endpointsControllerKey(endpointSlice)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Couldn't get key for EndpointSlice %+v (type %T): %v", endpointSlice, endpointSlice, err))
		return
	}

	c.queue.AddAfter(key, c.endpointUpdatesBatchPeriod)
}


deleteMirroredSlices

// deleteMirroredSlices will delete and EndpointSlices that have been mirrored
// for Endpoints with this namespace and name.
func (c *Controller) deleteMirroredSlices(namespace, name string) error {
	endpointSlices, err := endpointSlicesMirroredForService(c.endpointSliceLister, namespace, name)
	if err != nil {
		return err
	}

	c.endpointSliceTracker.DeleteService(namespace, name)
	return c.reconciler.deleteEndpoints(namespace, name, endpointSlices)
}


endpointSlicesMirroredForService

// endpointSlicesMirroredForService returns the EndpointSlices that have been
// mirrored for a Service by this controller.
func endpointSlicesMirroredForService(endpointSliceLister discoverylisters.EndpointSliceLister, namespace, name string) ([]*discovery.EndpointSlice, error) {
	esLabelSelector := labels.Set(map[string]string{
		discovery.LabelServiceName: name,
		discovery.LabelManagedBy:   controllerName,
	}).AsSelectorPreValidated()
	return endpointSliceLister.EndpointSlices(namespace).List(esLabelSelector)
}


NewCache

// NewCache returns a new Cache with the specified endpointsPerSlice.
func NewCache(endpointsPerSlice int32) *Cache {
	return &Cache{
		maxEndpointsPerSlice: endpointsPerSlice,
		cache:                map[types.NamespacedName]*EndpointPortCache{},
	}
}


NewEndpointPortCache

// NewEndpointPortCache initializes and returns a new EndpointPortCache.
func NewEndpointPortCache() *EndpointPortCache {
	return &EndpointPortCache{
		items: map[endpointutil.PortMapKey]EfficiencyInfo{},
	}
}


Set

// Set updates the EndpointPortCache to contain the provided EfficiencyInfo
// for the provided PortMapKey.
func (spc *EndpointPortCache) Set(pmKey endpointutil.PortMapKey, eInfo EfficiencyInfo) {
	spc.items[pmKey] = eInfo
}


numEndpoints

// numEndpoints returns the total number of endpoints represented by a
// EndpointPortCache.
func (spc *EndpointPortCache) numEndpoints() int {
	num := 0
	for _, eInfo := range spc.items {
		num += eInfo.Endpoints
	}
	return num
}


UpdateEndpointPortCache

// UpdateEndpointPortCache updates a EndpointPortCache in the global cache for a
// given Service and updates the corresponding metrics.
// Parameters:
// * endpointsNN refers to a NamespacedName representing the Endpoints resource.
// * epCache refers to a EndpointPortCache for the specified Endpoints reosource.
func (c *Cache) UpdateEndpointPortCache(endpointsNN types.NamespacedName, epCache *EndpointPortCache) {
	c.lock.Lock()
	defer c.lock.Unlock()

	prevNumEndpoints := 0
	if existingEPCache, ok := c.cache[endpointsNN]; ok {
		prevNumEndpoints = existingEPCache.numEndpoints()
	}

	currNumEndpoints := epCache.numEndpoints()
	// To keep numEndpoints up to date, add the difference between the number of
	// endpoints in the provided spCache and any spCache it might be replacing.
	c.numEndpoints = c.numEndpoints + currNumEndpoints - prevNumEndpoints

	c.cache[endpointsNN] = epCache
	c.updateMetrics()
}


DeleteEndpoints

// DeleteEndpoints removes references to an Endpoints resource from the global
// cache and updates the corresponding metrics.
func (c *Cache) DeleteEndpoints(endpointsNN types.NamespacedName) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if spCache, ok := c.cache[endpointsNN]; ok {
		c.numEndpoints = c.numEndpoints - spCache.numEndpoints()
		delete(c.cache, endpointsNN)
		c.updateMetrics()
	}
}


desiredAndActualSlices

// desiredAndActualSlices returns a metricsUpdate with the desired and actual
// number of EndpointSlices given the current values in the cache.
// Must be called holding lock.
func (c *Cache) desiredAndActualSlices() metricsUpdate {
	mUpdate := metricsUpdate{}
	for _, spCache := range c.cache {
		for _, eInfo := range spCache.items {
			mUpdate.actual += eInfo.Slices
			mUpdate.desired += numDesiredSlices(eInfo.Endpoints, int(c.maxEndpointsPerSlice))
		}
	}
	return mUpdate
}


updateMetrics

// updateMetrics updates metrics with the values from this Cache.
// Must be called holding lock.
func (c *Cache) updateMetrics() {
	mUpdate := c.desiredAndActualSlices()
	NumEndpointSlices.WithLabelValues().Set(float64(mUpdate.actual))
	DesiredEndpointSlices.WithLabelValues().Set(float64(mUpdate.desired))
	EndpointsDesired.WithLabelValues().Set(float64(c.numEndpoints))
}


numDesiredSlices

// numDesiredSlices calculates the number of EndpointSlices that would exist
// with ideal endpoint distribution.
func numDesiredSlices(numEndpoints, maxPerSlice int) int {
	return int(math.Ceil(float64(numEndpoints) / float64(maxPerSlice)))
}


RegisterMetrics

// RegisterMetrics registers EndpointSlice metrics.
func RegisterMetrics() {
	registerMetrics.Do(func() {
		legacyregistry.MustRegister(EndpointsAddedPerSync)
		legacyregistry.MustRegister(EndpointsUpdatedPerSync)
		legacyregistry.MustRegister(EndpointsRemovedPerSync)
		legacyregistry.MustRegister(AddressesSkippedPerSync)
		legacyregistry.MustRegister(EndpointsSyncDuration)
		legacyregistry.MustRegister(EndpointsDesired)
		legacyregistry.MustRegister(NumEndpointSlices)
		legacyregistry.MustRegister(DesiredEndpointSlices)
		legacyregistry.MustRegister(EndpointSliceChanges)
	})
}


reconcile

// reconcile takes an Endpoints resource and ensures that corresponding
// EndpointSlices exist. It creates, updates, or deletes EndpointSlices to
// ensure the desired set of addresses are represented by EndpointSlices.
func (r *reconciler) reconcile(endpoints *corev1.Endpoints, existingSlices []*discovery.EndpointSlice) error {
	// Calculate desired state.
	d := newDesiredCalc()

	numInvalidAddresses := 0
	addressesSkipped := 0

	// canonicalize the Endpoints subsets before processing them
	subsets := endpointsv1.RepackSubsets(endpoints.Subsets)
	for _, subset := range subsets {
		multiKey := d.initPorts(subset.Ports)

		totalAddresses := len(subset.Addresses) + len(subset.NotReadyAddresses)
		totalAddressesAdded := 0

		for _, address := range subset.Addresses {
			// Break if we've reached the max number of addresses to mirror
			// per EndpointSubset. This allows for a simple 1:1 mapping between
			// EndpointSubset and EndpointSlice.
			if totalAddressesAdded >= int(r.maxEndpointsPerSubset) {
				break
			}
			if ok := d.addAddress(address, multiKey, true); ok {
				totalAddressesAdded++
			} else {
				numInvalidAddresses++
				klog.Warningf("Address in %s/%s Endpoints is not a valid IP, it will not be mirrored to an EndpointSlice: %s", endpoints.Namespace, endpoints.Name, address.IP)
			}
		}

		for _, address := range subset.NotReadyAddresses {
			// Break if we've reached the max number of addresses to mirror
			// per EndpointSubset. This allows for a simple 1:1 mapping between
			// EndpointSubset and EndpointSlice.
			if totalAddressesAdded >= int(r.maxEndpointsPerSubset) {
				break
			}
			if ok := d.addAddress(address, multiKey, false); ok {
				totalAddressesAdded++
			} else {
				numInvalidAddresses++
				klog.Warningf("Address in %s/%s Endpoints is not a valid IP, it will not be mirrored to an EndpointSlice: %s", endpoints.Namespace, endpoints.Name, address.IP)
			}
		}

		addressesSkipped += totalAddresses - totalAddressesAdded
	}

	// This metric includes addresses skipped for being invalid or exceeding
	// MaxEndpointsPerSubset.
	metrics.AddressesSkippedPerSync.WithLabelValues().Observe(float64(addressesSkipped))

	// Record an event on the Endpoints resource if we skipped mirroring for any
	// invalid IP addresses.
	if numInvalidAddresses > 0 {
		r.eventRecorder.Eventf(endpoints, corev1.EventTypeWarning, InvalidIPAddress,
			"Skipped %d invalid IP addresses when mirroring to EndpointSlices", numInvalidAddresses)
	}

	// Record a separate event if we skipped mirroring due to the number of
	// addresses exceeding MaxEndpointsPerSubset.
	if addressesSkipped > numInvalidAddresses {
		klog.Warningf("%d addresses in %s/%s Endpoints were skipped due to exceeding MaxEndpointsPerSubset", addressesSkipped, endpoints.Namespace, endpoints.Name)
		r.eventRecorder.Eventf(endpoints, corev1.EventTypeWarning, TooManyAddressesToMirror,
			"A max of %d addresses can be mirrored to EndpointSlices per Endpoints subset. %d addresses were skipped", r.maxEndpointsPerSubset, addressesSkipped)
	}

	// Build data structures for existing state.
	existingSlicesByKey := endpointSlicesByKey(existingSlices)

	// Determine changes necessary for each group of slices by port map.
	epMetrics := metrics.NewEndpointPortCache()
	totals := totalsByAction{}
	slices := slicesByAction{}

	for portKey, desiredEndpoints := range d.endpointsByKey {
		numEndpoints := len(desiredEndpoints)
		pmSlices, pmTotals := r.reconcileByPortMapping(
			endpoints, existingSlicesByKey[portKey], desiredEndpoints, d.portsByKey[portKey], portKey.addressType())

		slices.append(pmSlices)
		totals.add(pmTotals)

		epMetrics.Set(endpointutil.PortMapKey(portKey), metrics.EfficiencyInfo{
			Endpoints: numEndpoints,
			Slices:    len(existingSlicesByKey[portKey]) + len(pmSlices.toCreate) - len(pmSlices.toDelete),
		})
	}

	// If there are unique sets of ports that are no longer desired, mark
	// the corresponding endpoint slices for deletion.
	for portKey, existingSlices := range existingSlicesByKey {
		if _, ok := d.endpointsByKey[portKey]; !ok {
			for _, existingSlice := range existingSlices {
				slices.toDelete = append(slices.toDelete, existingSlice)
			}
		}
	}

	metrics.EndpointsAddedPerSync.WithLabelValues().Observe(float64(totals.added))
	metrics.EndpointsUpdatedPerSync.WithLabelValues().Observe(float64(totals.updated))
	metrics.EndpointsRemovedPerSync.WithLabelValues().Observe(float64(totals.removed))

	endpointsNN := types.NamespacedName{Name: endpoints.Name, Namespace: endpoints.Namespace}
	r.metricsCache.UpdateEndpointPortCache(endpointsNN, epMetrics)

	return r.finalize(endpoints, slices)
}


reconcileByPortMapping

// reconcileByPortMapping compares the endpoints found in existing slices with
// the list of desired endpoints and returns lists of slices to create, update,
// and delete.
func (r *reconciler) reconcileByPortMapping(
	endpoints *corev1.Endpoints,
	existingSlices []*discovery.EndpointSlice,
	desiredSet endpointsliceutil.EndpointSet,
	endpointPorts []discovery.EndpointPort,
	addressType discovery.AddressType,
) (slicesByAction, totalsByAction) {
	slices := slicesByAction{}
	totals := totalsByAction{}

	// If no endpoints are desired, mark existing slices for deletion and
	// return.
	if desiredSet.Len() == 0 {
		slices.toDelete = existingSlices
		for _, epSlice := range existingSlices {
			totals.removed += len(epSlice.Endpoints)
		}
		return slices, totals
	}

	if len(existingSlices) == 0 {
		// if no existing slices, all desired endpoints will be added.
		totals.added = desiredSet.Len()
	} else {
		// if >0 existing slices, mark all but 1 for deletion.
		slices.toDelete = existingSlices[1:]

		// generated slices must mirror all endpoints annotations but EndpointsLastChangeTriggerTime and LastAppliedConfigAnnotation
		compareAnnotations := cloneAndRemoveKeys(endpoints.Annotations, corev1.EndpointsLastChangeTriggerTime, corev1.LastAppliedConfigAnnotation)
		compareLabels := cloneAndRemoveKeys(existingSlices[0].Labels, discovery.LabelManagedBy, discovery.LabelServiceName)
		// Return early if first slice matches desired endpoints, labels and annotations
		totals = totalChanges(existingSlices[0], desiredSet)
		if totals.added == 0 && totals.updated == 0 && totals.removed == 0 &&
			apiequality.Semantic.DeepEqual(endpoints.Labels, compareLabels) &&
			apiequality.Semantic.DeepEqual(compareAnnotations, existingSlices[0].Annotations) {
			return slices, totals
		}
	}

	// generate a new slice with the desired endpoints.
	var sliceName string
	if len(existingSlices) > 0 {
		sliceName = existingSlices[0].Name
	}
	newSlice := newEndpointSlice(endpoints, endpointPorts, addressType, sliceName)
	for desiredSet.Len() > 0 && len(newSlice.Endpoints) < int(r.maxEndpointsPerSubset) {
		endpoint, _ := desiredSet.PopAny()
		newSlice.Endpoints = append(newSlice.Endpoints, *endpoint)
	}

	if newSlice.Name != "" {
		slices.toUpdate = []*discovery.EndpointSlice{newSlice}
	} else { // Slices to be created set GenerateName instead of Name.
		slices.toCreate = []*discovery.EndpointSlice{newSlice}
	}

	return slices, totals
}


finalize

// finalize creates, updates, and deletes slices as specified
func (r *reconciler) finalize(endpoints *corev1.Endpoints, slices slicesByAction) error {
	// If there are slices to create and delete, recycle the slices marked for
	// deletion by replacing creates with updates of slices that would otherwise
	// be deleted.
	recycleSlices(&slices)

	epsClient := r.client.DiscoveryV1().EndpointSlices(endpoints.Namespace)

	// Don't create more EndpointSlices if corresponding Endpoints resource is
	// being deleted.
	if endpoints.DeletionTimestamp == nil {
		for _, endpointSlice := range slices.toCreate {
			createdSlice, err := epsClient.Create(context.TODO(), endpointSlice, metav1.CreateOptions{})
			if err != nil {
				// If the namespace is terminating, creates will continue to fail. Simply drop the item.
				if errors.HasStatusCause(err, corev1.NamespaceTerminatingCause) {
					return nil
				}
				return fmt.Errorf("failed to create EndpointSlice for Endpoints %s/%s: %v", endpoints.Namespace, endpoints.Name, err)
			}
			r.endpointSliceTracker.Update(createdSlice)
			metrics.EndpointSliceChanges.WithLabelValues("create").Inc()
		}
	}

	for _, endpointSlice := range slices.toUpdate {
		updatedSlice, err := epsClient.Update(context.TODO(), endpointSlice, metav1.UpdateOptions{})
		if err != nil {
			return fmt.Errorf("failed to update %s EndpointSlice for Endpoints %s/%s: %v", endpointSlice.Name, endpoints.Namespace, endpoints.Name, err)
		}
		r.endpointSliceTracker.Update(updatedSlice)
		metrics.EndpointSliceChanges.WithLabelValues("update").Inc()
	}

	for _, endpointSlice := range slices.toDelete {
		err := epsClient.Delete(context.TODO(), endpointSlice.Name, metav1.DeleteOptions{})
		if err != nil {
			return fmt.Errorf("failed to delete %s EndpointSlice for Endpoints %s/%s: %v", endpointSlice.Name, endpoints.Namespace, endpoints.Name, err)
		}
		r.endpointSliceTracker.ExpectDeletion(endpointSlice)
		metrics.EndpointSliceChanges.WithLabelValues("delete").Inc()
	}

	return nil
}


deleteEndpoints

// deleteEndpoints deletes any associated EndpointSlices and cleans up any
// Endpoints references from the metricsCache.
func (r *reconciler) deleteEndpoints(namespace, name string, endpointSlices []*discovery.EndpointSlice) error {
	r.metricsCache.DeleteEndpoints(types.NamespacedName{Namespace: namespace, Name: name})
	var errs []error
	for _, endpointSlice := range endpointSlices {
		err := r.client.DiscoveryV1().EndpointSlices(namespace).Delete(context.TODO(), endpointSlice.Name, metav1.DeleteOptions{})
		if err != nil {
			errs = append(errs, err)
		}
	}
	if len(errs) > 0 {
		return fmt.Errorf("error(s) deleting %d/%d EndpointSlices for %s/%s Endpoints, including: %s", len(errs), len(endpointSlices), namespace, name, errs[0])
	}
	return nil
}


endpointSlicesByKey

// endpointSlicesByKey returns a map that groups EndpointSlices by unique
// addrTypePortMapKey values.
func endpointSlicesByKey(existingSlices []*discovery.EndpointSlice) map[addrTypePortMapKey][]*discovery.EndpointSlice {
	slicesByKey := map[addrTypePortMapKey][]*discovery.EndpointSlice{}
	for _, existingSlice := range existingSlices {
		epKey := newAddrTypePortMapKey(existingSlice.Ports, existingSlice.AddressType)
		slicesByKey[epKey] = append(slicesByKey[epKey], existingSlice)
	}
	return slicesByKey
}


totalChanges

// totalChanges returns the total changes that will be required for an
// EndpointSlice to match a desired set of endpoints.
func totalChanges(existingSlice *discovery.EndpointSlice, desiredSet endpointsliceutil.EndpointSet) totalsByAction {
	totals := totalsByAction{}
	existingMatches := 0

	for _, endpoint := range existingSlice.Endpoints {
		got := desiredSet.Get(&endpoint)
		if got == nil {
			// If not desired, increment number of endpoints to be deleted.
			totals.removed++
		} else {
			existingMatches++

			// If existing version of endpoint doesn't match desired version
			// increment number of endpoints to be updated.
			if !endpointutil.EndpointsEqualBeyondHash(got, &endpoint) {
				totals.updated++
			}
		}
	}

	// Any desired endpoints that have not been found in the existing slice will
	// be added.
	totals.added = desiredSet.Len() - existingMatches
	return totals
}


append

// append appends slices from another slicesByAction struct.
func (s *slicesByAction) append(slices slicesByAction) {
	s.toCreate = append(s.toCreate, slices.toCreate...)
	s.toUpdate = append(s.toUpdate, slices.toUpdate...)
	s.toDelete = append(s.toDelete, slices.toDelete...)
}


add

// add adds totals from another totalsByAction struct.
func (t *totalsByAction) add(totals totalsByAction) {
	t.added += totals.added
	t.updated += totals.updated
	t.removed += totals.removed
}


newDesiredCalc

// newDesiredCalc initializes and returns a new desiredCalc.
func newDesiredCalc() *desiredCalc {
	return &desiredCalc{
		portsByKey:          map[addrTypePortMapKey][]discovery.EndpointPort{},
		endpointsByKey:      map[addrTypePortMapKey]endpointsliceutil.EndpointSet{},
		numDesiredEndpoints: 0,
	}
}


initPorts

// initPorts initializes ports for a subset and address type and returns the
// corresponding addrTypePortMapKey.
func (d *desiredCalc) initPorts(subsetPorts []v1.EndpointPort) multiAddrTypePortMapKey {
	endpointPorts := epPortsToEpsPorts(subsetPorts)
	addrTypes := []discovery.AddressType{discovery.AddressTypeIPv4, discovery.AddressTypeIPv6}
	multiKey := multiAddrTypePortMapKey{}

	for _, addrType := range addrTypes {
		multiKey[addrType] = newAddrTypePortMapKey(endpointPorts, addrType)
		if _, ok := d.endpointsByKey[multiKey[addrType]]; !ok {
			d.endpointsByKey[multiKey[addrType]] = endpointsliceutil.EndpointSet{}
		}
		d.portsByKey[multiKey[addrType]] = endpointPorts
	}

	return multiKey
}


addAddress

// addAddress adds an EndpointAddress to the desired state if it is valid. It
// returns false if the address was invalid.
func (d *desiredCalc) addAddress(address v1.EndpointAddress, multiKey multiAddrTypePortMapKey, ready bool) bool {
	endpoint := addressToEndpoint(address, ready)
	addrType := getAddressType(address.IP)
	if addrType == nil {
		return false
	}

	d.endpointsByKey[multiKey[*addrType]].Insert(endpoint)
	d.numDesiredEndpoints++
	return true
}


recycleSlices

// recycleSlices will recycle the slices marked for deletion by replacing
// creates with updates of slices that would otherwise be deleted.
func recycleSlices(slices *slicesByAction) {
	toCreateByAddrType := toSlicesByAddrType(slices.toCreate)
	toDeleteByAddrType := toSlicesByAddrType(slices.toDelete)

	for addrType, slicesToCreate := range toCreateByAddrType {
		slicesToDelete := toDeleteByAddrType[addrType]
		for i := 0; len(slicesToCreate) > i && len(slicesToDelete) > i; i++ {
			slices.toCreate = removeSlice(slices.toCreate, slicesToCreate[i])
			slices.toDelete = removeSlice(slices.toDelete, slicesToDelete[i])
			slice := slicesToCreate[i]
			slice.Name = slicesToDelete[i].Name
			slices.toUpdate = append(slices.toUpdate, slice)
		}
	}
}


removeSlice

// removeSlice removes an EndpointSlice from a list of EndpointSlices.
func removeSlice(slices []*discovery.EndpointSlice, sliceToRemove *discovery.EndpointSlice) []*discovery.EndpointSlice {
	for i, slice := range slices {
		if slice.Name == sliceToRemove.Name {
			return append(slices[:i], slices[i+1:]...)
		}
	}
	return slices
}


toSlicesByAddrType

// toSliceByAddrType returns lists of EndpointSlices grouped by address.
func toSlicesByAddrType(slices []*discovery.EndpointSlice) slicesByAddrType {
	byAddrType := slicesByAddrType{}
	for _, slice := range slices {
		byAddrType[slice.AddressType] = append(byAddrType[slice.AddressType], slice)
	}
	return byAddrType
}


newAddrTypePortMapKey

// newAddrTypePortMapKey generates a PortMapKey from endpoint ports.
func newAddrTypePortMapKey(endpointPorts []discovery.EndpointPort, addrType discovery.AddressType) addrTypePortMapKey {
	pmk := fmt.Sprintf("%s-%s", addrType, endpointutil.NewPortMapKey(endpointPorts))
	return addrTypePortMapKey(pmk)
}


addressType

func (pk addrTypePortMapKey) addressType() discovery.AddressType {
	if strings.HasPrefix(string(pk), string(discovery.AddressTypeIPv6)) {
		return discovery.AddressTypeIPv6
	}
	return discovery.AddressTypeIPv4
}


getAddressType

func getAddressType(address string) *discovery.AddressType {
	ip := netutils.ParseIPSloppy(address)
	if ip == nil {
		return nil
	}
	addressType := discovery.AddressTypeIPv4
	if ip.To4() == nil {
		addressType = discovery.AddressTypeIPv6
	}
	return &addressType
}


newEndpointSlice

// newEndpointSlice returns an EndpointSlice generated from an Endpoints
// resource, ports, and address type.
func newEndpointSlice(endpoints *corev1.Endpoints, ports []discovery.EndpointPort, addrType discovery.AddressType, sliceName string) *discovery.EndpointSlice {
	gvk := schema.GroupVersionKind{Version: "v1", Kind: "Endpoints"}
	ownerRef := metav1.NewControllerRef(endpoints, gvk)
	epSlice := &discovery.EndpointSlice{
		ObjectMeta: metav1.ObjectMeta{
			Labels:          map[string]string{},
			Annotations:     map[string]string{},
			OwnerReferences: []metav1.OwnerReference{*ownerRef},
			Namespace:       endpoints.Namespace,
		},
		Ports:       ports,
		AddressType: addrType,
		Endpoints:   []discovery.Endpoint{},
	}

	// clone all labels
	for label, val := range endpoints.Labels {
		epSlice.Labels[label] = val
	}

	// overwrite specific labels
	epSlice.Labels[discovery.LabelServiceName] = endpoints.Name
	epSlice.Labels[discovery.LabelManagedBy] = controllerName

	// clone all annotations but EndpointsLastChangeTriggerTime and LastAppliedConfigAnnotation
	for annotation, val := range endpoints.Annotations {
		if annotation == corev1.EndpointsLastChangeTriggerTime || annotation == corev1.LastAppliedConfigAnnotation {
			continue
		}
		epSlice.Annotations[annotation] = val
	}

	if sliceName == "" {
		epSlice.GenerateName = getEndpointSlicePrefix(endpoints.Name)
	} else {
		epSlice.Name = sliceName
	}

	return epSlice
}


getEndpointSlicePrefix

// getEndpointSlicePrefix returns a suitable prefix for an EndpointSlice name.
func getEndpointSlicePrefix(serviceName string) string {
	// use the dash (if the name isn't too long) to make the name a bit prettier.
	prefix := fmt.Sprintf("%s-", serviceName)
	if len(validation.ValidateEndpointSliceName(prefix, true)) != 0 {
		prefix = serviceName
	}
	return prefix
}


addressToEndpoint

// addressToEndpoint converts an address from an Endpoints resource to an
// EndpointSlice endpoint.
func addressToEndpoint(address corev1.EndpointAddress, ready bool) *discovery.Endpoint {
	endpoint := &discovery.Endpoint{
		Addresses: []string{address.IP},
		Conditions: discovery.EndpointConditions{
			Ready: &ready,
		},
		TargetRef: address.TargetRef,
	}

	if address.NodeName != nil {
		endpoint.NodeName = address.NodeName
	}
	if address.Hostname != "" {
		endpoint.Hostname = &address.Hostname
	}

	return endpoint
}


epPortsToEpsPorts

// epPortsToEpsPorts converts ports from an Endpoints resource to ports for an
// EndpointSlice resource.
func epPortsToEpsPorts(epPorts []corev1.EndpointPort) []discovery.EndpointPort {
	epsPorts := []discovery.EndpointPort{}
	for _, epPort := range epPorts {
		epp := epPort.DeepCopy()
		epsPorts = append(epsPorts, discovery.EndpointPort{
			Name:        &epp.Name,
			Port:        &epp.Port,
			Protocol:    &epp.Protocol,
			AppProtocol: epp.AppProtocol,
		})
	}
	return epsPorts
}


getServiceFromDeleteAction

// getServiceFromDeleteAction parses a Service resource from a delete
// action.
func getServiceFromDeleteAction(obj interface{}) *corev1.Service {
	if service, ok := obj.(*corev1.Service); ok {
		return service
	}
	// If we reached here it means the Service was deleted but its final state
	// is unrecorded.
	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj))
		return nil
	}
	service, ok := tombstone.Obj.(*corev1.Service)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Service resource: %#v", obj))
		return nil
	}
	return service
}


getEndpointsFromDeleteAction

// getEndpointsFromDeleteAction parses an Endpoints resource from a delete
// action.
func getEndpointsFromDeleteAction(obj interface{}) *corev1.Endpoints {
	if endpoints, ok := obj.(*corev1.Endpoints); ok {
		return endpoints
	}
	// If we reached here it means the Endpoints resource was deleted but its
	// final state is unrecorded.
	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj))
		return nil
	}
	endpoints, ok := tombstone.Obj.(*corev1.Endpoints)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not an Endpoints resource: %#v", obj))
		return nil
	}
	return endpoints
}


getEndpointSliceFromDeleteAction

// getEndpointSliceFromDeleteAction parses an EndpointSlice from a delete action.
func getEndpointSliceFromDeleteAction(obj interface{}) *discovery.EndpointSlice {
	if endpointSlice, ok := obj.(*discovery.EndpointSlice); ok {
		return endpointSlice
	}
	// If we reached here it means the EndpointSlice was deleted but its final
	// state is unrecorded.
	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj))
		return nil
	}
	endpointSlice, ok := tombstone.Obj.(*discovery.EndpointSlice)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not an EndpointSlice resource: %#v", obj))
		return nil
	}
	return endpointSlice
}


endpointsControllerKey

// endpointsControllerKey returns a controller key for an Endpoints resource but
// derived from an EndpointSlice.
func endpointsControllerKey(endpointSlice *discovery.EndpointSlice) (string, error) {
	if endpointSlice == nil {
		return "", fmt.Errorf("nil EndpointSlice passed to serviceControllerKey()")
	}
	serviceName, ok := endpointSlice.Labels[discovery.LabelServiceName]
	if !ok || serviceName == "" {
		return "", fmt.Errorf("EndpointSlice missing %s label", discovery.LabelServiceName)
	}
	return fmt.Sprintf("%s/%s", endpointSlice.Namespace, serviceName), nil
}


skipMirror

// skipMirror return true if the LabelSkipMirror label has been set to
// "true".
func skipMirror(labels map[string]string) bool {
	skipMirror, _ := labels[discovery.LabelSkipMirror]
	return skipMirror == "true"
}


hasLeaderElection

// hasLeaderElection returns true if the LeaderElectionRecordAnnotationKey is
// set as an annotation.
func hasLeaderElection(annotations map[string]string) bool {
	_, ok := annotations[resourcelock.LeaderElectionRecordAnnotationKey]
	return ok
}


cloneAndRemoveKeys

// cloneAndRemoveKeys is a copy of CloneAndRemoveLabels
// it is used here for annotations and labels
func cloneAndRemoveKeys(a map[string]string, keys ...string) map[string]string {
	if len(keys) == 0 {
		// Don't need to remove a key.
		return a
	}
	// Clone.
	newMap := map[string]string{}
	for k, v := range a {
		newMap[k] = v
	}
	// remove keys
	for _, key := range keys {
		delete(newMap, key)
	}
	return newMap
}


managedByChanged

// managedByChanged returns true if one of the provided EndpointSlices is
// managed by the EndpointSlice controller while the other is not.
func managedByChanged(endpointSlice1, endpointSlice2 *discovery.EndpointSlice) bool {
	return managedByController(endpointSlice1) != managedByController(endpointSlice2)
}


managedByController

// managedByController returns true if the controller of the provided
// EndpointSlices is the EndpointSlice controller.
func managedByController(endpointSlice *discovery.EndpointSlice) bool {
	managedBy, _ := endpointSlice.Labels[discovery.LabelManagedBy]
	return managedBy == controllerName
}


// Convert_v1alpha1_GarbageCollectorControllerConfiguration_To_config_GarbageCollectorControllerConfiguration is an autogenerated conversion function.
func Convert_v1alpha1_GarbageCollectorControllerConfiguration_To_config_GarbageCollectorControllerConfiguration(in *v1alpha1.GarbageCollectorControllerConfiguration, out *config.GarbageCollectorControllerConfiguration, s conversion.Scope) error {
	return autoConvert_v1alpha1_GarbageCollectorControllerConfiguration_To_config_GarbageCollectorControllerConfiguration(in, out, s)
}


// Convert_config_GarbageCollectorControllerConfiguration_To_v1alpha1_GarbageCollectorControllerConfiguration is an autogenerated conversion function.
func Convert_config_GarbageCollectorControllerConfiguration_To_v1alpha1_GarbageCollectorControllerConfiguration(in *config.GarbageCollectorControllerConfiguration, out *v1alpha1.GarbageCollectorControllerConfiguration, s conversion.Scope) error {
	return autoConvert_config_GarbageCollectorControllerConfiguration_To_v1alpha1_GarbageCollectorControllerConfiguration(in, out, s)
}


// RecommendedDefaultGarbageCollectorControllerConfiguration defaults a pointer to a
// GarbageCollectorControllerConfiguration struct. This will set the recommended default
// values, but they may be subject to change between API versions. This function
// is intentionally not registered in the scheme as a "normal" `SetDefaults_Foo`
// function to allow consumers of this type to set whatever defaults for their
// embedded configs. Forcing consumers to use these defaults would be problematic
// as defaulting in the scheme is done as part of the conversion, and there would
// be no easy way to opt-out. Instead, if you want to use this defaulting method
// run it in your wrapper struct of this type in its `SetDefaults_` method.
func RecommendedDefaultGarbageCollectorControllerConfiguration(obj *kubectrlmgrconfigv1alpha1.GarbageCollectorControllerConfiguration) {
	if obj.EnableGarbageCollector == nil {
		obj.EnableGarbageCollector = pointer.Bool(true)
	}
	if obj.ConcurrentGCSyncs == 0 {
		obj.ConcurrentGCSyncs = 20
	}
}


MarshalDOT

func (v *dotVertex) MarshalDOT(w io.Writer) error {
	attrs := v.Attributes()
	if _, err := fmt.Fprintf(w, "  %q [\n", v.uid); err != nil {
		return err
	}
	for _, a := range attrs {
		if _, err := fmt.Fprintf(w, "    %s=%q\n", a.Key, a.Value); err != nil {
			return err
		}
	}
	if _, err := fmt.Fprintf(w, "  ];\n"); err != nil {
		return err
	}
	return nil
}


String

func (v *dotVertex) String() string {
	kind := v.gvk.Kind + "." + v.gvk.Version
	if len(v.gvk.Group) > 0 {
		kind = kind + "." + v.gvk.Group
	}
	missing := ""
	if v.missingFromGraph {
		missing = "(missing)"
	}
	deleting := ""
	if v.beingDeleted {
		deleting = "(deleting)"
	}
	deletingDependents := ""
	if v.deletingDependents {
		deleting = "(deletingDependents)"
	}
	virtual := ""
	if v.virtual {
		virtual = "(virtual)"
	}
	return fmt.Sprintf(`%s/%s[%s]-%v%s%s%s%s`, kind, v.name, v.namespace, v.uid, missing, deleting, deletingDependents, virtual)
}


Attributes

func (v *dotVertex) Attributes() []attribute {
	kubectlString := v.gvk.Kind + "." + v.gvk.Version
	if len(v.gvk.Group) > 0 {
		kubectlString = kubectlString + "." + v.gvk.Group
	}
	kubectlString = kubectlString + "/" + v.name

	label := fmt.Sprintf(`uid=%v
namespace=%v
%v
`,
		v.uid,
		v.namespace,
		kubectlString,
	)

	conditionStrings := []string{}
	if v.beingDeleted {
		conditionStrings = append(conditionStrings, "beingDeleted")
	}
	if v.deletingDependents {
		conditionStrings = append(conditionStrings, "deletingDependents")
	}
	if v.virtual {
		conditionStrings = append(conditionStrings, "virtual")
	}
	if v.missingFromGraph {
		conditionStrings = append(conditionStrings, "missingFromGraph")
	}
	conditionString := strings.Join(conditionStrings, ",")
	if len(conditionString) > 0 {
		label = label + conditionString + "\n"
	}

	return []attribute{
		{Key: "label", Value: label},
		// these place metadata in the correct location, but don't conform to any normal attribute for rendering
		{Key: "group", Value: v.gvk.Group},
		{Key: "version", Value: v.gvk.Version},
		{Key: "kind", Value: v.gvk.Kind},
		{Key: "namespace", Value: v.namespace},
		{Key: "name", Value: v.name},
		{Key: "uid", Value: string(v.uid)},
		{Key: "missing", Value: fmt.Sprintf(`%v`, v.missingFromGraph)},
		{Key: "beingDeleted", Value: fmt.Sprintf(`%v`, v.beingDeleted)},
		{Key: "deletingDependents", Value: fmt.Sprintf(`%v`, v.deletingDependents)},
		{Key: "virtual", Value: fmt.Sprintf(`%v`, v.virtual)},
	}
}


NewDOTVertex

// NewDOTVertex creates a new dotVertex.
func NewDOTVertex(node *node) *dotVertex {
	gv, err := schema.ParseGroupVersion(node.identity.APIVersion)
	if err != nil {
		// this indicates a bad data serialization that should be prevented during storage of the API
		utilruntime.HandleError(err)
	}
	return &dotVertex{
		uid:                node.identity.UID,
		gvk:                gv.WithKind(node.identity.Kind),
		namespace:          node.identity.Namespace,
		name:               node.identity.Name,
		beingDeleted:       node.beingDeleted,
		deletingDependents: node.deletingDependents,
		virtual:            node.virtual,
	}
}


NewMissingdotVertex

// NewMissingdotVertex creates a new dotVertex.
func NewMissingdotVertex(ownerRef metav1.OwnerReference) *dotVertex {
	gv, err := schema.ParseGroupVersion(ownerRef.APIVersion)
	if err != nil {
		// this indicates a bad data serialization that should be prevented during storage of the API
		utilruntime.HandleError(err)
	}
	return &dotVertex{
		uid:              ownerRef.UID,
		gvk:              gv.WithKind(ownerRef.Kind),
		name:             ownerRef.Name,
		missingFromGraph: true,
	}
}


ToDOTNodesAndEdges

func (m *concurrentUIDToNode) ToDOTNodesAndEdges() ([]*dotVertex, []dotEdge) {
	m.uidToNodeLock.Lock()
	defer m.uidToNodeLock.Unlock()

	return toDOTNodesAndEdges(m.uidToNode)
}


MarshalDOT

func (e dotEdge) MarshalDOT(w io.Writer) error {
	_, err := fmt.Fprintf(w, "  %q -> %q;\n", e.F, e.T)
	return err
}


toDOTNodesAndEdges

func toDOTNodesAndEdges(uidToNode map[types.UID]*node) ([]*dotVertex, []dotEdge) {
	nodes := []*dotVertex{}
	edges := []dotEdge{}

	uidToVertex := map[types.UID]*dotVertex{}

	// add the vertices first, then edges.  That avoids having to deal with missing refs.
	for _, node := range uidToNode {
		// skip adding objects that don't have owner references and aren't referred to.
		if len(node.dependents) == 0 && len(node.owners) == 0 {
			continue
		}
		vertex := NewDOTVertex(node)
		uidToVertex[node.identity.UID] = vertex
		nodes = append(nodes, vertex)
	}
	for _, node := range uidToNode {
		currVertex := uidToVertex[node.identity.UID]
		for _, ownerRef := range node.owners {
			currOwnerVertex, ok := uidToVertex[ownerRef.UID]
			if !ok {
				currOwnerVertex = NewMissingdotVertex(ownerRef)
				uidToVertex[node.identity.UID] = currOwnerVertex
				nodes = append(nodes, currOwnerVertex)
			}
			edges = append(edges, dotEdge{F: currVertex.uid, T: currOwnerVertex.uid})
		}
	}

	sort.SliceStable(nodes, func(i, j int) bool { return nodes[i].uid < nodes[j].uid })
	sort.SliceStable(edges, func(i, j int) bool {
		if edges[i].F != edges[j].F {
			return edges[i].F < edges[j].F
		}
		return edges[i].T < edges[j].T
	})

	return nodes, edges
}


ToDOTNodesAndEdgesForObj

func (m *concurrentUIDToNode) ToDOTNodesAndEdgesForObj(uids ...types.UID) ([]*dotVertex, []dotEdge) {
	m.uidToNodeLock.Lock()
	defer m.uidToNodeLock.Unlock()

	return toDOTNodesAndEdgesForObj(m.uidToNode, uids...)
}


toDOTNodesAndEdgesForObj

func toDOTNodesAndEdgesForObj(uidToNode map[types.UID]*node, uids ...types.UID) ([]*dotVertex, []dotEdge) {
	uidsToCheck := append([]types.UID{}, uids...)
	interestingNodes := map[types.UID]*node{}

	// build the set of nodes to inspect first, then use the normal construction on the subset
	for i := 0; i < len(uidsToCheck); i++ {
		uid := uidsToCheck[i]
		// if we've already been observed, there was a bug, but skip it so we don't loop forever
		if _, ok := interestingNodes[uid]; ok {
			continue
		}
		node, ok := uidToNode[uid]
		// if there is no node for the UID, skip over it.  We may add it to the list multiple times
		// but we won't loop forever and hopefully the condition doesn't happen very often
		if !ok {
			continue
		}

		interestingNodes[node.identity.UID] = node

		for _, ownerRef := range node.owners {
			// if we've already inspected this UID, don't add it to be inspected again
			if _, ok := interestingNodes[ownerRef.UID]; ok {
				continue
			}
			uidsToCheck = append(uidsToCheck, ownerRef.UID)
		}
		for dependent := range node.dependents {
			// if we've already inspected this UID, don't add it to be inspected again
			if _, ok := interestingNodes[dependent.identity.UID]; ok {
				continue
			}
			uidsToCheck = append(uidsToCheck, dependent.identity.UID)
		}
	}

	return toDOTNodesAndEdges(interestingNodes)
}


NewDebugHandler

// NewDebugHandler creates a new debugHTTPHandler.
func NewDebugHandler(controller *GarbageCollector) http.Handler {
	return &debugHTTPHandler{controller: controller}
}


marshalDOT

func marshalDOT(w io.Writer, nodes []*dotVertex, edges []dotEdge) error {
	if _, err := w.Write([]byte("strict digraph full {\n")); err != nil {
		return err
	}
	if len(nodes) > 0 {
		if _, err := w.Write([]byte("  // Node definitions.\n")); err != nil {
			return err
		}
		for _, node := range nodes {
			if err := node.MarshalDOT(w); err != nil {
				return err
			}
		}
	}
	if len(edges) > 0 {
		if _, err := w.Write([]byte("  // Edge definitions.\n")); err != nil {
			return err
		}
		for _, edge := range edges {
			if err := edge.MarshalDOT(w); err != nil {
				return err
			}
		}
	}
	if _, err := w.Write([]byte("}\n")); err != nil {
		return err
	}
	return nil
}


ServeHTTP

func (h *debugHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.URL.Path != "/graph" {
		http.Error(w, "", http.StatusNotFound)
		return
	}

	var nodes []*dotVertex
	var edges []dotEdge
	if uidStrings := req.URL.Query()["uid"]; len(uidStrings) > 0 {
		uids := []types.UID{}
		for _, uidString := range uidStrings {
			uids = append(uids, types.UID(uidString))
		}
		nodes, edges = h.controller.dependencyGraphBuilder.uidToNode.ToDOTNodesAndEdgesForObj(uids...)

	} else {
		nodes, edges = h.controller.dependencyGraphBuilder.uidToNode.ToDOTNodesAndEdges()
	}

	b := bytes.NewBuffer(nil)
	if err := marshalDOT(b, nodes, edges); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "text/vnd.graphviz")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Write(b.Bytes())
	w.WriteHeader(http.StatusOK)
}


DebuggingHandler

func (gc *GarbageCollector) DebuggingHandler() http.Handler {
	return NewDebugHandler(gc)
}


Error

func (r *restMappingError) Error() string {
	versionKind := fmt.Sprintf("%s/%s", r.version, r.kind)
	return fmt.Sprintf("unable to get REST mapping for %s.", versionKind)
}


Message

// Message prints more details
func (r *restMappingError) Message() string {
	versionKind := fmt.Sprintf("%s/%s", r.version, r.kind)
	errMsg := fmt.Sprintf("unable to get REST mapping for %s. ", versionKind)
	errMsg += fmt.Sprintf(" If %s is an invalid resource, then you should manually remove ownerReferences that refer %s objects.", versionKind, versionKind)
	return errMsg
}


newRESTMappingError

func newRESTMappingError(kind, version string) *restMappingError {
	return &restMappingError{kind: kind, version: version}
}


NewGarbageCollector

// NewGarbageCollector creates a new GarbageCollector.
func NewGarbageCollector(
	kubeClient clientset.Interface,
	metadataClient metadata.Interface,
	mapper meta.ResettableRESTMapper,
	ignoredResources map[schema.GroupResource]struct{},
	sharedInformers informerfactory.InformerFactory,
	informersStarted <-chan struct{},
) (*GarbageCollector, error) {

	eventBroadcaster := record.NewBroadcaster()
	eventRecorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "garbage-collector-controller"})

	attemptToDelete := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_attempt_to_delete")
	attemptToOrphan := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_attempt_to_orphan")
	absentOwnerCache := NewReferenceCache(500)
	gc := &GarbageCollector{
		metadataClient:   metadataClient,
		restMapper:       mapper,
		attemptToDelete:  attemptToDelete,
		attemptToOrphan:  attemptToOrphan,
		absentOwnerCache: absentOwnerCache,
		kubeClient:       kubeClient,
		eventBroadcaster: eventBroadcaster,
	}
	gc.dependencyGraphBuilder = &GraphBuilder{
		eventRecorder:    eventRecorder,
		metadataClient:   metadataClient,
		informersStarted: informersStarted,
		restMapper:       mapper,
		graphChanges:     workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_graph_changes"),
		uidToNode: &concurrentUIDToNode{
			uidToNode: make(map[types.UID]*node),
		},
		attemptToDelete:  attemptToDelete,
		attemptToOrphan:  attemptToOrphan,
		absentOwnerCache: absentOwnerCache,
		sharedInformers:  sharedInformers,
		ignoredResources: ignoredResources,
	}

	metrics.Register()

	return gc, nil
}


resyncMonitors

// resyncMonitors starts or stops resource monitors as needed to ensure that all
// (and only) those resources present in the map are monitored.
func (gc *GarbageCollector) resyncMonitors(logger klog.Logger, deletableResources map[schema.GroupVersionResource]struct{}) error {
	if err := gc.dependencyGraphBuilder.syncMonitors(logger, deletableResources); err != nil {
		return err
	}
	gc.dependencyGraphBuilder.startMonitors(logger)
	return nil
}


Run

// Run starts garbage collector workers.
func (gc *GarbageCollector) Run(ctx context.Context, workers int) {
	defer utilruntime.HandleCrash()
	defer gc.attemptToDelete.ShutDown()
	defer gc.attemptToOrphan.ShutDown()
	defer gc.dependencyGraphBuilder.graphChanges.ShutDown()

	// Start events processing pipeline.
	gc.eventBroadcaster.StartStructuredLogging(0)
	gc.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: gc.kubeClient.CoreV1().Events("")})
	defer gc.eventBroadcaster.Shutdown()

	logger := klog.FromContext(ctx)
	logger.Info("Starting controller", "controller", "garbagecollector")
	defer logger.Info("Shutting down controller", "controller", "garbagecollector")

	go gc.dependencyGraphBuilder.Run(ctx)

	if !cache.WaitForNamedCacheSync("garbage collector", ctx.Done(), func() bool {
		return gc.dependencyGraphBuilder.IsSynced(logger)
	}) {
		return
	}

	logger.Info("All resource monitors have synced. Proceeding to collect garbage")

	// gc workers
	for i := 0; i < workers; i++ {
		go wait.UntilWithContext(ctx, gc.runAttemptToDeleteWorker, 1*time.Second)
		go wait.Until(func() { gc.runAttemptToOrphanWorker(logger) }, 1*time.Second, ctx.Done())
	}

	<-ctx.Done()
}


Sync

// Sync periodically resyncs the garbage collector when new resources are
// observed from discovery. When new resources are detected, Sync will stop all
// GC workers, reset gc.restMapper, and resync the monitors.
//
// Note that discoveryClient should NOT be shared with gc.restMapper, otherwise
// the mapper's underlying discovery client will be unnecessarily reset during
// the course of detecting new resources.
func (gc *GarbageCollector) Sync(ctx context.Context, discoveryClient discovery.ServerResourcesInterface, period time.Duration) {
	oldResources := make(map[schema.GroupVersionResource]struct{})
	wait.UntilWithContext(ctx, func(ctx context.Context) {
		logger := klog.FromContext(ctx)

		// Get the current resource list from discovery.
		newResources := GetDeletableResources(discoveryClient)

		// This can occur if there is an internal error in GetDeletableResources.
		if len(newResources) == 0 {
			logger.V(2).Info("no resources reported by discovery, skipping garbage collector sync")
			metrics.GarbageCollectorResourcesSyncError.Inc()
			return
		}

		// Decide whether discovery has reported a change.
		if reflect.DeepEqual(oldResources, newResources) {
			logger.V(5).Info("no resource updates from discovery, skipping garbage collector sync")
			return
		}

		// Ensure workers are paused to avoid processing events before informers
		// have resynced.
		gc.workerLock.Lock()
		defer gc.workerLock.Unlock()

		// Once we get here, we should not unpause workers until we've successfully synced
		attempt := 0
		wait.PollImmediateUntilWithContext(ctx, 100*time.Millisecond, func(ctx context.Context) (bool, error) {
			attempt++

			// On a reattempt, check if available resources have changed
			if attempt > 1 {
				newResources = GetDeletableResources(discoveryClient)
				if len(newResources) == 0 {
					logger.V(2).Info("no resources reported by discovery", "attempt", attempt)
					metrics.GarbageCollectorResourcesSyncError.Inc()
					return false, nil
				}
			}

			logger.V(2).Info(
				"syncing garbage collector with updated resources from discovery",
				"attempt", attempt,
				"diff", printDiff(oldResources, newResources),
			)

			// Resetting the REST mapper will also invalidate the underlying discovery
			// client. This is a leaky abstraction and assumes behavior about the REST
			// mapper, but we'll deal with it for now.
			gc.restMapper.Reset()
			logger.V(4).Info("reset restmapper")

			// Perform the monitor resync and wait for controllers to report cache sync.
			//
			// NOTE: It's possible that newResources will diverge from the resources
			// discovered by restMapper during the call to Reset, since they are
			// distinct discovery clients invalidated at different times. For example,
			// newResources may contain resources not returned in the restMapper's
			// discovery call if the resources appeared in-between the calls. In that
			// case, the restMapper will fail to map some of newResources until the next
			// attempt.
			if err := gc.resyncMonitors(logger, newResources); err != nil {
				utilruntime.HandleError(fmt.Errorf("failed to sync resource monitors (attempt %d): %v", attempt, err))
				metrics.GarbageCollectorResourcesSyncError.Inc()
				return false, nil
			}
			logger.V(4).Info("resynced monitors")

			// wait for caches to fill for a while (our sync period) before attempting to rediscover resources and retry syncing.
			// this protects us from deadlocks where available resources changed and one of our informer caches will never fill.
			// informers keep attempting to sync in the background, so retrying doesn't interrupt them.
			// the call to resyncMonitors on the reattempt will no-op for resources that still exist.
			// note that workers stay paused until we successfully resync.
			if !cache.WaitForNamedCacheSync("garbage collector", waitForStopOrTimeout(ctx.Done(), period), func() bool {
				return gc.dependencyGraphBuilder.IsSynced(logger)
			}) {
				utilruntime.HandleError(fmt.Errorf("timed out waiting for dependency graph builder sync during GC sync (attempt %d)", attempt))
				metrics.GarbageCollectorResourcesSyncError.Inc()
				return false, nil
			}

			// success, break out of the loop
			return true, nil
		})

		// Finally, keep track of our new state. Do this after all preceding steps
		// have succeeded to ensure we'll retry on subsequent syncs if an error
		// occurred.
		oldResources = newResources
		logger.V(2).Info("synced garbage collector")
	}, period)
}


printDiff

// printDiff returns a human-readable summary of what resources were added and removed
func printDiff(oldResources, newResources map[schema.GroupVersionResource]struct{}) string {
	removed := sets.NewString()
	for oldResource := range oldResources {
		if _, ok := newResources[oldResource]; !ok {
			removed.Insert(fmt.Sprintf("%+v", oldResource))
		}
	}
	added := sets.NewString()
	for newResource := range newResources {
		if _, ok := oldResources[newResource]; !ok {
			added.Insert(fmt.Sprintf("%+v", newResource))
		}
	}
	return fmt.Sprintf("added: %v, removed: %v", added.List(), removed.List())
}


waitForStopOrTimeout

// waitForStopOrTimeout returns a stop channel that closes when the provided stop channel closes or when the specified timeout is reached
func waitForStopOrTimeout(stopCh <-chan struct{}, timeout time.Duration) <-chan struct{} {
	stopChWithTimeout := make(chan struct{})
	go func() {
		select {
		case <-stopCh:
		case <-time.After(timeout):
		}
		close(stopChWithTimeout)
	}()
	return stopChWithTimeout
}


IsSynced

// IsSynced returns true if dependencyGraphBuilder is synced.
func (gc *GarbageCollector) IsSynced(logger klog.Logger) bool {
	return gc.dependencyGraphBuilder.IsSynced(logger)
}


runAttemptToDeleteWorker

func (gc *GarbageCollector) runAttemptToDeleteWorker(ctx context.Context) {
	for gc.processAttemptToDeleteWorker(ctx) {
	}
}


processAttemptToDeleteWorker

func (gc *GarbageCollector) processAttemptToDeleteWorker(ctx context.Context) bool {
	item, quit := gc.attemptToDelete.Get()
	gc.workerLock.RLock()
	defer gc.workerLock.RUnlock()
	if quit {
		return false
	}
	defer gc.attemptToDelete.Done(item)

	action := gc.attemptToDeleteWorker(ctx, item)
	switch action {
	case forgetItem:
		gc.attemptToDelete.Forget(item)
	case requeueItem:
		gc.attemptToDelete.AddRateLimited(item)
	}

	return true
}


attemptToDeleteWorker

func (gc *GarbageCollector) attemptToDeleteWorker(ctx context.Context, item interface{}) workQueueItemAction {
	n, ok := item.(*node)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("expect *node, got %#v", item))
		return forgetItem
	}

	logger := klog.FromContext(ctx)

	if !n.isObserved() {
		nodeFromGraph, existsInGraph := gc.dependencyGraphBuilder.uidToNode.Read(n.identity.UID)
		if !existsInGraph {
			// this can happen if attemptToDelete loops on a requeued virtual node because attemptToDeleteItem returned an error,
			// and in the meantime a deletion of the real object associated with that uid was observed
			logger.V(5).Info("item no longer in the graph, skipping attemptToDeleteItem", "item", n.identity)
			return forgetItem
		}
		if nodeFromGraph.isObserved() {
			// this can happen if attemptToDelete loops on a requeued virtual node because attemptToDeleteItem returned an error,
			// and in the meantime the real object associated with that uid was observed
			logger.V(5).Info("item no longer virtual in the graph, skipping attemptToDeleteItem on virtual node", "item", n.identity)
			return forgetItem
		}
	}

	err := gc.attemptToDeleteItem(ctx, n)
	if err == enqueuedVirtualDeleteEventErr {
		// a virtual event was produced and will be handled by processGraphChanges, no need to requeue this node
		return forgetItem
	} else if err == namespacedOwnerOfClusterScopedObjectErr {
		// a cluster-scoped object referring to a namespaced owner is an error that will not resolve on retry, no need to requeue this node
		return forgetItem
	} else if err != nil {
		if _, ok := err.(*restMappingError); ok {
			// There are at least two ways this can happen:
			// 1. The reference is to an object of a custom type that has not yet been
			//    recognized by gc.restMapper (this is a transient error).
			// 2. The reference is to an invalid group/version. We don't currently
			//    have a way to distinguish this from a valid type we will recognize
			//    after the next discovery sync.
			// For now, record the error and retry.
			logger.V(5).Error(err, "error syncing item", "item", n.identity)
		} else {
			utilruntime.HandleError(fmt.Errorf("error syncing item %s: %v", n, err))
		}
		// retry if garbage collection of an object failed.
		return requeueItem
	} else if !n.isObserved() {
		// requeue if item hasn't been observed via an informer event yet.
		// otherwise a virtual node for an item added AND removed during watch reestablishment can get stuck in the graph and never removed.
		// see https://issue.k8s.io/56121
		logger.V(5).Info("item hasn't been observed via informer yet", "item", n.identity)
		return requeueItem
	}

	return forgetItem
}


isDangling

// isDangling check if a reference is pointing to an object that doesn't exist.
// If isDangling looks up the referenced object at the API server, it also
// returns its latest state.
func (gc *GarbageCollector) isDangling(ctx context.Context, reference metav1.OwnerReference, item *node) (
	dangling bool, owner *metav1.PartialObjectMetadata, err error) {

	logger := klog.FromContext(ctx)
	// check for recorded absent cluster-scoped parent
	absentOwnerCacheKey := objectReference{OwnerReference: ownerReferenceCoordinates(reference)}
	if gc.absentOwnerCache.Has(absentOwnerCacheKey) {
		logger.V(5).Info("according to the absentOwnerCache, item's owner does not exist",
			"item", item.identity,
			"owner", reference,
		)
		return true, nil, nil
	}

	// check for recorded absent namespaced parent
	absentOwnerCacheKey.Namespace = item.identity.Namespace
	if gc.absentOwnerCache.Has(absentOwnerCacheKey) {
		logger.V(5).Info("according to the absentOwnerCache, item's owner does not exist in namespace",
			"item", item.identity,
			"owner", reference,
		)
		return true, nil, nil
	}

	// TODO: we need to verify the reference resource is supported by the
	// system. If it's not a valid resource, the garbage collector should i)
	// ignore the reference when decide if the object should be deleted, and
	// ii) should update the object to remove such references. This is to
	// prevent objects having references to an old resource from being
	// deleted during a cluster upgrade.
	resource, namespaced, err := gc.apiResource(reference.APIVersion, reference.Kind)
	if err != nil {
		return false, nil, err
	}
	if !namespaced {
		absentOwnerCacheKey.Namespace = ""
	}

	if len(item.identity.Namespace) == 0 && namespaced {
		// item is a cluster-scoped object referring to a namespace-scoped owner, which is not valid.
		// return a marker error, rather than retrying on the lookup failure forever.
		logger.V(2).Info("item is cluster-scoped, but refers to a namespaced owner",
			"item", item.identity,
			"owner", reference,
		)
		return false, nil, namespacedOwnerOfClusterScopedObjectErr
	}

	// TODO: It's only necessary to talk to the API server if the owner node
	// is a "virtual" node. The local graph could lag behind the real
	// status, but in practice, the difference is small.
	owner, err = gc.metadataClient.Resource(resource).Namespace(resourceDefaultNamespace(namespaced, item.identity.Namespace)).Get(ctx, reference.Name, metav1.GetOptions{})
	switch {
	case errors.IsNotFound(err):
		gc.absentOwnerCache.Add(absentOwnerCacheKey)
		logger.V(5).Info("item's owner is not found",
			"item", item.identity,
			"owner", reference,
		)
		return true, nil, nil
	case err != nil:
		return false, nil, err
	}

	if owner.GetUID() != reference.UID {
		logger.V(5).Info("item's owner is not found, UID mismatch",
			"item", item.identity,
			"owner", reference,
		)
		gc.absentOwnerCache.Add(absentOwnerCacheKey)
		return true, nil, nil
	}
	return false, owner, nil
}


classifyReferences

// classify the latestReferences to three categories:
// solid: the owner exists, and is not "waitingForDependentsDeletion"
// dangling: the owner does not exist
// waitingForDependentsDeletion: the owner exists, its deletionTimestamp is non-nil, and it has
// FinalizerDeletingDependents
// This function communicates with the server.
func (gc *GarbageCollector) classifyReferences(ctx context.Context, item *node, latestReferences []metav1.OwnerReference) (
	solid, dangling, waitingForDependentsDeletion []metav1.OwnerReference, err error) {
	for _, reference := range latestReferences {
		isDangling, owner, err := gc.isDangling(ctx, reference, item)
		if err != nil {
			return nil, nil, nil, err
		}
		if isDangling {
			dangling = append(dangling, reference)
			continue
		}

		ownerAccessor, err := meta.Accessor(owner)
		if err != nil {
			return nil, nil, nil, err
		}
		if ownerAccessor.GetDeletionTimestamp() != nil && hasDeleteDependentsFinalizer(ownerAccessor) {
			waitingForDependentsDeletion = append(waitingForDependentsDeletion, reference)
		} else {
			solid = append(solid, reference)
		}
	}
	return solid, dangling, waitingForDependentsDeletion, nil
}


ownerRefsToUIDs

func ownerRefsToUIDs(refs []metav1.OwnerReference) []types.UID {
	var ret []types.UID
	for _, ref := range refs {
		ret = append(ret, ref.UID)
	}
	return ret
}