Skip to content

operator: let layered CRs delete after their cluster is removed + opt-in GC owner ref (UX-1351)#1626

Draft
sago2k8 wants to merge 3 commits into
mainfrom
UX-1351-v1-cluster-gone-finalizer
Draft

operator: let layered CRs delete after their cluster is removed + opt-in GC owner ref (UX-1351)#1626
sago2k8 wants to merge 3 commits into
mainfrom
UX-1351-v1-cluster-gone-finalizer

Conversation

@sago2k8

@sago2k8 sago2k8 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

A RedpandaRole (and other layered CRs: User/Schema/Group/ShadowLink) that references a cluster via clusterRef could get stuck in ClusterRefInvalid and, in the v1 teardown case, block deletion of its namespace forever.

The trigger is specific to v1: deleting just the cluster CR was already handled (the lookup returns NotFound, which the finalizer-cleanup path swallows). But uninstalling the v1 (vectorized) operator removes the clusters.redpanda.vectorized.io CRD, so the lookup returns a *meta.NoKindMatchError instead of NotFound; stripping the type's RBAC returns Forbidden. ignoreAllConnectionErrors recognized neither, so DeleteResource returned an error, the finalizer was never removed, and the role hung in Terminating — taking the namespace with it.

Changes

  1. Fix (c91aea6a) — group the "cluster can no longer be resolved" cases into isClusterGone and fold in NoKindMatch + Forbidden. Strict superset of the prior swallow set; the only effect is that finalizer cleanup also completes when the whole kind is gone. Covers all five layered CRs (shared ignoreAllConnectionErrors).

  2. Feature (45e36107) — opt-in operator.redpanda.com/delete-with-cluster=true annotation makes the Role controller stamp a GC owner reference at the referenced cluster (v1/v2/StretchCluster GVK, same-namespace only, controller=false/blockOwnerDeletion=false so it never blocks the cluster's own deletion). Applied under a dedicated SSA field manager so it can't relinquish the finalizer. Factory gains ClusterForObject + GetClient.

  3. Acceptance (3b152776)role-crds scenario asserting the owner reference is stamped; generalized kubernetesObjectHasClusterOwner to be variant-aware (v1 in the vectorized variant, v2 elsewhere). v2 path unchanged, so the migration scenario is unaffected.

Testing

  • Unit: TestIgnoreAllConnectionErrorsClusterGone pins the classifier — NoKindMatch/Forbidden were RED before the fix, green after (demonstrated both directions). TestOwnerReferenceForCluster covers the builder (v1/v2 GVK, cross-namespace, nil).
  • Envtest: TestRoleDeletionWithDeletedClusterRef covers the CR-deleted boundary; existing TestRoleReconcile still green after the SyncResource restructure.
  • Acceptance (kind): the new GC scenario passed in both variants (v1 vectorized and v2). Three unrelated scenarios (cluster/Updating_admin_ports, rack-awareness, scale-down) failed on host-level nodePort contention from running the full suite in parallel locally — not touched by this change; CI isolation should clear them.

Note: the literal "namespace stuck Terminating" can't be reproduced in envtest (no namespace controller/GC; warm RESTMapper returns NotFound not NoKindMatch), so the deterministic RED→GREEN lives in the unit test and the GC cascade is covered by the kind suite.

Follow-ups (not in this PR)

  • Extend the owner-ref opt-in to User/Schema/Group/ShadowLink (the isClusterGone fix already covers all five).
  • Map NoKindMatchClusterRefInvalid in handleResourceSyncErrors so a live role whose v1 CRD vanished shows a clean condition instead of UnexpectedError.

🤖 Generated with Claude Code

sago2k8 and others added 3 commits June 23, 2026 19:06
A RedpandaRole (and any layered CR: User/Schema/Group/ShadowLink) that
references a cluster via clusterRef would hang in Terminating when the
referenced cluster was fully torn down. Deleting just the cluster CR was
already handled: the lookup returns NotFound, which the finalizer cleanup
path swallows. But uninstalling the v1 (vectorized) operator removes the
clusters.redpanda.vectorized.io CRD, so the lookup returns a
*meta.NoKindMatchError instead of NotFound; stripping the type's RBAC
returns Forbidden. ignoreAllConnectionErrors didn't recognize either, so
DeleteResource returned an error, the finalizer was never removed, and the
CR could not be deleted.

Group the "cluster can no longer be resolved" cases into isClusterGone and
fold NoKindMatch and Forbidden into it. The change is a strict superset of
the prior swallow set, so nothing previously surfaced is now hidden; the
only effect is that finalizer cleanup also completes when the whole kind is
gone, not just when the CR is absent.

Tests: TestIgnoreAllConnectionErrorsClusterGone pins the classifier
(NoKindMatch/Forbidden were RED before this change); TestRoleDeletionWith
DeletedClusterRef covers the green boundary end-to-end in envtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…leted

Adds an opt-in path so a RedpandaRole is garbage-collected when the cluster
CR it references is deleted (the CRD staying installed). Setting the
annotation operator.redpanda.com/delete-with-cluster=true makes the role
controller stamp an owner reference pointing at the resolved cluster.

Details:
- ownerReferenceForCluster builds the GC edge with the correct GVK for v1
  (redpanda.vectorized.io/Cluster), v2 (cluster.redpanda.com/Redpanda), and
  StretchCluster refs. It returns nil for cross-namespace refs (Kubernetes GC
  ignores those) and uses controller=false, blockOwnerDeletion=false so it
  never blocks the cluster's own deletion.
- The owner reference is applied as a metadata patch under a dedicated
  server-side-apply field manager, separate from the finalizer/status owner,
  so SSA cannot relinquish the finalizer.
- Factory gains ClusterForObject (resolve the referenced cluster as a
  client.Object) and exposes GetClient on the ClientFactory interface.

Wired into the Role controller only for now. The actual GC cascade is
exercised by the kind acceptance suite; envtest runs no garbage collector.
ownerReferenceForCluster is unit-tested directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a role-crds scenario asserting that a RedpandaRole annotated with
operator.redpanda.com/delete-with-cluster=true is stamped with an owner
reference pointing at its referenced cluster, the garbage-collection edge
that lets the role (and thus a namespace) be deleted along with the cluster.

The actual GC cascade depends on the kube controller-manager's garbage
collector, which only runs in a full control plane, so this lives in the
kind acceptance suite rather than envtest. The deterministic RED->GREEN for
the underlying finalizer fix is covered by the unit tests.

Generalizes kubernetesObjectHasClusterOwner to be variant-aware: the
vectorized variant rewrites clusterRefs to the v1 Cluster, so the expected
owner reference GVK is resolved per variant. The v2 path is unchanged, so
the existing migration scenario is unaffected.

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

secpanda commented Jun 25, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

This PR is stale because it has been open 5 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions Bot added the stale label Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants