Skip to content

feat: implement CAPI failure domain contract for zones#720

Open
emanuelebosetti wants to merge 3 commits into
ionos-cloud:mainfrom
ElmecOSS:feat/failure-domains
Open

feat: implement CAPI failure domain contract for zones#720
emanuelebosetti wants to merge 3 commits into
ionos-cloud:mainfrom
ElmecOSS:feat/failure-domains

Conversation

@emanuelebosetti

Copy link
Copy Markdown

Issue: #717
Description of changes:

Map existing CAPMOX zones to CAPI failure domains so that KubeadmControlPlane automatically distributes control plane nodes across Proxmox zones.

API changes (additive, backwards-compatible):

  • Add Nodes []string to ZoneConfigSpec for per-zone node lists
  • Add ControlPlane *bool to ZoneConfigSpec (defaults to true)
  • Add FailureDomains to ProxmoxClusterStatus (CAPI v1beta2 contract)
  • Add spec.failureDomain to ProxmoxMachineSpec (InfraMachine contract)
  • Add GetZoneNodes() helper on ProxmoxCluster
  • Add FailureDomainNotReady condition reason

Controller changes:

  • Add reconcileFailureDomains in cluster controller (sorted by name to prevent reconcile loops)
  • Read Machine.Spec.FailureDomain in machine controller with retryable FailureDomainNotReady condition
  • Use effectiveZone/effectiveAllowedNodes on MachineScope to avoid spec mutation from the controller
  • Pass zone override to GetInClusterPools for IPAM pool selection
  • Set Zone on NodeLocation in createVM
  • Update scheduler to use GetEffectiveAllowedNodes()

Conversion:

  • Restore Status.FailureDomains and Spec.FailureDomain in v1alpha1 conversion webhook
  • Add manual conversion stub for ProxmoxMachineSpec.FailureDomain

Testing performed:

  • Unit tests for API types, scheduler, VM service helpers, and machine scope
  • Validated on a real Proxmox cluster (10 nodes, 2 zones) with 3 CP + 2 worker nodes
  • KCP correctly distributes CP replicas round-robin across failure domains
  • Workers pinned to target zone, IPAM pools selected per-zone
  • make lint test verify passed
  • Full backwards compatibility verified — clusters without zoneConfig work as before

@wikkyk

wikkyk commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Thanks!
If you could please provide attribution if you used AI/coding agent(s) to develop your PR.

@emanuelebosetti

Copy link
Copy Markdown
Author

I've amended the commit to include the Co-Authored-By trailer for the AI coding agent used during development.
Let me know if there are any additional attribution policies or guidelines I should follow for AI-assisted contributions to this project.

@wikkyk

wikkyk commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Thanks. No, we don't have any other policies. The field moves fast and we just didn't get to writing things down yet.

@emanuelebosetti

Copy link
Copy Markdown
Author

Thanks. No, we don't have any other policies. The field moves fast and we just didn't get to writing things down yet.

We're going through the same process internally — introducing AI across dev teams and figuring out contribution policies.

Three references we found particularly useful:

Could be useful input for #716 as well.

@wikkyk wikkyk mentioned this pull request Apr 16, 2026
@emanuelebosetti

Copy link
Copy Markdown
Author

Hi @wikkyk , do you think it would be possible to include these failure domain changes in the upcoming v0.9.0 release? Happy to address any remaining feedback if needed. Thanks!

@wikkyk

wikkyk commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Sure. v0.9.0 is still a fair bit far off, though. This only adds a field so it could reasonably be released in the 0.8 series.

However, as the linter points out, the type of the FailureDomain field has the wrong type. It's optional, so it should be a pointer. You'll need to re-check your pointer handling.

