Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: applicationset reduce redundant reconciles (#12457) #12480

Merged
merged 38 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e8b57c8
fix: applicationset reduce redundant reconciles
rumstead Feb 15, 2023
6036975
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 16, 2023
64f076f
fix: applicationset reduce redundant reconciles
rumstead Feb 16, 2023
6189803
adding tests
rumstead Feb 16, 2023
a6451b4
every line counts
rumstead Feb 16, 2023
4eeddc5
deep copy applications from event object
rumstead Feb 16, 2023
c3ed88d
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 16, 2023
119ee25
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 17, 2023
c2ade13
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 20, 2023
9d4f5c1
Merge branch 'master' of github.com:argoproj/argo-cd into fix/reduce-…
rumstead Feb 22, 2023
6c8c5dc
update from code review
rumstead Feb 22, 2023
c4be723
check progressive sync fields
rumstead Feb 22, 2023
81fc1ba
check progressive sync fields
rumstead Feb 22, 2023
0bb8c56
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 23, 2023
2fd0104
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 24, 2023
127c73d
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 24, 2023
86f65a5
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 24, 2023
c310299
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 27, 2023
9953638
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 28, 2023
2428347
Merge branch 'master' into fix/reduce-reconciles
rumstead Feb 28, 2023
4033fba
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 2, 2023
1c51480
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 3, 2023
aed10ec
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 7, 2023
5470173
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 8, 2023
8d63a7a
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 9, 2023
7cb9f7d
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 10, 2023
a8c1c8e
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 13, 2023
764771a
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 20, 2023
a231f86
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 20, 2023
53212ee
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 20, 2023
964a037
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 21, 2023
7814eba
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 22, 2023
f643811
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 22, 2023
0415a52
Merge branch 'master' into fix/reduce-reconciles
rumstead Mar 27, 2023
d4faece
selective checks for progressive syncs
rumstead Mar 27, 2023
791ee4a
Merge branch 'fix/reduce-reconciles' of github.com:rumstead/argo-cd i…
rumstead Mar 27, 2023
0a9589c
selective checks for progressive syncs
rumstead Mar 27, 2023
72f96e0
pural
rumstead Mar 28, 2023
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
69 changes: 68 additions & 1 deletion applicationset/controllers/applicationset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package controllers
import (
"context"
"fmt"
"reflect"
"time"

log "github.com/sirupsen/logrus"
Expand All @@ -29,9 +30,12 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"

"github.com/argoproj/argo-cd/v2/applicationset/generators"
Expand Down Expand Up @@ -533,7 +537,7 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager) error {

return ctrl.NewControllerManagedBy(mgr).
For(&argov1alpha1.ApplicationSet{}).
Owns(&argov1alpha1.Application{}).
Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
Watches(
&source.Kind{Type: &corev1.Secret{}},
&clusterSecretEventHandler{
Expand Down Expand Up @@ -1312,4 +1316,67 @@ func syncApplication(application argov1alpha1.Application, prune bool) (argov1al
return application, nil
}

var ownsHandler = predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
// if we are the owner and there is a create event, we most likely created it and do not need to
// re-reconcile
log.Debugln("received create event from owning an application")
return false
},
DeleteFunc: func(e event.DeleteEvent) bool {
log.Debugln("received delete event from owning an application")
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
log.Debugln("received update event from owning an application")
appOld, isApp := e.ObjectOld.(*argov1alpha1.Application)
if !isApp {
return false
}
appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
if !isApp {
return false
}
requeue := shouldRequeueApplicationSet(appOld, appNew)
log.Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
return requeue
},
GenericFunc: func(e event.GenericEvent) bool {
log.Debugln("received generic event from owning an application")
return true
},
}

// shouldRequeueApplicationSet determines when we want to requeue an ApplicationSet for reconciling based on an owned
// application change
// The applicationset controller owns a subset of the Application CR.
// We do not need to re-reconcile if parts of the application change outside the applicationset's control.
// An example being, Application.ApplicationStatus.ReconciledAt which gets updated by the application controller.
// Additionally, Application.ObjectMeta.ResourceVersion and Application.ObjectMeta.Generation which are set by K8s.
func shouldRequeueApplicationSet(appOld *argov1alpha1.Application, appNew *argov1alpha1.Application) bool {
if appOld == nil || appNew == nil {
return false
}
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved

// the applicationset controller owns the application spec, labels, annotations, and finalizers on the applications
if !reflect.DeepEqual(appOld.Spec, appNew.Spec) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations()) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels()) ||
!reflect.DeepEqual(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers()) {
return true
}

// progressive syncs use the application status for updates. if they differ, requeue to trigger the next progression
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved
if appOld.Status.Health.Status != appNew.Status.Health.Status || appOld.Status.Sync.Status != appNew.Status.Sync.Status {
return true
}

if appOld.Status.OperationState != nil && appNew.Status.OperationState != nil &&
appOld.Status.OperationState.Phase != appNew.Status.OperationState.Phase {
rumstead marked this conversation as resolved.
Show resolved Hide resolved
return true
}
rumstead marked this conversation as resolved.
Show resolved Hide resolved

return false
}

var _ handler.EventHandler = &clusterSecretEventHandler{}
103 changes: 103 additions & 0 deletions applicationset/controllers/applicationset_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"

"github.com/argoproj/argo-cd/v2/applicationset/generators"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
Expand Down Expand Up @@ -4838,3 +4839,105 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
})
}
}

func TestOwnsHandler(t *testing.T) {
assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{}))
assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{}))
assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{}))

now := metav1.Now()
type args struct {
e event.UpdateEvent
}
tests := []struct {
name string
args args
want bool
}{
{name: "SameApplicationReconciledAtDiff", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{ReconciledAt: &now}},
ObjectNew: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{ReconciledAt: &now}},
}}, want: false},
{name: "SameApplicationResourceVersionDiff", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "foo",
}},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "bar",
}},
}}, want: false},
{name: "ApplicationHealthStatusDiff", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
Health: argov1alpha1.HealthStatus{
Status: "Unknown",
},
}},
ObjectNew: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
Health: argov1alpha1.HealthStatus{
Status: "Healthy",
},
}},
}}, want: true},
{name: "ApplicationSyncStatusDiff", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
Sync: argov1alpha1.SyncStatus{
Status: "OutOfSync",
},
}},
ObjectNew: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
Sync: argov1alpha1.SyncStatus{
Status: "Synced",
},
}},
}}, want: true},
{name: "ApplicationOperationStateDiff", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
OperationState: &argov1alpha1.OperationState{
Phase: "foo",
},
}},
ObjectNew: &argov1alpha1.Application{Status: argov1alpha1.ApplicationStatus{
OperationState: &argov1alpha1.OperationState{
Phase: "bar",
},
}},
}}, want: true},
{name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
Generation: 1,
}},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
Generation: 2,
}},
}}, want: false},
{name: "DifferentApplicationSpec", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{Spec: argov1alpha1.ApplicationSpec{Project: "default"}},
ObjectNew: &argov1alpha1.Application{Spec: argov1alpha1.ApplicationSpec{Project: "not-default"}},
}}, want: true},
{name: "DifferentApplicationLabels", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
}}, want: true},
{name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}},
}}, want: true},
{name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}},
}}, want: true},
{name: "NotAnAppOld", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.AppProject{},
ObjectNew: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
}}, want: false},
{name: "NotAnAppNew", args: args{e: event.UpdateEvent{
ObjectOld: &argov1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
ObjectNew: &argov1alpha1.AppProject{},
}}, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ownsHandler.UpdateFunc(tt.args.e), "UpdateFunc(%v)", tt.args.e)
})
}
}