Skip to content

Conversation

@xbhouse
Copy link

@xbhouse xbhouse commented Jan 27, 2026

Updating or deleting a template from more than 1000 systems at one time results in a candlepin error, likely because we are hitting a defined limit:

{"@timestamp":"2026-01-24T17:00:14.896Z","err":"candlepin call https://admin.rhsm.stage.redhat.com/candlepin/owners/20007024/consumers/environments failed: HTTP call failed, status code: 400: Request failed: received non 2xx status code, status code: 400: candlepin error","level":"warning","message":"call to candlepin failed"}

This PR limits the number of requested systems to 1000 in the update and delete template system apis.

Summary by Sourcery

Enforce a maximum number of systems that can be updated or removed from a template in a single API call to prevent downstream Candlepin errors.

Bug Fixes:

  • Reject update and delete template system requests that include more systems than the supported limit to avoid Candlepin 400 errors.

Enhancements:

  • Introduce a configurable constant for the maximum number of systems allowed per template systems update/delete request and validate incoming payloads against this limit.
  • Add tests covering over-limit template systems update and delete scenarios and their corresponding error responses.

@jira-linking
Copy link

jira-linking bot commented Jan 27, 2026

Referenced Jiras:
https://issues.redhat.com/browse/RHINENG-22863

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 27, 2026

Reviewer's Guide

Adds a hard limit of 1000 systems per request to the template systems update and delete APIs and validates incoming payloads against this limit, returning a 400 error with a clear message when exceeded, along with tests for the new behavior.

Sequence diagram for template systems update with max-systems validation

sequenceDiagram
    actor Client
    participant GinRouter
    participant TemplateSystemsUpdateHandler
    participant Utils
    participant Candlepin

    Client->>GinRouter: HTTP PATCH /templates/{id}/systems
    GinRouter->>TemplateSystemsUpdateHandler: TemplateSystemsUpdateHandler(c)
    TemplateSystemsUpdateHandler->>TemplateSystemsUpdateHandler: ParseAndValidateRequestBody
    TemplateSystemsUpdateHandler->>TemplateSystemsUpdateHandler: Check account and groups
    TemplateSystemsUpdateHandler->>TemplateSystemsUpdateHandler: len(req.Systems) > TemplateSystemsUpdateLimit?
    alt Over_limit
        TemplateSystemsUpdateHandler->>Utils: LogAndRespBadRequest(c, err, msg)
        Utils-->>Client: HTTP 400 Cannot update more than 1000 systems at once
    else Within_limit
        TemplateSystemsUpdateHandler->>TemplateSystemsUpdateHandler: DBFromContext(c)
        TemplateSystemsUpdateHandler->>Candlepin: Update template systems
        Candlepin-->>TemplateSystemsUpdateHandler: Success
        TemplateSystemsUpdateHandler-->>Client: HTTP 200 OK
    end
Loading

Sequence diagram for template systems delete with max-systems validation

sequenceDiagram
    actor Client
    participant GinRouter
    participant TemplateSystemsDeleteHandler
    participant Utils
    participant Candlepin

    Client->>GinRouter: HTTP DELETE /templates/{id}/systems
    GinRouter->>TemplateSystemsDeleteHandler: TemplateSystemsDeleteHandler(c)
    TemplateSystemsDeleteHandler->>TemplateSystemsDeleteHandler: ParseAndValidateRequestBody
    TemplateSystemsDeleteHandler->>TemplateSystemsDeleteHandler: Check account and groups
    TemplateSystemsDeleteHandler->>TemplateSystemsDeleteHandler: len(req.Systems) > TemplateSystemsUpdateLimit?
    alt Over_limit
        TemplateSystemsDeleteHandler->>Utils: LogAndRespBadRequest(c, err, msg)
        Utils-->>Client: HTTP 400 Cannot update more than 1000 systems at once
    else Within_limit
        TemplateSystemsDeleteHandler->>TemplateSystemsDeleteHandler: DBFromContext(c)
        TemplateSystemsDeleteHandler->>Candlepin: Remove systems from template
        Candlepin-->>TemplateSystemsDeleteHandler: Success
        TemplateSystemsDeleteHandler-->>Client: HTTP 200 OK
    end
Loading

Updated class diagram for template systems controllers with max-systems limit

classDiagram
    class TemplateSystemsUpdateRequest {
        +[]string Remove
        +[]string Apply
        +[]string Systems
    }

    class TemplateSystemsUpdateHandlerController {
        +TemplateSystemsUpdateHandler(c *gin.Context)
        +int TemplateSystemsUpdateLimit
    }

    class TemplateSystemsDeleteHandlerController {
        +TemplateSystemsDeleteHandler(c *gin.Context)
    }

    class Utils {
        +LogAndRespBadRequest(c *gin.Context, err error, msg string)
    }

    TemplateSystemsUpdateHandlerController --> TemplateSystemsUpdateRequest : uses
    TemplateSystemsUpdateHandlerController --> Utils : calls
    TemplateSystemsDeleteHandlerController --> Utils : calls
    TemplateSystemsDeleteHandlerController --> TemplateSystemsUpdateHandlerController : uses_TemplateSystemsUpdateLimit
Loading

File-Level Changes

Change Details Files
Enforce a maximum systems-per-request limit for template system updates and deletions.
  • Introduce a shared TemplateSystemsUpdateLimit constant set to 1000.
  • In TemplateSystemsUpdateHandler, validate the incoming Systems slice length against TemplateSystemsUpdateLimit and short‑circuit with a 400 response when exceeded.
  • In TemplateSystemsDeleteHandler, add the same length check and 400 response behavior using utils.LogAndRespBadRequest.
manager/controllers/template_systems_update.go
manager/controllers/template_systems_delete.go
Extend tests to cover over-limit scenarios for template system update and delete APIs and assert error responses.
  • Modify testTemplateSystemsDelete helper to return the response recorder so callers can inspect the response body.
  • Add TestTemplateSystemsDeleteTooManySystems to construct a template with TemplateSystemsUpdateLimit+1 systems, invoke delete, and assert a 400 with the expected error message.
  • Add TestUpdateTemplateTooManySystems to send a PUT with TemplateSystemsUpdateLimit+1 systems and assert a 400 response and error message.
  • Use uuid generation and sonic JSON marshaling in tests to create large system lists and request bodies.
manager/controllers/template_systems_delete_test.go
manager/controllers/template_systems_update_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@xbhouse xbhouse marked this pull request as ready for review January 27, 2026 20:22
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The over-limit check and error message are duplicated between TemplateSystemsUpdateHandler and TemplateSystemsDeleteHandler; consider extracting a small shared helper (or shared error message constant) to keep behavior and wording in sync.
  • The error message uses "update" even in the delete handler and its tests; consider rephrasing to something operation-agnostic like "process" or using separate messages for update vs delete to avoid confusing API consumers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The over-limit check and error message are duplicated between TemplateSystemsUpdateHandler and TemplateSystemsDeleteHandler; consider extracting a small shared helper (or shared error message constant) to keep behavior and wording in sync.
- The error message uses "update" even in the delete handler and its tests; consider rephrasing to something operation-agnostic like "process" or using separate messages for update vs delete to avoid confusing API consumers.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov-commenter
Copy link

codecov-commenter commented Jan 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.30%. Comparing base (50a0d9e) to head (95ef4b9).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2031      +/-   ##
==========================================
+ Coverage   59.25%   59.30%   +0.05%     
==========================================
  Files         134      134              
  Lines        8615     8626      +11     
==========================================
+ Hits         5105     5116      +11     
  Misses       2967     2967              
  Partials      543      543              
Flag Coverage Δ
unittests 59.30% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@Dugowitch Dugowitch left a comment

Choose a reason for hiding this comment

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

Looks good!

Copy link
Collaborator

@MichaelMraka MichaelMraka left a comment

Choose a reason for hiding this comment

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

Thanks and congratulation on your first PR to Patch. ;)

err := errors.New(msg)
utils.LogAndRespBadRequest(c, err, msg)
return
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think sourcery has a good point this could be a small helper function to keep wording an behavior consitent, e.g.

func CheckTemplateSystemsLimit(nSystems int) err {
	if len(nSystems) > TemplateSystemsUpdateLimit {
		msg := fmt.Sprintf("Cannot update more than %d systems at once", TemplateSystemsUpdateLimit)
		err := errors.New(msg)
		utils.LogAndRespBadRequest(c, err, msg)
		return err
	}
    return nil
}

and then in code

if err = CheckTemplateSystemsLimit(len(req.Systems)); err != nil {
    // result code handeled in CheckTemplateSystemsLimit()
    return
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

And possibly 'Cannot update' could be changed to some neutral wording which fits both update and delete. Maybe 'Cannot process'?!?

Copy link
Author

Choose a reason for hiding this comment

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

oh yes this is better, thank you! updated :)

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.

4 participants