Skip to content

HYPERFLEET-971 - feat: reject nodepool create/patch on soft-deleted cluster #113

Open
kuudori wants to merge 2 commits intoopenshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-971
Open

HYPERFLEET-971 - feat: reject nodepool create/patch on soft-deleted cluster #113
kuudori wants to merge 2 commits intoopenshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-971

Conversation

@kuudori
Copy link
Copy Markdown

@kuudori kuudori commented Apr 23, 2026

Prevent nodepool creation and updates on clusters marked for deletion by returning 409 Conflict, avoiding orphaned resources after cluster cleanup.

  • Add ConflictState error constructor with HYPERFLEET-CNF-003 code
  • Check cluster.DeletedTime in Create and Patch handlers before proceeding
  • Check nodepool.DeletedTime in Patch handler before proceeding
  • Reject cluster patch on soft-deleted cluster with 409 Conflict
  • Add unit tests for 409 rejection and happy-path scenarios on all endpoints

Summary

  • HYPERFLEET-971

Test Plan

  • Unit tests added/updated
  • make test-all passes
  • make lint passes
  • Helm chart changes validated with make test-helm (if applicable)
  • Deployed to a development cluster and verified (if Helm/config changes)
  • E2E tests passed (if cross-component or major changes)

Summary by CodeRabbit

  • Bug Fixes

    • API now prevents modifications to clusters marked for deletion, returning a 409 conflict with a "marked for deletion" detail.
    • Node pool create and patch operations return 409 when the cluster or target node pool is marked for deletion.
    • Validation tightened to enforce consistency during soft-delete flows.
  • New

    • Introduced a dedicated conflict error code for "state conflict" responses.
  • Tests

    • Added coverage for patch/create behaviors and soft-delete conflict scenarios.

@openshift-ci openshift-ci Bot requested review from jsell-rh and vkareh April 23, 2026 16:59
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 23, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign crizzo71 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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Walkthrough

This PR adds a new RFC 9457 conflict error code HYPERFLEET-CNF-003 (CodeConflictState) and a constructor ConflictState(...) in the errors package. Cluster and nodepool handlers now check for soft-deleted resources (non-nil DeletedTime) and return the new conflict error when operations target deleting clusters or nodepools (PATCH and nodepool CREATE/PATCH). Tests are added/updated to cover successful paths and conflict/404 responses for deleted or missing resources.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Handler as ClusterHandler / NodePoolsHandler
  participant Service as ClusterService
  participant Errors as errors.Registry

  Client->>Handler: HTTP PATCH/POST (cluster/nodepool)
  Handler->>Service: Get(clusterID)
  Service-->>Handler: cluster (maybe nil)
  alt cluster missing
    Handler-->>Client: 404 Not Found
  else cluster present
    alt cluster.DeletedTime != nil
      Handler->>Errors: ConflictState("marked for deletion", ...)
      Errors-->>Handler: ProblemDetails (409, HYPERFLEET-CNF-003)
      Handler-->>Client: 409 Conflict (problem-details)
    else cluster active
      opt nodepool flow: check nodepool.DeletedTime
        alt nodepool.DeletedTime != nil
          Handler->>Errors: ConflictState("marked for deletion", ...)
          Errors-->>Handler: ProblemDetails (409,...)
          Handler-->>Client: 409 Conflict
        else nodepool active
          Handler->>Service: Replace/Create(...)
          Service-->>Handler: updated resource
          Handler-->>Client: 200/201 with body
        end
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly and specifically describes the main change: adding soft-delete rejection for nodepool create/patch operations on deleted clusters, with explicit feature type and issue tracking.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

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

@openshift-hyperfleet openshift-hyperfleet deleted a comment from coderabbitai Bot Apr 23, 2026
@openshift-hyperfleet openshift-hyperfleet deleted a comment from coderabbitai Bot Apr 23, 2026
@kuudori
Copy link
Copy Markdown
Author

kuudori commented Apr 23, 2026

accidentally clicked generate unit tests under the coderabbit comment, deleted his comments with an unsuccessful attempt to do that

@rafabene
Copy link
Copy Markdown
Contributor

Nice work! The soft-delete guards on the cluster-scoped endpoints look solid.

