in pkg/controllers/member/serviceexport/controller.go [78:253]
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
svcRef := klog.KRef(req.Namespace, req.Name)
startTime := time.Now()
klog.V(2).InfoS("Reconciliation starts", "service", svcRef)
defer func() {
latency := time.Since(startTime).Milliseconds()
klog.V(2).InfoS("Reconciliation ends", "service", svcRef, "latency", latency)
}()
// Retrieve the ServiceExport object.
var svcExport fleetnetv1alpha1.ServiceExport
if err := r.MemberClient.Get(ctx, req.NamespacedName, &svcExport); err != nil {
if apierrors.IsNotFound(err) {
// Skip the reconciliation if the ServiceExport does not exist; this happens when the controller detects
// changes in a Service that has not been exported yet, or when a ServiceExport is deleted before the
// corresponding Service is exported to the fleet (and a cleanup finalizer is added). Either case requires
// no action on this controller's end.
klog.V(2).InfoS("Service export is not found", "service", svcRef)
return ctrl.Result{}, nil
}
// An error has occurred when getting the ServiceExport.
klog.ErrorS(err, "Failed to get service export", "service", svcRef)
return ctrl.Result{}, err
}
// Check if the ServiceExport has been deleted and needs cleanup (unexporting Service).
// A ServiceExport needs cleanup when it has the ServiceExport cleanup finalizer added; the absence of this
// finalizer guarantees that the corresponding Service has never been exported to the fleet, thus no action
// is needed.
if svcExport.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&svcExport, svcExportCleanupFinalizer) {
klog.V(2).InfoS("Service export is deleted; unexport the service", "service", svcRef)
res, err := r.unexportService(ctx, &svcExport)
if err != nil {
klog.ErrorS(err, "Failed to unexport the service", "service", svcRef)
}
return res, err
}
return ctrl.Result{}, nil
}
// Check if the Service to export exists.
svc := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: req.Namespace,
Name: req.Name,
},
}
err := r.MemberClient.Get(ctx, req.NamespacedName, &svc)
switch {
// The Service to export does not exist or has been deleted.
case apierrors.IsNotFound(err) || svc.DeletionTimestamp != nil:
r.Recorder.Eventf(&svcExport, corev1.EventTypeWarning, "ServiceNotFound", "Service %s is not found or in the deleting state", svc.Name)
// Unexport the Service if the ServiceExport has the cleanup finalizer added.
klog.V(2).InfoS("Service is deleted; unexport the service", "service", svcRef)
if controllerutil.ContainsFinalizer(&svcExport, svcExportCleanupFinalizer) {
if _, err = r.unexportService(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to unexport the service", "service", svcRef)
return ctrl.Result{}, err
}
}
// Mark the ServiceExport as invalid.
klog.V(2).InfoS("Mark service export as invalid (service not found)", "service", svcRef)
if err := r.markServiceExportAsInvalidNotFound(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to mark service export as invalid (service not found)", "service", svcRef)
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
// An unexpected error occurs when retrieving the Service.
case err != nil:
klog.ErrorS(err, "Failed to get the service", "service", svcRef)
return ctrl.Result{}, err
}
// Check if the Service is eligible for export.
if !isServiceEligibleForExport(&svc) {
r.Recorder.Eventf(&svcExport, corev1.EventTypeWarning, "ServiceNotEligible", "Service %s is not eligible for exporting and please check service spec", svc.Name)
// Unexport ineligible Service if the ServiceExport has the cleanup finalizer added.
if controllerutil.ContainsFinalizer(&svcExport, svcExportCleanupFinalizer) {
klog.V(2).InfoS("Service is ineligible; unexport the service", "service", svcRef)
if _, err = r.unexportService(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to unexport the service", "service", svcRef)
return ctrl.Result{}, err
}
}
// Mark the ServiceExport as invalid.
klog.V(2).InfoS("Mark service export as invalid (service ineligible)", "service", svcRef)
err = r.markServiceExportAsInvalidSvcIneligible(ctx, &svcExport)
if err != nil {
klog.ErrorS(err, "Failed to mark service export as invalid (service ineligible)", "service", svcRef)
}
return ctrl.Result{}, err
}
// Get the weight from the serviceExport annotation and validate it.
exportWeight, err := objectmeta.ExtractWeightFromServiceExport(&svcExport)
if err != nil {
// Here we don't unexport the service as it will interrupt the current traffic.
// There is no need to requeue the error as the controller should be triggered when the user corrects the annotation.
klog.ErrorS(controller.NewUserError(err), "service export has invalid annotation weight", "service", svcRef)
curValidCond := meta.FindStatusCondition(svcExport.Status.Conditions, string(fleetnetv1alpha1.ServiceExportValid))
expectedValidCond := metav1.Condition{
Type: string(fleetnetv1alpha1.ServiceExportValid),
Status: metav1.ConditionFalse,
Reason: svcExportInvalidWeightAnnotationReason,
ObservedGeneration: svcExport.Generation,
Message: fmt.Sprintf("serviceExport %s/%s has an invalid weight annotation, err = %s", svcExport.Namespace, svcExport.Name, err),
}
// We have to compare the message since we cannot rely on the object generation as annotation does not change generation.
if condition.EqualConditionWithMessage(curValidCond, &expectedValidCond) {
// no need to retry if the condition is already set
return ctrl.Result{}, nil
}
r.Recorder.Eventf(&svcExport, corev1.EventTypeWarning, svcExportInvalidWeightAnnotationReason, "ServiceExport %s has invalid weight value in the annotation", svc.Name)
meta.SetStatusCondition(&svcExport.Status.Conditions, expectedValidCond)
return ctrl.Result{}, r.MemberClient.Status().Update(ctx, &svcExport)
}
if exportWeight == 0 {
// The weight is 0, unexport the service.
klog.V(2).InfoS("Service has weight 0; unexport the service", "service", svcRef)
r.Recorder.Eventf(&svcExport, corev1.EventTypeNormal, "Service", "Service %s weight is set to 0", svc.Name)
if controllerutil.ContainsFinalizer(&svcExport, svcExportCleanupFinalizer) {
if _, err = r.unexportService(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to unexport the service", "service", svcRef)
return ctrl.Result{}, err
}
}
validCond := meta.FindStatusCondition(svcExport.Status.Conditions, string(fleetnetv1alpha1.ServiceExportValid))
expectedValidCond := metav1.Condition{
Type: string(fleetnetv1alpha1.ServiceExportValid),
Status: metav1.ConditionTrue,
Reason: svcExportValidCondReason,
ObservedGeneration: svcExport.Generation,
Message: fmt.Sprintf("exported service %s/%s with 0 weight", svcExport.Namespace, svcExport.Name),
}
// Since the annotation won't change the generation, we compare the message here.
if condition.EqualConditionWithMessage(validCond, &expectedValidCond) {
// no need to retry if the condition is already set
return ctrl.Result{}, nil
}
meta.SetStatusCondition(&svcExport.Status.Conditions, expectedValidCond)
return ctrl.Result{}, r.MemberClient.Status().Update(ctx, &svcExport)
}
// Add the cleanup finalizer to the ServiceExport; this must happen before the Service is actually exported.
if !controllerutil.ContainsFinalizer(&svcExport, svcExportCleanupFinalizer) {
klog.V(2).InfoS("Add cleanup finalizer to service export", "service", svcRef)
if err := r.addServiceExportCleanupFinalizer(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to add cleanup finalizer to svc export", "service", svcRef)
return ctrl.Result{}, err
}
}
// Mark the ServiceExport as valid.
klog.V(2).InfoS("Mark service export as valid", "service", svcRef)
if err = r.markServiceExportAsValid(ctx, &svcExport); err != nil {
klog.ErrorS(err, "Failed to mark service export as valid", "service", svcRef)
return ctrl.Result{}, err
}
// Retrieve the last seen resource version and the last seen timestamp; these two values are used for metric collection.
// If the two values are not present or not valid, annotate ServiceExport with new values.
//
// Note that the two values are not tamperproof.
exportedSince, err := r.collectAndVerifyLastSeenResourceVersionAndTimestamp(ctx, &svc, &svcExport, startTime)
if err != nil {
klog.Warning("Failed to annotate last seen generation and timestamp", "serviceExport", svcRef)
}
// Export the Service or update the exported Service.
return r.exportService(ctx, &svcExport, &svc, exportedSince, exportWeight)
}