func()

in pkg/controllers/member/endpointslice/controller.go [62:201]


func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	endpointSliceRef := klog.KRef(req.Namespace, req.Name)
	startTime := time.Now()
	klog.V(2).InfoS("Reconciliation starts", "endpointSlice", endpointSliceRef)
	defer func() {
		latency := time.Since(startTime).Milliseconds()
		klog.V(2).InfoS("Reconciliation ends", "endpointSlice", endpointSliceRef, "latency", latency)
	}()

	// Retrieve the EndpointSlice object.
	var endpointSlice discoveryv1.EndpointSlice
	if err := r.MemberClient.Get(ctx, req.NamespacedName, &endpointSlice); err != nil {
		// Skip the reconciliation if the EndpointSlice does not exist; this should only happen when an EndpointSlice
		// is deleted right before the controller gets a chance to reconcile it. If the EndpointSlice has never
		// been exported to the fleet, no action is required on this controller's end; on the other hand, if the
		// EndpointSlice has been exported before, this may result in an EndpointSlice being left over on the
		// hub cluster, and it is up to another controller, EndpointSliceExport controller, to pick up the leftover
		// and clean it out.
		if errors.IsNotFound(err) {
			klog.V(4).InfoS("Ignoring NotFound endpointSlice", "endpointSlice", endpointSliceRef)
			return ctrl.Result{}, nil
		}
		klog.ErrorS(err, "Failed to get endpoint slice", "endpointSlice", endpointSliceRef)
		return ctrl.Result{}, err
	}

	// Check if the EndpointSlice should be skipped for reconciliation or unexported.
	skipOrUnexportOp, err := r.shouldSkipOrUnexportEndpointSlice(ctx, &endpointSlice)
	if err != nil {
		// An unexpected error occurs.
		klog.ErrorS(err,
			"Failed to determine whether an endpoint slice should be skipped for reconciliation or unexported",
			"endpointSlice", endpointSliceRef)
		return ctrl.Result{}, err
	}

	switch skipOrUnexportOp {
	case shouldSkipEndpointSliceOp:
		// Skip reconciling the EndpointSlice.
		klog.V(4).InfoS("Endpoint slice should be skipped for reconciliation", "endpointSlice", endpointSliceRef)
		return ctrl.Result{}, nil
	case shouldUnexportEndpointSliceOp:
		// Unexport the EndpointSlice.
		klog.V(4).InfoS("Endpoint slice should be unexported", "endpointSlice", endpointSliceRef)
		if err := r.unexportEndpointSlice(ctx, &endpointSlice); err != nil {
			klog.ErrorS(err, "Failed to unexport the endpoint slice", "endpointSlice", endpointSliceRef)
			return ctrl.Result{}, err
		}
		return ctrl.Result{}, nil
	}

	// Retrieve the unique name assigned; if none has been assigned, or the one assigned is not valid, possibly due
	// to user tampering with the annotation, assign a new unique name.
	fleetUniqueName, ok := endpointSlice.Annotations[objectmeta.ExportedObjectAnnotationUniqueName]
	if !ok || !isUniqueNameValid(fleetUniqueName) {
		klog.V(2).InfoS("The endpoint slice does not have a unique name assigned or the one assigned is not valid; a new one will be assigned",
			"endpointSlice", endpointSliceRef)
		var err error
		// Unique name annotation must be added before an EndpointSlice is exported.
		fleetUniqueName, err = r.assignUniqueNameAsAnnotation(ctx, &endpointSlice)
		if err != nil {
			klog.ErrorS(err, "Failed to assign unique name as an annotation", "endpointSlice", endpointSliceRef)
			return ctrl.Result{}, err
		}
	}

	// Retrieve the last seen generation and the last seen timestamp; these two values are used for metric collection.
	// If the two values are not present or not valid, annotate EndpointSlice with new values.
	//
	// Note that the two values are not tamperproof.
	exportedSince, err := r.collectAndVerifyLastSeenGenerationAndTimestamp(ctx, &endpointSlice, startTime)
	if err != nil {
		klog.Warning("Failed to annotate last seen generation and timestamp", "endpointSlice", endpointSliceRef)
	}

	// Create an EndpointSliceExport in the hub cluster if the EndpointSlice has never been exported; otherwise
	// update the corresponding EndpointSliceExport.
	extractedEndpoints := extractEndpointsFromEndpointSlice(&endpointSlice)
	endpointSliceExport := fleetnetv1alpha1.EndpointSliceExport{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.HubNamespace,
			Name:      fleetUniqueName,
		},
	}
	klog.V(2).InfoS("Endpoint slice will be exported",
		"endpointSlice", endpointSliceRef,
		"endpointSliceExport", klog.KObj(&endpointSliceExport))
	createOrUpdateOp, err := controllerutil.CreateOrUpdate(ctx, r.HubClient, &endpointSliceExport, func() error {
		// Set up an EndpointSliceReference and only when an EndpointSliceExport is first created; this is because
		// most fields in EndpointSliceReference should be immutable after creation.
		if endpointSliceExport.CreationTimestamp.IsZero() {
			endpointSliceReference := fleetnetv1alpha1.FromMetaObjects(r.MemberClusterID,
				endpointSlice.TypeMeta, endpointSlice.ObjectMeta, metav1.NewTime(exportedSince))
			endpointSliceExport.Spec.EndpointSliceReference = endpointSliceReference
		}

		// Return an error if an attempt is made to update an EndpointSliceExport that references a different
		// EndpointSlice from the one that is being reconciled. This usually happens when one unique name is assigned
		// to multiple EndpointSliceExports, either by chance or through direct manipulation.
		if !isEndpointSliceExportLinkedWithEndpointSlice(&endpointSliceExport, &endpointSlice) {
			return errors.NewAlreadyExists(
				schema.GroupResource{Group: fleetnetv1alpha1.GroupVersion.Group, Resource: "EndpointSliceExport"},
				fleetUniqueName,
			)
		}

		endpointSliceExport.Spec.AddressType = discoveryv1.AddressTypeIPv4
		endpointSliceExport.Spec.Endpoints = extractedEndpoints
		endpointSliceExport.Spec.Ports = endpointSlice.Ports
		endpointSliceExport.Spec.OwnerServiceReference = fleetnetv1alpha1.OwnerServiceReference{
			// The owner Service is guaranteed to reside in the same namespace as the EndpointSlice to export.
			Namespace:      endpointSlice.Namespace,
			Name:           endpointSlice.Labels[discoveryv1.LabelServiceName],
			NamespacedName: fmt.Sprintf("%s/%s", endpointSlice.Namespace, endpointSlice.Labels[discoveryv1.LabelServiceName]),
		}

		endpointSliceExport.Spec.EndpointSliceReference.UpdateFromMetaObject(endpointSlice.ObjectMeta, metav1.NewTime(exportedSince))
		return nil
	})
	switch {
	case errors.IsAlreadyExists(err):
		// Remove the unique name annotation; a new one will be assigned in future reciliation attempts.
		klog.V(2).InfoS("The unique name assigned to the endpoint slice has been used; it will be removed", "endpointSlice", endpointSliceRef)
		delete(endpointSlice.Annotations, objectmeta.ExportedObjectAnnotationUniqueName)
		if err := r.MemberClient.Update(ctx, &endpointSlice); err != nil {
			klog.ErrorS(err, "Failed to remove endpointslice unique name annotation", "endpointSlice", endpointSliceRef)
			return ctrl.Result{}, err
		}
		return ctrl.Result{}, nil
	case err != nil:
		klog.ErrorS(err,
			"Failed to create/update endpointslice export",
			"endpointSlice", endpointSliceRef,
			"endpointSliceExport", klog.KObj(&endpointSliceExport),
			"op", createOrUpdateOp)
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}