Skip to content

Hold a deleted IPAddressClass until its addresses are gone - #10

Open
Timofei Larkin (lllamnyp) wants to merge 2 commits into
mainfrom
feat/class-deletion-guard
Open

Timofei Larkin (lllamnyp) wants to merge 2 commits into
mainfrom
feat/class-deletion-guard

Conversation

@lllamnyp

Copy link
Copy Markdown
Member

Implements the class-deletion guard of the ip-address-claims proposal (Failure and edge cases: "Class deleted with bound addresses → refused … A finalizer holds the class until its addresses are gone"). A review of the distribution package in cozystack/cozystack#3382 raised it under lifecycle and teardown. Stacked on #7.

Changes

  • Finalizer. A new IPAddressClassReconciler puts local.sdn.cozystack.io/class-protection on every IPAddressClass. On a deleted class it removes the finalizer only once no IPAddress with that spec.className is left, in any phase. While it waits, it records a ClassInUse event naming the addresses. Address events re-trigger the class.
  • Index. A new field index on IPAddress spec.className.
  • RBAC. The ClusterRole, regenerated, gains update/patch on classes and update on ipaddressclasses/finalizers.
  • Docs. The Class deletion paragraph in docs/design.md §4 and the ownership table are updated.

Why the class must outlive its addresses

Drivers reach their backend configuration through the class. metallb-iad, after cozystack/metallb-iad#8, finds the pool to hold and pin an address through the class's parameters.pool. A class deleted while addresses remain would leave them unservable.

Behaviour to know

  • A Released address holds the class too. Deleting a class means deleting its IPAddress objects first, or letting reclaimPolicy: Delete do it.
  • Classes that exist when this is deployed get the finalizer on their first reconcile.
  • The claim side is unaffected. Bound claims never read their class, as before.

go test ./internal/controller/ and golangci-lint run pass.

The core now puts a class-protection finalizer on every IPAddressClass
and removes it from a deleted class only once no IPAddress of the class
remains, recording a ClassInUse event that names the addresses it waits
for. Drivers resolve their backend configuration through the class to
hold and pin its addresses, so the class must outlive them.

Assisted-By: LLM
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f0a0f576-6064-4e8e-851e-986879c64d11
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

IvanHunters
IvanHunters previously approved these changes Nov 6, 2026

@IvanHunters IvanHunters 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.

Review: hold a deleted IPAddressClass until its addresses are gone

Conforms to the design's class-deletion guard. The class-protection finalizer is added to a live class and removed only when no IPAddress of the class remains, counted through the spec.className index (internal/controller/ipaddressclass_controller.go). The controller never touches the MetalLB pool or advertisement, so the admin's pool survives class deletion, and it does not delete addresses. The release path (a watch requeuing the class on the last address's deletion) works, and I found no deadlock across the class/claim/address finalizers. RBAC is accurate.

[MINOR] A Terminating class is still selected as default and for auto-claim

Neither the default-class selection (ipaddressclaim_controller.go:241) nor the auto-claim-class selection (service_controller.go:164) skips a class with a non-zero DeletionTimestamp. While the finalizer holds a class in Terminating, new claims still resolve to it and auto-claim still mints into it, which keeps minting addresses that extend the hold and can keep the class from draining. Skipping classes with !DeletionTimestamp.IsZero() in those two loops closes it.

Note (cross-PR, not a blocker)

The finalizer holds on an IPAddress of the class in any phase, which the design intends. Combined with reclaim Retain and the releaseDelay of #11, this means a class whose pool was ever used accumulates Released/Available addresses that block its deletion, and releaseDelay returns them to Available rather than deleting them, so it does not help teardown. Deleting such a class then needs the operator to delete its addresses first. Worth documenting in the operator guide (#14) so this reads as expected rather than a stuck deletion.

LGTM, with the Terminating-class selection worth fixing.

@lllamnyp
Timofei Larkin (lllamnyp) changed the base branch from feat/service-auto-claim to main November 6, 2026 19:03
@lllamnyp
Timofei Larkin (lllamnyp) dismissed IvanHunters’s stale review November 6, 2026 19:03

The base branch was changed.

The class-protection finalizer keeps a deleted class until its addresses
are gone, and while it waited, default-class resolution and auto-claim
still chose it, minting addresses that kept it from draining. Both now
skip a class that is being deleted.

Assisted-By: LLM
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@lllamnyp

Copy link
Copy Markdown
Member Author

Fixed in db6042e: default-class resolution and auto-claim skip a class that is being deleted, so a Terminating class no longer draws new claims while the finalizer holds it. Tests: TestDeletedClassIsNotChosenByDefault, TestDeletedClassIsNotChosenForAutoClaim.

The cross-PR note on teardown (a used class's Released/Available addresses hold it, and releaseDelay returns rather than deletes them) will go into the operator guide in #14.

This PR now targets main, since #7 is merged. The retarget and the push dismissed the earlier approval, so it needs another one.

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.

2 participants