Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌱 v1beta2: implement VSphereCluster's Ready, FailureDomainsReady, ClusterModulesReady and VCenterAvailable conditions for govmomi #3396

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
4 changes: 0 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,6 @@ issues:
- linters:
- staticcheck
text: "SA1019: \"sigs.k8s.io/cluster-api-provider-vsphere/apis/(v1alpha3|v1alpha4)\" is deprecated: This package will be removed in one of the next releases."
# Deprecations for Cluster API's predicates.ClusterUnpausedAndInfrastructureReady
- linters:
- staticcheck
text: "SA1019: predicates.ClusterUnpausedAndInfrastructureReady is deprecated: This predicate is deprecated and will be removed in a future version, use ClusterPausedTransitionsOrInfrastructureReady instead."
- linters:
- revive
text: "exported: exported method .*\\.(Reconcile|SetupWithManager|SetupWebhookWithManager) should have comment or be unexported"
Expand Down
74 changes: 73 additions & 1 deletion apis/v1beta1/vspherecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,78 @@ const (
ClusterFinalizer = "vspherecluster.infrastructure.cluster.x-k8s.io"
)

// VSphereCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
const (
// VSphereClusterReadyV1Beta2Condition is true if the VSphereCluster's deletionTimestamp is not set, VSphereCluster's
// FailureDomainsReady, VCenterAvailable and ClusterModulesReady conditions are true.
VSphereClusterReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition

// VSphereClusterReadyV1Beta2Reason surfaces when the VSphereCluster readiness criteria is met.
VSphereClusterReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason

// VSphereClusterNotReadyV1Beta2Reason surfaces when the VSphereCluster readiness criteria is not met.
VSphereClusterNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason

// VSphereClusterReadyUnknownV1Beta2Reason surfaces when at least one VSphereCluster readiness criteria is unknown
// and no VSphereCluster readiness criteria is not met.
VSphereClusterReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
)

// VSphereCluster's FailureDomainsReady condition and corresponding reasons that will be used in v1Beta2 API version.
const (
// VSphereClusterFailureDomainsReadyV1Beta2Condition documents the status of failure domains for a VSphereCluster.
VSphereClusterFailureDomainsReadyV1Beta2Condition = "FailureDomainsReady"

// VSphereClusterFailureDomainsReadyV1Beta2Reason surfaces when the failure domains for a VSphereCluster are ready.
VSphereClusterFailureDomainsReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason

// VSphereClusterFailureDomainsWaitingForFailureDomainStatusV1Beta2Reason surfaces when not all VSphereFailureDomains for a VSphereCluster are ready.
VSphereClusterFailureDomainsWaitingForFailureDomainStatusV1Beta2Reason = "WaitingForFailureDomainStatus"

// VSphereClusterFailureDomainsNotReadyV1Beta2Reason surfaces when the failure domains for a VSphereCluster are not ready.
VSphereClusterFailureDomainsNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason

// VSphereClusterFailureDomainsDeletingV1Beta2Reason surfaces when the failure domains for a VSphereCluster are being deleted.
VSphereClusterFailureDomainsDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
)

// VSphereCluster's VCenterAvailable condition and corresponding reasons that will be used in v1Beta2 API version.
const (
// VSphereClusterVCenterAvailableV1Beta2Condition documents the status of vCenter for a VSphereCluster.
VSphereClusterVCenterAvailableV1Beta2Condition = "VCenterAvailable"

// VSphereClusterVCenterAvailableV1Beta2Reason surfaces when the vCenter for a VSphereCluster is available.
VSphereClusterVCenterAvailableV1Beta2Reason = clusterv1.AvailableV1Beta2Reason

// VSphereClusterVCenterUnreachableV1Beta2Reason surfaces when the vCenter for a VSphereCluster is unreachable.
VSphereClusterVCenterUnreachableV1Beta2Reason = "VCenterUnreachable"

// VSphereClusterVCenterNotAvailableV1Beta2Reason surfaces when the vCenter for a VSphereCluster is not available.
VSphereClusterVCenterNotAvailableV1Beta2Reason = clusterv1.NotAvailableV1Beta2Reason

// VSphereClusterVCenterAvailableDeletingV1Beta2Reason surfaces when the VSphereCluster is being deleted.
VSphereClusterVCenterAvailableDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
)
Comment on lines +66 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should look into making this condition consistent across objects


