Repository: kubernetes-sigs/cluster-api-provider-vsphere

Issue:
CAPI v1.10.0-beta.0 has been released and is ready for testing
CAPI v1.10.0-beta.0 has been released and is ready for testing.
Looking forward to your feedback before v1.10.0 release!

## For quick reference

<!-- body -->
- [CAPI v1.10.0-beta.0 release notes](https://github.com/kubernetes-sigs/cluster-api/releases/tag/v1.10.0-beta.0)
- [Shortcut to CAPI git issues](https://github.com/kubernetes-sigs/cluster-api/issues)

## Following are the planned dates for the upcoming releases

CAPI v1.10.0 will be released on **Tuesday, 18th March 2025**.

More details of the upcoming schedule can be seen at [CAPI v1.10.0 release timeline](https://github.com/kubernetes-sigs/cluster-api/tree/main/docs/release/releases/release-1.10.md#timeline).

<!-- [List of CAPI providers](https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/release/role-handbooks/communications/README.md#communicate-beta-to-providers) -->
<!-- body -->


Hints from issue comments:
@sbueringer: Currently using CAPI main from yesterday. Will pickup a newer version soon@fabriziopandini: /assign

Solution patch:
diff --git a/Makefile b/Makefile
index fa12c66c..dc5f4cb2 100644
--- a/Makefile
+++ b/Makefile
@@ -190,7 +190,7 @@ IMPORT_BOSS_VER := v0.28.1
 IMPORT_BOSS := $(abspath $(TOOLS_BIN_DIR)/$(IMPORT_BOSS_BIN))
 IMPORT_BOSS_PKG := k8s.io/code-generator/cmd/import-boss
 
-CAPI_HACK_TOOLS_VER := 9e7afa749358adc7e0c6ae5408d0d8eeb2affa0a # Note: this a commit ID of from CAPI main, supposed to be in v1.10
+CAPI_HACK_TOOLS_VER := 4f022adb6164359b9c414d8c41752b2da27246fe # Note: this a commit ID of from CAPI release-1.0 branch, supposed to be in v1.10
 
 BOSKOSCTL_BIN := boskosctl
 BOSKOSCTL := $(abspath $(TOOLS_BIN_DIR)/$(BOSKOSCTL_BIN))
diff --git a/apis/v1beta1/vsphereclusteridentity_types.go b/apis/v1beta1/vsphereclusteridentity_types.go
index 502a1831..ae23092f 100644
--- a/apis/v1beta1/vsphereclusteridentity_types.go
+++ b/apis/v1beta1/vsphereclusteridentity_types.go
@@ -28,6 +28,27 @@ const (
 	VSphereClusterIdentityFinalizer = "vsphereclusteridentity/infrastructure.cluster.x-k8s.io"
 )
 
+// VSphereClusterIdentity's Available condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// VSphereClusterIdentityAvailableV1Beta2Condition documents the availability for a VSphereClusterIdentity.
+	VSphereClusterIdentityAvailableV1Beta2Condition = clusterv1.AvailableV1Beta2Condition
+
+	// VSphereClusterIdentityAvailableV1Beta2Reason surfaces when the VSphereClusterIdentity is available.
+	VSphereClusterIdentityAvailableV1Beta2Reason = clusterv1.AvailableV1Beta2Reason
+
+	// VSphereClusterIdentitySecretNotAvailableV1Beta2Reason surfaces when the VSphereClusterIdentity secret is not available.
+	VSphereClusterIdentitySecretNotAvailableV1Beta2Reason = "SecretNotAvailable"
+
+	// VSphereClusterIdentitySecretAlreadyInUseV1Beta2Reason surfaces when the VSphereClusterIdentity secret is already in use.
+	VSphereClusterIdentitySecretAlreadyInUseV1Beta2Reason = "SecretAlreadyInUse"
+
+	// VSphereClusterIdentitySettingSecretOwnerReferenceFailedV1Beta2Reason surfaces when setting the owner reference on the VSphereClusterIdentity secret failed.
+	VSphereClusterIdentitySettingSecretOwnerReferenceFailedV1Beta2Reason = "SettingSecretOwnerReferenceFailed"
+
+	// VSphereClusterIdentityDeletingV1Beta2Reason surfaces when the VSphereClusterIdentity is being deleted.
+	VSphereClusterIdentityDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
 // VSphereClusterIdentitySpec contains a secret reference and a group of allowed namespaces.
 type VSphereClusterIdentitySpec struct {
 	// SecretName references a Secret inside the controller namespace with the credentials to use
@@ -59,7 +80,7 @@ type VSphereClusterIdentityStatus struct {
 // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context.
 type VSphereClusterIdentityV1Beta2Status struct {
 	// conditions represents the observations of a VSphereClusterIdentity's current state.
-	// Known condition types are Paused.
+	// Known condition types are Available and Paused.
 	// +optional
 	// +listType=map
 	// +listMapKey=type
diff --git a/apis/v1beta1/vspheredeploymentzone_types.go b/apis/v1beta1/vspheredeploymentzone_types.go
index cf9d7b84..6b288361 100644
--- a/apis/v1beta1/vspheredeploymentzone_types.go
+++ b/apis/v1beta1/vspheredeploymentzone_types.go
@@ -28,6 +28,92 @@ const (
 	DeploymentZoneFinalizer = "vspheredeploymentzone.infrastructure.cluster.x-k8s.io"
 )
 
+// VSphereDeploymentZone's Ready condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// VSphereDeploymentZoneReadyV1Beta2Condition is true if the VSphereDeploymentZone's deletionTimestamp is not set, VSphereDeploymentZone's
+	// VCenterAvailable, PlacementConstraintReady and FailureDomainValidated conditions are true.
+	VSphereDeploymentZoneReadyV1Beta2Condition = clusterv1.ReadyV1Beta2Condition
+
+	// VSphereDeploymentZoneReadyV1Beta2Reason surfaces when the VSphereDeploymentZone readiness criteria is met.
+	VSphereDeploymentZoneReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// VSphereDeploymentZoneNotReadyV1Beta2Reason surfaces when the VSphereDeploymentZone readiness criteria is not met.
+	VSphereDeploymentZoneNotReadyV1Beta2Reason = clusterv1.NotReadyV1Beta2Reason
+
+	// VSphereDeploymentZoneReadyUnknownV1Beta2Reason surfaces when at least one VSphereDeploymentZone readiness criteria is unknown
+	// and no VSphereDeploymentZone readiness criteria is not met.
+	VSphereDeploymentZoneReadyUnknownV1Beta2Reason = clusterv1.ReadyUnknownV1Beta2Reason
+)
+
+// VSphereDeploymentZone's VCenterAvailable condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// VSphereDeploymentZoneVCenterAvailableV1Beta2Condition documents the status of vCenter for a VSphereDeploymentZone.
+	VSphereDeploymentZoneVCenterAvailableV1Beta2Condition = "VCenterAvailable"
+
+	// VSphereDeploymentZoneVCenterAvailableV1Beta2Reason surfaces when the vCenter for a VSphereDeploymentZone is available.
+	VSphereDeploymentZoneVCenterAvailableV1Beta2Reason = clusterv1.AvailableV1Beta2Reason
+
+	// VSphereDeploymentZoneVCenterUnreachableV1Beta2Reason surfaces when the vCenter for a VSphereDeploymentZone is unreachable.
+	VSphereDeploymentZoneVCenterUnreachableV1Beta2Reason = "VCenterUnreachable"
+
+	// VSphereDeploymentZoneVCenterAvailableDeletingV1Beta2Reason surfaces when the vCenter for a VSphereDeploymentZone is being deleted.
+	VSphereDeploymentZoneVCenterAvailableDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// VSphereDeploymentZone's PlacementConstraintReady condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition documents the placement constraint status for a VSphereDeploymentZone.
+	VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition = "PlacementConstraintReady"
+
+	// VSphereDeploymentZonePlacementConstraintReadyV1Beta2Reason surfaces when the placement status for a VSphereDeploymentZone is ready.
+	VSphereDeploymentZonePlacementConstraintReadyV1Beta2Reason = clusterv1.ReadyV1Beta2Reason
+
+	// VSphereDeploymentZonePlacementConstraintResourcePoolNotFoundV1Beta2Reason surfaces when the resource pool for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZonePlacementConstraintResourcePoolNotFoundV1Beta2Reason = "ResourcePoolNotFound"
+
+	// VSphereDeploymentZonePlacementConstraintFolderNotFoundV1Beta2Reason surfaces when the folder for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZonePlacementConstraintFolderNotFoundV1Beta2Reason = "FolderNotFound"
+
+	// VSphereDeploymentZonePlacementConstraintDeletingV1Beta2Reason surfaces when the VSphereDeploymentZone is being deleted.
+	VSphereDeploymentZonePlacementConstraintDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// VSphereDeploymentZone's FailureDomainValidated condition and corresponding reasons that will be used in v1Beta2 API version.
+const (
+	// VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition documents failure domain validation status for a VSphereDeploymentZone.
+	VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition = "FailureDomainValidated"
+
+	// VSphereDeploymentZoneFailureDomainValidatedV1Beta2Reason surfaces when the failure domain for a VSphereDeploymentZone is validated.
+	VSphereDeploymentZoneFailureDomainValidatedV1Beta2Reason = "Validated"
+
+	// VSphereDeploymentZoneFailureDomainValidationFailedV1Beta2Reason surfaces when the failure domain's validation for a VSphereDeploymentZone failed.
+	VSphereDeploymentZoneFailureDomainValidationFailedV1Beta2Reason = "ValidationFailed"
+
+	// VSphereDeploymentZoneFailureDomainRegionMisconfiguredV1Beta2Reason surfaces when the failure domain's region for a VSphereDeploymentZone is misconfigured.
+	VSphereDeploymentZoneFailureDomainRegionMisconfiguredV1Beta2Reason = "RegionMisconfigured"
+
+	// VSphereDeploymentZoneFailureDomainZoneMisconfiguredV1Beta2Reason surfaces when the failure domain's zone for a VSphereDeploymentZone is misconfigured.
+	VSphereDeploymentZoneFailureDomainZoneMisconfiguredV1Beta2Reason = "ZoneMisconfigured"
+
+	// VSphereDeploymentZoneFailureDomainHostsMisconfiguredV1Beta2Reason surfaces when the failure domain's hosts for a VSphereDeploymentZone are misconfigured.
+	VSphereDeploymentZoneFailureDomainHostsMisconfiguredV1Beta2Reason = "HostsMisconfigured"
+
+	// VSphereDeploymentZoneFailureDomainDatastoreNotFoundV1Beta2Reason surfaces when the failure domain's datastore for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZoneFailureDomainDatastoreNotFoundV1Beta2Reason = "DatastoreNotFound"
+
+	// VSphereDeploymentZoneFailureDomainNetworkNotFoundV1Beta2Reason surfaces when the failure domain's network for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZoneFailureDomainNetworkNotFoundV1Beta2Reason = "NetworkNotFound"
+
+	// VSphereDeploymentZoneFailureDomainComputeClusterNotFoundV1Beta2Reason surfaces when the failure domain's compute cluster for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZoneFailureDomainComputeClusterNotFoundV1Beta2Reason = "ComputeClusterNotFound"
+
+	// VSphereDeploymentZoneFailureDomainResourcePoolNotFoundV1Beta2Reason surfaces when the failure domain's resource pool for a VSphereDeploymentZone is not found.
+	VSphereDeploymentZoneFailureDomainResourcePoolNotFoundV1Beta2Reason = "ResourcePoolNotFound"
+
+	// VSphereDeploymentZoneFailureDomainDeletingV1Beta2Reason surfaces when the VSphereDeploymentZone is being deleted.
+	VSphereDeploymentZoneFailureDomainDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
 // VSphereDeploymentZoneSpec defines the desired state of VSphereDeploymentZone.
 type VSphereDeploymentZoneSpec struct {
 
@@ -93,7 +179,7 @@ type VSphereDeploymentZoneStatus struct {
 // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context.
 type VSphereDeploymentZoneV1Beta2Status struct {
 	// conditions represents the observations of a VSphereDeploymentZone's current state.
-	// Known condition types are Paused.
+	// Known condition types are Ready, VCenterAvailable, PlacementConstraintReady, FailureDomainValidated and Paused.
 	// +optional
 	// +listType=map
 	// +listMapKey=type
diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusteridentities.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusteridentities.yaml
index 7bb212d4..af327768 100644
--- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusteridentities.yaml
+++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusteridentities.yaml
@@ -451,7 +451,7 @@ spec:
                   conditions:
                     description: |-
                       conditions represents the observations of a VSphereClusterIdentity's current state.
-                      Known condition types are Paused.
+                      Known condition types are Available and Paused.
                     items:
                       description: Condition contains details for one aspect of the
                         current state of this API Resource.
diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheredeploymentzones.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheredeploymentzones.yaml
index 8f169ab5..a2860cbc 100644
--- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheredeploymentzones.yaml
+++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheredeploymentzones.yaml
@@ -371,7 +371,7 @@ spec:
                   conditions:
                     description: |-
                       conditions represents the observations of a VSphereDeploymentZone's current state.
-                      Known condition types are Paused.
+                      Known condition types are Ready, VCenterAvailable, PlacementConstraintReady, FailureDomainValidated and Paused.
                     items:
                       description: Condition contains details for one aspect of the
                         current state of this API Resource.
diff --git a/controllers/vsphereclusteridentity_controller.go b/controllers/vsphereclusteridentity_controller.go
index a00047c9..99faa682 100644
--- a/controllers/vsphereclusteridentity_controller.go
+++ b/controllers/vsphereclusteridentity_controller.go
@@ -30,6 +30,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"
@@ -102,6 +103,7 @@ func (r clusterIdentityReconciler) Reconcile(ctx context.Context, req reconcile.
 
 		if err := patchHelper.Patch(ctx, identity, patch.WithOwnedV1Beta2Conditions{Conditions: []string{
 			clusterv1.PausedV1Beta2Condition,
+			infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
 		}}); err != nil {
 			reterr = kerrors.NewAggregate([]error{reterr, err})
 		}
@@ -119,12 +121,24 @@ func (r clusterIdentityReconciler) Reconcile(ctx context.Context, req reconcile.
 	}
 	if err := r.Client.Get(ctx, secretKey, secret); err != nil {
 		conditions.MarkFalse(identity, infrav1.CredentialsAvailableCondidtion, infrav1.SecretNotAvailableReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(identity, metav1.Condition{
+			Type:    infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereClusterIdentitySecretNotAvailableV1Beta2Reason,
+			Message: err.Error(),
+		})
 		return reconcile.Result{}, errors.Wrapf(err, "failed to get Secret %s", klog.KRef(secretKey.Namespace, secretKey.Name))
 	}
 
 	// If this secret is owned by a different VSphereClusterIdentity or a VSphereCluster, mark the identity as not ready and return an error.
 	if !clusterutilv1.IsOwnedByObject(secret, identity) && pkgidentity.IsOwnedByIdentityOrCluster(secret.GetOwnerReferences()) {
 		conditions.MarkFalse(identity, infrav1.CredentialsAvailableCondidtion, infrav1.SecretAlreadyInUseReason, clusterv1.ConditionSeverityError, "secret being used by another Cluster/VSphereIdentity")
+		v1beta2conditions.Set(identity, metav1.Condition{
+			Type:    infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereClusterIdentitySecretAlreadyInUseV1Beta2Reason,
+			Message: "secret being used by another Cluster/VSphereIdentity",
+		})
 		identity.Status.Ready = false
 		return reconcile.Result{}, errors.New("secret being used by another Cluster/VSphereIdentity")
 	}
@@ -145,10 +159,22 @@ func (r clusterIdentityReconciler) Reconcile(ctx context.Context, req reconcile.
 	err = r.Client.Update(ctx, secret)
 	if err != nil {
 		conditions.MarkFalse(identity, infrav1.CredentialsAvailableCondidtion, infrav1.SecretOwnerReferenceFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
+		v1beta2conditions.Set(identity, metav1.Condition{
+			Type:    infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereClusterIdentitySettingSecretOwnerReferenceFailedV1Beta2Reason,
+			Message: err.Error(),
+		})
 		return reconcile.Result{}, err
 	}
 
 	conditions.MarkTrue(identity, infrav1.CredentialsAvailableCondidtion)
+	v1beta2conditions.Set(identity, metav1.Condition{
+		Type:   infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.VSphereClusterIdentityAvailableV1Beta2Reason,
+	})
+
 	identity.Status.Ready = true
 	return reconcile.Result{}, nil
 }
@@ -160,6 +186,13 @@ func (r clusterIdentityReconciler) reconcileDelete(ctx context.Context, identity
 		Namespace: r.ControllerManagerCtx.Namespace,
 		Name:      identity.Spec.SecretName,
 	}
+
+	v1beta2conditions.Set(identity, metav1.Condition{
+		Type:   infrav1.VSphereClusterIdentityAvailableV1Beta2Condition,
+		Status: metav1.ConditionFalse,
+		Reason: infrav1.VSphereClusterIdentityDeletingV1Beta2Reason,
+	})
+
 	err := r.Client.Get(ctx, secretKey, secret)
 	if err != nil {
 		if apierrors.IsNotFound(err) {
diff --git a/controllers/vspheredeploymentzone_controller.go b/controllers/vspheredeploymentzone_controller.go
index 7ebd6d39..a1aef6cf 100644
--- a/controllers/vspheredeploymentzone_controller.go
+++ b/controllers/vspheredeploymentzone_controller.go
@@ -31,6 +31,7 @@ import (
 	clusterutilv1 "sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/collections"
 	"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"
@@ -122,7 +123,7 @@ func (r vsphereDeploymentZoneReconciler) Reconcile(ctx context.Context, request
 		PatchHelper:              patchHelper,
 	}
 	defer func() {
-		if err := vsphereDeploymentZoneContext.Patch(ctx); err != nil {
+		if err := r.patch(ctx, vsphereDeploymentZoneContext); err != nil {
 			reterr = kerrors.NewAggregate([]error{reterr, err})
 		}
 	}()
@@ -134,6 +135,48 @@ func (r vsphereDeploymentZoneReconciler) Reconcile(ctx context.Context, request
 	return ctrl.Result{}, r.reconcileNormal(ctx, vsphereDeploymentZoneContext)
 }
 
+// Patch patches the VSphereDeploymentZone.
+func (r vsphereDeploymentZoneReconciler) patch(ctx context.Context, vsphereDeploymentZoneContext *capvcontext.VSphereDeploymentZoneContext) error {
+	conditions.SetSummary(vsphereDeploymentZoneContext.VSphereDeploymentZone,
+		conditions.WithConditions(
+			infrav1.VCenterAvailableCondition,
+			infrav1.VSphereFailureDomainValidatedCondition,
+			infrav1.PlacementConstraintMetCondition,
+		),
+	)
+
+	if err := v1beta2conditions.SetSummaryCondition(vsphereDeploymentZoneContext.VSphereDeploymentZone, vsphereDeploymentZoneContext.VSphereDeploymentZone, infrav1.VSphereDeploymentZoneReadyV1Beta2Condition,
+		v1beta2conditions.ForConditionTypes{
+			infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+			infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Condition,
+			infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+		},
+		// Using a custom merge strategy to override reasons applied during merge.
+		v1beta2conditions.CustomMergeStrategy{
+			MergeStrategy: v1beta2conditions.DefaultMergeStrategy(
+				// Use custom reasons.
+				v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc(
+					infrav1.VSphereDeploymentZoneNotReadyV1Beta2Reason,
+					infrav1.VSphereDeploymentZoneReadyUnknownV1Beta2Reason,
+					infrav1.VSphereDeploymentZoneReadyV1Beta2Reason,
+				)),
+			),
+		},
+	); err != nil {
+		return errors.Wrapf(err, "failed to set %s condition", infrav1.VSphereDeploymentZoneReadyV1Beta2Condition)
+	}
+
+	return vsphereDeploymentZoneContext.PatchHelper.Patch(ctx, vsphereDeploymentZoneContext.VSphereDeploymentZone,
+		patch.WithOwnedV1Beta2Conditions{Conditions: []string{
+			clusterv1.PausedV1Beta2Condition,
+			infrav1.VSphereDeploymentZoneReadyV1Beta2Condition,
+			infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+			infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Condition,
+			infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+		}},
+	)
+}
+
 func (r vsphereDeploymentZoneReconciler) reconcileNormal(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) error {
 	failureDomain := &infrav1.VSphereFailureDomain{}
 	failureDomainKey := client.ObjectKey{Name: deploymentZoneCtx.VSphereDeploymentZone.Spec.FailureDomain}
@@ -144,17 +187,28 @@ func (r vsphereDeploymentZoneReconciler) reconcileNormal(ctx context.Context, de
 	authSession, err := r.getVCenterSession(ctx, deploymentZoneCtx, failureDomain.Spec.Topology.Datacenter)
 	if err != nil {
 		conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VCenterAvailableCondition, infrav1.VCenterUnreachableReason, clusterv1.ConditionSeverityError, err.Error())
+		v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+			Type:    infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereDeploymentZoneVCenterUnreachableV1Beta2Reason,
+			Message: err.Error(),
+		})
 		deploymentZoneCtx.VSphereDeploymentZone.Status.Ready = ptr.To(false)
 		return err
 	}
+
 	deploymentZoneCtx.AuthSession = authSession
 	conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VCenterAvailableCondition)
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Reason,
+	})
 
 	if err := r.reconcilePlacementConstraint(ctx, deploymentZoneCtx); err != nil {
 		deploymentZoneCtx.VSphereDeploymentZone.Status.Ready = ptr.To(false)
 		return err
 	}
-	conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition)
 
 	// reconcile the failure domain
 	if err := r.reconcileFailureDomain(ctx, deploymentZoneCtx, failureDomain); err != nil {
@@ -173,6 +227,12 @@ func (r vsphereDeploymentZoneReconciler) reconcilePlacementConstraint(ctx contex
 	if resourcePool := placementConstraint.ResourcePool; resourcePool != "" {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.ResourcePool(ctx, resourcePool); err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition, infrav1.ResourcePoolNotFoundReason, clusterv1.ConditionSeverityError, "resource pool %s is misconfigured", resourcePool)
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZonePlacementConstraintResourcePoolNotFoundV1Beta2Reason,
+				Message: fmt.Sprintf("resource pool %s is misconfigured", resourcePool),
+			})
 			return errors.Wrapf(err, "failed to reconcile placement contraint: unable to find resource pool %s", resourcePool)
 		}
 	}
@@ -180,9 +240,23 @@ func (r vsphereDeploymentZoneReconciler) reconcilePlacementConstraint(ctx contex
 	if folder := placementConstraint.Folder; folder != "" {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.Folder(ctx, placementConstraint.Folder); err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition, infrav1.FolderNotFoundReason, clusterv1.ConditionSeverityError, "folder %s is misconfigured", folder)
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZonePlacementConstraintFolderNotFoundV1Beta2Reason,
+				Message: fmt.Sprintf("folder %s is misconfigured", folder),
+			})
 			return errors.Wrapf(err, "failed to reconcile placement contraint: unable to find folder %s", folder)
 		}
 	}
+
+	conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition)
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Reason,
+	})
+
 	return nil
 }
 
@@ -228,6 +302,22 @@ func (r vsphereDeploymentZoneReconciler) getVCenterSession(ctx context.Context,
 func (r vsphereDeploymentZoneReconciler) reconcileDelete(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) error {
 	log := ctrl.LoggerFrom(ctx)
 
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZoneVCenterAvailableV1Beta2Condition,
+		Status: metav1.ConditionFalse,
+		Reason: infrav1.VSphereDeploymentZoneVCenterAvailableDeletingV1Beta2Reason,
+	})
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZonePlacementConstraintReadyV1Beta2Condition,
+		Status: metav1.ConditionFalse,
+		Reason: infrav1.VSphereDeploymentZonePlacementConstraintDeletingV1Beta2Reason,
+	})
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+		Status: metav1.ConditionFalse,
+		Reason: infrav1.VSphereDeploymentZoneFailureDomainDeletingV1Beta2Reason,
+	})
+
 	machines := &clusterv1.MachineList{}
 	if err := r.Client.List(ctx, machines); err != nil {
 		return errors.Wrapf(err, "failed to list Machines")
diff --git a/controllers/vspheredeploymentzone_controller_domain.go b/controllers/vspheredeploymentzone_controller_domain.go
index 32052bf1..5dc516d1 100644
--- a/controllers/vspheredeploymentzone_controller_domain.go
+++ b/controllers/vspheredeploymentzone_controller_domain.go
@@ -18,6 +18,7 @@ package controllers
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -25,6 +26,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"
 	ctrl "sigs.k8s.io/controller-runtime"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
@@ -38,12 +40,24 @@ func (r vsphereDeploymentZoneReconciler) reconcileFailureDomain(ctx context.Cont
 	// verify the failure domain for the region
 	if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Region); err != nil {
 		conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.RegionMisconfiguredReason, clusterv1.ConditionSeverityError, err.Error())
+		v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+			Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereDeploymentZoneFailureDomainRegionMisconfiguredV1Beta2Reason,
+			Message: err.Error(),
+		})
 		return errors.Wrapf(err, "failed to reconcile failure domain: region is not configured correctly")
 	}
 
 	// verify the failure domain for the zone
 	if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone); err != nil {
 		conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.ZoneMisconfiguredReason, clusterv1.ConditionSeverityError, err.Error())
