Skip to content

Commit fee8b63

Browse files
committed
Implement Cluster TopologyReconciled v1beta2 condition
Signed-off-by: Stefan Büringer buringerst@vmware.com
1 parent 0f319c6 commit fee8b63

File tree

4 files changed

+146
-22
lines changed

4 files changed

+146
-22
lines changed

api/v1beta1/cluster_types.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,59 @@ const (
5656
// ClusterTopologyReconciledV1Beta2Condition is true if the topology controller is working properly.
5757
// Note: This condition is added only if the Cluster is referencing a ClusterClass / defining a managed Topology.
5858
ClusterTopologyReconciledV1Beta2Condition = "TopologyReconciled"
59+
60+
// ClusterTopologyReconcileSucceededV1Beta2Reason documents the reconciliation of a Cluster topology succeeded.
61+
ClusterTopologyReconcileSucceededV1Beta2Reason = "TopologyReconcileSucceeded"
62+
63+
// ClusterTopologyReconciledFailedV1Beta2Reason documents the reconciliation of a Cluster topology
64+
// failing due to an error.
65+
ClusterTopologyReconciledFailedV1Beta2Reason = "TopologyReconcileFailed"
66+
67+
// ClusterTopologyReconciledControlPlaneUpgradePendingV1Beta2Reason documents reconciliation of a Cluster topology
68+
// not yet completed because Control Plane is not yet updated to match the desired topology spec.
69+
ClusterTopologyReconciledControlPlaneUpgradePendingV1Beta2Reason = "ControlPlaneUpgradePending"
70+
71+
// ClusterTopologyReconciledMachineDeploymentsCreatePendingV1Beta2Reason documents reconciliation of a Cluster topology
72+
// not yet completed because at least one of the MachineDeployments is yet to be created.
73+
// This generally happens because new MachineDeployment creations are held off while the ControlPlane is not stable.
74+
ClusterTopologyReconciledMachineDeploymentsCreatePendingV1Beta2Reason = "MachineDeploymentsCreatePending"
75+
76+
// ClusterTopologyReconciledMachineDeploymentsUpgradePendingV1Beta2Reason documents reconciliation of a Cluster topology
77+
// not yet completed because at least one of the MachineDeployments is not yet updated to match the desired topology spec.
78+
ClusterTopologyReconciledMachineDeploymentsUpgradePendingV1Beta2Reason = "MachineDeploymentsUpgradePending"
79+
80+
// ClusterTopologyReconciledMachineDeploymentsUpgradeDeferredV1Beta2Reason documents reconciliation of a Cluster topology
81+
// not yet completed because the upgrade for at least one of the MachineDeployments has been deferred.
82+
ClusterTopologyReconciledMachineDeploymentsUpgradeDeferredV1Beta2Reason = "MachineDeploymentsUpgradeDeferred"
83+
84+
// ClusterTopologyReconciledMachinePoolsUpgradePendingV1Beta2Reason documents reconciliation of a Cluster topology
85+
// not yet completed because at least one of the MachinePools is not yet updated to match the desired topology spec.
86+
ClusterTopologyReconciledMachinePoolsUpgradePendingV1Beta2Reason = "MachinePoolsUpgradePending"
87+
88+
// ClusterTopologyReconciledMachinePoolsCreatePendingV1Beta2Reason documents reconciliation of a Cluster topology
89+
// not yet completed because at least one of the MachinePools is yet to be created.
90+
// This generally happens because new MachinePool creations are held off while the ControlPlane is not stable.
91+
ClusterTopologyReconciledMachinePoolsCreatePendingV1Beta2Reason = "MachinePoolsCreatePending"
92+
93+
// ClusterTopologyReconciledMachinePoolsUpgradeDeferredV1Beta2Reason documents reconciliation of a Cluster topology
94+
// not yet completed because the upgrade for at least one of the MachinePools has been deferred.
95+
ClusterTopologyReconciledMachinePoolsUpgradeDeferredV1Beta2Reason = "MachinePoolsUpgradeDeferred"
96+
97+
// ClusterTopologyReconciledHookBlockingV1Beta2Reason documents reconciliation of a Cluster topology
98+
// not yet completed because at least one of the lifecycle hooks is blocking.
99+
ClusterTopologyReconciledHookBlockingV1Beta2Reason = "LifecycleHookBlocking"
100+
101+
// ClusterTopologyReconciledClusterClassNotReconciledV1Beta2Reason documents reconciliation of a Cluster topology not
102+
// yet completed because the ClusterClass has not reconciled yet. If this condition persists there may be an issue
103+
// with the ClusterClass surfaced in the ClusterClass status or controller logs.
104+
ClusterTopologyReconciledClusterClassNotReconciledV1Beta2Reason = "ClusterClassNotReconciled"
105+
106+
// ClusterTopologyReconciledDeletionTimestampSetV1Beta2Reason surfaces when the Cluster is deleting because the
107+
// DeletionTimestamp is set.
108+
ClusterTopologyReconciledDeletionTimestampSetV1Beta2Reason = DeletionTimestampSetV1Beta2Reason
109+
110+
// ClusterTopologyReconcilePausedV1Beta2Reason surfaces when the Cluster is paused.
111+
ClusterTopologyReconcilePausedV1Beta2Reason = PausedV1Beta2Reason
59112
)
60113