// VSphereCluster's ClusterModulesReady condition and corresponding reasons that will be used in v1Beta2 API version.
const (
// VSphereClusterClusterModulesReadyV1Beta2Condition documents the status of vCenter for a VSphereCluster.
VSphereClusterClusterModulesReadyV1Beta2Condition = "ClusterModulesReady"

// VSphereClusterClusterModulesReadyV1Beta2Reason surfaces when the cluster modules for a VSphereCluster are ready.
VSphereClusterClusterModulesReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason

// VSphereClusterModulesInvalidVCenterVersionV1Beta2Reason surfaces when the cluster modules for a VSphereCluster can't be reconciled
// due to an invalid vCenter version.
VSphereClusterModulesInvalidVCenterVersionV1Beta2Reason = "InvalidVCenterVersion"

// VSphereClusterClusterModulesNotReadyV1Beta2Reason surfaces when the cluster modules for a VSphereCluster are not ready.
VSphereClusterClusterModulesNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason

// VSphereClusterClusterModulesDeletingV1Beta2Reason surfaces when the cluster modules for a VSphereCluster are being deleted.
VSphereClusterClusterModulesDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
)

// VCenterVersion conveys the API version of the vCenter instance.
type VCenterVersion string

Expand Down Expand Up @@ -114,7 +186,7 @@ type VSphereClusterStatus struct {
// See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context.
type VSphereClusterV1Beta2Status struct {
// conditions represents the observations of a VSphereCluster's current state.
// Known condition types are Paused.
// Known condition types are Ready, FailureDomainsReady, VCenterAvailable, ClusterModulesReady and Paused.
// +optional
// +listType=map
// +listMapKey=type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ spec:
conditions:
description: |-
conditions represents the observations of a VSphereCluster's current state.
Known condition types are Paused.
Known condition types are Ready, FailureDomainsReady, VCenterAvailable, ClusterModulesReady and Paused.
items:
description: Condition contains details for one aspect of the
current state of this API Resource.
Expand Down
21 changes: 20 additions & 1 deletion controllers/clustermodule_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/conditions"
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
Expand Down Expand Up @@ -69,6 +70,12 @@ func (r Reconciler) Reconcile(ctx context.Context, clusterCtx *capvcontext.Clust
if !clustermodule.IsClusterCompatible(clusterCtx) {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition, infrav1.VCenterVersionIncompatibleReason, clusterv1.ConditionSeverityInfo,
"vCenter version %s does not support cluster modules", clusterCtx.VSphereCluster.Status.VCenterVersion)
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterModulesInvalidVCenterVersionV1Beta2Reason,
Message: fmt.Sprintf("vCenter version %s does not support cluster modules", clusterCtx.VSphereCluster.Status.VCenterVersion),
})
log.V(5).Info(fmt.Sprintf("vCenter version %s does not support cluster modules to implement anti affinity (vCenter >= 7 required)", clusterCtx.VSphereCluster.Status.VCenterVersion))
return reconcile.Result{}, nil
}
Expand Down Expand Up @@ -170,11 +177,23 @@ func (r Reconciler) Reconcile(ctx context.Context, clusterCtx *capvcontext.Clust
}
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition, infrav1.ClusterModuleSetupFailedReason,
clusterv1.ConditionSeverityWarning, generateClusterModuleErrorMessage(modErrs))
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterClusterModulesNotReadyV1Beta2Reason,
Message: generateClusterModuleErrorMessage(modErrs),
})
return reconcile.Result{}, err
case len(modErrs) == 0 && len(clusterModuleSpecs) > 0:
conditions.MarkTrue(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition)
default:
conditions.Delete(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition)
}
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: why this is not after L188

Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionTrue,
Reason: infrav1.VSphereClusterClusterModulesReadyV1Beta2Reason,
})
return reconcile.Result{}, err
}

Expand Down Expand Up @@ -319,7 +338,7 @@ type clusterModError struct {

func generateClusterModuleErrorMessage(errList []clusterModError) string {
sb := strings.Builder{}
sb.WriteString("failed to create cluster modules for: ")
sb.WriteString("Failed to create cluster modules for: ")

for _, e := range errList {
sb.WriteString(fmt.Sprintf("%s %s, ", e.name, e.err.Error()))
Expand Down
127 changes: 123 additions & 4 deletions controllers/vspherecluster_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
clusterutilv1 "sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/conditions"
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
"sigs.k8s.io/cluster-api/util/finalizers"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/cluster-api/util/paused"
Expand Down Expand Up @@ -107,7 +108,7 @@ func (r *clusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_
// Always issue a patch when exiting this function so changes to the
// resource are patched back to the API server.
defer func() {
if err := clusterContext.Patch(ctx); err != nil {
if err := r.patch(ctx, clusterContext); err != nil {
reterr = kerrors.NewAggregate([]error{reterr, err})
}
}()
Expand All @@ -126,9 +127,71 @@ func (r *clusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_
return r.reconcileNormal(ctx, clusterContext)
}

// patch updates the VSphereCluster and its status on the API server.
func (r *clusterReconciler) patch(ctx context.Context, clusterCtx *capvcontext.ClusterContext) error {
// always update the readyCondition.
conditions.SetSummary(clusterCtx.VSphereCluster,
conditions.WithConditions(
infrav1.VCenterAvailableCondition,
),
)

if err := v1beta2conditions.SetSummaryCondition(clusterCtx.VSphereCluster, clusterCtx.VSphereCluster, infrav1.VSphereClusterReadyV1Beta2Condition,
v1beta2conditions.ForConditionTypes{
infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
// FailureDomainsReady and ClusterModuelsReady may not be always set.
infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
},
v1beta2conditions.IgnoreTypesIfMissing{
infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
},
// Using a custom merge strategy to override reasons applied during merge.
v1beta2conditions.CustomMergeStrategy{
MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
// Use custom reasons.
v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
infrav1.VSphereClusterNotReadyV1Beta2Reason,
infrav1.VSphereClusterReadyUnknownV1Beta2Reason,
infrav1.VSphereClusterReadyV1Beta2Reason,
)),
),
},
); err != nil {
return pkgerrors.Wrapf(err, "failed to set %s condition", infrav1.VSphereClusterReadyV1Beta2Condition)
}

return clusterCtx.PatchHelper.Patch(ctx, clusterCtx.VSphereCluster,
patch.WithOwnedV1Beta2Conditions{Conditions: []string{
clusterv1.PausedV1Beta2Condition,
infrav1.VSphereClusterReadyV1Beta2Condition,
infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
}},
)
}

func (r *clusterReconciler) reconcileDelete(ctx context.Context, clusterCtx *capvcontext.ClusterContext) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)

v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterVCenterAvailableDeletingV1Beta2Reason,
})
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterClusterModulesDeletingV1Beta2Reason,
})
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterFailureDomainsDeletingV1Beta2Reason,
})

var vsphereMachines []client.Object
var err error
if clusterCtx.Cluster != nil {
Expand Down Expand Up @@ -191,37 +254,74 @@ func (r *clusterReconciler) reconcileDelete(ctx context.Context, clusterCtx *cap
func (r *clusterReconciler) reconcileNormal(ctx context.Context, clusterCtx *capvcontext.ClusterContext) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)

// Reconcile failure domains.
ok, err := r.reconcileDeploymentZones(ctx, clusterCtx)
if err != nil {
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterFailureDomainsNotReadyV1Beta2Reason,
Message: err.Error(),
})
return reconcile.Result{}, err
}
if !ok {
log.Info("Waiting for failure domains to be reconciled")
return reconcile.Result{RequeueAfter: 10 * time.Second}, nil
}

// Reconcile vCenter availability.
if err := r.reconcileIdentitySecret(ctx, clusterCtx); err != nil {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.VCenterAvailableCondition, infrav1.VCenterUnreachableReason, clusterv1.ConditionSeverityError, err.Error())
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterVCenterUnreachableV1Beta2Reason,
Message: err.Error(),
})
return reconcile.Result{}, err
}

vcenterSession, err := r.reconcileVCenterConnectivity(ctx, clusterCtx)
if err != nil {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.VCenterAvailableCondition, infrav1.VCenterUnreachableReason, clusterv1.ConditionSeverityError, err.Error())
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterVCenterUnreachableV1Beta2Reason,
Message: err.Error(),
})
return reconcile.Result{}, pkgerrors.Wrapf(err,
"unexpected error while probing vcenter for %s", clusterCtx)
}
conditions.MarkTrue(clusterCtx.VSphereCluster, infrav1.VCenterAvailableCondition)
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterVCenterAvailableV1Beta2Condition,
Status: metav1.ConditionTrue,
Reason: infrav1.VSphereClusterVCenterAvailableV1Beta2Reason,
})

// Reconcile cluster modules.
err = r.reconcileVCenterVersion(clusterCtx, vcenterSession)
if err != nil || clusterCtx.VSphereCluster.Status.VCenterVersion == "" {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition, infrav1.MissingVCenterVersionReason, clusterv1.ConditionSeverityWarning, "vCenter version not set")
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition, infrav1.MissingVCenterVersionReason, clusterv1.ConditionSeverityWarning, err.Error())
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterModulesInvalidVCenterVersionV1Beta2Reason,
Message: err.Error(),
})
log.Error(err, "could not reconcile vCenter version")
}

affinityReconcileResult, err := r.reconcileClusterModules(ctx, clusterCtx)
if err != nil {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.ClusterModulesAvailableCondition, infrav1.ClusterModuleSetupFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterClusterModulesNotReadyV1Beta2Reason,
Message: err.Error(),
})
return affinityReconcileResult, err
}

Expand Down Expand Up @@ -295,13 +395,14 @@ func (r *clusterReconciler) reconcileVCenterConnectivity(ctx context.Context, cl
func (r *clusterReconciler) reconcileVCenterVersion(clusterCtx *capvcontext.ClusterContext, s *session.Session) error {
version, err := s.GetVersion()
if err != nil {
return err
return pkgerrors.Wrapf(err, "invalid vCenter version")
}
clusterCtx.VSphereCluster.Status.VCenterVersion = version
return nil
}

func (r *clusterReconciler) reconcileDeploymentZones(ctx context.Context, clusterCtx *capvcontext.ClusterContext) (bool, error) {
log := ctrl.LoggerFrom(ctx)
// If there is no failure domain selector, skip reconciliation
if clusterCtx.VSphereCluster.Spec.FailureDomainSelector == nil {
return true, nil
Expand Down Expand Up @@ -346,15 +447,33 @@ func (r *clusterReconciler) reconcileDeploymentZones(ctx context.Context, cluste

clusterCtx.VSphereCluster.Status.FailureDomains = failureDomains
if readyNotReported > 0 {
log.Info("Waiting for failure domains to be reconciled")
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.FailureDomainsAvailableCondition, infrav1.WaitingForFailureDomainStatusReason, clusterv1.ConditionSeverityInfo, "waiting for failure domains to report ready status")
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterFailureDomainsNotReadyV1Beta2Reason,
Message: "Waiting for failure domains to report ready status",
})
return false, nil
}

if len(failureDomains) > 0 {
if notReady > 0 {
conditions.MarkFalse(clusterCtx.VSphereCluster, infrav1.FailureDomainsAvailableCondition, infrav1.FailureDomainsSkippedReason, clusterv1.ConditionSeverityInfo, "one or more failure domains are not ready")
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
Status: metav1.ConditionFalse,
Reason: infrav1.VSphereClusterFailureDomainsNotReadyV1Beta2Reason,
Message: "One or more failure domains are not ready",
})
} else {
conditions.MarkTrue(clusterCtx.VSphereCluster, infrav1.FailureDomainsAvailableCondition)
v1beta2conditions.Set(clusterCtx.VSphereCluster, metav1.Condition{
Type: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Condition,
Status: metav1.ConditionTrue,
Reason: infrav1.VSphereClusterFailureDomainsReadyV1Beta2Reason,
})
}
} else {
// Remove the condition if failure domains do not exist
Expand Down
Loading