Skip to content

Commit 9456abe

Browse files
committed
Improve controller logging coverage and fix tests
* route every controller reconcile path through the shared logging helpers, ensuring consistent outcome/errors/logic-gate instrumentation * update controller unit tests to pass explicit loggers after the helper signature changes Signed-off-by: Maximilian Rink <[email protected]>
1 parent 0770c07 commit 9456abe

15 files changed

+222
-93
lines changed

controllers/logging_helpers.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package controllers
2+
3+
import (
4+
"github.com/go-logr/logr"
5+
"github.com/metal3-io/cluster-api-provider-metal3/baremetal"
6+
)
7+
8+
// logReconcileOutcome centralizes logging for controller reconciliation lifecycle events.
9+
// It ensures every reconciliation emits a completion log on the debug level and records
10+
// failures uniformly so that callers do not have to duplicate error logging.
11+
func logReconcileOutcome(logger logr.Logger, controllerName string, rerr *error) {
12+
if rerr == nil {
13+
return
14+
}
15+
16+
if *rerr != nil {
17+
// Controller-runtime already logs returned errors; avoid duplicate noise here.
18+
return
19+
}
20+
21+
logger.V(baremetal.VerbosityLevelDebug).Info("Reconciliation completed", "controller", controllerName)
22+
}
23+
24+
// logLogicGate makes it easy to record decision points at debug level while keeping
25+
// a consistent verbosity across controllers.
26+
func logLogicGate(logger logr.Logger, message string, keysAndValues ...any) {
27+
logger.V(baremetal.VerbosityLevelDebug).Info(message, keysAndValues...)
28+
}