61114
// Cluster's InfrastructureReady condition and corresponding reasons that will be used in v1Beta2 API version.

api/v1beta1/condition_consts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ const (
337337
// yet completed because the ClusterClass has not reconciled yet. If this condition persists there may be an issue
338338
// with the ClusterClass surfaced in the ClusterClass status or controller logs.
339339
TopologyReconciledClusterClassNotReconciledReason = "ClusterClassNotReconciled"
340+
341+
// TopologyReconciledPausedReason (Severity=Info) surfaces when the Cluster is paused.
342+
TopologyReconciledPausedReason = "Paused"
340343
)
341344

342345
// Conditions and condition reasons for ClusterClass.

internal/controllers/topology/cluster/cluster_controller.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
120120
builder.WithPredicates(predicates.ResourceIsTopologyOwned(mgr.GetScheme(), predicateLog)),
121121
).
122122
WithOptions(options).
123-
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(mgr.GetScheme(), predicateLog, r.WatchFilterValue)).
123+
WithEventFilter(predicates.ResourceHasFilterLabel(mgr.GetScheme(), predicateLog, r.WatchFilterValue)).
124124
Build(r)
125125

126126
if err != nil {
@@ -175,13 +175,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
175175
return ctrl.Result{}, nil
176176
}
177177

178-
// Return early if the Cluster is paused.
179-
// TODO: What should we do if the cluster class is paused?
180-
if annotations.IsPaused(cluster, cluster) {
181-
log.Info("Reconciliation is paused for this object")
182-
return ctrl.Result{}, nil
183-
}
184-
185178
patchHelper, err := patch.NewHelper(cluster, r.Client)
186179
if err != nil {
187180
return ctrl.Result{}, err
@@ -200,14 +193,21 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
200193
patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
201194
clusterv1.TopologyReconciledCondition,
202195
}},
203-
patch.WithForceOverwriteConditions{},
196+
patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
197+
clusterv1.ClusterTopologyReconciledV1Beta2Condition,
198+
}},
204199
}
205200
if err := patchHelper.Patch(ctx, cluster, options...); err != nil {
206201
reterr = kerrors.NewAggregate([]error{reterr, err})
207202
return
208203
}
209204
}()
210205

