func()

in pkg/controllers/member/serviceexport/controller.go [255:336]


func (r *Reconciler) exportService(ctx context.Context, svcExport *fleetnetv1alpha1.ServiceExport, svc *corev1.Service,
	exportedSince time.Time, exportWeight int64) (ctrl.Result, error) {
	svcRef := klog.KObj(svc)
	// Create or update the InternalServiceExport object.
	internalSvcExport := fleetnetv1alpha1.InternalServiceExport{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.HubNamespace,
			Name:      formatInternalServiceExportName(svcExport),
		},
	}
	svcExportPorts := extractServicePorts(svc)
	klog.V(2).InfoS("Export the service or update the exported service",
		"service", svcExport,
		"internalServiceExport", klog.KObj(&internalSvcExport))
	createOrUpdateOp, err := controllerutil.CreateOrUpdate(ctx, r.HubClient, &internalSvcExport, func() error {
		if internalSvcExport.CreationTimestamp.IsZero() {
			// Set the ServiceReference only when the InternalServiceExport is created; most of the fields in
			// an ExportedObjectReference should be immutable.
			internalSvcExport.Spec.ServiceReference = fleetnetv1alpha1.FromMetaObjects(r.MemberClusterID,
				svc.TypeMeta, svc.ObjectMeta, metav1.NewTime(exportedSince))
		}

		// Return an error if an attempt is made to update an InternalServiceExport that references a different
		// Service from the one that is being reconciled. This usually happens when a service is deleted and
		// re-created immediately.
		if internalSvcExport.Spec.ServiceReference.UID != svc.UID {
			klog.V(2).InfoS("Failed to create/update internalServiceExport, UIDs mismatch",
				"service", svcRef,
				"internalServiceExport", klog.KObj(&internalSvcExport),
				"newUID", svc.UID,
				"oldUID", internalSvcExport.Spec.ServiceReference.UID)
			// The AlreadyExists error returned here features a different GVR source (service, rather than
			// internalServiceExport); such an error would never be yielded in the normal workflow.
			return apierrors.NewAlreadyExists(
				schema.GroupResource{Group: fleetnetv1alpha1.GroupVersion.Group, Resource: "Service"},
				fmt.Sprintf("%s/%s", svc.Namespace, svc.Name),
			)
		}

		internalSvcExport.Spec.Ports = svcExportPorts
		internalSvcExport.Spec.ServiceReference.UpdateFromMetaObject(svc.ObjectMeta, metav1.NewTime(exportedSince))

		if r.EnableTrafficManagerFeature {
			klog.V(2).InfoS("Collecting Traffic Manager related information and set to the internal service export", "service", svcRef)
			internalSvcExport.Spec.Weight = ptr.To(exportWeight)
			if err := r.setAzureRelatedInformation(ctx, svc, &internalSvcExport); err != nil {
				klog.ErrorS(err, "Failed to populate the Azure information for the Traffic Manager feature in the internal service export", "service", svcRef)
				return err
			}
		}
		return nil
	})
	statusErr := &apierrors.StatusError{}
	ok := errors.As(err, &statusErr)
	switch {
	case apierrors.IsAlreadyExists(err) && ok && statusErr.Status().Details.Kind == "Service":
		// An export with the same key but different UID already exists; unexport the Service first, and
		// requeue a new attempt to export the Service.
		// Additional checks are performed here as two forms of AlreadyExists error can be returned in the CreateOrUpdate
		// call: it could be that an actual UID mismatch is found, however, since CreateOrUpdate is, in essence, a two-part op
		// (the function first gets the object, and then decides whether to create the object or update it according to the get
		// result), a racing condition may lead to an AlreadyExists error being yielded even if there is no UID mismatch at all.
		// This can happen, albeit quite rarely, when the system is under heavy load, and the informers cannot sync caches
		// fast enough; the out-of-date cache will return that an object does not exist when read, even though the object is
		// already present in the persistent store, and any subsequent create call would fail.
		if _, err := r.unexportService(ctx, svcExport); err != nil {
			klog.ErrorS(err, "Failed to unexport the service", "service", svcRef)
			return ctrl.Result{}, err
		}
		// Unexporting a Service removes the cleanup finalizer from the ServiceExport, which in normal cases
		// will trigger another reconciliation loop automatically; for better clarity here the controller requests
		// the new reconciliation attempt explicitly.
		return ctrl.Result{Requeue: true}, nil
	case err != nil:
		klog.ErrorS(err, "Failed to create/update InternalServiceExport",
			"internalServiceExport", klog.KObj(&internalSvcExport),
			"service", svcRef,
			"op", createOrUpdateOp)
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}