helmrepo-oci: check before rec on type switching

When a HelmRepository with "default" spec.type is switched to "oci", the
existing HelmRepository is processed by HelmRepositoryReconciler by
running reconcileDelete() which removes all the previous status
information and allows the HelmRepositoryOCIReconciler to process the
object and add its own status data. But at times, when
HelmRepositoryOCIReconciler starts processing a HelmRepository with
stale status data from the client cache, it contains the stale
conditions that are owned only by HelmRepositoryReconciler and isn't
managed by HelmRepositoryOCIReconciler. This results in situations where
Ready is marked as True with the latest generation of the object and the
unmanaged stale conditions remain in the previous generation, resulting
in unexpected status conditions.

In the observed flaky tests,
`TestHelmRepositoryReconciler_ReconcileTypeUpdatePredicateFilter` would
fail because of stale ArtifactInStorage condition with previous
generation value.

This change adds a check in the HelmRepositoryOCIReconciler to start
processing the object only once the stale unmanaged conditions have been
removed.

Signed-off-by: Sunny <darkowlzz@protonmail.com>
This commit is contained in:
Sunny 2023-02-06 10:43:25 +00:00
parent 75cde08ff0
commit 42bc3e8b0a
2 changed files with 54 additions and 0 deletions

View File

@ -40,6 +40,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/oci" "github.com/fluxcd/pkg/oci"
"github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/conditions"
@ -82,6 +83,11 @@ type HelmRepositoryOCIReconciler struct {
RegistryClientGenerator RegistryClientGeneratorFunc RegistryClientGenerator RegistryClientGeneratorFunc
patchOptions []patch.Option patchOptions []patch.Option
// unmanagedConditions are the conditions that are not managed by this
// reconciler and need to be removed from the object before taking ownership
// of the object being reconciled.
unmanagedConditions []string
} }
// RegistryClientGeneratorFunc is a function that returns a registry client // RegistryClientGeneratorFunc is a function that returns a registry client
@ -95,6 +101,7 @@ func (r *HelmRepositoryOCIReconciler) SetupWithManager(mgr ctrl.Manager) error {
} }
func (r *HelmRepositoryOCIReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error { func (r *HelmRepositoryOCIReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error {
r.unmanagedConditions = conditionsDiff(helmRepositoryReadyCondition.Owned, helmRepositoryOCIOwnedConditions)
r.patchOptions = getPatchOptions(helmRepositoryOCIOwnedConditions, r.ControllerName) r.patchOptions = getPatchOptions(helmRepositoryOCIOwnedConditions, r.ControllerName)
recoverPanic := true recoverPanic := true
@ -124,6 +131,16 @@ func (r *HelmRepositoryOCIReconciler) Reconcile(ctx context.Context, req ctrl.Re
return ctrl.Result{}, client.IgnoreNotFound(err) return ctrl.Result{}, client.IgnoreNotFound(err)
} }
// If the object contains any of the unmanaged conditions, requeue and wait
// for those conditions to be removed first before processing the object.
// NOTE: This will happen only when a HelmRepository's spec.type is switched
// from "default" to "oci".
if conditions.HasAny(obj, r.unmanagedConditions) {
r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "IncompleteTransition",
"object contains conditions managed by other reconciler")
return ctrl.Result{RequeueAfter: time.Second}, nil
}
// Record suspended status metric // Record suspended status metric
r.RecordSuspend(ctx, obj, obj.Spec.Suspend) r.RecordSuspend(ctx, obj, obj.Spec.Suspend)
@ -428,3 +445,18 @@ func makeLoginOption(auth authn.Authenticator, keychain authn.Keychain, registry
return nil, nil return nil, nil
} }
func conditionsDiff(a, b []string) []string {
bMap := make(map[string]struct{}, len(b))
for _, j := range b {
bMap[j] = struct{}{}
}
r := []string{}
for _, i := range a {
if _, exists := bMap[i]; !exists {
r = append(r, i)
}
}
return r
}

View File

@ -19,6 +19,7 @@ package controllers
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strconv"
"testing" "testing"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -320,3 +321,24 @@ func TestHelmRepositoryOCIReconciler_authStrategy(t *testing.T) {
}) })
} }
} }
func TestConditionsDiff(t *testing.T) {
tests := []struct {
a, b, want []string
}{
{[]string{"a", "b", "c"}, []string{"b", "d"}, []string{"a", "c"}},
{[]string{"a", "b", "c"}, []string{}, []string{"a", "b", "c"}},
{[]string{}, []string{"b", "d"}, []string{}},
{[]string{}, []string{}, []string{}},
{[]string{"a", "b"}, nil, []string{"a", "b"}},
{nil, []string{"a", "b"}, []string{}},
{nil, nil, []string{}},
}
for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := NewWithT(t)
g.Expect(conditionsDiff(tt.a, tt.b)).To(Equal(tt.want))
})
}
}