206+
// Return early if the Cluster is paused.
207+
if cluster.Spec.Paused || annotations.HasPaused(cluster) {
208+
return ctrl.Result{}, nil
209+
}
210+
211211
// In case the object is deleted, the managed topology stops to reconcile;
212212
// (the other controllers will take care of deletion).
213213
if !cluster.ObjectMeta.DeletionTimestamp.IsZero() {

internal/controllers/topology/cluster/conditions.go

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ import (
2121
"strings"
2222

2323
"github.com/pkg/errors"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425

2526
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2627
"sigs.k8s.io/cluster-api/exp/topology/scope"
2728
"sigs.k8s.io/cluster-api/internal/contract"
29+
"sigs.k8s.io/cluster-api/util/annotations"
2830
"sigs.k8s.io/cluster-api/util/conditions"
31+
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
2932
)
3033

3134
func (r *Reconciler) reconcileConditions(s *scope.Scope, cluster *clusterv1.Cluster, reconcileErr error) error {
@@ -36,32 +39,62 @@ func (r *Reconciler) reconcileConditions(s *scope.Scope, cluster *clusterv1.Clus
3639
// The TopologyReconciled condition is considered true if spec of all the objects associated with the
3740
// cluster are in sync with the topology defined in the cluster.
3841
// The condition is false under the following conditions:
42+
// - The cluster is paused.
3943
// - An error occurred during the reconcile process of the cluster topology.
4044
// - The ClusterClass has not been successfully reconciled with its current spec.
4145
// - The cluster upgrade has not yet propagated to all the components of the cluster.
4246
// - For a managed topology cluster the version upgrade is propagated one component at a time.
4347
// In such a case, since some of the component's spec would be adrift from the topology the
4448
// topology cannot be considered fully reconciled.
4549
func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluster *clusterv1.Cluster, reconcileErr error) error {
50+
// Mark TopologyReconciled as false if the Cluster is paused.
51+
if cluster.Spec.Paused || annotations.HasPaused(cluster) {
52+
var messages []string
53+
if cluster.Spec.Paused {
54+
messages = append(messages, "Cluster spec.paused is set to true")
55+
}
56+
if annotations.HasPaused(cluster) {
57+
messages = append(messages, "Cluster has the cluster.x-k8s.io/paused annotation")
58+
}
59+
conditions.Set(cluster,
60+
conditions.FalseCondition(
61+
clusterv1.TopologyReconciledCondition,
62+
clusterv1.TopologyReconciledPausedReason,
63+
clusterv1.ConditionSeverityInfo,
64+
strings.Join(messages, ", "),
65+
),
66+
)
67+
v1beta2conditions.Set(cluster, metav1.Condition{
68+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
69+
Status: metav1.ConditionFalse,
70+
Reason: clusterv1.ClusterTopologyReconcilePausedV1Beta2Reason,
71+
Message: strings.Join(messages, ", "),
72+
})
73+
return nil
74+
}
75+
4676
// Mark TopologyReconciled as false due to cluster deletion.
4777
if !cluster.ObjectMeta.DeletionTimestamp.IsZero() {
48-
conditions.Set(
49-
cluster,
78+
conditions.Set(cluster,
5079
conditions.FalseCondition(
5180
clusterv1.TopologyReconciledCondition,
5281
clusterv1.DeletedReason,
5382
clusterv1.ConditionSeverityInfo,
5483
"",
5584
),
5685
)
86+
v1beta2conditions.Set(cluster, metav1.Condition{
87+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
88+
Status: metav1.ConditionFalse,
89+
Reason: clusterv1.ClusterTopologyReconciledDeletionTimestampSetV1Beta2Reason,
90+
})
5791
return nil
5892
}
5993

6094
// If an error occurred during reconciliation set the TopologyReconciled condition to false.
6195
// Add the error message from the reconcile function to the message of the condition.
6296
if reconcileErr != nil {
63-
conditions.Set(
64-
cluster,
97+
conditions.Set(cluster,
6598
conditions.FalseCondition(
6699
clusterv1.TopologyReconciledCondition,
67100
clusterv1.TopologyReconcileFailedReason,
@@ -70,15 +103,21 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
70103
reconcileErr.Error(),
71104
),
72105
)
106+
v1beta2conditions.Set(cluster, metav1.Condition{
107+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
108+
Status: metav1.ConditionFalse,
109+
Reason: clusterv1.ClusterTopologyReconciledFailedV1Beta2Reason,
110+
// TODO: Add a protection for messages continuously changing leading to Cluster object changes/reconcile.
111+
Message: reconcileErr.Error(),
112+
})
73113
return nil
74114
}
75115

76116
// If the ClusterClass `metadata.Generation` doesn't match the `status.ObservedGeneration` requeue as the ClusterClass
77117
// is not up to date.
78118
if s.Blueprint != nil && s.Blueprint.ClusterClass != nil &&
79119
s.Blueprint.ClusterClass.GetGeneration() != s.Blueprint.ClusterClass.Status.ObservedGeneration {
80-
conditions.Set(
81-
cluster,
120+
conditions.Set(cluster,
82121
conditions.FalseCondition(
83122
clusterv1.TopologyReconciledCondition,
84123
clusterv1.TopologyReconciledClusterClassNotReconciledReason,
@@ -87,14 +126,20 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
87126
".status.observedGeneration == .metadata.generation is true. If this is not the case either ClusterClass reconciliation failed or the ClusterClass is paused",
88127
),
89128
)
129+
v1beta2conditions.Set(cluster, metav1.Condition{
130+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
131+
Status: metav1.ConditionFalse,
132+
Reason: clusterv1.ClusterTopologyReconciledClusterClassNotReconciledV1Beta2Reason,
133+
Message: "ClusterClass not reconciled. If this condition persists please check ClusterClass status. A ClusterClass is reconciled if" +
134+
".status.observedGeneration == .metadata.generation is true. If this is not the case either ClusterClass reconciliation failed or the ClusterClass is paused",
135+
})
90136
return nil
91137
}
92138

93139
// If any of the lifecycle hooks are blocking any part of the reconciliation then topology
94140
// is not considered as fully reconciled.
95141
if s.HookResponseTracker.AggregateRetryAfter() != 0 {
96-
conditions.Set(
97-
cluster,
142+
conditions.Set(cluster,
98143
conditions.FalseCondition(
99144
clusterv1.TopologyReconciledCondition,
100145
clusterv1.TopologyReconciledHookBlockingReason,
@@ -103,6 +148,13 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
103148
s.HookResponseTracker.AggregateMessage(),
104149
),
105150
)
151+
v1beta2conditions.Set(cluster, metav1.Condition{
152+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
153+
Status: metav1.ConditionFalse,
154+
Reason: clusterv1.ClusterTopologyReconciledHookBlockingV1Beta2Reason,
155+
// TODO: Add a protection for messages continuously changing leading to Cluster object changes/reconcile.
156+
Message: s.HookResponseTracker.AggregateMessage(),
157+
})
106158
return nil
107159
}
108160

@@ -121,6 +173,7 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
121173
s.UpgradeTracker.MachinePools.DeferredUpgrade() {
122174
msgBuilder := &strings.Builder{}
123175
var reason string
176+
var v1beta2Reason string
124177

125178
// TODO(ykakarap): Evaluate potential improvements to building the condition. Multiple causes can trigger the
126179
// condition to be false at the same time (Example: ControlPlane.IsPendingUpgrade and MachineDeployments.IsAnyPendingCreate can
@@ -130,40 +183,47 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
130183
case s.UpgradeTracker.ControlPlane.IsPendingUpgrade:
131184
fmt.Fprintf(msgBuilder, "Control plane rollout and upgrade to version %s on hold.", s.Blueprint.Topology.Version)
132185
reason = clusterv1.TopologyReconciledControlPlaneUpgradePendingReason
186+
v1beta2Reason = clusterv1.ClusterTopologyReconciledControlPlaneUpgradePendingV1Beta2Reason
133187
case s.UpgradeTracker.MachineDeployments.IsAnyPendingUpgrade():
134188
fmt.Fprintf(msgBuilder, "MachineDeployment(s) %s rollout and upgrade to version %s on hold.",
135189
computeNameList(s.UpgradeTracker.MachineDeployments.PendingUpgradeNames()),
136190
s.Blueprint.Topology.Version,
137191
)
138192
reason = clusterv1.TopologyReconciledMachineDeploymentsUpgradePendingReason
193+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachineDeploymentsUpgradePendingV1Beta2Reason
139194
case s.UpgradeTracker.MachineDeployments.IsAnyPendingCreate():
140195
fmt.Fprintf(msgBuilder, "MachineDeployment(s) for Topologies %s creation on hold.",
141196
computeNameList(s.UpgradeTracker.MachineDeployments.PendingCreateTopologyNames()),
142197
)
143198
reason = clusterv1.TopologyReconciledMachineDeploymentsCreatePendingReason
199+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachineDeploymentsCreatePendingV1Beta2Reason
144200
case s.UpgradeTracker.MachineDeployments.DeferredUpgrade():
145201
fmt.Fprintf(msgBuilder, "MachineDeployment(s) %s rollout and upgrade to version %s deferred.",
146202
computeNameList(s.UpgradeTracker.MachineDeployments.DeferredUpgradeNames()),
147203
s.Blueprint.Topology.Version,
148204
)
149205
reason = clusterv1.TopologyReconciledMachineDeploymentsUpgradeDeferredReason
206+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachineDeploymentsUpgradeDeferredV1Beta2Reason
150207
case s.UpgradeTracker.MachinePools.IsAnyPendingUpgrade():
151208
fmt.Fprintf(msgBuilder, "MachinePool(s) %s rollout and upgrade to version %s on hold.",
152209
computeNameList(s.UpgradeTracker.MachinePools.PendingUpgradeNames()),
153210
s.Blueprint.Topology.Version,
154211
)
155212
reason = clusterv1.TopologyReconciledMachinePoolsUpgradePendingReason
213+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachinePoolsUpgradePendingV1Beta2Reason
156214
case s.UpgradeTracker.MachinePools.IsAnyPendingCreate():
157215
fmt.Fprintf(msgBuilder, "MachinePool(s) for Topologies %s creation on hold.",
158216
computeNameList(s.UpgradeTracker.MachinePools.PendingCreateTopologyNames()),
159217
)
160218
reason = clusterv1.TopologyReconciledMachinePoolsCreatePendingReason
219+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachinePoolsCreatePendingV1Beta2Reason
161220
case s.UpgradeTracker.MachinePools.DeferredUpgrade():
162221
fmt.Fprintf(msgBuilder, "MachinePool(s) %s rollout and upgrade to version %s deferred.",
163222
computeNameList(s.UpgradeTracker.MachinePools.DeferredUpgradeNames()),
164223
s.Blueprint.Topology.Version,
165224
)
166225
reason = clusterv1.TopologyReconciledMachinePoolsUpgradeDeferredReason
226+
v1beta2Reason = clusterv1.ClusterTopologyReconciledMachinePoolsUpgradeDeferredV1Beta2Reason
167227
}
168228

169229
switch {
@@ -191,26 +251,34 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
191251
)
192252
}
193253

194-
conditions.Set(
195-
cluster,
254+
conditions.Set(cluster,
196255
conditions.FalseCondition(
197256
clusterv1.TopologyReconciledCondition,
198257
reason,
199258
clusterv1.ConditionSeverityInfo,
200259
msgBuilder.String(),
201260
),
202261
)
262+
v1beta2conditions.Set(cluster, metav1.Condition{
263+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
264+
Status: metav1.ConditionFalse,
265+
Reason: v1beta2Reason,
266+
Message: msgBuilder.String(),
267+
})
203268
return nil
204269
}
205270

206271
// If there are no errors while reconciling and if the topology is not holding out changes
207272
// we can consider that spec of all the objects is reconciled to match the topology. Set the
208273
// TopologyReconciled condition to true.
209-
conditions.Set(
210-
cluster,
274+
conditions.Set(cluster,
211275
conditions.TrueCondition(clusterv1.TopologyReconciledCondition),
212276
)
213-
277+
v1beta2conditions.Set(cluster, metav1.Condition{
278+
Type: clusterv1.ClusterTopologyReconciledV1Beta2Condition,
279+
Status: metav1.ConditionTrue,
280+
Reason: clusterv1.ClusterTopologyReconcileSucceededV1Beta2Reason,
281+
})
214282
return nil
215283
}
216284

0 commit comments

Comments
 (0)