+		v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+			Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereDeploymentZoneFailureDomainZoneMisconfiguredV1Beta2Reason,
+			Message: err.Error(),
+		})
 		return errors.Wrapf(err, "failed to reconcile failure domain: zone is not configured correctly")
 	}
 
@@ -69,11 +83,22 @@ func (r vsphereDeploymentZoneReconciler) reconcileFailureDomain(ctx context.Cont
 					UID:        deploymentZoneCtx.VSphereDeploymentZone.UID,
 				})
 		}); err != nil {
+		v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+			Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereDeploymentZoneFailureDomainValidationFailedV1Beta2Reason,
+			Message: "failed to update owner references of failure domain",
+		})
 		return err
 	}
 
 	// Mark the VSphereDeploymentZone as having a valid VSphereFailureDomain.
 	conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition)
+	v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+		Type:   infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Reason,
+	})
 	return nil
 }
 
@@ -89,6 +114,12 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context,
 	if datastore := topology.Datastore; datastore != "" {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.Datastore(ctx, datastore); err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.DatastoreNotFoundReason, clusterv1.ConditionSeverityError, "datastore %s is misconfigured", datastore)
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainDatastoreNotFoundV1Beta2Reason,
+				Message: fmt.Sprintf("datastore %s is misconfigured", datastore),
+			})
 			return errors.Wrapf(err, "unable to find datastore %s", datastore)
 		}
 	}
