Skip to content

Commit

Permalink
Try to reduce the log verbosity of the SPC controller. (#1131)
Browse files Browse the repository at this point in the history
* Try to reduce the log verbosity of the SPC controller.

This is done by introducing a predicate for the watch that
should ignore any changes to the TCs that are only caused
by the changes in the timestamps of the conditions.

Co-authored-by: Matous Jobanek <mjobanek@redhat.com>
  • Loading branch information
metlos and MatousJobanek authored Jan 30, 2025
1 parent f35e54f commit e2d2b30
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 4 deletions.
2 changes: 1 addition & 1 deletion controllers/spaceprovisionerconfig/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func MapToolchainClusterToSpaceProvisionerConfigs(cl runtimeclient.Client) func(context.Context, runtimeclient.Object) []reconcile.Request {
func mapToolchainClusterToSpaceProvisionerConfigs(cl runtimeclient.Client) func(context.Context, runtimeclient.Object) []reconcile.Request {
return func(ctx context.Context, obj runtimeclient.Object) []reconcile.Request {
if _, ok := obj.(*toolchainv1alpha1.ToolchainCluster); !ok {
return nil
Expand Down
4 changes: 2 additions & 2 deletions controllers/spaceprovisionerconfig/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestMapToolchainClusterToSpaceProvisionerConfigs(t *testing.T) {
cl := test.NewFakeClient(t, spc0, spc1, spc2)

// when
reqs := MapToolchainClusterToSpaceProvisionerConfigs(cl)(context.TODO(), &toolchainv1alpha1.ToolchainCluster{
reqs := mapToolchainClusterToSpaceProvisionerConfigs(cl)(context.TODO(), &toolchainv1alpha1.ToolchainCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
Namespace: test.HostOperatorNs,
Expand All @@ -123,7 +123,7 @@ func TestMapToolchainClusterToSpaceProvisionerConfigs(t *testing.T) {
}

// when
reqs := MapToolchainClusterToSpaceProvisionerConfigs(cl)(context.TODO(), &toolchainv1alpha1.ToolchainCluster{
reqs := mapToolchainClusterToSpaceProvisionerConfigs(cl)(context.TODO(), &toolchainv1alpha1.ToolchainCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
Namespace: test.HostOperatorNs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/host-operator/controllers/toolchainconfig"
toolchainpredicate "github.com/codeready-toolchain/host-operator/pkg/predicate"
"github.com/codeready-toolchain/toolchain-common/pkg/condition"
"github.com/redhat-cop/operator-utils/pkg/util"
corev1 "k8s.io/api/core/v1"
Expand All @@ -27,12 +28,32 @@ type Reconciler struct {

var _ reconcile.Reconciler = (*Reconciler)(nil)

func getToolchainClusterConditions(obj runtimeclient.Object) []toolchainv1alpha1.Condition {
tc, ok := obj.(*toolchainv1alpha1.ToolchainCluster)
if !ok {
return nil
}
return tc.Status.Conditions
}

func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&toolchainv1alpha1.SpaceProvisionerConfig{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Watches(
&toolchainv1alpha1.ToolchainCluster{},
handler.EnqueueRequestsFromMapFunc(MapToolchainClusterToSpaceProvisionerConfigs(r.Client)),
handler.EnqueueRequestsFromMapFunc(mapToolchainClusterToSpaceProvisionerConfigs(r.Client)),
builder.WithPredicates(
// NOTE: this is ignoring the changes in the Spec as well as the Status.OperatorNamespace or Status.APIEndpoint
//
// The spec of the TC contains just the reference to the secret with the connection information. We're not actually
// interested in that change here because the SPC is not interested in HOW we connect to the cluster but rather IF
// we're connected to the cluster.
//
// For the same reason we're not actually interested in the values of the Status.OperatorNamespace or Status.APIEndpoint.
//
// So in another words, we're only interested in the change of the ready condition of the TC.
&toolchainpredicate.ToolchainConditionChanged{GetConditions: getToolchainClusterConditions, Type: toolchainv1alpha1.ConditionReady},
),
).
// we use the same information as the ToolchainStatus specific for the SPCs. Because memory consumption is
// read directly out of the member clusters using remote connections, let's look for it only once
Expand Down
62 changes: 62 additions & 0 deletions pkg/predicate/toolchainconditionchanged.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package predicate

import (
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/toolchain-common/pkg/condition"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

var _ predicate.Predicate = (*ToolchainConditionChanged)(nil)

type (
GetToolchainConditions func(client.Object) []toolchainv1alpha1.Condition

// ToolchainConditionChanged is a predicate to check that some conditions changed in a toolchain object.
ToolchainConditionChanged struct {
// GetConditions is function used to obtain the conditions from an object. There is no generic way of
// doing that using the standard k8s api.
GetConditions GetToolchainConditions

// Type is the type of the condition to look at
Type toolchainv1alpha1.ConditionType
}
)

// Create implements predicate.Predicate.
func (t *ToolchainConditionChanged) Create(event.CreateEvent) bool {
return true
}

// Delete implements predicate.Predicate.
func (t *ToolchainConditionChanged) Delete(event.DeleteEvent) bool {
return true
}

// Generic implements predicate.Predicate.
func (t *ToolchainConditionChanged) Generic(event.GenericEvent) bool {
return true
}

func (t *ToolchainConditionChanged) Update(evt event.UpdateEvent) bool {
oldConds := t.GetConditions(evt.ObjectOld)
newConds := t.GetConditions(evt.ObjectNew)

oldCond, oldOk := condition.FindConditionByType(oldConds, t.Type)
newCond, newOk := condition.FindConditionByType(newConds, t.Type)

if oldOk != newOk {
// the condition appeared or disappeared
return true
}

if !oldOk {
// neither the old nor the new version contains a condition of the required type
return false
}

// we're intentionally ignoring changes to *Time fields of the conditions. If nothing else changed, those do not represent a factual
// change in the condition.
return oldCond.Status != newCond.Status || oldCond.Reason != newCond.Reason || oldCond.Message != newCond.Message
}
172 changes: 172 additions & 0 deletions pkg/predicate/toolchainconditionchanged_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package predicate

import (
"testing"

"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/toolchain-common/pkg/test"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestToolchainConditionChangedPredicate(t *testing.T) {
// given
blueprint := &toolchainv1alpha1.ToolchainCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "tc",
Namespace: test.HostOperatorNs,
},
Spec: toolchainv1alpha1.ToolchainClusterSpec{
SecretRef: toolchainv1alpha1.LocalSecretReference{
Name: "secret",
},
},
Status: toolchainv1alpha1.ToolchainClusterStatus{
APIEndpoint: "https://endpoint",
OperatorNamespace: "member-op-ns",
Conditions: []toolchainv1alpha1.Condition{
{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionTrue,
Reason: "because",
Message: "I CAN",
},
},
},
}

predicate := ToolchainConditionChanged{
GetConditions: func(o client.Object) []toolchainv1alpha1.Condition {
return o.(*toolchainv1alpha1.ToolchainCluster).Status.Conditions
},
Type: toolchainv1alpha1.ConditionReady,
}

test := func(t *testing.T, expectedResult bool, updateObjects func(old, new *toolchainv1alpha1.ToolchainCluster)) {
t.Helper()

// given
tcOld := blueprint.DeepCopy()
tcNew := blueprint.DeepCopy()

updateObjects(tcOld, tcNew)

ev := event.UpdateEvent{
ObjectOld: tcOld,
ObjectNew: tcNew,
}

t.Run("update", func(t *testing.T) {
// when
result := predicate.Update(ev)

// then
assert.Equal(t, expectedResult, result)
})

t.Run("create", func(t *testing.T) {
// when
result := predicate.Create(event.CreateEvent{Object: tcNew})

// then
assert.True(t, result)
})

t.Run("delete", func(t *testing.T) {
// when
result := predicate.Delete(event.DeleteEvent{Object: tcNew})

// then
assert.True(t, result)
})

t.Run("generic", func(t *testing.T) {
// when
result := predicate.Generic(event.GenericEvent{Object: tcNew})

// then
assert.True(t, result)
})
}

t.Run("ignores no change", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {})
})

t.Run("ignores changes in the object meta", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Annotations = map[string]string{"k": "v"}
})
})

t.Run("ignores changes in the spec", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Spec.SecretRef.Name = "other"
})
})

t.Run("ignores changes in the status outside of conditions", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.OperatorNamespace = "other"
})
})

t.Run("ignores change in other condition types", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions = append(new.Status.Conditions, toolchainv1alpha1.Condition{Type: toolchainv1alpha1.ConditionType("other")})
})
})

t.Run("ignores changes in the last transition time", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions[0].LastTransitionTime = metav1.Now()
})
})

t.Run("ignores changes in the updated time", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
time := metav1.Now()
new.Status.Conditions[0].LastUpdatedTime = &time
})
})

t.Run("detects when condition appears", func(t *testing.T) {
test(t, true, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions = old.Status.Conditions
old.Status.Conditions = nil
})
})
t.Run("ignores missing conditions", func(t *testing.T) {
test(t, false, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions = nil
old.Status.Conditions = nil
})
})
t.Run("detects change when condition disappears", func(t *testing.T) {
test(t, true, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions = nil
})
})

t.Run("detects change in the status", func(t *testing.T) {
test(t, true, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions[0].Status = corev1.ConditionFalse
})
})

t.Run("detects change in the reason", func(t *testing.T) {
test(t, true, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions[0].Reason = "other"
})
})

t.Run("detects change in the message", func(t *testing.T) {
test(t, true, func(old, new *toolchainv1alpha1.ToolchainCluster) {
new.Status.Conditions[0].Message = "other"
})
})
}

0 comments on commit e2d2b30

Please sign in to comment.