One thing I noticed: the standalone NodePoolHandler.Patch in pkg/handlers/node_pool.go doesn't have the same DeletedTime check you added to ClusterNodePoolsHandler.Patch. So a deleted nodepool could still be patched through PATCH /nodepools/:id even though the nested endpoint now correctly rejects it with 409.

Worth adding the same guard there for consistency — or at least tracking it as a follow-up.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/handlers/cluster_nodepools.go`:
- Around line 193-200: The soft-delete check on cluster.DeletedTime in the
handler (after clusterService.Get) is racy; move this validation into the
service/DAO layer and enforce it atomically with the write. Update the nodepool
creation/replacement paths (the service methods that call Create()/Replace() on
the DAO) to either re-check DeletedTime inside the same transaction or add a
precondition to the DB write (e.g., include WHERE cluster.deleted_time IS NULL
in the INSERT/UPDATE SQL) so Create()/Replace() fail if the cluster is marked
deleted; ensure the service/DAO returns a clear ConflictState error when that
condition is hit.
🪄 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: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 9ca03664-3434-4b51-9885-8613945b3352

📥 Commits

Reviewing files that changed from the base of the PR and between 0dcbabd and 1c2efd3.

📒 Files selected for processing (5)
  • pkg/errors/errors.go
  • pkg/handlers/cluster.go
  • pkg/handlers/cluster_nodepools.go
  • pkg/handlers/cluster_nodepools_test.go
  • pkg/handlers/cluster_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/errors/errors.go
  • pkg/handlers/cluster.go

Comment on lines +193 to +200
cluster, err := h.clusterService.Get(ctx, clusterID)
if err != nil {
return nil, err
}

if cluster.DeletedTime != nil {
return nil, errors.ConflictState("Cluster '%s' is marked for deletion", clusterID)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect NodePool/Cluster service implementations for atomic soft-delete enforcement on write paths.

rg -nP --type go 'func \(.*\) (Create|Replace)\(' -C 4
rg -nP --type go 'DeletedTime|ConflictState|transaction|tx\b|FOR UPDATE|optimistic' -C 4

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 50392


🏁 Script executed:

# Find Create/Replace implementations in service and DAO layers with DeletedTime checks
rg -n 'func.*Create\(|func.*Replace\(' pkg/services pkg/dao -A 20 | head -100

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 6057


🏁 Script executed:

# Search for DeletedTime checks in Create/Replace context
rg -B 5 -A 15 'DeletedTime' pkg/services pkg/dao | grep -E '(func|Create|Replace|DeletedTime)' | head -50

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 3722


🏁 Script executed:

# Look at the actual handler code around line 193-200 and 269-271
sed -n '190,210p' pkg/handlers/cluster_nodepools.go
sed -n '265,275p' pkg/handlers/cluster_nodepools.go

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 996


🏁 Script executed:

# Check if the write operations (Create/Replace) in services revalidate state
rg -A 30 'func \(s \*sqlClusterService\) Create\(' pkg/services/cluster.go
rg -A 30 'func \(s \*sqlNodePoolService\) Create\(' pkg/services/node_pool.go

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 1760


The soft-delete check must be re-validated atomically in the write path.

The handler's Get and subsequent Create/Replace are separate operations, allowing a cluster to be marked for deletion between the check (line 195–197) and the write. The service and DAO implementations skip re-validation—Create() and Replace() proceed without re-checking DeletedTime.

Move the validation into the service or DAO layer and enforce it transactionally with the write, or add an explicit precondition check (e.g., WHERE cluster.deleted_time IS NULL) in the database write to prevent orphaned nodepools.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/handlers/cluster_nodepools.go` around lines 193 - 200, The soft-delete
check on cluster.DeletedTime in the handler (after clusterService.Get) is racy;
move this validation into the service/DAO layer and enforce it atomically with
the write. Update the nodepool creation/replacement paths (the service methods
that call Create()/Replace() on the DAO) to either re-check DeletedTime inside
the same transaction or add a precondition to the DB write (e.g., include WHERE
cluster.deleted_time IS NULL in the INSERT/UPDATE SQL) so Create()/Replace()
fail if the cluster is marked deleted; ensure the service/DAO returns a clear
ConflictState error when that condition is hit.

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