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
}