@@ -96,6 +127,12 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context,
 	for _, network := range topology.Networks {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.Network(ctx, network); err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is not found", network)
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainNetworkNotFoundV1Beta2Reason,
+				Message: fmt.Sprintf("network %s is not found", network),
+			})
 			return errors.Wrapf(err, "unable to find network %s", network)
 		}
 	}
@@ -103,23 +140,34 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context,
 	for _, networkConfig := range topology.NetworkConfigurations {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.Network(ctx, networkConfig.NetworkName); err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is not found", networkConfig.NetworkName)
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainNetworkNotFoundV1Beta2Reason,
+				Message: fmt.Sprintf("network %s is not found", networkConfig.NetworkName),
+			})
 			return errors.Wrapf(err, "unable to find network %s", networkConfig.NetworkName)
 		}
 	}
 
 	if hostPlacementInfo := topology.Hosts; hostPlacementInfo != nil {
 		rule, err := cluster.VerifyAffinityRule(ctx, deploymentZoneCtx, *topology.ComputeCluster, hostPlacementInfo.HostGroupName, hostPlacementInfo.VMGroupName)
-		switch {
-		case err != nil:
+		if err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.HostsMisconfiguredReason, clusterv1.ConditionSeverityError, "vm host affinity does not exist")
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainHostsMisconfiguredV1Beta2Reason,
+				Message: "vm host affinity rule does not exist",
+			})
 			return err
-		case rule.Disabled():
+		}
+
+		if rule.Disabled() {
 			ctrl.LoggerFrom(ctx).V(4).Info("WARNING: vm-host rule for the failure domain is disabled", "hostGroup", hostPlacementInfo.HostGroupName, "vmGroup", hostPlacementInfo.VMGroupName)
-			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.HostsAffinityMisconfiguredReason, clusterv1.ConditionSeverityWarning, "vm host affinity is disabled")
-		default:
-			conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition)
 		}
 	}
+
 	return nil
 }
 
@@ -132,6 +180,12 @@ func (r vsphereDeploymentZoneReconciler) reconcileComputeCluster(ctx context.Con
 	ccr, err := deploymentZoneCtx.AuthSession.Finder.ClusterComputeResource(ctx, *computeCluster)
 	if err != nil {
 		conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.ComputeClusterNotFoundReason, clusterv1.ConditionSeverityError, "compute cluster %s not found", *computeCluster)
+		v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+			Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+			Status:  metav1.ConditionFalse,
+			Reason:  infrav1.VSphereDeploymentZoneFailureDomainComputeClusterNotFoundV1Beta2Reason,
+			Message: fmt.Sprintf("compute cluster %s not found", *computeCluster),
+		})
 		return errors.Wrap(err, "compute cluster not found")
 	}
 
@@ -144,10 +198,22 @@ func (r vsphereDeploymentZoneReconciler) reconcileComputeCluster(ctx context.Con
 		ref, err := rp.Owner(ctx)
 		if err != nil {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.ComputeClusterNotFoundReason, clusterv1.ConditionSeverityError, "resource pool owner not found")
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainComputeClusterNotFoundV1Beta2Reason,
+				Message: "resource pool owner not found",
+			})
 			return errors.Wrap(err, "unable to find owner compute resource")
 		}
 		if ref.Reference() != ccr.Reference() {
 			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.ResourcePoolNotFoundReason, clusterv1.ConditionSeverityError, "resource pool is not owned by compute cluster")
+			v1beta2conditions.Set(deploymentZoneCtx.VSphereDeploymentZone, metav1.Condition{
+				Type:    infrav1.VSphereDeploymentZoneFailureDomainValidatedV1Beta2Condition,
+				Status:  metav1.ConditionFalse,
+				Reason:  infrav1.VSphereDeploymentZoneFailureDomainResourcePoolNotFoundV1Beta2Reason,
+				Message: "resource pool is not owned by compute cluster",
+			})
 			return errors.Errorf("compute cluster %s does not own resource pool %s", *computeCluster, resourcePool)
 		}
 	}
diff --git a/go.mod b/go.mod
index b11976ed..b7713a1d 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module sigs.k8s.io/cluster-api-provider-vsphere
 
 go 1.23.0
 
-replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358
+replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164
 
 replace github.com/vmware-tanzu/vm-operator/pkg/constants/testlabels => github.com/vmware-tanzu/vm-operator/pkg/constants/testlabels v0.0.0-20240404200847-de75746a9505
 
diff --git a/go.sum b/go.sum
index abb72117..762ae476 100644
--- a/go.sum
+++ b/go.sum
@@ -417,8 +417,8 @@ k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6J
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358 h1:prMTNmeGPlCYD5YSc3Oi4NuA2dTA7Bj/5G4TaeeWSNg=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164 h1:Oizd0NIHcrm8ltWqLZCnuFVBt3snHbi9iXYG9iyRJGo=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
 sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
 sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
diff --git a/main.go b/main.go
index 9034d532..b4fe04d0 100644
--- a/main.go
+++ b/main.go
@@ -336,20 +336,20 @@ func main() {
 		// with the CRDs that should be migrated by this provider.
 		crdMigratorConfig := map[client.Object]crdmigrator.ByObjectConfig{}
 		if isGovmomiCRDLoaded {
-			crdMigratorConfig[&infrav1.VSphereCluster{}] = crdmigrator.ByObjectConfig{UseCache: true}
+			crdMigratorConfig[&infrav1.VSphereCluster{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
 			crdMigratorConfig[&infrav1.VSphereClusterTemplate{}] = crdmigrator.ByObjectConfig{UseCache: false}
-			crdMigratorConfig[&infrav1.VSphereMachine{}] = crdmigrator.ByObjectConfig{UseCache: true}
+			crdMigratorConfig[&infrav1.VSphereMachine{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
 			crdMigratorConfig[&infrav1.VSphereMachineTemplate{}] = crdmigrator.ByObjectConfig{UseCache: true}
-			crdMigratorConfig[&infrav1.VSphereVM{}] = crdmigrator.ByObjectConfig{UseCache: true}
-			crdMigratorConfig[&infrav1.VSphereClusterIdentity{}] = crdmigrator.ByObjectConfig{UseCache: true}
-			crdMigratorConfig[&infrav1.VSphereDeploymentZone{}] = crdmigrator.ByObjectConfig{UseCache: true}
+			crdMigratorConfig[&infrav1.VSphereVM{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
+			crdMigratorConfig[&infrav1.VSphereClusterIdentity{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
+			crdMigratorConfig[&infrav1.VSphereDeploymentZone{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
 			crdMigratorConfig[&infrav1.VSphereFailureDomain{}] = crdmigrator.ByObjectConfig{UseCache: true}
 		}
 		if isSupervisorCRDLoaded {
-			crdMigratorConfig[&vmwarev1.VSphereCluster{}] = crdmigrator.ByObjectConfig{UseCache: true}
+			crdMigratorConfig[&vmwarev1.VSphereCluster{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
 			crdMigratorConfig[&vmwarev1.VSphereClusterTemplate{}] = crdmigrator.ByObjectConfig{UseCache: false}
-			crdMigratorConfig[&vmwarev1.VSphereMachine{}] = crdmigrator.ByObjectConfig{UseCache: true}
-			crdMigratorConfig[&vmwarev1.VSphereMachineTemplate{}] = crdmigrator.ByObjectConfig{UseCache: true}
+			crdMigratorConfig[&vmwarev1.VSphereMachine{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
+			crdMigratorConfig[&vmwarev1.VSphereMachineTemplate{}] = crdmigrator.ByObjectConfig{UseCache: true, UseStatusForStorageVersionMigration: true}
 			crdMigratorConfig[&vmwarev1.ProviderServiceAccount{}] = crdmigrator.ByObjectConfig{UseCache: true}
 		}
 
diff --git a/packaging/go.mod b/packaging/go.mod
index d61318c9..62915382 100644
--- a/packaging/go.mod
+++ b/packaging/go.mod
@@ -2,7 +2,7 @@ module sigs.k8s.io/cluster-api-provider-vsphere/packaging
 
 go 1.23.0
 
-replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358
+replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164
 
 replace sigs.k8s.io/cluster-api-provider-vsphere => ../
 
diff --git a/packaging/go.sum b/packaging/go.sum
index 5d11378f..b5b95f37 100644
--- a/packaging/go.sum
+++ b/packaging/go.sum
@@ -231,8 +231,8 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJ
 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358 h1:prMTNmeGPlCYD5YSc3Oi4NuA2dTA7Bj/5G4TaeeWSNg=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164 h1:Oizd0NIHcrm8ltWqLZCnuFVBt3snHbi9iXYG9iyRJGo=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
 sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
 sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
diff --git a/pkg/context/vspheredeploymentzone_context.go b/pkg/context/vspheredeploymentzone_context.go
index 30ffcac1..3e84cc47 100644
--- a/pkg/context/vspheredeploymentzone_context.go
+++ b/pkg/context/vspheredeploymentzone_context.go
@@ -17,11 +17,8 @@ limitations under the License.
 package context
 
 import (
-	"context"
 	"fmt"
 
-	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
-	"sigs.k8s.io/cluster-api/util/conditions"
 	"sigs.k8s.io/cluster-api/util/patch"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
@@ -36,20 +33,6 @@ type VSphereDeploymentZoneContext struct {
 	AuthSession           *session.Session
 }
 
-// Patch patches the VSphereDeploymentZone.
-func (c *VSphereDeploymentZoneContext) Patch(ctx context.Context) error {
-	conditions.SetSummary(c.VSphereDeploymentZone,
-		conditions.WithConditions(
-			infrav1.VCenterAvailableCondition,
-			infrav1.VSphereFailureDomainValidatedCondition,
-			infrav1.PlacementConstraintMetCondition,
-		),
-	)
-	return c.PatchHelper.Patch(ctx, c.VSphereDeploymentZone, patch.WithOwnedV1Beta2Conditions{Conditions: []string{
-		clusterv1.PausedV1Beta2Condition,
-	}})
-}
-
 // String returns a string with the GroupVersionKind and name of the VSphereDeploymentZone.
 func (c *VSphereDeploymentZoneContext) String() string {
 	return fmt.Sprintf("%s %s", c.VSphereDeploymentZone.GroupVersionKind(), c.VSphereDeploymentZone.Name)



Test patch:
diff --git a/test/e2e/config/vsphere.yaml b/test/e2e/config/vsphere.yaml
index e7dc9ee0..3792b6ae 100644
--- a/test/e2e/config/vsphere.yaml
+++ b/test/e2e/config/vsphere.yaml
@@ -27,7 +27,7 @@ providers:
     type: CoreProvider
     versions:
       - name: "v1.10.99"
-        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/nightly_main_20250316/core-components.yaml"
+        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/release-1.10/core-components.yaml"
         type: "url"
         contract: v1beta1
         files:
@@ -76,7 +76,7 @@ providers:
     type: BootstrapProvider
     versions:
       - name: "v1.10.99"
-        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/nightly_main_20250316/bootstrap-components.yaml"
+        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/release-1.10/bootstrap-components.yaml"
         type: "url"
         contract: v1beta1
         files:
@@ -125,7 +125,7 @@ providers:
     type: ControlPlaneProvider
     versions:
       - name: "v1.10.99"
-        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/nightly_main_20250316/control-plane-components.yaml"
+        value: "https://storage.googleapis.com/k8s-staging-cluster-api/components/release-1.10/control-plane-components.yaml"
         type: "url"
         contract: v1beta1
         files:
diff --git a/test/go.mod b/test/go.mod
index b5dcf1c5..b29749ee 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -2,9 +2,9 @@ module sigs.k8s.io/cluster-api-provider-vsphere/test
 
 go 1.23.0
 
-replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358
+replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164
 
-replace sigs.k8s.io/cluster-api/test => sigs.k8s.io/cluster-api/test v1.9.0-rc.0.0.20250317123349-9e7afa749358
+replace sigs.k8s.io/cluster-api/test => sigs.k8s.io/cluster-api/test v1.10.0-beta.0.0.20250319172205-4f022adb6164
 
 replace sigs.k8s.io/cluster-api-provider-vsphere => ../
 
diff --git a/test/go.sum b/test/go.sum
index fa6010b1..64b976e1 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -511,10 +511,10 @@ k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6J
 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358 h1:prMTNmeGPlCYD5YSc3Oi4NuA2dTA7Bj/5G4TaeeWSNg=
-sigs.k8s.io/cluster-api v1.9.0-rc.0.0.20250317123349-9e7afa749358/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
-sigs.k8s.io/cluster-api/test v1.9.0-rc.0.0.20250317123349-9e7afa749358 h1:cJe9rQ48Hsuv1b+Mue7LtEZa1p9bpkPiM0ZoB70WNSA=
-sigs.k8s.io/cluster-api/test v1.9.0-rc.0.0.20250317123349-9e7afa749358/go.mod h1:EUwHeqQ2jUF+ccC2+nyie7JsP0XMX9KyGzbRhM+h5EU=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164 h1:Oizd0NIHcrm8ltWqLZCnuFVBt3snHbi9iXYG9iyRJGo=
+sigs.k8s.io/cluster-api v1.10.0-beta.0.0.20250319172205-4f022adb6164/go.mod h1:ZTghHCPfdpVC1aAAPKd/6GfQRmbJ3WvbJtHFMkOq0ho=
+sigs.k8s.io/cluster-api/test v1.10.0-beta.0.0.20250319172205-4f022adb6164 h1:bSeyqwxLlVQ56HR4Z5wt1WTAsLieH7KSEfBwmxnengU=
+sigs.k8s.io/cluster-api/test v1.10.0-beta.0.0.20250319172205-4f022adb6164/go.mod h1:EUwHeqQ2jUF+ccC2+nyie7JsP0XMX9KyGzbRhM+h5EU=
 sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
 sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=