Repository: kubernetes-sigs/cluster-api-provider-vsphere
Pull request #3396 (2025-03-17 10:58:12)

🌱 v1beta2: implement VSphereCluster's Ready, FailureDomainsReady, ClusterModulesReady and VCenterAvailable conditions for govmomi
<!--  Thanks for sending a pull request! Please add a icon to the title of this PR (see https://sigs.k8s.io/cluster-api-provider-vsphere/CONTRIBUTING.md#contributing-a-patch), and delete this line and similar ones -->
<!-- the icon will be either ⚠️ (:warning:, major or breaking changes), ✨ (:sparkles:, feature additions), 🐛 (:bug:, patch and bugfixes), 📖 (:book:, documentation or proposals), or 🌱 (:seedling:, minor or other)
Here are some other tips for you:
1. If this is your first time, read our contributor guidelines https://git.k8s.io/community/contributors/guide/pull-requests.md#the-pull-request-submit-process and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
3. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
4. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
5. If this PR changes image versions, please title this PR "Bump <image name> from x.x.x to y.y.y."
-->

**What this PR does / why we need it**:

Implements the following v1beta2 conditions for govmomi mode's VSphereCluster
- Ready
- FailureDomainsReady
- ClusterModulesReady
- VCenterAvailable

Part of #3221

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [chrischdi](https://github.com/chrischdi). For more information see [the Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-vsphere).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["chrischdi"]} -->
chrischdi: /hold

For rebase after

- #3394

merged
k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | aea0988ad05d81d5248f0373955ed40194aee90b | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901588886543405056) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | ed5924afe36180cde88b19e79d05597763187684 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901604840484638720) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | ed5924afe36180cde88b19e79d05597763187684 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901604840484638720) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`
pull-cluster-api-provider-vsphere-verify-main | ed5924afe36180cde88b19e79d05597763187684 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-verify-main/1901604840551747584) | true | `/test pull-cluster-api-provider-vsphere-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-verify-main | ed5924afe36180cde88b19e79d05597763187684 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-verify-main/1901604840551747584) | true | `/test pull-cluster-api-provider-vsphere-verify-main`
pull-cluster-api-provider-vsphere-apidiff-main | dd8d3d613102c9d90ba429b2a78dd7dace70fffd | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901607215832240128) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | dd8d3d613102c9d90ba429b2a78dd7dace70fffd | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901607215832240128) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`
pull-cluster-api-provider-vsphere-verify-main | dd8d3d613102c9d90ba429b2a78dd7dace70fffd | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-verify-main/1901607215895154688) | true | `/test pull-cluster-api-provider-vsphere-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: /hold

Waiting for CI at:

- https://github.com/team-cluster-api/cluster-api-provider-vsphere/pull/70
k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | d21999432c4cefe5aa1979940dedd11b4dc0e108 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901615360512102400) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-apidiff-main | 83337903635f902cc1452a60b5b589b7f71f0ded | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-apidiff-main/1901684355047100416) | false | `/test pull-cluster-api-provider-vsphere-apidiff-main`
pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main | 83337903635f902cc1452a60b5b589b7f71f0ded | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3396/pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main/1901684355298758656) | true | `/test pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3396). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrischdi). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index d714cb5b..c742366a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -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"
diff --git a/apis/v1beta1/vspherecluster_types.go b/apis/v1beta1/vspherecluster_types.go
index ddf7df67..4dccb519 100644
--- a/apis/v1beta1/vspherecluster_types.go
+++ b/apis/v1beta1/vspherecluster_types.go
@@ -28,6 +28,75 @@ 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"
+
+	// VSphereClusterVCenterAvailableDeletingV1Beta2Reason surfaces when the VSphereCluster is being deleted.
+	VSphereClusterVCenterAvailableDeletingV1Beta2Reason = clusterv1.DeletingV1Beta2Reason
+)
+
+// 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
 
@@ -114,7 +183,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
diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml
index ae003f16..97df12fd 100644
--- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml
+++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml
@@ -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.
diff --git a/controllers/clustermodule_reconciler.go b/controllers/clustermodule_reconciler.go
index fa1edcd1..a2bed77d 100644
--- a/controllers/clustermodule_reconciler.go
+++ b/controllers/clustermodule_reconciler.go
@@ -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"
@@ -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
 	}
@@ -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{
+		Type:   infrav1.VSphereClusterClusterModulesReadyV1Beta2Condition,
+		Status: metav1.ConditionTrue,
+		Reason: infrav1.VSphereClusterClusterModulesReadyV1Beta2Reason,
+	})
 	return reconcile.Result{}, err
 }
 
@@ -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()))
diff --git a/controllers/vspherecluster_reconciler.go b/controllers/vspherecluster_reconciler.go
index 07c48f42..bf7418de 100644
--- a/controllers/vspherecluster_reconciler.go
+++ b/controllers/vspherecluster_reconciler.go
@@ -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"
@@ -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})
 		}
 	}()
@@ -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 {
@@ -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
 	}
 
@@ -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
@@ -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
diff --git a/go.mod b/go.mod
index 5abfbee2..b11976ed 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	// The version of vm-operator should be kept in sync with the manifests at: config/deployments/integration-tests
 	github.com/vmware-tanzu/vm-operator/api v1.8.6
 	github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505
-	github.com/vmware/govmomi v0.48.1
+	github.com/vmware/govmomi v0.49.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index d073f213..abb72117 100644
--- a/go.sum
+++ b/go.sum
@@ -245,8 +245,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
 github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
-github.com/vmware/govmomi v0.48.1 h1:aAjmoFzSShYA9ED66JaOJzSBvukvrQLYZljZL+pgfKQ=
-github.com/vmware/govmomi v0.48.1/go.mod h1:UFM2aCkggPToQf8TqY3xfd9bOX58vbVa+UAK1JdDTNM=
+github.com/vmware/govmomi v0.49.0 h1:M80ExmFq3kOfeMvMJcHnXgA/4w5hUAFfYfc+Qm3lmPg=
+github.com/vmware/govmomi v0.49.0/go.mod h1:+oZ0tYJw/pXKoeWHLR9Egq5KENVr2hLePRzisFhEWpA=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
diff --git a/packaging/go.sum b/packaging/go.sum
index e665a102..5d11378f 100644
--- a/packaging/go.sum
+++ b/packaging/go.sum
@@ -134,8 +134,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
 github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
-github.com/vmware/govmomi v0.48.1 h1:aAjmoFzSShYA9ED66JaOJzSBvukvrQLYZljZL+pgfKQ=
-github.com/vmware/govmomi v0.48.1/go.mod h1:UFM2aCkggPToQf8TqY3xfd9bOX58vbVa+UAK1JdDTNM=
+github.com/vmware/govmomi v0.49.0 h1:M80ExmFq3kOfeMvMJcHnXgA/4w5hUAFfYfc+Qm3lmPg=
+github.com/vmware/govmomi v0.49.0/go.mod h1:+oZ0tYJw/pXKoeWHLR9Egq5KENVr2hLePRzisFhEWpA=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
diff --git a/pkg/context/cluster_context.go b/pkg/context/cluster_context.go
index 1166cc95..79d24cb7 100644
--- a/pkg/context/cluster_context.go
+++ b/pkg/context/cluster_context.go
@@ -17,11 +17,9 @@ 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"
@@ -38,17 +36,3 @@ type ClusterContext struct {
 func (c *ClusterContext) String() string {
 	return fmt.Sprintf("%s %s/%s", c.VSphereCluster.GroupVersionKind(), c.VSphereCluster.Namespace, c.VSphereCluster.Name)
 }
-
-// Patch updates the object and its status on the API server.
-func (c *ClusterContext) Patch(ctx context.Context) error {
-	// always update the readyCondition.
-	conditions.SetSummary(c.VSphereCluster,
-		conditions.WithConditions(
-			infrav1.VCenterAvailableCondition,
-		),
-	)
-
-	return c.PatchHelper.Patch(ctx, c.VSphereCluster, patch.WithOwnedV1Beta2Conditions{Conditions: []string{
-		clusterv1.PausedV1Beta2Condition,
-	}})
-}
diff --git a/pkg/session/error.go b/pkg/session/error.go
deleted file mode 100644
index f53fb5b4..00000000
--- a/pkg/session/error.go
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-Copyright 2022 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package session
-
-import (
-	"fmt"
-	"strings"
-)
-
-const (
-	errString = "vCenter version cannot be identified"
-)
-
-// TODO (srm09): figure out a common place for custom errors.
-type unidentifiedVCenterVersion struct {
-	version string
-}
-
-func (e unidentifiedVCenterVersion) Error() string {
-	return fmt.Sprintf("%s: %s", errString, e.version)
-}
-
-// IsUnidentifiedVCenterVersion identies an error as an unidentifiedVCenterVersion error.
-func IsUnidentifiedVCenterVersion(err error) bool {
-	return strings.HasPrefix(err.Error(), errString)
-}
diff --git a/pkg/session/session.go b/pkg/session/session.go
index efc8885d..0f12d696 100644
--- a/pkg/session/session.go
+++ b/pkg/session/session.go
@@ -268,13 +268,13 @@ func (s *Session) GetVersion() (infrav1.VCenterVersion, error) {
 	svcVersion := s.ServiceContent.About.Version
 	version, err := semver.New(svcVersion)
 	if err != nil {
-		return "", err
+		return "", errors.Wrapf(err, "failed to parse version %q", svcVersion)
 	}
 
 	if version.Major >= 6 {
 		return infrav1.NewVCenterVersion(svcVersion), nil
 	}
-	return "", unidentifiedVCenterVersion{version: svcVersion}
+	return "", errors.Errorf("version %q lower than 6", svcVersion)
 }
 
 // Clear is meant to destroy all the cached sessions.


Test patch:
diff --git a/hack/ci-e2e-lib.sh b/hack/ci-e2e-lib.sh
index 28094522..f0f93798 100644
--- a/hack/ci-e2e-lib.sh
+++ b/hack/ci-e2e-lib.sh
@@ -30,16 +30,16 @@ k8s::prepareKindestImagesVariables() {
   # Ensure kind image get's built full e2e tests.
   if [[ "${GINKGO_SKIP:-}" == "\\[Conformance\\] \\[specialized-infra\\]" ]]; then
     if [[ "${GINKGO_FOCUS:-}" == "\\[supervisor\\]" ]] || [[ "${GINKGO_FOCUS:-}" == "" ]]; then
-      KUBERNETES_VERSION_LATEST_CI=$(grep KUBERNETES_VERSION_LATEST_CI: < "$E2E_CONF_FILE" | awk -F'"' '{ print $2}')
-      echo "Defaulting KUBERNETES_VERSION_LATEST_CI to ${KUBERNETES_VERSION_LATEST_CI} to trigger image build (because env var is not set)"
+      KUBERNETES_VERSION_MANAGEMENT_LATEST_CI=$(grep KUBERNETES_VERSION_MANAGEMENT_LATEST_CI: < "$E2E_CONF_FILE" | awk -F'"' '{ print $2}')
+      echo "Defaulting KUBERNETES_VERSION_MANAGEMENT_LATEST_CI to ${KUBERNETES_VERSION_MANAGEMENT_LATEST_CI} to trigger image build (because env var is not set)"
     fi
   fi
 
   # Ensure kind image get's built vcsim e2e tests.
   if [[ -z "${GINKGO_SKIP:-}" ]]; then
     if [[ "${GINKGO_FOCUS:-}" == "\\[vcsim\\]" ]] || [[ "${GINKGO_FOCUS:-}" == "\\[vcsim\\] \\[supervisor\\]" ]]; then
-      KUBERNETES_VERSION_LATEST_CI=$(grep KUBERNETES_VERSION_LATEST_CI: < "$E2E_CONF_FILE" | awk -F'"' '{ print $2}')
-      echo "Defaulting KUBERNETES_VERSION_LATEST_CI to ${KUBERNETES_VERSION_LATEST_CI} to trigger image build (because env var is not set)"
+      KUBERNETES_VERSION_MANAGEMENT_LATEST_CI=$(grep KUBERNETES_VERSION_MANAGEMENT_LATEST_CI: < "$E2E_CONF_FILE" | awk -F'"' '{ print $2}')
+      echo "Defaulting KUBERNETES_VERSION_MANAGEMENT_LATEST_CI to ${KUBERNETES_VERSION_MANAGEMENT_LATEST_CI} to trigger image build (because env var is not set)"
     fi
   fi
 }
@@ -54,9 +54,9 @@ k8s::prepareKindestImages() {
     kind::prepareKindestImage "$resolveVersion"
   fi
 
-  if [ -n "${KUBERNETES_VERSION_LATEST_CI:-}" ]; then
-    k8s::resolveVersion "KUBERNETES_VERSION_LATEST_CI" "$KUBERNETES_VERSION_LATEST_CI"
-    export KUBERNETES_VERSION_LATEST_CI=$resolveVersion
+  if [ -n "${KUBERNETES_VERSION_MANAGEMENT_LATEST_CI:-}" ]; then
+    k8s::resolveVersion "KUBERNETES_VERSION_MANAGEMENT_LATEST_CI" "$KUBERNETES_VERSION_MANAGEMENT_LATEST_CI"
+    export KUBERNETES_VERSION_MANAGEMENT_LATEST_CI=$resolveVersion
 
     kind::prepareKindestImage "$resolveVersion"
   fi
@@ -197,10 +197,14 @@ EOL
 kind::prepullImage () {
   local image=$1
   image="${image//+/_}"
+  mirrored_image="gcr.io/k8s-staging-capi-vsphere/extra/${image}"
 
   retVal=0
   if [[ "$(docker images -q "$image" 2> /dev/null)" == "" ]]; then
-    echo "+ Pulling $image"
+    echo "+ Trying to pull $image from mirror first"
+    docker pull "$mirrored_image" && docker tag "$mirrored_image" "$image" && return
+
+    echo "+ Falling pack to pull from original registry $image"
     docker pull "$image" || retVal=$?
   else
     echo "+ image $image already present in the system, skipping pre-pull"
diff --git a/pkg/services/govmomi/create_test.go b/pkg/services/govmomi/create_test.go
index 8cfbd005..aff6c333 100644
--- a/pkg/services/govmomi/create_test.go
+++ b/pkg/services/govmomi/create_test.go
@@ -43,7 +43,7 @@ func TestCreate(t *testing.T) {
 		t.Fatalf("unable to create simulator: %s", err)
 	}
 	defer simr.Destroy()
-	vmRef := simulator.Map.Any("VirtualMachine")
+	vmRef := model.Map().Any("VirtualMachine")
 	vm, ok := vmRef.(*simulator.VirtualMachine)
 	if !ok {
 		t.Fatal("failed to get reference to an existing VM on the vcsim instance")
diff --git a/pkg/services/govmomi/vcenter/clone_test.go b/pkg/services/govmomi/vcenter/clone_test.go
index de34a35b..b8cad6f3 100644
--- a/pkg/services/govmomi/vcenter/clone_test.go
+++ b/pkg/services/govmomi/vcenter/clone_test.go
@@ -39,7 +39,7 @@ func TestGetDiskSpec(t *testing.T) {
 	model, session, server := initSimulator(t)
 	t.Cleanup(model.Remove)
 	t.Cleanup(server.Close)
-	vm := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
+	vm := model.Map().Any("VirtualMachine").(*simulator.VirtualMachine)
 	machine := object.NewVirtualMachine(session.Client.Client, vm.Reference())
 
 	devices, err := machine.Device(ctx.TODO())
@@ -157,7 +157,7 @@ func TestCreateDataDisks(t *testing.T) {
 	model, session, server := initSimulator(t)
 	t.Cleanup(model.Remove)
 	t.Cleanup(server.Close)
-	vm := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine)
+	vm := model.Map().Any("VirtualMachine").(*simulator.VirtualMachine)
 	machine := object.NewVirtualMachine(session.Client.Client, vm.Reference())
 
 	deviceList, err := machine.Device(ctx.TODO())
diff --git a/test/e2e/clusterctl_upgrade_test.go b/test/e2e/clusterctl_upgrade_test.go
index 51c2f5b2..a6586dc1 100644
--- a/test/e2e/clusterctl_upgrade_test.go
+++ b/test/e2e/clusterctl_upgrade_test.go
@@ -54,7 +54,7 @@ var _ = Describe("When testing clusterctl upgrades using ClusterClass (CAPV 1.12
 			capvVersion := "1.12"
 			capvStableRelease, err := getStableReleaseOfMinor(ctx, capvReleaseMarkerPrefix, capvVersion)
 			Expect(err).ToNot(HaveOccurred(), "Failed to get stable version for minor release : %s", capvVersion)
-			initKubernetesVersion, err := kubernetesversions.ResolveVersion(ctx, e2eConfig.MustGetVariable("KUBERNETES_VERSION_LATEST_CI"))
+			initKubernetesVersion, err := kubernetesversions.ResolveVersion(ctx, e2eConfig.MustGetVariable("KUBERNETES_VERSION_MANAGEMENT_LATEST_CI"))
 			Expect(err).ToNot(HaveOccurred())
 			return capi_e2e.ClusterctlUpgradeSpecInput{
 				E2EConfig:                         e2eConfig,
diff --git a/test/e2e/config/vsphere.yaml b/test/e2e/config/vsphere.yaml
index 3878efff..e7dc9ee0 100644
--- a/test/e2e/config/vsphere.yaml
+++ b/test/e2e/config/vsphere.yaml
@@ -304,6 +304,8 @@ providers:
 variables:
   # Ensure all Kubernetes versions used here are covered in patch-vsphere-template.yaml
   KUBERNETES_VERSION_MANAGEMENT: "v1.33.0-beta.0"
+  # Note: currently not ci/latest-1.33 because of rate-limiting issues.
+  KUBERNETES_VERSION_MANAGEMENT_LATEST_CI: "v1.33.0-beta.0"
   KUBERNETES_VERSION: "v1.33.0-beta.0"
   KUBERNETES_VERSION_UPGRADE_FROM: "v1.32.0"
   KUBERNETES_VERSION_UPGRADE_TO: "v1.33.0-beta.0"
diff --git a/test/go.mod b/test/go.mod
index a04a2a64..b5dcf1c5 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/vmware-tanzu/net-operator-api v0.0.0-20240326163340-1f32d6bf7f9d
 	// The version of vm-operator should be kept in sync with the manifests at: config/deployments/integration-tests
 	github.com/vmware-tanzu/vm-operator/api v1.8.6
-	github.com/vmware/govmomi v0.48.1
+	github.com/vmware/govmomi v0.49.0
 )
 
 require (
diff --git a/test/go.sum b/test/go.sum
index 6b235022..fa6010b1 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -329,8 +329,8 @@ github.com/vmware-tanzu/vm-operator/api v1.8.6 h1:NIndORjcnSmIlQsCMIewpIwg/ocRVD
 github.com/vmware-tanzu/vm-operator/api v1.8.6/go.mod h1:HHA2SNI9B5Yqtyp5t+Gt9WTWBi/fIkM6+MukDDSf11A=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505 h1:y4wXx1FUFqqSgJ/xUOEM1DLS2Uu0KaeLADWpzpioGTU=
 github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505/go.mod h1:5rqRJ9zGR+KnKbkGx373WgN8xJpvAj99kHnfoDYRO5I=
-github.com/vmware/govmomi v0.48.1 h1:aAjmoFzSShYA9ED66JaOJzSBvukvrQLYZljZL+pgfKQ=
-github.com/vmware/govmomi v0.48.1/go.mod h1:UFM2aCkggPToQf8TqY3xfd9bOX58vbVa+UAK1JdDTNM=
+github.com/vmware/govmomi v0.49.0 h1:M80ExmFq3kOfeMvMJcHnXgA/4w5hUAFfYfc+Qm3lmPg=
+github.com/vmware/govmomi v0.49.0/go.mod h1:+oZ0tYJw/pXKoeWHLR9Egq5KENVr2hLePRzisFhEWpA=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=