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
6 changes: 5 additions & 1 deletion internal/controller/etcdcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ func (r *EtcdClusterReconciler) fetchAndValidateState(ctx context.Context, req c
// Ensure the operator has TLS credentials when the cluster requests TLS.
if ec.Spec.TLS != nil {
if err := createClientCertificate(ctx, ec, r.Client); err != nil {
logger.Error(err, "Failed to create Client Certificate.")
// The data path relies on this client certificate existing; do not
// proceed with reconciliation when it cannot be provisioned.
// Requeue with backoff so the failure is retried instead of swallowed.
logger.Error(err, "Failed to create Client Certificate. Requesting requeue")
return nil, ctrl.Result{RequeueAfter: requeueDuration}, nil
}
} else {
// TODO: instead of logging error, set default autoConfig
Expand Down
44 changes: 44 additions & 0 deletions internal/controller/etcdcluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,50 @@ func TestFetchAndValidateState(t *testing.T) {
}
}

// TestFetchAndValidateStateClientCertificateError verifies that when the cluster
// requests TLS but the client certificate cannot be provisioned,
// fetchAndValidateState requeues with backoff instead of swallowing the error and
// proceeding. The fake client's scheme intentionally omits the cert-manager
// types, so the certificate lookup performed by createClientCertificate fails
// with a non-NotFound error, exercising the provisioning failure path.
func TestFetchAndValidateStateClientCertificateError(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = appsv1.AddToScheme(scheme)
_ = ecv1alpha1.AddToScheme(scheme)
// Note: cert-manager's certv1 scheme is deliberately NOT registered.

ec := &ecv1alpha1.EtcdCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "etcd",
Namespace: "default",
UID: "1",
},
Spec: ecv1alpha1.EtcdClusterSpec{
Size: 1,
Version: "3.5.17",
TLS: &ecv1alpha1.TLSCertificate{
Provider: "cert-manager",
},
},
}

ctx := t.Context()

fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(ec).Build()
r := &EtcdClusterReconciler{Client: fakeClient, Scheme: scheme}

req := ctrl.Request{NamespacedName: types.NamespacedName{Name: "etcd", Namespace: "default"}}
state, res, err := r.fetchAndValidateState(ctx, req)

// Reconciliation must not proceed past cert provisioning: no state is
// returned, and the result is a requeue with the standard backoff.
assert.Nil(t, state, "reconcile should not proceed past client-certificate provisioning")
assert.NoError(t, err, "the failure should be surfaced via requeue, not a returned error")
assert.Equal(t, ctrl.Result{RequeueAfter: requeueDuration}, res, "expected a requeue with backoff")
assert.NotZero(t, res.RequeueAfter, "RequeueAfter must be non-zero")
}

// TestBootstrapStatefulSet outlines tests for ensuring StatefulSet and Service
// creation and bootstrap logic.
func TestBootstrapStatefulSet(t *testing.T) {
Expand Down