Skip to content

fix(operator): reject a DHCHAP pool created with no allowed nodes - #498

Merged
noctarius merged 1 commit into
mainfrom
fix/dhchap-node-selector-updates
Sep 8, 2026
Merged

noctarius merged 1 commit into
mainfrom
fix/dhchap-node-selector-updates

Conversation

@boddumanohar

@boddumanohar boddumanohar commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Problem

A StoragePool created with dhchap: true and an empty allowedNodes can never enforce them.

createStorageClassIfNotExists decides the gate once:

dhchapGated := storagePoolCR.Spec.DHCHAP && len(storagePoolCR.Spec.AllowedNodes) > 0

Storage class parameters once set, cannot be modified later. So initially when a storage class is created without allowedNodes, dhchap_node_selector parameter is not set. And the later when allowedHosts is specified, they cannot be updated.

A StoragePool created with dhchap: true and an empty allowedNodes can never
enforce them. createStorageClassIfNotExists decides the gate once, from
DHCHAP && len(AllowedNodes) > 0, and only then writes dhchap_node_selector.
StorageClass parameters are immutable and the class is create-only, so
populating allowedNodes later syncs the node labels and the backend allowed
hosts but never adds the parameter: no PersistentVolume from that pool ever
gets a nodeAffinity. Recreating the pool was the only way out.

Reject the combination instead of repairing it. dhchap is immutable, so it
can only be set at creation, which makes this the one place the state can
arise. Replacing the class to add the parameter was the alternative and is
worse: it would discard anything a user put on the class, including
is-default-class, and leave a window with no class at all.

Growing or shrinking a non-empty allowedNodes is untouched and needs no
class change, because the parameter holds the pool's label key rather than
the node list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noctarius
noctarius merged commit d2d10b4 into main Sep 8, 2026
17 checks passed
@noctarius
noctarius deleted the fix/dhchap-node-selector-updates branch September 8, 2026 14:53
boddumanohar added a commit that referenced this pull request Sep 10, 2026
handleStoragePoolDeletion cleared spec.allowedNodes on the object it then
wrote back to drop the finalizer. The StoragePool CRD rejects that shape for
a DHCHAP pool — "dhchap requires a non-empty allowedNodes", added in #498 —
so the rejection landed on the very update that removes the finalizer, and
the CR stayed in Terminating for as long as the reconciler kept retrying.

syncNodeLabels does need an empty allowed-nodes list to clear the labels, so
it now gets one on a copy, and the finalizer comes off through a patch that
carries nothing but the finalizer list.

The regression spec runs against envtest rather than a fake client because
the rule lives in the CRD: a fake client validates nothing and would pass
either way. Driving the mock control plane from a Ginkgo spec needed
NewSpecServerFromFile to take an interface, since testing.TB cannot be
implemented outside the standard library.

Also deletes the commented-out pool-update block this file carried: the
quality gate checks the prose of a changed file, and dead code is not worth
rewording.

Regression: 2026-09-09-dhchap-pool-delete-frozen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
boddumanohar added a commit that referenced this pull request Sep 10, 2026
handleStoragePoolDeletion cleared spec.allowedNodes on the object it then
updated to drop the finalizer, and the StoragePool CRD rejects that shape for
a DHCHAP pool — "dhchap requires a non-empty allowedNodes", added in #498. The
rejection landed on the very update that removes the finalizer, so the CR
stayed in Terminating for as long as the reconciler kept retrying, and no
DHCHAP pool could be deleted at all.

syncNodeLabels reads Spec.AllowedNodes, so clearing the labels still needs an
empty list. It now gets one on a copy, and the object written back keeps the
spec the CRD accepts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
boddumanohar added a commit that referenced this pull request Sep 10, 2026
handleStoragePoolDeletion cleared spec.allowedNodes on the object it then
updated to drop the finalizer, and the StoragePool CRD rejects that shape for
a DHCHAP pool — "dhchap requires a non-empty allowedNodes", added in #498. The
rejection landed on the very update that removes the finalizer, so the CR
stayed in Terminating for as long as the reconciler kept retrying, and no
DHCHAP pool could be deleted at all.

syncNodeLabels reads Spec.AllowedNodes, so clearing the labels still needs an
empty list. It now gets one on a copy, and the object written back keeps the
spec the CRD accepts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
noctarius pushed a commit that referenced this pull request Sep 10, 2026
handleStoragePoolDeletion cleared spec.allowedNodes on the object it then
updated to drop the finalizer, and the StoragePool CRD rejects that shape for
a DHCHAP pool — "dhchap requires a non-empty allowedNodes", added in #498. The
rejection landed on the very update that removes the finalizer, so the CR
stayed in Terminating for as long as the reconciler kept retrying, and no
DHCHAP pool could be deleted at all.

syncNodeLabels reads Spec.AllowedNodes, so clearing the labels still needs an
empty list. It now gets one on a copy, and the object written back keeps the
spec the CRD accepts.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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