Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkg/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
// Conflict errors (CNF) - 409
CodeConflictExists = "HYPERFLEET-CNF-001"
CodeConflictVersion = "HYPERFLEET-CNF-002"
CodeConflictState = "HYPERFLEET-CNF-003"

// Rate Limit errors (LMT) - 429
CodeRateLimitExceeded = "HYPERFLEET-LMT-001"
Expand Down Expand Up @@ -166,6 +167,10 @@ var errorDefinitions = map[string]errorDefinition{
CodeConflictVersion: {
ErrorTypeConflict, "Version Conflict", "The resource version does not match", http.StatusConflict,
},
CodeConflictState: {
ErrorTypeConflict, "State Conflict",
"Operation not allowed in current resource state", http.StatusConflict,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

nit — non-blocking suggestion

Category: Standards

The error model standard defines HYPERFLEET-CNF-003 as "Operation not allowed in current state" but the implementation here says "Operation not allowed in current resource state". In practice it doesn't matter since ConflictState() calls always pass a custom reason, but matching the standard keeps things consistent.

CodeConflictState: {
    ErrorTypeConflict, "State Conflict",
    "Operation not allowed in current state", http.StatusConflict,
},

},

// Rate Limit errors (LMT) - 429
CodeRateLimitExceeded: {
Expand Down Expand Up @@ -363,6 +368,10 @@ func Conflict(reason string, values ...interface{}) *ServiceError {
return New(CodeConflictExists, reason, values...)
}

func ConflictState(reason string, values ...interface{}) *ServiceError {
return New(CodeConflictState, reason, values...)
}

func Validation(reason string, values ...interface{}) *ServiceError {
return New(CodeValidationMultiple, reason, values...)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/handlers/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (h ClusterHandler) Patch(w http.ResponseWriter, r *http.Request) {
return nil, err
}

if found.DeletedTime != nil {
return nil, errors.ConflictState("Cluster '%s' is marked for deletion", id)
}

if patch.Spec != nil {
specJSON, err := json.Marshal(*patch.Spec)
if err != nil {
Expand Down
14 changes: 13 additions & 1 deletion pkg/handlers/cluster_nodepools.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,15 @@ func (h ClusterNodePoolsHandler) Patch(w http.ResponseWriter, r *http.Request) {
clusterID := mux.Vars(r)["id"]
nodePoolID := mux.Vars(r)["nodepool_id"]

_, err := h.clusterService.Get(ctx, clusterID)
cluster, err := h.clusterService.Get(ctx, clusterID)
if err != nil {
return nil, err
}

if cluster.DeletedTime != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

nit — non-blocking suggestion

Category: JIRA

The JIRA ticket HYPERFLEET-971 acceptance criteria says "PUT /clusters/:clusterID/nodepools/:nodepoolID returns 409" but the API only exposes PATCH for nodepool updates — no PUT endpoint exists. The implementation here is correct (guarding PATCH), but worth updating the JIRA AC to match reality so the ticket closes cleanly.

return nil, errors.ConflictState("Cluster '%s' is marked for deletion", clusterID)
}
Comment thread
kuudori marked this conversation as resolved.

found, err := h.nodePoolService.Get(ctx, nodePoolID)
if err != nil {
return nil, err
Expand All @@ -204,6 +208,10 @@ func (h ClusterNodePoolsHandler) Patch(w http.ResponseWriter, r *http.Request) {
return nil, errors.NotFound("NodePool '%s' not found for cluster '%s'", nodePoolID, clusterID)
}

if found.DeletedTime != nil {
return nil, errors.ConflictState("NodePool '%s' is marked for deletion", nodePoolID)
}

if patch.Spec != nil {
specJSON, jsonErr := json.Marshal(*patch.Spec)
if jsonErr != nil {
Expand Down Expand Up @@ -258,6 +266,10 @@ func (h ClusterNodePoolsHandler) Create(w http.ResponseWriter, r *http.Request)
return nil, err
}

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

// Use the presenters.ConvertNodePool helper to convert the request
nodePoolModel, convErr := presenters.ConvertNodePool(&req, cluster.ID, "system@hyperfleet.local")
if convErr != nil {
Expand Down
Loading