emanuelebosetti added a commit to ElmecOSS/cluster-api-provider-proxmox that referenced this pull request May 5, 2026
Address review feedback (ionos-cloud#720): the field is optional with no
validation, so kube-api-linter requires a pointer. Switch
ProxmoxMachineSpec.FailureDomain to *string in v1alpha2 and regen
deepcopy.

Move the v1alpha2-only restore of FailureDomain into
restoreProxmoxMachineSpec so both ProxmoxMachine and
ProxmoxMachineTemplate ConvertTo paths preserve the field across the
hub→spoke→hub roundtrip. The pointer change shifted the symptom from
"" to nil and exposed the pre-existing missing restore on the Template
path (TestFuzzyConversion/for_ProxmoxMachineTemplate/hub-spoke-hub).

Validated end-to-end on a 10-node Proxmox test cluster: KCP rolled
three fresh control plane Machines and both MachineDeployments rolled
their workers through the failureDomain path
(Machine.Spec.FailureDomain → ApplyFailureDomainNodes → IPAM zone
scope → createVM) without errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@emanuelebosetti

Copy link
Copy Markdown
Author

@wikkyk pushed the pointer fix as a separate commit. Could you re-trigger the workflows? Thanks!

Comment thread internal/controller/proxmoxmachine_controller.go Outdated
Comment thread internal/controller/proxmoxcluster_controller.go Outdated
@wikkyk

wikkyk commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Don't worry about the test workflow 'failing', it just can't upload results to sonarqube. I'm happy with this PR, just rename fd/fds as I pointed out inline to avoid the confusion.

wikkyk
wikkyk previously approved these changes May 13, 2026
@wikkyk

wikkyk commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Please rebase this PR on current main.

emanuelebosetti added a commit to ElmecOSS/cluster-api-provider-proxmox that referenced this pull request Jun 12, 2026
Address review feedback (ionos-cloud#720): the field is optional with no
validation, so kube-api-linter requires a pointer. Switch
ProxmoxMachineSpec.FailureDomain to *string in v1alpha2 and regen
deepcopy.

Move the v1alpha2-only restore of FailureDomain into
restoreProxmoxMachineSpec so both ProxmoxMachine and
ProxmoxMachineTemplate ConvertTo paths preserve the field across the
hub→spoke→hub roundtrip. The pointer change shifted the symptom from
"" to nil and exposed the pre-existing missing restore on the Template
path (TestFuzzyConversion/for_ProxmoxMachineTemplate/hub-spoke-hub).

Validated end-to-end on a 10-node Proxmox test cluster: KCP rolled
three fresh control plane Machines and both MachineDeployments rolled
their workers through the failureDomain path
(Machine.Spec.FailureDomain → ApplyFailureDomainNodes → IPAM zone
scope → createVM) without errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@emanuelebosetti

Copy link
Copy Markdown
Author

Hi @wikkyk
I've rebased the PR on current main as requested

emanuelebosetti and others added 3 commits June 30, 2026 10:41
Map existing CAPMOX zones to CAPI failure domains so that
KubeadmControlPlane automatically distributes control plane
nodes across Proxmox zones.

API changes:
- Add Nodes []string to ZoneConfigSpec for per-zone node lists
- Add ControlPlane *bool to ZoneConfigSpec (defaults to true)
- Add FailureDomains to ProxmoxClusterStatus (CAPI v1beta2 contract)
- Add spec.failureDomain to ProxmoxMachineSpec (InfraMachine contract)
- Add GetZoneNodes() helper on ProxmoxCluster
- Add FailureDomainNotReady condition reason

Controller changes:
- Add reconcileFailureDomains in cluster controller (sorted by name)
- Read Machine.Spec.FailureDomain in machine controller with
  retryable FailureDomainNotReady condition
- Use effectiveZone/effectiveAllowedNodes on MachineScope to avoid
  spec mutation from the controller
- Pass zone override to GetInClusterPools for IPAM pool selection
- Set Zone on NodeLocation in createVM
- Update scheduler to use GetEffectiveAllowedNodes()

Conversion:
- Restore Status.FailureDomains and Spec.FailureDomain in v1alpha1
  conversion webhook
- Add manual conversion stub for ProxmoxMachineSpec.FailureDomain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback (ionos-cloud#720): the field is optional with no
validation, so kube-api-linter requires a pointer. Switch
ProxmoxMachineSpec.FailureDomain to *string in v1alpha2 and regen
deepcopy.

Move the v1alpha2-only restore of FailureDomain into
restoreProxmoxMachineSpec so both ProxmoxMachine and
ProxmoxMachineTemplate ConvertTo paths preserve the field across the
hub→spoke→hub roundtrip. The pointer change shifted the symptom from
"" to nil and exposed the pre-existing missing restore on the Template
path (TestFuzzyConversion/for_ProxmoxMachineTemplate/hub-spoke-hub).

Validated end-to-end on a 10-node Proxmox test cluster: KCP rolled
three fresh control plane Machines and both MachineDeployments rolled
their workers through the failureDomain path
(Machine.Spec.FailureDomain → ApplyFailureDomainNodes → IPAM zone
scope → createVM) without errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@emanuelebosetti

Copy link
Copy Markdown
Author

Hi @wikkyk, rebased against 0.9.0

Spec: ProxmoxClusterSpec{
ZoneConfigs: []ZoneConfigSpec{
{
Zone: ptr.To("zone-a"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Incomplete port/rebase? ptr.To won't pass make verify. Use new instead.

clusterv1.Convert_int32_To_Pointer_int32(src.NumSockets, ok, restored.NumSockets, &dst.NumSockets)
clusterv1.Convert_int32_To_Pointer_int32(src.MemoryMiB, ok, restored.MemoryMiB, &dst.MemoryMiB)

// Restore FailureDomain (v1alpha2-only field, set by CAPI machine controller).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment is somewhat superfluous.

Comment on lines +495 to +498
// Convert_v1alpha2_ProxmoxMachineSpec_To_v1alpha1_ProxmoxMachineSpec handles
// the lossy conversion of ProxmoxMachineSpec from v1alpha2 to v1alpha1.
// The FailureDomain field is intentionally dropped (it does not exist in v1alpha1
// and is restored from annotation on ConvertTo).

@wikkyk wikkyk Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment is extraneous.

// Use effective allowed nodes (failure-domain override > machine spec > cluster spec).
allowedNodes := machineScope.GetEffectiveAllowedNodes()
if len(allowedNodes) == 0 {
allowedNodes = machineScope.InfraCluster.ProxmoxCluster.Spec.AllowedNodes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment now describes an implementation detail that might change in the future.
Also, the if(allowedNodes)==0 doesn't make much sense to me. GetEffectiveAllowedNodes either returns the effective nodes override or ProxmoxMachine.Spec.AllowedNodes, yet then when it's called, you also check InfraCluster.ProxmoxCluster.Spec.AllowedNodes. It feels like the implementation of GetEffectiveAllowedNodes is incomplete.

// controlPlane indicates whether this zone is eligible for control plane machines.
// Defaults to true when not set.
// +optional
ControlPlane *bool `json:"controlPlane,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I really don't like the name of this field. Without reading the comment, I would've guessed that it indicates that it is a control plane, which doesn't make a lot of sense, leading to confusion. The name needs to be more explicit about what this field is and does.

@wikkyk wikkyk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like the idea but the implementation feels like an attempt at making the minimal change. It doesn't seem fully thought through. I don't like the implementation of GetEffectiveAllowedNodes and I don't like the way every place that touches something that can be overridden needs to check if it might be. It's easy to miss a call/usage site, particularly when merging new changes.

That's not to say that anything in this PR is very bad, the idea is sound and the implementation mostly is sound too. However, merging this as-is (even after applying the cosmetic changes) would increase the maintenance burden.

@wikkyk

wikkyk commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

For example, this will conflict with #784.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement CAPI failure domain contract for v1alpha2 zones

2 participants