controllers/metal3cluster_controller.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,21 @@ type Metal3ClusterReconciler struct {
7373
// Reconcile reads that state of the cluster for a Metal3Cluster object and makes changes based on the state read
7474
// and what is in the Metal3Cluster.Spec.
7575
func (r *Metal3ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, rerr error) {
76-
clusterLog := log.Log.WithName(clusterControllerName).WithValues("metal3-cluster", req.NamespacedName)
76+
clusterLog := log.Log.WithName(clusterControllerName).WithValues(
77+
"metal3-cluster", req.NamespacedName,
78+
)
79+
defer logReconcileOutcome(clusterLog, clusterControllerName, &rerr)
7780

7881
// Fetch the Metal3Cluster instance
7982
metal3Cluster := &infrav1.Metal3Cluster{}
8083

8184
if err := r.Client.Get(ctx, req.NamespacedName, metal3Cluster); err != nil {
8285
if apierrors.IsNotFound(err) {
86+
logLogicGate(clusterLog, "Metal3Cluster resource not found in cache, skipping")
8387
return ctrl.Result{}, nil
8488
}
8589

86-
return ctrl.Result{}, err
90+
return ctrl.Result{}, errors.Wrap(err, "Failed to get Metal3Cluster")
8791
}
8892

8993
// This is checking if default values are changed or not if the default
@@ -121,6 +125,8 @@ func (r *Metal3ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
121125
// Fetch the Cluster.
122126
cluster, err := util.GetOwnerCluster(ctx, r.Client, metal3Cluster.ObjectMeta)
123127
if err != nil {
128+
// Only log error once, with full context
129+
clusterLog.Error(err, "Unable to get owner cluster for Metal3Cluster")
124130
invalidConfigError := capierrors.InvalidConfigurationClusterError
125131
metal3Cluster.Status.FailureReason = &invalidConfigError
126132
metal3Cluster.Status.FailureMessage = ptr.To("Unable to get owner cluster")
@@ -142,6 +148,10 @@ func (r *Metal3ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
142148
// Return early if Metal3Cluster or Cluster is paused.
143149
var isPaused, requeue bool
144150
if isPaused, requeue, err = paused.EnsurePausedCondition(ctx, r.Client, cluster, metal3Cluster); err != nil || isPaused || requeue {
151+
if err != nil {
152+
clusterLog.Error(err, "Failed to evaluate paused condition")
153+
}
154+
logLogicGate(clusterLog, "Pause condition active", "isPaused", isPaused, "requeue", requeue)
145155
return ctrl.Result{Requeue: requeue, RequeueAfter: requeueAfter}, nil
146156
}
147157

@@ -158,13 +168,14 @@ func (r *Metal3ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
158168

159169
// Handle deleted clusters
160170
if !metal3Cluster.DeletionTimestamp.IsZero() {
171+
logLogicGate(clusterLog, "Metal3Cluster has deletion timestamp, entering delete reconciliation")
161172
v1beta2conditions.Set(metal3Cluster, metav1.Condition{
162173
Type: infrav1.Metal3ClusterReadyV1Beta2Condition,
163174
Status: metav1.ConditionFalse,
164175
Reason: infrav1.Metal3ClusterDeletingV1Beta2Reason,
165176
})
166177
var res ctrl.Result
167-
res, err = reconcileDelete(ctx, clusterMgr)
178+
res, err = reconcileDelete(ctx, clusterMgr, clusterLog)
168179
// Requeue if the reconcile failed because the ClusterCache was locked for
169180
// the current cluster because of concurrent access.
170181
if errors.Is(err, clustercache.ErrClusterNotConnected) {
@@ -175,7 +186,8 @@ func (r *Metal3ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
175186
}
176187

177188
// Handle non-deleted clusters
178-
res, err := reconcileNormal(ctx, clusterMgr)
189+
logLogicGate(clusterLog, "Metal3Cluster is active, entering normal reconciliation")
190+
res, err := reconcileNormal(ctx, clusterMgr, clusterLog)
179191
// Requeue if the reconcile failed because the ClusterCache was locked for
180192
// the current cluster because of concurrent access.
181193
if errors.Is(err, clustercache.ErrClusterNotConnected) {
@@ -228,42 +240,52 @@ func patchMetal3Cluster(ctx context.Context, patchHelper *v1beta1patch.Helper, m
228240
return patchHelper.Patch(ctx, metal3Cluster, options...)
229241
}
230242

231-
func reconcileNormal(ctx context.Context, clusterMgr baremetal.ClusterManagerInterface) (ctrl.Result, error) { //nolint:unparam
243+
func reconcileNormal(ctx context.Context, clusterMgr baremetal.ClusterManagerInterface, logger logr.Logger) (ctrl.Result, error) { //nolint:unparam
232244
// If the Metal3Cluster doesn't have finalizer, add it.
245+
logLogicGate(logger, "Ensuring finalizer exists on Metal3Cluster")
233246
clusterMgr.SetFinalizer()
234247

235248
// Create the Metal3 cluster (no-op)
249+
logLogicGate(logger, "Invoking cluster creation/update flow")
236250
if err := clusterMgr.Create(ctx); err != nil {
237-
return ctrl.Result{}, err
251+
return ctrl.Result{}, errors.Wrap(err, "Failed during Metal3Cluster create")
238252
}
239253

240254
// Set APIEndpoints so the Cluster API Cluster Controller can pull it
241255
if err := clusterMgr.UpdateClusterStatus(); err != nil {
242256
return ctrl.Result{}, errors.Wrap(err, "failed to get ip for the API endpoint")
243257
}
258+
logLogicGate(logger, "Successfully reconciled Metal3Cluster status")
244259

245260
return ctrl.Result{}, nil
246261
}
247262

248263
func reconcileDelete(ctx context.Context,
249-
clusterMgr baremetal.ClusterManagerInterface) (ctrl.Result, error) {
264+
clusterMgr baremetal.ClusterManagerInterface,
265+
logger logr.Logger,
266+
) (ctrl.Result, error) {
250267
// Verify that no metal3machine depend on the metal3cluster
268+
logLogicGate(logger, "Checking for descendant resources before delete")
251269
descendants, err := clusterMgr.CountDescendants(ctx)
252270
if err != nil {
253-
return ctrl.Result{}, err
271+
return ctrl.Result{}, errors.Wrap(err, "Failed to count descendants")
254272
}
255273
if descendants > 0 {
256274
// Requeue so we can check the next time to see if there are still any
257275
// descendants left.
276+
logLogicGate(logger, "Descendants still present, will requeue delete", "descendantCount", descendants)
258277
return ctrl.Result{Requeue: true, RequeueAfter: requeueAfter}, nil
259278
}
260279

280+
logLogicGate(logger, "No descendants found, deleting Metal3Cluster resources")
261281
if err := clusterMgr.Delete(); err != nil {
262282
return ctrl.Result{}, errors.Wrap(err, "failed to delete Metal3Cluster")
263283
}
264284

265285
// Cluster is deleted so remove the finalizer.
286+
logLogicGate(logger, "Removing Metal3Cluster finalizer")
266287
clusterMgr.UnsetFinalizer()
288+
logLogicGate(logger, "Delete reconciliation completed")
267289

268290
return ctrl.Result{}, nil
269291
}

controllers/metal3cluster_controller_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
. "github.com/onsi/ginkgo/v2"
2323
. "github.com/onsi/gomega"
2424
"github.com/pkg/errors"
25+
"k8s.io/klog/v2/klogr"
2526
)
2627

2728
var _ = Describe("Metal3Cluster controller", func() {
@@ -73,7 +74,8 @@ var _ = Describe("Metal3Cluster controller", func() {
7374
m.EXPECT().
7475
Create(context.TODO()).Return(returnedError)
7576

76-
res, err := reconcileNormal(context.TODO(), m)
77+
testLog := klogr.New()
78+
res, err := reconcileNormal(context.TODO(), m, testLog)
7779

7880
if tc.ExpectError {
7981
Expect(err).To(HaveOccurred())
@@ -139,7 +141,8 @@ var _ = Describe("Metal3Cluster controller", func() {
139141
returnedError,
140142
)
141143

142-
res, err := reconcileDelete(context.TODO(), m)
144+
testLog := klogr.New()
145+
res, err := reconcileDelete(context.TODO(), m, testLog)
143146

144147
if tc.ExpectError {
145148
Expect(err).To(HaveOccurred())

controllers/metal3data_controller.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,20 @@ type Metal3DataReconciler struct {
6262

6363
// Reconcile handles Metal3Data events.
6464
func (r *Metal3DataReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, rerr error) {
65-
metadataLog := r.Log.WithName(dataControllerName).WithValues("metal3-data", req.NamespacedName)
65+
metadataLog := r.Log.WithName(dataControllerName).WithValues(
66+
"metal3-data", req.NamespacedName,
67+
)
68+
defer logReconcileOutcome(metadataLog, dataControllerName, &rerr)
6669

6770
// Fetch the Metal3Data instance.
6871
metal3Data := &infrav1.Metal3Data{}
6972

7073
if err := r.Client.Get(ctx, req.NamespacedName, metal3Data); err != nil {
7174
if apierrors.IsNotFound(err) {
75+
logLogicGate(metadataLog, "Metal3Data resource not found, skipping")
7276
return ctrl.Result{}, nil
7377
}
74-
return ctrl.Result{}, err
78+
return ctrl.Result{}, errors.Wrap(err, "Failed to fetch Metal3Data")
7579
}
7680
helper, err := v1beta1patch.NewHelper(metal3Data, r.Client)
7781
if err != nil {
@@ -86,11 +90,11 @@ func (r *Metal3DataReconciler) Reconcile(ctx context.Context, req ctrl.Request)
8690
metadataLog.Info("Metal3Data no longer exists, skipping patch")
8791
return
8892
}
89-
metadataLog.Info("Failed to check if Metal3Data exists, attempting patch")
93+
metadataLog.Error(err, "Failed to verify Metal3Data existence prior to patching")
9094
}
9195
err = helper.Patch(ctx, metal3Data)
9296
if err != nil {
93-
metadataLog.Info("failed to Patch Metal3Data")
97+
metadataLog.Error(err, "Failed to patch Metal3Data")
9498
rerr = err
9599
}
96100
}()
@@ -126,12 +130,14 @@ func (r *Metal3DataReconciler) Reconcile(ctx context.Context, req ctrl.Request)
126130

127131
// Handle deletion of Metal3Data
128132
if !metal3Data.ObjectMeta.DeletionTimestamp.IsZero() {
133+
logLogicGate(metadataLog, "Metal3Data marked for deletion")
129134
// Check if the Metal3DataClaim is gone. We cannot clean up until it is.
130135
err := r.Client.Get(ctx, types.NamespacedName{Name: metal3Data.Spec.Claim.Name, Namespace: metal3Data.Spec.Claim.Namespace}, &infrav1.Metal3DataClaim{})
131136
if err != nil {
132137
if apierrors.IsNotFound(err) {
133-
return r.reconcileDelete(ctx, metadataMgr)
138+
return r.reconcileDelete(ctx, metadataMgr, metadataLog)
134139
}
140+
metadataLog.Error(err, "Failed to check Metal3DataClaim during delete")
135141
return ctrl.Result{}, err
136142
}
137143
// Requeue until Metal3DataClaim is gone.
@@ -140,31 +146,38 @@ func (r *Metal3DataReconciler) Reconcile(ctx context.Context, req ctrl.Request)
140146
}
141147

142148
// Handle non-deleted machines
143-
return r.reconcileNormal(ctx, metadataMgr)
149+
logLogicGate(metadataLog, "Metal3Data active, entering normal reconciliation")
150+
return r.reconcileNormal(ctx, metadataMgr, metadataLog)
144151
}
145152

146153
func (r *Metal3DataReconciler) reconcileNormal(ctx context.Context,
147154
metadataMgr baremetal.DataManagerInterface,
155+
metadataLog logr.Logger,
148156
) (ctrl.Result, error) {
149157
// If the Metal3Data doesn't have finalizer, add it.
158+
logLogicGate(metadataLog, "Ensuring Metal3Data finalizer is present")
150159
metadataMgr.SetFinalizer()
151160

152161
err := metadataMgr.Reconcile(ctx)
153162
if err != nil {
154-
return checkReconcileError(err, "Failed to create secrets")
163+
return checkReconcileError(metadataLog, err, "Failed to reconcile Metal3Data secrets")
155164
}
165+
logLogicGate(metadataLog, "Metal3Data reconcile completed")
156166
return ctrl.Result{}, nil
157167
}
158168

159169
func (r *Metal3DataReconciler) reconcileDelete(ctx context.Context,
160170
metadataMgr baremetal.DataManagerInterface,
171+
metadataLog logr.Logger,
161172
) (ctrl.Result, error) {
173+
logLogicGate(metadataLog, "Releasing IP address leases before delete")
162174
err := metadataMgr.ReleaseLeases(ctx)
163175
if err != nil {
164-
return checkReconcileError(err, "Failed to release IP address leases")
176+
return checkReconcileError(metadataLog, err, "Failed to release IP address leases")
165177
}
166178

167179
metadataMgr.UnsetFinalizer()
180+
logLogicGate(metadataLog, "Removed Metal3Data finalizer after cleanup")
168181

169182
return ctrl.Result{}, nil
170183
}

controllers/metal3data_controller_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/pkg/errors"
3131
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3232
"k8s.io/apimachinery/pkg/types"
33+
"k8s.io/klog/v2/klogr"
3334
"k8s.io/utils/ptr"
3435
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
3536
ctrl "sigs.k8s.io/controller-runtime"
@@ -303,7 +304,7 @@ var _ = Describe("Metal3Data manager", func() {
303304
m.EXPECT().Reconcile(context.TODO()).Return(nil)
304305
}
305306

306-
res, err := dataReconcile.reconcileNormal(context.TODO(), m)
307+
res, err := dataReconcile.reconcileNormal(context.TODO(), m, klogr.New())
307308
gomockCtrl.Finish()
308309

309310
if tc.ExpectError {
@@ -364,7 +365,7 @@ var _ = Describe("Metal3Data manager", func() {
364365
m.EXPECT().UnsetFinalizer()
365366
}
366367

367-
res, err := dataReconcile.reconcileDelete(context.TODO(), m)
368+
res, err := dataReconcile.reconcileDelete(context.TODO(), m, klogr.New())
368369
gomockCtrl.Finish()
369370

370371
if tc.ExpectError {

0 commit comments

Comments
 (0)