Skip to content

OCPBUGS-91668: vSphere machinepool: preserve old-style RHCOS template for upgraded clusters - #2921

Open
chdeshpa-hue wants to merge 1 commit into
openshift:masterfrom
chdeshpa-hue:chdeshpa-hue/vsphere-template-backfill
Open

OCPBUGS-91668: vSphere machinepool: preserve old-style RHCOS template for upgraded clusters#2921
chdeshpa-hue wants to merge 1 commit into
openshift:masterfrom
chdeshpa-hue:chdeshpa-hue/vsphere-template-backfill

Conversation

@chdeshpa-hue

@chdeshpa-hue chdeshpa-hue commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Problem

After upgrading to MCE 2.9+ (ACM 2.14+ / OCP 4.19+), new Hive MachinePool objects for existing vSphere clusters generate MachineSets that reference an RHCOS template that does not exist in vCenter.

Root cause chain:

  1. Installer commit (Dec 2024): Changed vSphere MachineSets() to call GenerateVSphereTemplateName(clusterID, failureDomain.Name) — template name format changed from {infraID}-rhcos-{region}-{zone} to {infraID}-rhcos-{fdName}.

  2. HIVE-2391 (PR HIVE-2391: vsphere zonal #2851, Feb 2026): Rewrote VSphereActuator to use Infrastructure.DeepCopy(), removing the explicit osImage / Topology.Template backfill. FailureDomain.Topology.Template is now empty by default.

  3. Combined effect: On clusters installed before Dec 2024, the template {infraID}-rhcos-{region}-{zone} exists in vCenter but Hive now requests {infraID}-rhcos-{fdName} — which doesn't exist → machine provisioning fails.

Workaround (customer): Avoid creating new MachinePools; only scale existing MachinePools. Or manually set Topology.Template in the ClusterDeployment.

Fix

In GenerateMachineSets, after building the InstallConfig from Infrastructure.DeepCopy(), check each failure domain with an empty Topology.Template. Construct the old-style template name {infraID}-rhcos-{region}-{zone} and scan existing remote MachineSets for a reference to it. If found, set Topology.Template to the old-style name, letting the installer use it directly via the existing if failureDomain.Topology.Template == "" escape hatch.

The fix is strictly additive and conservative:

Before/After

Before (MCE 2.9 on a pre-2024 cluster):

$ oc get machineset -n openshift-machine-api
NAME                                                    DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-failure-domain-1             1        1        0      0          5m

$ oc describe machine {infraID}-worker-xxx
...
Message: "template '{infraID}-rhcos-generated-failure-domain' not found in datacenter"

After (with fix):

$ oc get machineset -n openshift-machine-api
NAME                                                              DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-region-generated-zone-1               1        1        1      1          5m

Generated MachineSet references {infraID}-rhcos-generated-region-generated-zone — the existing template.

Test Evidence

$ go test ./pkg/controller/machinepool/... -run TestVSphere -v -count=1

=== RUN   TestVSphereActuator
=== RUN   TestVSphereActuator/deprecated_vsphere_fields
=== RUN   TestVSphereActuator/generate_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets
--- PASS: TestVSphereActuator (0.00s)
    --- PASS: TestVSphereActuator/deprecated_vsphere_fields (0.00s)
    --- PASS: TestVSphereActuator/generate_machineset (0.00s)
    --- PASS: TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset (0.00s)
    --- PASS: TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets (0.00s)
PASS
ok      github.com/openshift/hive/pkg/controller/machinepool    2.195s

Go version: go1.25.9 darwin/arm64
gofmt: clean
go vet: clean

Files Changed

  • pkg/controller/machinepool/vsphereactuator.go — core fix (backfill logic + helpers)
  • pkg/controller/machinepool/machinepool_controller.go — pass remoteMachineSets to constructor
  • pkg/controller/machinepool/vsphereactuator_test.go — 2 new regression tests + template assertion

Note on PR #2884 (HIVE-3134)

This PR is open and removes the machine-api-operator dependency from vsphereactuator.go. Our fix does NOT re-add that dependency — it inlines the same providerSpecFromRawExtension() function body using encoding/json. When HIVE-3134 merges, there will be no conflict.

Made with Cursor

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved vSphere machine set generation by backfilling missing failure-domain Topology.Template values from compatible remote machine sets, while preserving any user-supplied templates.
    • Updated vSphere cluster deployment update validation to allow empty-to-non-empty template transitions and block other immutable template changes.
  • Tests

    • Added/extended unit and webhook tests covering template backfill, matching/override behavior, and allowed vs. blocked update scenarios.
  • Chores

    • Cleaned up build and vendored dependencies.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jun 23, 2026
@openshift-ci-robot

openshift-ci-robot commented Jun 23, 2026

Copy link
Copy Markdown

@chdeshpa-hue: This pull request references HIVE-2846 which is a valid jira issue.

Details

In response to this:

Problem

After upgrading to MCE 2.9+ (ACM 2.14+ / OCP 4.19+), new Hive MachinePool objects for existing vSphere clusters generate MachineSets that reference an RHCOS template that does not exist in vCenter.

Root cause chain:

  1. Installer commit (Dec 2024): Changed vSphere MachineSets() to call GenerateVSphereTemplateName(clusterID, failureDomain.Name) — template name format changed from {infraID}-rhcos-{region}-{zone} to {infraID}-rhcos-{fdName}.

  2. HIVE-2391 (PR HIVE-2391: vsphere zonal #2851, Feb 2026): Rewrote VSphereActuator to use Infrastructure.DeepCopy(), removing the explicit osImage / Topology.Template backfill. FailureDomain.Topology.Template is now empty by default.

  3. Combined effect: On clusters installed before Dec 2024, the template {infraID}-rhcos-{region}-{zone} exists in vCenter but Hive now requests {infraID}-rhcos-{fdName} — which doesn't exist → machine provisioning fails.

Workaround (customer): Avoid creating new MachinePools; only scale existing MachinePools. Or manually set Topology.Template in the ClusterDeployment.

Fix

In GenerateMachineSets, after building the InstallConfig from Infrastructure.DeepCopy(), check each failure domain with an empty Topology.Template. Construct the old-style template name {infraID}-rhcos-{region}-{zone} and scan existing remote MachineSets for a reference to it. If found, set Topology.Template to the old-style name, letting the installer use it directly via the existing if failureDomain.Topology.Template == "" escape hatch.

The fix is strictly additive and conservative:

Before/After

Before (MCE 2.9 on a pre-2024 cluster):

$ oc get machineset -n openshift-machine-api
NAME                                                    DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-failure-domain-1             1        1        0      0          5m

$ oc describe machine {infraID}-worker-xxx
...
Message: "template '{infraID}-rhcos-generated-failure-domain' not found in datacenter"

After (with fix):

$ oc get machineset -n openshift-machine-api
NAME                                                              DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-region-generated-zone-1               1        1        1      1          5m

Generated MachineSet references {infraID}-rhcos-generated-region-generated-zone — the existing template.

Test Evidence

$ go test ./pkg/controller/machinepool/... -run TestVSphere -v -count=1

=== RUN   TestVSphereActuator
=== RUN   TestVSphereActuator/deprecated_vsphere_fields
=== RUN   TestVSphereActuator/generate_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets
--- PASS: TestVSphereActuator (0.00s)
   --- PASS: TestVSphereActuator/deprecated_vsphere_fields (0.00s)
   --- PASS: TestVSphereActuator/generate_machineset (0.00s)
   --- PASS: TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset (0.00s)
   --- PASS: TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets (0.00s)
PASS
ok      github.com/openshift/hive/pkg/controller/machinepool    2.195s

Go version: go1.25.9 darwin/arm64
gofmt: clean
go vet: clean

Files Changed

  • pkg/controller/machinepool/vsphereactuator.go — core fix (backfill logic + helpers)
  • pkg/controller/machinepool/machinepool_controller.go — pass remoteMachineSets to constructor
  • pkg/controller/machinepool/vsphereactuator_test.go — 2 new regression tests + template assertion

Note on PR #2884 (HIVE-3134)

This PR is open and removes the machine-api-operator dependency from vsphereactuator.go. Our fix does NOT re-add that dependency — it inlines the same providerSpecFromRawExtension() function body using encoding/json. When HIVE-3134 merges, there will be no conflict.

Made with Cursor

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

vSphere failure-domain templates are backfilled from remote MachineSets and exempted from immutable-field checks. Vendored machine-api-operator, gcfg, warnings, and cloud-provider-vsphere metadata was pruned from the module and vendor listings.

Changes

vSphere template flow

Layer / File(s) Summary
Controller wiring and actuator setup
pkg/controller/machinepool/machinepool_controller.go, pkg/controller/machinepool/vsphereactuator_test.go
The controller now passes vSphere failure domains and infra ID into the actuator, and the test setup mirrors the new constructor inputs.
Template backfill logic
pkg/controller/machinepool/vsphereactuator.go, pkg/controller/machinepool/vsphereactuator_test.go
The actuator precomputes failure-domain template mappings, fills empty topology templates during machine-set generation, and the tests cover matching, mismatch, and encoding paths.
Template validation updates
pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go, pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook_test.go
The update validator copies populated failure-domain templates into the old object before immutable-field comparison, and the test matrix adds template transition cases.

Dependency pruning

Layer / File(s) Summary
Module metadata updates
go.mod, vendor/modules.txt
Indirect requirements and vendor package listings are updated to remove stale entries for gcfg, warnings, and cloud-provider-vsphere.
License text removal
vendor/gopkg.in/gcfg.v1/LICENSE
The vendored gcfg license text is removed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, specific, and matches the main change: preserving legacy RHCOS templates for upgraded vSphere clusters.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@openshift-ci
openshift-ci Bot requested review from dlom and jstuever June 23, 2026 11:20
@openshift-ci

openshift-ci Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: chdeshpa-hue
Once this PR has been reviewed and has the lgtm label, please assign suhanime for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.17910% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.42%. Comparing base (2ea50dc) to head (29791fd).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
pkg/controller/machinepool/vsphereactuator.go 71.15% 10 Missing and 5 partials ⚠️
...g/controller/machinepool/machinepool_controller.go 0.00% 7 Missing ⚠️
.../v1/clusterdeployment_validating_admission_hook.go 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2921      +/-   ##
==========================================
+ Coverage   50.39%   50.42%   +0.02%     
==========================================
  Files         281      281              
  Lines       34367    34430      +63     
==========================================
+ Hits        17320    17360      +40     
- Misses      15695    15712      +17     
- Partials     1352     1358       +6     
Files with missing lines Coverage Δ
.../v1/clusterdeployment_validating_admission_hook.go 86.14% <75.00%> (-0.16%) ⬇️
...g/controller/machinepool/machinepool_controller.go 63.90% <0.00%> (-0.44%) ⬇️
pkg/controller/machinepool/vsphereactuator.go 72.61% <71.15%> (-4.53%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@2uasimojo

Copy link
Copy Markdown
Member

Thanks for working on this, @chdeshpa-hue

Somehow your commit/PR got attached to an unrelated jira card. Would you please update both to reference https://redhat.atlassian.net/browse/OCPBUGS-91668 ?

I don't love the idea of essentially porting code from an ancient version of installer to generate a very specific template format. There are several potential problems with this, including that we still need an answer for the case where that format isn't valid. While I appreciate that this only triggers if we find that template on an existing MachineSet, I'm not convinced the additional code maintenance burden is worth it. For instance, what if the matching MachineSet is itself bogus for that reason? And sure, we can add yet more code to check on that... but you see what I'm getting at: we're throwing increasingly messy code at this issue.

In other actuators -- and in this one prior to the big zonal changes -- we try to copy the template from an existing master machine. However, that probably doesn't work here since the FD name is part of that template: we would need to find a master in the right FD, and I'm not sure that's always possible.

I'm thinking the least-worst approach here may be to make it easier for the user to set the Template in the ClusterDeployment. We have precedent for easing our webhooks to allow setting certain values if previously unset, but still disallow edits if already set. That, plus some documentation (hive repo docs; a KCS article; something else that can easily be found by customers/TAMs) describing the issue and the remedy, would be clean and crisp, if not the ultimate ideal UX.

WDYT?

@chdeshpa-hue chdeshpa-hue changed the title HIVE-2846: vSphere machinepool: preserve old-style RHCOS template for upgraded clusters OCPBUGS-91668: vSphere machinepool: preserve old-style RHCOS template for upgraded clusters Jun 24, 2026
@openshift-ci-robot openshift-ci-robot added the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label Jun 24, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@chdeshpa-hue: This pull request references Jira Issue OCPBUGS-91668, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Problem

After upgrading to MCE 2.9+ (ACM 2.14+ / OCP 4.19+), new Hive MachinePool objects for existing vSphere clusters generate MachineSets that reference an RHCOS template that does not exist in vCenter.

Root cause chain:

  1. Installer commit (Dec 2024): Changed vSphere MachineSets() to call GenerateVSphereTemplateName(clusterID, failureDomain.Name) — template name format changed from {infraID}-rhcos-{region}-{zone} to {infraID}-rhcos-{fdName}.

  2. HIVE-2391 (PR HIVE-2391: vsphere zonal #2851, Feb 2026): Rewrote VSphereActuator to use Infrastructure.DeepCopy(), removing the explicit osImage / Topology.Template backfill. FailureDomain.Topology.Template is now empty by default.

  3. Combined effect: On clusters installed before Dec 2024, the template {infraID}-rhcos-{region}-{zone} exists in vCenter but Hive now requests {infraID}-rhcos-{fdName} — which doesn't exist → machine provisioning fails.

Workaround (customer): Avoid creating new MachinePools; only scale existing MachinePools. Or manually set Topology.Template in the ClusterDeployment.

Fix

In GenerateMachineSets, after building the InstallConfig from Infrastructure.DeepCopy(), check each failure domain with an empty Topology.Template. Construct the old-style template name {infraID}-rhcos-{region}-{zone} and scan existing remote MachineSets for a reference to it. If found, set Topology.Template to the old-style name, letting the installer use it directly via the existing if failureDomain.Topology.Template == "" escape hatch.

The fix is strictly additive and conservative:

Before/After

Before (MCE 2.9 on a pre-2024 cluster):

$ oc get machineset -n openshift-machine-api
NAME                                                    DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-failure-domain-1             1        1        0      0          5m

$ oc describe machine {infraID}-worker-xxx
...
Message: "template '{infraID}-rhcos-generated-failure-domain' not found in datacenter"

After (with fix):

$ oc get machineset -n openshift-machine-api
NAME                                                              DESIRED  CURRENT  READY  AVAILABLE  AGE
{infraID}-worker-generated-region-generated-zone-1               1        1        1      1          5m

Generated MachineSet references {infraID}-rhcos-generated-region-generated-zone — the existing template.

Test Evidence

$ go test ./pkg/controller/machinepool/... -run TestVSphere -v -count=1

=== RUN   TestVSphereActuator
=== RUN   TestVSphereActuator/deprecated_vsphere_fields
=== RUN   TestVSphereActuator/generate_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset
=== RUN   TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets
--- PASS: TestVSphereActuator (0.00s)
   --- PASS: TestVSphereActuator/deprecated_vsphere_fields (0.00s)
   --- PASS: TestVSphereActuator/generate_machineset (0.00s)
   --- PASS: TestVSphereActuator/new_machinepool_uses_old-style_template_from_existing_machineset (0.00s)
   --- PASS: TestVSphereActuator/new_machinepool_uses_generated_template_when_no_existing_machinesets (0.00s)
PASS
ok      github.com/openshift/hive/pkg/controller/machinepool    2.195s

Go version: go1.25.9 darwin/arm64
gofmt: clean
go vet: clean

Files Changed

  • pkg/controller/machinepool/vsphereactuator.go — core fix (backfill logic + helpers)
  • pkg/controller/machinepool/machinepool_controller.go — pass remoteMachineSets to constructor
  • pkg/controller/machinepool/vsphereactuator_test.go — 2 new regression tests + template assertion

Note on PR #2884 (HIVE-3134)

This PR is open and removes the machine-api-operator dependency from vsphereactuator.go. Our fix does NOT re-add that dependency — it inlines the same providerSpecFromRawExtension() function body using encoding/json. When HIVE-3134 merges, there will be no conflict.

Made with Cursor

Summary by CodeRabbit

Release Notes

  • Bug Fixes

  • Improved VSphere machine template handling to maintain compatibility with existing remote MachineSets by preserving legacy template references during cluster operations.

  • Tests

  • Added test coverage for VSphere template compatibility scenarios with existing remote MachineSets.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed feedback @2uasimojo — appreciate you taking the time.

Acknowledging your concern

You're right that the current implementation ports installer-era naming logic ({infraID}-rhcos-{region}-{zone}) into Hive, and that's a coupling we shouldn't carry. If the installer changes naming conventions again, this becomes a maintenance point. I agree that's not the direction we want.

The workaround we've been providing

We've already been advising customers on exactly the manual approach you're describing. In OCPBUGS-91668, the documented workaround is:

Manually set spec.platform.vsphere.infrastructure.failureDomains[].topology.template in the ClusterDeployment to your existing template name (e.g., {clusterID}-rhcos-generated-region-generated-zone) before creating the MachinePool.

spec:
  platform:
    vsphere:
      infrastructure:
        failureDomains:
          - topology:
              template: "{clusterID}-rhcos-generated-region-generated-zone"
            # ... rest of failure domain config

This is safe on all MCE versions and prevents the issue regardless of Hive version. We would also prepare a KCS article documenting this workaround and the upgrade impact so it's discoverable through the support knowledge base.

Why manual workaround alone isn't sufficient

That said, with the growing adoption of RHACM/Hive for vSphere ClusterDeployment management, relying solely on a manual workaround has practical limitations:

  1. Discovery burden — the customer must know to look for this before MachinePool creation fails. Nothing in the upgrade path signals that Topology.Template needs attention.
  2. Template name lookup — the customer needs vCenter access or must run oc get machineset -o json on the managed cluster and parse the provider spec to find the correct template name.
  3. Per-failure-domain — each failure domain needs the template set individually. Multi-FD deployments multiply the manual effort.
  4. Reactive, not preventive — customers discover the problem only after machines fail to provision. By that point, they're already in a support case.

Two active SFDC cases are linked to this bug, which suggests the workaround documentation alone isn't catching the affected population in time.

Revised code proposal: "Read, Don't Reconstruct"

Rather than constructing any template name format, the simplified approach reads the template directly from existing remote MachineSet provider specs:

for each failureDomain where Topology.Template == "":
    for each remoteMachineSet:
        template := extractTemplateFromProviderSpec(remoteMachineSet)
        if template != "":
            failureDomain.Topology.Template = template
            break

What this changes:

  • No installer code ported — we never construct a template name, just read what's already running in the cluster
  • Format-agnostic — works regardless of naming convention (old or new)
  • ~15 lines of backfill code instead of ~40
  • Same safety — only activates when Topology.Template is empty AND remote MachineSets exist with a template set

Proposed path forward (two small PRs)

PR Scope Purpose
This PR (simplified) Auto-backfill Topology.Template from remote MachineSet provider specs Preventive fix — catches it silently using the same data source the customer would query manually
Companion PR (new) Relax webhook to allow setting Topology.Template when previously unset Per your suggestion — gives customers a manual escape hatch alongside the auto-fix

Both together give us defense in depth: the code catches it automatically for new MachinePool creates, and the webhook relaxation lets customers (or support) manually override if needed. The KCS article would document both paths.

Happy to push the simplified diff and discuss further.

@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

Follow-up — tested locally:

While validating the manual workaround path, I found that Topology.Template is immutable on existing ClusterDeployments. Hive's admission webhook rejects the change:

Attempted to change ClusterDeployment.Spec which is immutable
Unsupported change:
Platform.VSphere.Infrastructure.FailureDomains.Topology.Template

This applies whether the value is a bare template name or a full vSphere inventory path.

This means for the affected population (existing clusters installed pre-Dec 2024, now on MCE 2.9+), there is currently no manual path to fix this — the ClusterDeployment already exists with an empty Topology.Template, and it cannot be patched.

The auto-backfill code fix is unaffected (it operates on a DeepCopy() during MachineSet generation, not a CD mutation), making it the only viable fix path for existing clusters today. The webhook relaxation companion PR moves from "nice-to-have" to "required for any manual workaround to work."

@2uasimojo

Copy link
Copy Markdown
Member

Okay, I still don't love this, but I'll concede it's a lesser evil than forcing consumers to notice, discover, and remedy by hand. To summarize:

  • Cycle through remote msets looking for FD-specific Template values to populate into the in-memory install config structure we're passing to MachineSets(). I suppose we just take the first value we see for a given FD, and stop when all the FDs requested for the given pool are populated?
  • Relax the webhook, so customers can preemptively remedy the issue by hand without having to go through the awkwardness of pausing reconciliation and deleting the VWHC.
  • Write a KCS article describing this failure mode, what our code is trying to do about it, how that code can fail (e.g. we don't find sufficient info in existing remote msets -- is it also possible for more than one template to exist in a given FD and we choose the wrong one??), and how to remedy it manually (by setting the values in the CD).

Thanks.

@chdeshpa-hue
chdeshpa-hue force-pushed the chdeshpa-hue/vsphere-template-backfill branch from da94488 to e647cb0 Compare June 28, 2026 10:52
@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

Thanks @2uasimojo — confirmed on all three points. The force-push (e647cb0) addresses each:

1. FD-specific template matching:

Correct — we cycle through remote MachineSets, decode each provider spec, and match by datacenter (same datacenter as the failure domain) + infraID prefix (template name starts with {infraID}-). First match for a given FD wins, and we stop looking for that FD once populated. If all FDs in the pool get populated, we stop entirely.

On "more than one template in a given FD": In practice, a single FD has one RHCOS template per cluster (the installer creates exactly one per failure domain at install time). However, if somehow two different templates existed for the same FD (e.g., a leftover from a failed install attempt), we'd pick the first one found in the remote MachineSet list. Since all existing MachineSets in a healthy cluster reference the same template for a given FD, this isn't a realistic failure mode — but the KCS will document it as an edge case with the manual override as the remedy.

2. Webhook relaxation:

Included in this commit. Topology.Template can now be set if previously empty (unset → set is allowed; set → changed is still rejected). This gives customers/TAMs the manual path without needing to pause reconciliation.

3. KCS article:

In progress. Will cover: the failure mode (upgrade path that leaves Topology.Template empty), what the auto-backfill code does, when it can't help (no remote MachineSets exist, or template can't be determined), and the manual remedy via the now-relaxed webhook.

Also — Jira target version is being set to 5.0.0 so /jira refresh should clear the invalid-bug label once that's done.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/controller/machinepool/vsphereactuator.go`:
- Around line 133-139: The template ownership check in the vSphere actuator is
too loose because `strings.HasPrefix(templateBaseName(spec.Template), infraID)`
can match another cluster with a longer infraID sharing the same prefix. Update
the check in the template selection logic to anchor on the installer template
naming pattern by matching `infraID + "-"` (or equivalent separator-based
prefix) before returning `spec.Template`, so only templates created for the
exact cluster are accepted.

In
`@pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go`:
- Around line 896-897: The current
`cmpopts.IgnoreFields(installervsphere.Topology{}, "Template")` in the cluster
deployment validating hook is too broad because it hides all `Template` changes.
Update the immutability check in
`clusterdeployment_validating_admission_hook.go` to use conditional logic around
`Topology.Template`, so only an unset-to-set transition is allowed and any
change when the old value is already non-empty is still rejected. Prefer a
custom `cmp.FilterValues`/comparer or an explicit pre-check near the existing
`IgnoreFields` setup that preserves the diff unless `oldValue == ""`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d5eb6075-48b1-4a32-bb7f-51fda7d501a0

📥 Commits

Reviewing files that changed from the base of the PR and between da94488 and e647cb0.

📒 Files selected for processing (4)
  • pkg/controller/machinepool/machinepool_controller.go
  • pkg/controller/machinepool/vsphereactuator.go
  • pkg/controller/machinepool/vsphereactuator_test.go
  • pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/controller/machinepool/machinepool_controller.go

Comment thread pkg/controller/machinepool/vsphereactuator.go Outdated
Comment thread pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go Outdated
Comment on lines +133 to +138
// Verify the template belongs to this cluster. Installer-generated templates are
// always prefixed with the cluster's infraID. This prevents picking up templates
// from other clusters sharing the same vCenter datacenter.
if !strings.HasPrefix(templateBaseName(spec.Template), infraID) {
continue
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think it's possible to get a different infra ID's Template because the source of the data is a list of remote MachineSets already retrieved from the target cluster. So I don't think we need this check (and coderabbit's comment below becomes n/a).

Furthermore, it is at least theoretically possible for the user to have built those MachineSets with a specified Template that doesn't have the infra ID in it at all, in which scenario this would have caused us not to grab it, resulting in the original failure mode.

@coderabbitai please confirm.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@2uasimojo Confirmed — your reasoning is sound, and my earlier concern should be withdrawn.

Because a.remoteMachineSets are already sourced from the target cluster, the infraID prefix guard here does not add meaningful protection against selecting another cluster’s template. And as you noted, it can incorrectly reject a valid user-specified spec.Template that does not include the infra ID, which would preserve the original failure mode.

So yes: in this context, the strings.HasPrefix(templateBaseName(spec.Template), infraID) check looks unnecessary and potentially harmful.

(ᵔᴥᵔ)


✏️ Learnings added
Learnt from: 2uasimojo
Repo: openshift/hive PR: 2921
File: pkg/controller/machinepool/vsphereactuator.go:133-138
Timestamp: 2026-06-29T16:36:56.894Z
Learning: In `openshift/hive` PR `#2921`, for `pkg/controller/machinepool/vsphereactuator.go`, the `VSphereActuator.remoteMachineSets` input is already retrieved from the target cluster by the machinepool controller. When backfilling `FailureDomain.Topology.Template` from those remote MachineSets, do not assume the template name must be prefixed with the cluster infra ID; user-specified vSphere template names may legitimately omit the infra ID, so such a prefix check can block valid backfill and preserve the original provisioning failure.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: 2uasimojo
Repo: openshift/hive PR: 2915
File: hack/modcheck.go:74-74
Timestamp: 2026-06-01T17:43:41.782Z
Learning: When reviewing Go code that calls `os.WriteFile(name, data, perm)`: note that the `perm` argument is only used if the file is created (i.e., when the underlying open uses an `O_CREATE`-style path). If `name` already exists, the existing file permissions are preserved by `open(2)` (on Linux), so `perm` will not change the mode. Therefore: (1) don’t rely on `perm` to update permissions for an existing file; (2) if the code passes a harmless `perm` value (e.g., `0`), verify via surrounding logic/guarantees that the file is already present—otherwise this can lead to incorrect permissions on initial creation.

Comment on lines +130 to +132
if spec.Workspace.Datacenter != fd.Topology.Datacenter {
continue
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Need to phone a friend here. @jcpowermac is Datacenter always sufficient to disambiguate two FDs? I thought we could have e.g. same Datacenter, different Datastores? Folders? Other?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nope, vm-host zonal will always be the same datacenter. prior-4.22 zonal could be the same datacenter but isn't required.

really it should be the fd, region,zone names, those shouldn't be duplicated

Comment thread pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go Outdated
@chdeshpa-hue
chdeshpa-hue force-pushed the chdeshpa-hue/vsphere-template-backfill branch from e647cb0 to d7d1911 Compare July 1, 2026 11:21
@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

v2: Addressing review feedback

1. Removed infraID prefix check (per @2uasimojo)
Remote MachineSets are already fetched from the target cluster — no cross-cluster contamination. Removed strings.HasPrefix check that would reject user-specified templates.

2. Webhook: spoof pattern for Template (per @2uasimojo)
Replaced cmpopts.IgnoreFields(Topology{}, "Template") with the established spoof pattern: if old Template was blank, copy new value into oldObject before immutability diff. Same convention as vSphere zonal conversion (line 704). Added 3 unit tests (blank-to-set allowed, set-to-different rejected, set-to-same allowed).

3. Architecture: backfill as preprocessing (per Eric's suggestion)
Moved template backfill out of the actuator struct. A standalone backfillVSphereTemplates() function enriches the in-memory install config in the controller before actuator creation. The actuator stays stateless about remote cluster state (aligned with GCP's scalar-extraction pattern and Nutanix/OpenStack actuators).

4. MCO-style 5-field FD matching (per @jcpowermac)
Replaced datacenter-only matching with the same 5-field conjunction MCO uses (PR #5745, OCPBUGS-77883): Datacenter + Datastore + Server + VMGroup + ResourcePool. This handles all three topologies:

Topology Key disambiguator
Non-zonal Datacenter + Datastore (single FD)
Compute-cluster zonal ResourcePool (unique per CC)
Host-group zonal VMGroup = {infraID}-{fdName}

The fd name does not appear in MachineSet metadata (name is {infraID}-{pool}-{index}, no fd labels). Workspace fields are the only stable mapping.

5. Known limitation: pre-corrupted state
If a prior Hive version already synced the wrong template to the remote MachineSet, the backfill reads that incorrect value. Remediation: set Topology.Template explicitly on the CD's failure domain — the webhook change (#2) now permits this for previously-blank fields.

@jcpowermac — does the MCO-style 5-field match satisfy the FD matching concern? This is the same approach MCO landed in PR #5745 after fixing the identical class of bug.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/controller/machinepool/vsphereactuator.go (1)

30-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Backfill is decoupled from GenerateMachineSets, relying on callers to invoke it first.

NewVSphereActuator/GenerateMachineSets have no knowledge of remoteMachineSets or infraID; the backfill only happens if the caller (createActuator) remembers to call backfillVSphereTemplates beforehand and mutate the shared FailureDomains slice. Any future/alternate caller that constructs a VSphereActuator and calls GenerateMachineSets directly (as the existing TestVSphereActuator unit test already does) will silently skip backfilling. Consider storing remoteMachineSets/infraID on the actuator (mirroring the GCP actuator's constructor pattern) and invoking backfill from within GenerateMachineSets itself so the behavior is self-contained.

Also applies to: 38-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/controller/machinepool/vsphereactuator.go` around lines 30 - 34, The
backfill logic for vSphere templates is currently dependent on the caller
mutating shared state before calling GenerateMachineSets, so direct callers can
skip it. Update VSphereActuator to retain the needed remoteMachineSets and
infraID from NewVSphereActuator (similar to the GCP actuator pattern), and move
the backfill invocation into GenerateMachineSets itself so template backfilling
always happens internally. Make sure the constructor and GenerateMachineSets use
the existing VSphereActuator symbols consistently so the behavior is
self-contained for tests and future callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/controller/machinepool/vsphereactuator.go`:
- Around line 30-34: The backfill logic for vSphere templates is currently
dependent on the caller mutating shared state before calling
GenerateMachineSets, so direct callers can skip it. Update VSphereActuator to
retain the needed remoteMachineSets and infraID from NewVSphereActuator (similar
to the GCP actuator pattern), and move the backfill invocation into
GenerateMachineSets itself so template backfilling always happens internally.
Make sure the constructor and GenerateMachineSets use the existing
VSphereActuator symbols consistently so the behavior is self-contained for tests
and future callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fcaf58e5-ae02-4b76-a3e8-a886ea45fbcd

📥 Commits

Reviewing files that changed from the base of the PR and between e647cb0 and d7d1911.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • go.mod
  • pkg/controller/machinepool/machinepool_controller.go
  • pkg/controller/machinepool/vsphereactuator.go
  • pkg/controller/machinepool/vsphereactuator_test.go
  • pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook.go
  • pkg/validating-webhooks/hive/v1/clusterdeployment_validating_admission_hook_test.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/actuator.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/machine_scope.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/reconciler.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/session/session.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/session/tag_ids_caching_client.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/util.go
  • vendor/github.com/openshift/machine-api-operator/pkg/util/ipam/util.go
  • vendor/gopkg.in/gcfg.v1/LICENSE
  • vendor/gopkg.in/gcfg.v1/README
  • vendor/gopkg.in/gcfg.v1/doc.go
  • vendor/gopkg.in/gcfg.v1/errors.go
  • vendor/gopkg.in/gcfg.v1/read.go
  • vendor/gopkg.in/gcfg.v1/scanner/errors.go
  • vendor/gopkg.in/gcfg.v1/scanner/scanner.go
  • vendor/gopkg.in/gcfg.v1/set.go
  • vendor/gopkg.in/gcfg.v1/token/position.go
  • vendor/gopkg.in/gcfg.v1/token/serialize.go
  • vendor/gopkg.in/gcfg.v1/token/token.go
  • vendor/gopkg.in/gcfg.v1/types/bool.go
  • vendor/gopkg.in/gcfg.v1/types/doc.go
  • vendor/gopkg.in/gcfg.v1/types/enum.go
  • vendor/gopkg.in/gcfg.v1/types/int.go
  • vendor/gopkg.in/gcfg.v1/types/scan.go
  • vendor/gopkg.in/warnings.v0/LICENSE
  • vendor/gopkg.in/warnings.v0/README
  • vendor/gopkg.in/warnings.v0/warnings.go
  • vendor/k8s.io/cloud-provider-vsphere/LICENSE
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config_ini_legacy.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config_yaml.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/consts_and_errors.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_common.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_ini_legacy.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_yaml.go
  • vendor/modules.txt
💤 Files with no reviewable changes (35)
  • vendor/k8s.io/cloud-provider-vsphere/LICENSE
  • vendor/gopkg.in/warnings.v0/README
  • vendor/gopkg.in/gcfg.v1/README
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_common.go
  • vendor/gopkg.in/gcfg.v1/types/scan.go
  • vendor/gopkg.in/gcfg.v1/token/token.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_yaml.go
  • vendor/gopkg.in/gcfg.v1/types/enum.go
  • vendor/gopkg.in/warnings.v0/LICENSE
  • vendor/gopkg.in/gcfg.v1/set.go
  • vendor/gopkg.in/gcfg.v1/LICENSE
  • vendor/gopkg.in/gcfg.v1/errors.go
  • vendor/gopkg.in/gcfg.v1/types/bool.go
  • vendor/gopkg.in/gcfg.v1/types/doc.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/types_ini_legacy.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/actuator.go
  • vendor/gopkg.in/gcfg.v1/types/int.go
  • vendor/gopkg.in/gcfg.v1/doc.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/reconciler.go
  • vendor/gopkg.in/gcfg.v1/scanner/scanner.go
  • vendor/gopkg.in/gcfg.v1/read.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/session/session.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/consts_and_errors.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/util.go
  • vendor/gopkg.in/gcfg.v1/scanner/errors.go
  • vendor/gopkg.in/gcfg.v1/token/serialize.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config_yaml.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config_ini_legacy.go
  • vendor/gopkg.in/warnings.v0/warnings.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/session/tag_ids_caching_client.go
  • vendor/k8s.io/cloud-provider-vsphere/pkg/common/config/config.go
  • vendor/github.com/openshift/machine-api-operator/pkg/util/ipam/util.go
  • vendor/gopkg.in/gcfg.v1/token/position.go
  • vendor/github.com/openshift/machine-api-operator/pkg/controller/vsphere/machine_scope.go
  • vendor/modules.txt

}

func testVSphereMachineSpec(machineType string) machineapi.MachineSpec {
rawVSphereProviderSpec, err := vsphereutil.RawExtensionFromProviderSpec(testVSphereProviderSpec())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC inlining this func is what got rid of ~7KLOC worth of vendored files. Cool 👍

// Derive expected VMGroup: only host-group zonal FDs have a VMGroup set on their MachineSets.
vmGroup := ""
if fd.ZoneType == installervsphere.HostGroupFailureDomain {
vmGroup = fmt.Sprintf("%s-%s", infraID, fd.Name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this pattern reliable? The whole reason we're here is because such a pattern changed upstream.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still this. @jcpowermac ?

case cd.Spec.Platform.OpenStack != nil:
return NewOpenStackActuator(masterMachine, r.Client, logger)
case cd.Spec.Platform.VSphere != nil:
if cd.Spec.Platform.VSphere.Infrastructure != nil && cd.Spec.ClusterMetadata != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't agree with positioning this in the actuator creation flow. It's especially problematic that we're modifying the cd here, as nothing guarantees something downstream won't Update() it, resulting in unintended side effects (though arguably "good" ones in some cases).

The move appears to be related to:

3. Architecture: backfill as preprocessing (per Eric's suggestion)
Moved template backfill out of the actuator struct. A standalone backfillVSphereTemplates() function enriches the in-memory install config in the controller before actuator creation. The actuator stays stateless about remote cluster state (aligned with GCP's scalar-extraction pattern and Nutanix/OpenStack actuators).

I don't think I made such a suggestion -- can you point to it? If I did, I must have worded it poorly :)

The part about "in-memory install config" is valid... but we don't have that yet here. But "before actuator creation" is something I wouldn't have suggested intentionally.

TL;DR: This logic should live in the vsphere actuator, modifying the in-memory install-config via the copy of the Infrastructure from the CD.

Comment on lines +136 to +140
if spec.Workspace.Datacenter != fd.Topology.Datacenter ||
spec.Workspace.Datastore != fd.Topology.Datastore ||
spec.Workspace.Server != fd.Server ||
spec.Workspace.VMGroup != vmGroup ||
path.Clean(spec.Workspace.ResourcePool) != path.Clean(expectedRP) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@jcpowermac Please vet?

For reference: https://github.com/openshift/machine-config-operator/pull/5723/changes

In particular, I can't tell if the computation of vmGroup is valid.

}

// Allow setting Topology.Template on failure domains where it was previously blank.
// This permits the machinepool controller's backfill to persist and allows customers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See, this implies that we're deliberately/explicitly updating the CD after the backfill, which a) I don't see; and b) we shouldn't do.

Comment on lines +714 to +715
if oldObject.Spec.Platform.VSphere != nil && oldObject.Spec.Platform.VSphere.Infrastructure != nil &&
cd.Spec.Platform.VSphere != nil && cd.Spec.Platform.VSphere.Infrastructure != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Could fold this into the block above where we've already checked that old/new have Spec.Platform.VSphere.

@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

Thanks @2uasimojo — working through each point.

1. Architecture: backfill positioning (machinepool_controller.go:1396)

You're right, and it's actually worse than the mutation risk you flagged — cd comes from r.Get() (line 275), which in controller-runtime is a pointer into the informer cache. Mutating cd.Spec.Platform.VSphere.Infrastructure.FailureDomains in-place corrupts the shared cache for every controller reading that CD in the same process.

The root cause of the design mistake: we modeled NewVSphereActuator after the Nutanix and OpenStack actuators, which have minimal constructors (masterMachine, client, logger) because they don't need remote cluster data. But our template backfill does need remote cluster data, which left us with nowhere to put it — so it ended up in createActuator mutating cd directly.

The correct pattern is the GCP actuator (gcpactuator.go:61-110), which is the only actuator that faces the same problem: it extracts imageID, network, and subnet from remoteMachineSets at construction time and stores them on the struct. cd is never mutated.

Will refactor to match: NewVSphereActuator accepts remoteMachineSets/infraID/poolResourcePool, builds the FD→template map at construction, and applies it in GenerateMachineSets after the DeepCopy(). This also eliminates the need for the webhook change entirely (nothing persists to the CD), so that will be dropped.

2. VMGroup computation (vsphereactuator.go:115, 140)

Waiting for @jcpowermac's review on this.

3. Attribution

Apologies for the misattribution — that was my error in summarizing the review thread. Will correct in the next push description.

4. Webhook nit (clusterdeployment_validating_admission_hook.go:715)

Moot — the webhook change will be dropped entirely with the architecture fix above.

@2uasimojo

Copy link
Copy Markdown
Member

I don't think we should drop the webhook relaxation change. The backfill can still be invalid if the existing remote MachineSet we happen to choose is already invalid.

@chdeshpa-hue
chdeshpa-hue force-pushed the chdeshpa-hue/vsphere-template-backfill branch from d7d1911 to c70960f Compare July 8, 2026 03:58
Clusters installed before the HIVE-2391 template naming change have empty
Topology.Template fields in their ClusterDeployment. When the machinepool
controller generates new MachineSets, Infrastructure.DeepCopy() preserves
the empty Template, causing the installer to generate a new-format name
that does not exist on the vCenter.

Fix: follow the GCP actuator pattern — NewVSphereActuator preprocesses
remoteMachineSets into a fd-name→template map at construction time, then
GenerateMachineSets applies it to the DeepCopy'd install-config. The
ClusterDeployment is never mutated.

Matching uses a 5-field workspace conjunction (Datacenter, Datastore,
Server, VMGroup, ResourcePool) mirroring MCO PR #5745. VMGroup is derived
as {infraID}-{fdName} only for host-group zonal failure domains.

Additionally relaxes the ClusterDeployment webhook to allow setting
Topology.Template when previously blank, using the established spoof
pattern (6 existing instances). This permits customers to set the template
explicitly and avoids rejecting updates when the backfill-sourced template
from a remote MachineSet is itself invalid.

Removes the machine-api-operator vendor dependency (~7KLOC) by inlining
the VSphereMachineProviderSpec unmarshalling with encoding/json.

Assisted-by: Claude Code
Co-authored-by: Cursor <cursoragent@cursor.com>
@chdeshpa-hue
chdeshpa-hue force-pushed the chdeshpa-hue/vsphere-template-backfill branch from c70960f to 29791fd Compare July 8, 2026 04:26
@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

v3: Addressing review feedback (force-push 29791fd)

1. Architecture: backfill moved into the actuator (machinepool_controller.go:1396)

You're right — the prior version mutated cd in createActuator, which is an informer cache pointer. Fixed by following the GCP actuator pattern:

  • NewVSphereActuator now accepts remoteMachineSets, infraID, and failureDomains
  • Builds a read-only templates map[string]string at construction time via buildTemplateMap()
  • Templates are applied inside GenerateMachineSets on the DeepCopy() — the original CD is never mutated
  • createActuator only passes immutable data to the constructor; no cd mutation occurs

2. Webhook: folded into existing vSphere block (line 715 nit)

The Topology.Template spoof logic is now inside the existing if oldObject.Spec.Platform.VSphere != nil && cd.Spec.Platform.VSphere != nil block, not a separate top-level check.

3. Webhook relaxation kept (per your July 6 comment)

Confirmed — the spoof pattern stays. If the backfill reads an invalid template from a corrupted remote MachineSet, customers can still manually set the correct value via CD edit.

4. ResourcePool override bug fix

Found during code review: findTemplateForFD was using the MachinePool's resourcePool override for matching, but existing remote MachineSets always carry the FD's default ResourcePool. This meant a new MachinePool with a custom resource pool would fail to discover templates. Fixed: matching always uses the FD's own Topology.ResourcePool. The pool override only applies when generating new MachineSets (in GenerateMachineSets).

Still pending: @jcpowermac vetting of the vmGroup computation + 5-field match approach (from your July 1 request).

@openshift-ci

openshift-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@chdeshpa-hue: 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
ci/prow/security 29791fd link true /test security

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@2uasimojo 2uasimojo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is starting to look sane. I'm still concerned about the hardcoded vmgroup format string. Is there a more generic/future-proof way to handle this?

// Derive expected VMGroup: only host-group zonal FDs have a VMGroup set on their MachineSets.
vmGroup := ""
if fd.ZoneType == installervsphere.HostGroupFailureDomain {
vmGroup = fmt.Sprintf("%s-%s", infraID, fd.Name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still this. @jcpowermac ?

@chdeshpa-hue

Copy link
Copy Markdown
Contributor Author

Thanks @2uasimojo — on the future-proofing question specifically:

You're right that fmt.Sprintf("%s-%s", infraID, fd.Name) is Hive reconstructing an installer naming convention. I kept exact equality (not a looser match) because for HostGroup FDs the other workspace fields often collide, and VMGroup is the discriminator installer/MCO already use.

That said, I agree reconstructing the formula in Hive isn't ideal if the contract moves again.

A more generic option would be to stop building the string and instead read the expected VMGroup from the remote Infrastructure object we already fetch (failureDomains[].zoneAffinity.hostGroup.vmGroup), then exact-compare that to the MachineSet's Workspace.VMGroup. Same matching strength, no format knowledge in Hive — closer to how MCO treats Infra as source of truth.

Happy to take that follow-up if you prefer it over the reconstruct. Leaving the "is this pattern durable?" question for @jcpowermac as tagged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants