Skip to content

Commit eb202bf

Browse files
committed
Simplify Cluster webhook
Signed-off-by: Stefan Büringer [email protected]
1 parent 2923124 commit eb202bf

File tree

2 files changed

+69
-79
lines changed

2 files changed

+69
-79
lines changed

internal/webhooks/cluster.go

Lines changed: 67 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@ type Cluster struct {
7979
var _ webhook.CustomDefaulter = &Cluster{}
8080
var _ webhook.CustomValidator = &Cluster{}
8181

82-
var errClusterClassNotReconciled = errors.New("ClusterClass is not successfully reconciled")
83-
8482
// Default satisfies the defaulting webhook interface.
8583
func (webhook *Cluster) Default(ctx context.Context, obj runtime.Object) error {
8684
// We gather all defaulting errors and return them together.
@@ -109,14 +107,15 @@ func (webhook *Cluster) Default(ctx context.Context, obj runtime.Object) error {
109107
return apierrors.NewInvalid(clusterv1.GroupVersion.WithKind("Cluster").GroupKind(), cluster.Name, allErrs)
110108
}
111109

112-
clusterClass, err := webhook.pollClusterClassForCluster(ctx, cluster)
110+
clusterClass, clusterClassNotReconciled, clusterClassNotFound, err := webhook.pollClusterClassForCluster(ctx, cluster)
113111
if err != nil {
114-
// If the ClusterClass can't be found or is not up to date ignore the error.
115-
if apierrors.IsNotFound(err) || errors.Is(err, errClusterClassNotReconciled) {
116-
return nil
117-
}
118112
return apierrors.NewInternalError(errors.Wrapf(err, "Cluster %s can't be defaulted. ClusterClass %s can not be retrieved", cluster.Name, cluster.GetClassKey().Name))
119113
}
114+
if clusterClassNotReconciled || clusterClassNotFound {
115+
// If the ClusterClass can't be found or is not reconciled, return as we shouldn't
116+
// default and validate variables in that case.
117+
return nil
118+
}
120119

121120
// Validate cluster class variables transitions that may be enforced by CEL validation rules on variables.
122121
// If no request found in context, then this has not come via a webhook request, so skip validation of old cluster.
@@ -312,39 +311,34 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
312311
}
313312

314313
// Get the ClusterClass referenced in the Cluster.
315-
clusterClass, warnings, clusterClassPollErr := webhook.validateClusterClassExistsAndIsReconciled(ctx, newCluster)
316-
// If the error is anything other than "NotFound" or "NotReconciled" return all errors.
317-
if clusterClassPollErr != nil && (!apierrors.IsNotFound(clusterClassPollErr) && !errors.Is(clusterClassPollErr, errClusterClassNotReconciled)) {
318-
allErrs = append(
319-
allErrs, field.InternalError(
320-
fldPath.Child("class"),
321-
clusterClassPollErr))
322-
return allWarnings, allErrs
314+
// Note: If the ClusterClass is not found, a warning and no err is returned and the clusterClass is nil.
315+
// Note: If the ClusterClass is not reconciled, a warning and no err is returned and the clusterClass is returned.
316+
clusterClass, warnings, err := webhook.validateClusterClassExistsAndIsReconciled(ctx, newCluster)
317+
if err != nil {
318+
return allWarnings, append(allErrs, field.InternalError(fldPath.Child("class"), err))
323319
}
324-
325-
// Add the warnings if no error was returned.
326320
allWarnings = append(allWarnings, warnings...)
327321

328-
// If there's no error validate the Cluster based on the ClusterClass.
329-
if clusterClassPollErr == nil {
322+
// If we could get the ClusterClass validate the Cluster based on the ClusterClass.
323+
if clusterClass != nil {
330324
allErrs = append(allErrs, ValidateClusterForClusterClass(newCluster, clusterClass)...)
331325
}
332326

333-
// Validate the Cluster and associated ClusterClass' autoscaler annotations.
327+
// Validate the Cluster and associated ClusterClass' autoscaler annotations
328+
// Note: ClusterClass validation is only run if clusterClass is not nil.
334329
allErrs = append(allErrs, validateAutoscalerAnnotationsForCluster(newCluster, clusterClass)...)
335330

336331
if oldCluster != nil { // On update
337-
// The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was
338-
// not found.
339-
if apierrors.IsNotFound(clusterClassPollErr) {
332+
// The ClusterClass must exist to proceed with update validation. Return an error if the ClusterClass was not found.
333+
if clusterClass == nil {
340334
allErrs = append(
341335
allErrs, field.InternalError(
342336
fldPath.Child("class"),
343-
clusterClassPollErr))
337+
errors.Errorf("ClusterClass %s not found", newCluster.GetClassKey())))
344338
return allWarnings, allErrs
345339
}
346340

347-
// Topology or Class can not be added on update unless ClusterTopologyUnsafeUpdateClassNameAnnotation is set.
341+
// Topology or Class can not be added or update unless ClusterTopologyUnsafeUpdateClassNameAnnotation is set.
348342
if !oldCluster.Spec.Topology.IsDefined() || oldCluster.GetClassKey().Name == "" {
349343
if _, ok := newCluster.Annotations[clusterv1.ClusterTopologyUnsafeUpdateClassNameAnnotation]; ok {
350344
return allWarnings, allErrs
@@ -402,29 +396,21 @@ func (webhook *Cluster) validateTopology(ctx context.Context, oldCluster, newClu
402396

403397
// If the ClusterClass referenced in the Topology has changed compatibility checks are needed.
404398
if oldCluster.GetClassKey() != newCluster.GetClassKey() {
405-
if clusterClassPollErr != nil {
406-
allErrs = append(
407-
allErrs, field.Forbidden(
408-
fldPath.Child("class"),
409-
fmt.Sprintf("cannot rebase to ClusterClass %q: %s",
410-
newCluster.GetClassKey(), clusterClassPollErr.Error())))
411-
// Return early with errors if the new ClusterClass can't be retrieved.
412-
return allWarnings, allErrs
413-
}
414-
415399
// Check to see if the ClusterClass referenced in the old version of the Cluster exists.
416-
oldClusterClass, err := webhook.pollClusterClassForCluster(ctx, oldCluster)
417-
if err != nil {
400+
// Return early with errors if the old ClusterClass can't be retrieved.
401+
oldClusterClass := &clusterv1.ClusterClass{}
402+
if err := webhook.Client.Get(ctx, oldCluster.GetClassKey(), oldClusterClass); err != nil {
418403
allErrs = append(
419404
allErrs, field.Forbidden(
420405
fldPath.Child("class"),
421406
fmt.Sprintf("valid ClusterClass with name %q could not be retrieved, change from class %[1]q to class %q cannot be validated. Error: %s",
422407
oldCluster.GetClassKey(), newCluster.GetClassKey(), err.Error())))
423-
424-
// Return early with errors if the old ClusterClass can't be retrieved.
425408
return allWarnings, allErrs
426409
}
427410

411+
// Note: We don't care if the old ClusterClass is reconciled as the validation below doesn't need it
412+
// and we want to allow to rebase away from a broken ClusterClass.
413+
428414
// Check if the new and old ClusterClasses are compatible with one another.
429415
allErrs = append(allErrs, check.ClusterClassesAreCompatible(oldClusterClass, clusterClass)...)
430416
}
@@ -926,76 +912,80 @@ func ValidateClusterForClusterClass(cluster *clusterv1.Cluster, clusterClass *cl
926912

927913
// validateClusterClassExistsAndIsReconciled will try to get the ClusterClass referenced in the Cluster. If it does not exist or is not reconciled it will add a warning.
928914
// In any other case it will return an error.
929-
func (webhook *Cluster) validateClusterClassExistsAndIsReconciled(ctx context.Context, newCluster *clusterv1.Cluster) (*clusterv1.ClusterClass, admission.Warnings, error) {
915+
func (webhook *Cluster) validateClusterClassExistsAndIsReconciled(ctx context.Context, newCluster *clusterv1.Cluster) (_ *clusterv1.ClusterClass, _ admission.Warnings, _ error) {
930916
var allWarnings admission.Warnings
931-
clusterClass, clusterClassPollErr := webhook.pollClusterClassForCluster(ctx, newCluster)
932-
if clusterClassPollErr != nil {
933-
// Add a warning if the Class does not exist or if it has not been successfully reconciled.
934-
switch {
935-
case apierrors.IsNotFound(clusterClassPollErr):
936-
allWarnings = append(allWarnings,
937-
fmt.Sprintf(
938-
"Cluster refers to ClusterClass %s, but this ClusterClass does not exist. "+
939-
"Cluster topology has not been fully validated. "+
940-
"The ClusterClass must be created to reconcile the Cluster", newCluster.GetClassKey()),
941-
)
942-
case errors.Is(clusterClassPollErr, errClusterClassNotReconciled):
943-
allWarnings = append(allWarnings,
944-
fmt.Sprintf(
945-
"Cluster refers to ClusterClass %s, but this ClusterClass hasn't been successfully reconciled. "+
946-
"Cluster topology has not been fully validated. "+
947-
"Please take a look at the ClusterClass status", newCluster.GetClassKey()),
948-
)
949-
// If there's any other error return a generic warning with the error message.
950-
default:
951-
allWarnings = append(allWarnings,
952-
fmt.Sprintf(
953-
"Cluster refers to ClusterClass %s, but this ClusterClass could not be retrieved. "+
954-
"Cluster topology has not been fully validated: %s", newCluster.GetClassKey(), clusterClassPollErr.Error()),
955-
)
956-
}
917+
clusterClass, clusterClassNotReconciled, clusterClassNotFound, err := webhook.pollClusterClassForCluster(ctx, newCluster)
918+
// Add a warning if the Class does not exist or if it has not been successfully reconciled.
919+
switch {
920+
case err != nil:
921+
allWarnings = append(allWarnings,
922+
fmt.Sprintf(
923+
"Cluster refers to ClusterClass %s, but this ClusterClass could not be retrieved. "+
924+
"Cluster topology has not been fully validated: %s", newCluster.GetClassKey(), err.Error()),
925+
)
926+
case clusterClassNotFound:
927+
allWarnings = append(allWarnings,
928+
fmt.Sprintf(
929+
"Cluster refers to ClusterClass %s, but this ClusterClass does not exist. "+
930+
"Cluster topology has not been fully validated. "+
931+
"The ClusterClass must be created to reconcile the Cluster", newCluster.GetClassKey()),
932+
)
933+
case clusterClassNotReconciled:
934+
allWarnings = append(allWarnings,
935+
fmt.Sprintf(
936+
"Cluster refers to ClusterClass %s, but this ClusterClass hasn't been successfully reconciled. "+
937+
"Cluster topology has not been fully validated. "+
938+
"Please take a look at the ClusterClass status", newCluster.GetClassKey()),
939+
)
957940
}
958-
return clusterClass, allWarnings, clusterClassPollErr
941+
return clusterClass, allWarnings, err
959942
}
960943

961944
// pollClusterClassForCluster will retry getting the ClusterClass referenced in the Cluster for two seconds.
962-
func (webhook *Cluster) pollClusterClassForCluster(ctx context.Context, cluster *clusterv1.Cluster) (*clusterv1.ClusterClass, error) {
945+
func (webhook *Cluster) pollClusterClassForCluster(ctx context.Context, cluster *clusterv1.Cluster) (_ *clusterv1.ClusterClass, clusterClassNotReconciled, clusterClassNotFound bool, _ error) {
946+
var errClusterClassNotReconciled = errors.New("ClusterClass is not successfully reconciled")
947+
963948
clusterClass := &clusterv1.ClusterClass{}
964949
var clusterClassPollErr error
965950
_ = wait.PollUntilContextTimeout(ctx, 200*time.Millisecond, 2*time.Second, true, func(ctx context.Context) (bool, error) {
966951
if clusterClassPollErr = webhook.Client.Get(ctx, cluster.GetClassKey(), clusterClass); clusterClassPollErr != nil {
967952
return false, nil //nolint:nilerr
968953
}
969954

970-
if clusterClassPollErr = clusterClassIsReconciled(clusterClass); clusterClassPollErr != nil {
971-
return false, nil //nolint:nilerr
955+
if !clusterClassIsReconciled(clusterClass) {
956+
clusterClassPollErr = errClusterClassNotReconciled
957+
return false, nil
972958
}
959+
973960
clusterClassPollErr = nil
974961
return true, nil
975962
})
976963
if clusterClassPollErr != nil {
964+
if apierrors.IsNotFound(clusterClassPollErr) {
965+
return nil, false, true, nil
966+
}
977967
if errors.Is(clusterClassPollErr, errClusterClassNotReconciled) {
978968
// Return ClusterClass if we were able to get it and it's just not reconciled.
979-
return clusterClass, clusterClassPollErr
969+
return clusterClass, true, false, nil
980970
}
981-
return nil, clusterClassPollErr
971+
return nil, false, false, clusterClassPollErr
982972
}
983-
return clusterClass, nil
973+
return clusterClass, false, false, nil
984974
}
985975

986976
// clusterClassIsReconciled returns errClusterClassNotReconciled if the ClusterClass has not successfully reconciled or if the
987977
// ClusterClass variables have not been successfully reconciled.
988-
func clusterClassIsReconciled(clusterClass *clusterv1.ClusterClass) error {
978+
func clusterClassIsReconciled(clusterClass *clusterv1.ClusterClass) bool {
989979
// If the clusterClass metadata generation does not match the status observed generation, the ClusterClass has not been successfully reconciled.
990980
if clusterClass.Generation != clusterClass.Status.ObservedGeneration {
991-
return errClusterClassNotReconciled
981+
return false
992982
}
993983
// If the clusterClass does not have ClusterClassVariablesReconciled==True, the ClusterClass has not been successfully reconciled.
994984
if !conditions.Has(clusterClass, clusterv1.ClusterClassVariablesReadyCondition) ||
995985
conditions.IsFalse(clusterClass, clusterv1.ClusterClassVariablesReadyCondition) {
996-
return errClusterClassNotReconciled
986+
return false
997987
}
998-
return nil
988+
return true
999989
}
1000990

1001991
func validateTopologyMetadata(topology clusterv1.Topology, fldPath *field.Path) field.ErrorList {

internal/webhooks/cluster_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3415,11 +3415,11 @@ func TestClusterClassPollingErrors(t *testing.T) {
34153415
wantErr: false,
34163416
},
34173417
{
3418-
name: "Fail on update if oldCluster ClusterClass generation does not match observedGeneration",
3418+
name: "Pass on update if oldCluster ClusterClass generation does not match observedGeneration",
34193419
cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").WithTopology(secondTopology).Build(),
34203420
oldCluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").WithTopology(topology).Build(),
34213421
clusterClasses: []*clusterv1.ClusterClass{ccGenerationMismatch, secondFullyReconciled},
3422-
wantErr: true,
3422+
wantErr: false,
34233423
},
34243424
{
34253425
name: "Fail on update if old Cluster ClusterClass is not found",

0 commit comments

Comments
 (0)