Skip to content

Commit f26398b

Browse files
Add clusterClass generation check to Cluster reconciler
1 parent 501f5ed commit f26398b

File tree

4 files changed

+68
-0
lines changed

4 files changed

+68
-0
lines changed

api/v1beta1/condition_consts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ const (
276276
// TopologyReconciledHookBlockingReason (Severity=Info) documents reconciliation of a Cluster topology
277277
// not yet completed because at least one of the lifecycle hooks is blocking.
278278
TopologyReconciledHookBlockingReason = "LifecycleHookBlocking"
279+
280+
// TopologyReconciledClusterClassNotReconciledReason (Severity=Info) documents reconciliation of a Cluster topology not
281+
// yet completed because the ClusterClass has not reconciled yet. If this condition persists there may be an issue
282+
// with the ClusterClass surfaced in the ClusterClass status or controller logs.
283+
TopologyReconciledClusterClassNotReconciledReason = "ClusterClassNotReconciled"
279284
)
280285

281286
// Conditions and condition reasons for ClusterClass.

internal/controllers/topology/cluster/cluster_controller.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,31 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
221221
func (r *Reconciler) reconcile(ctx context.Context, s *scope.Scope) (ctrl.Result, error) {
222222
var err error
223223

224+
// Get ClusterClass.
225+
clusterClass := &clusterv1.ClusterClass{}
226+
key := client.ObjectKey{Name: s.Current.Cluster.Spec.Topology.Class, Namespace: s.Current.Cluster.Namespace}
227+
if err := r.Client.Get(ctx, key, clusterClass); err != nil {
228+
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve ClusterClass %s", s.Current.Cluster.Spec.Topology.Class)
229+
}
230+
231+
s.Blueprint.ClusterClass = clusterClass
232+
// If the ClusterClass `metadata.Generation` doesn't match the `status.ObservedGeneration` return as the ClusterClass
233+
// is not up to date.
234+
// Note: This doesn't require requeue as a change to ClusterClass observedGeneration will cause an additional reconcile
235+
// in the Cluster.
236+
if clusterClass.GetGeneration() != clusterClass.Status.ObservedGeneration {
237+
return ctrl.Result{}, nil
238+
}
239+
240+
// Default and Validate the Cluster based on information from the ClusterClass.
241+
// This step is needed as if the ClusterClass does not exist at Cluster creation some fields may not be defaulted or
242+
// validated in the webhook.
243+
if errs := webhooks.DefaultVariables(s.Current.Cluster, clusterClass); len(errs) > 0 {
244+
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
245+
}
246+
if errs := webhooks.ValidateClusterForClusterClass(s.Current.Cluster, clusterClass, field.NewPath("spec", "topology")); len(errs) > 0 {
247+
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
248+
}
224249
// Gets the blueprint with the ClusterClass and the referenced templates
225250
// and store it in the request scope.
226251
s.Blueprint, err = r.getBlueprint(ctx, s.Current.Cluster, s.Blueprint.ClusterClass)

internal/controllers/topology/cluster/conditions.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (r *Reconciler) reconcileConditions(s *scope.Scope, cluster *clusterv1.Clus
3737
// cluster are in sync with the topology defined in the cluster.
3838
// The condition is false under the following conditions:
3939
// - An error occurred during the reconcile process of the cluster topology.
40+
// - The ClusterClass has not been successfully reconciled with its current spec.
4041
// - The cluster upgrade has not yet propagated to all the components of the cluster.
4142
// - For a managed topology cluster the version upgrade is propagated one component at a time.
4243
// In such a case, since some of the component's spec would be adrift from the topology the
@@ -58,6 +59,22 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
5859
return nil
5960
}
6061

62+
// If the ClusterClass `metadata.Generation` doesn't match the `status.ObservedGeneration` requeue as the ClusterClass
63+
// is not up to date.
64+
if s.Blueprint != nil && s.Blueprint.ClusterClass != nil &&
65+
s.Blueprint.ClusterClass.GetGeneration() != s.Blueprint.ClusterClass.Status.ObservedGeneration {
66+
conditions.Set(
67+
cluster,
68+
conditions.FalseCondition(
69+
clusterv1.TopologyReconciledCondition,
70+
clusterv1.TopologyReconciledClusterClassNotReconciledReason,
71+
clusterv1.ConditionSeverityInfo,
72+
"ClusterClass is outdated. If this condition persists please check ClusterClass status.",
73+
),
74+
)
75+
return nil
76+
}
77+
6178
// If any of the lifecycle hooks are blocking any part of the reconciliation then topology
6279
// is not considered as fully reconciled.
6380
if s.HookResponseTracker.AggregateRetryAfter() != 0 {

internal/controllers/topology/cluster/conditions_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
. "github.com/onsi/gomega"
2323
"github.com/pkg/errors"
2424
corev1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526

2627
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2728
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
@@ -48,6 +49,26 @@ func TestReconcileTopologyReconciledCondition(t *testing.T) {
4849
wantConditionReason: clusterv1.TopologyReconcileFailedReason,
4950
wantErr: false,
5051
},
52+
{
53+
name: "should set the condition to false if the ClusterClass is out of date",
54+
cluster: &clusterv1.Cluster{},
55+
s: &scope.Scope{
56+
Blueprint: &scope.ClusterBlueprint{
57+
ClusterClass: &clusterv1.ClusterClass{
58+
ObjectMeta: metav1.ObjectMeta{
59+
Name: "class1",
60+
Generation: 10,
61+
},
62+
Status: clusterv1.ClusterClassStatus{
63+
ObservedGeneration: 999,
64+
},
65+
},
66+
},
67+
},
68+
wantConditionStatus: corev1.ConditionFalse,
69+
wantConditionReason: clusterv1.TopologyReconciledClusterClassNotReconciledReason,
70+
wantErr: false,
71+
},
5172
{
5273
name: "should set the condition to false if the there is a blocking hook",
5374
reconcileErr: nil,

0 commit comments

Comments
 (0)