Skip to content

Commit 327aae2

Browse files
authored
Merge pull request #8023 from killianmuldoon/pr-exvars-generation-check
🌱 Add ClusterClass generation check to Cluster Topology reconciler
2 parents cb410f7 + d7f1009 commit 327aae2

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -175,26 +175,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
175175
return ctrl.Result{}, err
176176
}
177177

178-
// Get ClusterClass.
179-
clusterClass := &clusterv1.ClusterClass{}
180-
key := client.ObjectKey{Name: cluster.Spec.Topology.Class, Namespace: cluster.Namespace}
181-
if err := r.Client.Get(ctx, key, clusterClass); err != nil {
182-
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve ClusterClass %s", cluster.Spec.Topology.Class)
183-
}
184-
// Default and Validate the Cluster based on information from the ClusterClass.
185-
// This step is needed as if the ClusterClass does not exist at Cluster creation some fields may not be defaulted or
186-
// validated in the webhook.
187-
if errs := webhooks.DefaultVariables(cluster, clusterClass); len(errs) > 0 {
188-
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), cluster.Name, errs)
189-
}
190-
if errs := webhooks.ValidateClusterForClusterClass(cluster, clusterClass, field.NewPath("spec", "topology")); len(errs) > 0 {
191-
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), cluster.Name, errs)
192-
}
193-
194178
// Create a scope initialized with only the cluster; during reconcile
195179
// additional information will be added about the Cluster blueprint, current state and desired state.
196180
s := scope.New(cluster)
197-
s.Blueprint.ClusterClass = clusterClass
198181

199182
defer func() {
200183
if err := r.reconcileConditions(s, cluster, reterr); err != nil {
@@ -221,6 +204,31 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
221204
func (r *Reconciler) reconcile(ctx context.Context, s *scope.Scope) (ctrl.Result, error) {
222205
var err error
223206

207+
// Get ClusterClass.
208+
clusterClass := &clusterv1.ClusterClass{}
209+
key := client.ObjectKey{Name: s.Current.Cluster.Spec.Topology.Class, Namespace: s.Current.Cluster.Namespace}
210+
if err := r.Client.Get(ctx, key, clusterClass); err != nil {
211+
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve ClusterClass %s", s.Current.Cluster.Spec.Topology.Class)
212+
}
213+
214+
s.Blueprint.ClusterClass = clusterClass
215+
// If the ClusterClass `metadata.Generation` doesn't match the `status.ObservedGeneration` return as the ClusterClass
216+
// is not up to date.
217+
// Note: This doesn't require requeue as a change to ClusterClass observedGeneration will cause an additional reconcile
218+
// in the Cluster.
219+
if clusterClass.GetGeneration() != clusterClass.Status.ObservedGeneration {
220+
return ctrl.Result{}, nil
221+
}
222+
223+
// Default and Validate the Cluster based on information from the ClusterClass.
224+
// This step is needed as if the ClusterClass does not exist at Cluster creation some fields may not be defaulted or
225+
// validated in the webhook.
226+
if errs := webhooks.DefaultVariables(s.Current.Cluster, clusterClass); len(errs) > 0 {
227+
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
228+
}
229+
if errs := webhooks.ValidateClusterForClusterClass(s.Current.Cluster, clusterClass, field.NewPath("spec", "topology")); len(errs) > 0 {
230+
return ctrl.Result{}, apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), s.Current.Cluster.Name, errs)
231+
}
224232
// Gets the blueprint with the ClusterClass and the referenced templates
225233
// and store it in the request scope.
226234
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)