Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion controllers/apps/apimanager_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (r *APIManagerReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return specResult, nil
}

if statusResult.Requeue {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only check the last one? reconcileAPIManagerStatus is called more than once

if statusResult.Requeue || statusResult.RequeueAfter > 0 {
logger.Info("Reconciling not finished. Requeueing.")
return statusResult, nil
}
Expand Down
34 changes: 12 additions & 22 deletions controllers/apps/apimanager_status_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sort"
"strings"
"time"

appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1"
subController "github.com/3scale/3scale-operator/controllers/subscription"
Expand Down Expand Up @@ -49,33 +50,22 @@ func (s *APIManagerStatusReconciler) Reconcile() (reconcile.Result, error) {
return reconcile.Result{}, fmt.Errorf("failed to calculate status: %w", err)
}

// Read availability from the newly computed status, not the stale pre-reconcile snapshot.
// Using old status caused a True-to-False requeue gap: old=Available=True would suppress
// the requeue even after writing Available=False to the API server (THREESCALE-10754).
newAvailable := newStatus.Conditions.IsTrueFor(appsv1alpha1.APIManagerAvailableConditionType)

equalStatus := s.apimanagerResource.Status.Equals(newStatus, s.logger)
s.logger.V(1).Info("Status", "status is different", !equalStatus)
if equalStatus && newAvailable {
// Steady state
s.logger.V(1).Info("Status was not updated")
return reconcile.Result{}, nil
}

s.apimanagerResource.Status = *newStatus
updateErr := s.Client().Status().Update(s.Context(), s.apimanagerResource)
if updateErr != nil {
// Ignore conflicts, resource might just be outdated.
if errors.IsConflict(updateErr) {
s.logger.Info("Failed to update status: resource might just be outdated")
return reconcile.Result{Requeue: true}, nil
if !equalStatus {
s.logger.V(1).Info("Status is different")
s.apimanagerResource.Status = *newStatus
if err := s.Client().Status().Update(s.Context(), s.apimanagerResource); err != nil {
return reconcile.Result{}, fmt.Errorf("failed to update status: %w", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now ignore the Conflict error?

}

return reconcile.Result{}, fmt.Errorf("failed to update status: %w", updateErr)
} else {
s.logger.V(1).Info("Status was not updated")
}

// Re-check status periodically specifically only when availability is failing; this
// is an optimization - once we're available we rely only on watch events + re-sync interval
newAvailable := newStatus.Conditions.IsTrueFor(appsv1alpha1.APIManagerAvailableConditionType)
if !newAvailable {
return reconcile.Result{Requeue: true}, nil
return reconcile.Result{RequeueAfter: 30 * time.Second}, nil
}

return reconcile.Result{}, nil
Expand Down
12 changes: 6 additions & 6 deletions controllers/apps/apimanager_status_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,10 +609,10 @@ func TestAPIManagerStatusReconciler_Reconcile_statusConditions(t *testing.T) {
}
}

// TestAPIManagerStatusReconciler_Reconcile_requeueOnTrueToFalseTransition is a regression
// test for the stale-read requeue bug: when Available transitions from True to False the
// reconciler must requeue, not settle silently at Available=False.
func TestAPIManagerStatusReconciler_Reconcile_requeueOnTrueToFalseTransition(t *testing.T) {
// TestAPIManagerStatusReconciler_Reconcile_requeueAfterWhenUnavailable verifies that when
// Available transitions from True to False the reconciler schedules a requeue rather than
// settling silently at Available=False.
func TestAPIManagerStatusReconciler_Reconcile_requeueAfterWhenUnavailable(t *testing.T) {
namespace := "test-namespace"
t.Setenv("PREFLIGHT_CHECKS_BYPASS", "true")

Expand Down Expand Up @@ -640,7 +640,7 @@ func TestAPIManagerStatusReconciler_Reconcile_requeueOnTrueToFalseTransition(t *
if err != nil {
t.Fatalf("Reconcile() unexpected error: %v", err)
}
if !result.Requeue {
t.Errorf("Reconcile() Requeue = false, want true on Available True-to-False transition")
if result.RequeueAfter == 0 {
t.Errorf("Reconcile() RequeueAfter = 0, want non-zero on Available True-to-False transition")
}
}
Loading