Skip to content

feat: Support filter by location info in Flow Tray API#545

Open
kunzhao-nv wants to merge 4 commits into
mainfrom
feat/tray-location
Open

feat: Support filter by location info in Flow Tray API#545
kunzhao-nv wants to merge 4 commits into
mainfrom
feat/tray-location

Conversation

@kunzhao-nv
Copy link
Copy Markdown
Contributor

@kunzhao-nv kunzhao-nv commented May 18, 2026

Description

The tray REST APIs already accepted rackId/rackName for selecting trays in a rack, but had no way to point at a specific position within that rack — the natural human identifier of "rack a12, slot 3". Add optional slotId to TrayFilter, APITrayGetAllRequest and APITrayValidateAllRequest.

Type of Change

  • Feature - New feature or functionality (feat:)
  • Fix - Bug fixes (fix:)
  • Chore - Modification or removal of existing functionality (chore:)
  • Refactor - Refactoring of existing functionality (refactor:)
  • Docs - Changes in documentation or OpenAPI schema (docs:)
  • CI - Changes in GitHub workflows. Requires additional scrutiny (ci:)
  • Version - Issuing a new release version (version:)

Services Affected

  • API - API models or endpoints updated
  • Workflow - Workflow service updated
  • DB - DB DAOs or migrations updated
  • Site Manager - Site Manager updated
  • Cert Manager - Cert Manager updated
  • Site Agent - Site Agent updated
  • Flow - Flow service updated
  • Powershelf Manager - Powershelf Manager updated
  • NVSwitch Manager - NVSwitch Manager updated

Related Issues (Optional)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

@kunzhao-nv kunzhao-nv requested a review from a team as a code owner May 18, 2026 07:41
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 18, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@kunzhao-nv kunzhao-nv marked this pull request as draft May 18, 2026 07:42
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d63efa04-0e13-4359-934e-55007f96134c

📥 Commits

Reviewing files that changed from the base of the PR and between 2de950b and 3acfcc2.

📒 Files selected for processing (7)
  • api/pkg/api/handler/tray.go
  • api/pkg/api/model/tray.go
  • api/pkg/api/model/tray_test.go
  • docs/index.html
  • openapi/spec.yaml
  • sdk/standard/api_tray.go
  • sdk/standard/model_tray_filter.go
✅ Files skipped from review due to trivial changes (1)
  • sdk/standard/api_tray.go

Summary by CodeRabbit

  • New Features

    • Tray filtering by rack slot: add optional slotId to list, validate, and batch tray operations to target trays at a specific rack slot.
  • Documentation

    • OpenAPI and API docs updated to describe the slotId query parameter, constraints, and composition semantics.
  • Behavioral Changes

    • Server recalculates totals and applies REST-side filtering/pagination when slotId is used; requests resolving to no trays return empty successful responses.
  • SDK

    • Client models and request builders expose and transmit slotId.
  • Tests

    • Added unit tests covering slotId validation and slot-based filtering.

Walkthrough

Adds optional slotId rack-slot filtering to tray APIs: model fields and helpers, handler helpers that resolve slotId to component UUIDs via Flow GetTrays, REST-side filtering with adjusted pagination, validation and batch integration, tests, OpenAPI, and SDK updates.

Changes

Tray Position Targeting

Layer / File(s) Summary
API model: slotId and position helpers
api/pkg/api/model/tray.go
Adds SlotID *int32 to TrayFilter, APITrayGetAllRequest, APITrayValidateAllRequest; imports strconv; adds RackComponentSlotMatcher, slot validation helpers, HasSlotFilter/MatchesSlot, and includes slotId in QueryValues when present.
Handler helpers, GetAll filtering & pagination
api/pkg/api/handler/tray.go
Adds internal helpers to resolve slotId→component UUIDs by calling Flow GetTrays, builds component-target OperationTargetSpec; GetAllTrayHandler disables Flow pagination when slotId present, filters returned components by position.slotId, recalculates total, applies REST-side pagination, and converts filtered components to APITray.
Validate and batch operations: slot resolution
api/pkg/api/handler/tray.go
ValidateTraysHandler, BatchUpdateTrayPowerStateHandler, and BatchUpdateTrayFirmwareHandler resolve slotId into component UUIDs and target those UUIDs; when resolution yields no matches they return HTTP 200 with empty/nil responses instead of invoking Flow with empty component lists.
Unit tests for position handling
api/pkg/api/model/tray_test.go
Adds tests for SlotID validation and matcher behavior: expanded TestAPITrayGetAllRequest_Validate cases, TestTrayFilter_SlotFilter, TestRackComponentSlotMatcher, and APITrayValidateAllRequest slot-related tests and QueryValues coverage.
OpenAPI and SDK model surface updates
openapi/spec.yaml, sdk/standard/*
OpenAPI: add slotId query parameter to tray list and validation endpoints and TrayFilter.slotId schema property; SDK: add slotId field and builder methods to ApiGetAllTrayRequest and ApiValidateTraysRequest; generated TrayFilter model gains SlotId field, accessors, and serialization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% 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
Title check ✅ Passed The title 'feat: Support filter by location info in Flow Tray API' directly and clearly describes the primary change: adding location-based filtering (slotId) to tray APIs.
Description check ✅ Passed The description explains the feature addition (optional slotId to TrayFilter and request types) and why it was needed (precise position selection within racks).
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tray-location

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

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (3)
api/pkg/api/model/tray_test.go (1)

637-722: ⚡ Quick win

Add symmetric position-filter tests for APITrayValidateAllRequest.

The model now adds slot/tray-index validation/matching/query serialization for APITrayValidateAllRequest, but this file only adds position tests for TrayFilter and APITrayGetAllRequest. Add a small table test set for Validate/HasPositionFilter/MatchesPosition/QueryValues to prevent contract drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/pkg/api/model/tray_test.go` around lines 637 - 722, Add a new
table-driven test in tray_test.go that mirrors the TrayFilter position tests but
targets APITrayValidateAllRequest: create cases exercising Validate(),
HasPositionFilter(), MatchesPosition(component *flowv1.Component) and
QueryValues() for combinations (nil request, rack-only, slot-only with rack,
slot+trayIdx, trayIdx without slot, slot with IDs) and assert expected boolean
results and validation errors; reference the APITrayValidateAllRequest type and
its methods Validate, HasPositionFilter, MatchesPosition, and QueryValues so the
test verifies validation, position matching behavior, and generated query params
to prevent contract drift.
api/pkg/api/model/tray.go (1)

193-208: ⚡ Quick win

Consolidate duplicated position-matching logic to prevent drift.

MatchesPosition is implemented three times with identical logic. Extracting a shared helper will reduce future divergence risk and keep behavior consistent across filters/requests.

♻️ Proposed refactor
+func hasPositionFilter(slot *int32) bool {
+	return slot != nil
+}
+
+func matchesRackPosition(comp *flowv1.Component, slot, trayIdx *int32) bool {
+	if slot == nil {
+		return true
+	}
+	pos := comp.GetPosition()
+	if pos == nil {
+		return false
+	}
+	if pos.GetSlotId() != *slot {
+		return false
+	}
+	if trayIdx != nil && pos.GetTrayIdx() != *trayIdx {
+		return false
+	}
+	return true
+}
+
 func (f *TrayFilter) HasPositionFilter() bool {
-	return f != nil && f.Slot != nil
+	return f != nil && hasPositionFilter(f.Slot)
 }
 
 func (f *TrayFilter) MatchesPosition(comp *flowv1.Component) bool {
-	if f == nil || f.Slot == nil {
-		return true
-	}
-	pos := comp.GetPosition()
-	if pos == nil {
-		return false
-	}
-	if pos.GetSlotId() != *f.Slot {
-		return false
-	}
-	if f.TrayIdx != nil && pos.GetTrayIdx() != *f.TrayIdx {
-		return false
-	}
-	return true
+	if f == nil {
+		return true
+	}
+	return matchesRackPosition(comp, f.Slot, f.TrayIdx)
 }

Also applies to: 374-389, 572-587

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/pkg/api/model/tray.go` around lines 193 - 208, Multiple identical
implementations of MatchesPosition are drifting; extract a single helper
function (e.g., PositionMatches or matchesComponentPosition) in the same package
and replace each MatchesPosition implementation with a call to it. The helper
should accept a *flowv1.Component or its Position plus the slot and trayIdx
pointers, perform the nil checks and comparisons (f == nil || slot == nil =>
true; pos == nil => false; slot mismatch => false; trayIdx mismatch => false),
and return the boolean; update the existing methods named MatchesPosition to
delegate to this helper so all three locations use the same shared logic.
api/pkg/api/handler/tray.go (1)

559-559: 💤 Low value

Consider a more explicit slice initialization for clarity.

The expression components[:0:0] is a valid Go idiom to create a new slice that will allocate a fresh backing array on append, but it may hinder readability for developers unfamiliar with three-index slice syntax. A more conventional approach improves intent signaling.

♻️ Suggested refactor
-		filtered := components[:0:0]
+		filtered := make([]*flowv1.Component, 0, len(components))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/pkg/api/handler/tray.go` at line 559, Replace the terse three-index slice
expression components[:0:0] (used to initialize filtered) with an explicit make
call using the element type of components (e.g., filtered := make([]ElementType,
0, 0)); locate the element type from the declaration of components and use that
type in make to create a clear zero-length zero-capacity slice, keeping the rest
of the logic that appends into filtered unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@api/pkg/api/handler/tray.go`:
- Line 559: Replace the terse three-index slice expression components[:0:0]
(used to initialize filtered) with an explicit make call using the element type
of components (e.g., filtered := make([]ElementType, 0, 0)); locate the element
type from the declaration of components and use that type in make to create a
clear zero-length zero-capacity slice, keeping the rest of the logic that
appends into filtered unchanged.

In `@api/pkg/api/model/tray_test.go`:
- Around line 637-722: Add a new table-driven test in tray_test.go that mirrors
the TrayFilter position tests but targets APITrayValidateAllRequest: create
cases exercising Validate(), HasPositionFilter(), MatchesPosition(component
*flowv1.Component) and QueryValues() for combinations (nil request, rack-only,
slot-only with rack, slot+trayIdx, trayIdx without slot, slot with IDs) and
assert expected boolean results and validation errors; reference the
APITrayValidateAllRequest type and its methods Validate, HasPositionFilter,
MatchesPosition, and QueryValues so the test verifies validation, position
matching behavior, and generated query params to prevent contract drift.

In `@api/pkg/api/model/tray.go`:
- Around line 193-208: Multiple identical implementations of MatchesPosition are
drifting; extract a single helper function (e.g., PositionMatches or
matchesComponentPosition) in the same package and replace each MatchesPosition
implementation with a call to it. The helper should accept a *flowv1.Component
or its Position plus the slot and trayIdx pointers, perform the nil checks and
comparisons (f == nil || slot == nil => true; pos == nil => false; slot mismatch
=> false; trayIdx mismatch => false), and return the boolean; update the
existing methods named MatchesPosition to delegate to this helper so all three
locations use the same shared logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cd04c7de-a0a2-47d7-ba1f-a6042581bc7b

📥 Commits

Reviewing files that changed from the base of the PR and between 538260d and 2ff17c9.

📒 Files selected for processing (4)
  • api/pkg/api/handler/tray.go
  • api/pkg/api/model/tray.go
  • api/pkg/api/model/tray_test.go
  • openapi/spec.yaml

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Test Results

9 605 tests  +29   9 605 ✅ +29   6m 42s ⏱️ -43s
  159 suites ± 0       0 💤 ± 0 
   14 files   ± 0       0 ❌ ± 0 

Results for commit 3acfcc2. ± Comparison against base commit 23be0ac.

♻️ This comment has been updated with latest results.

Add slot and trayIdx as independent position filters on TrayFilter and
the list/validate request types. Either, both, or neither may be set,
and they compose with the rest of the filter via AND: a tray must
satisfy every set field to match. An incompatible combination
(e.g. ids that don't sit at the requested slot) is not a 400 — it
just yields an empty result, matching the AND-of-filters semantics
the rest of the filter already follows.

Flow has no by-position component-target shape, so the REST layer
resolves position to component UUIDs by running GetTrays against
whatever scope the request would otherwise have produced (rack scope,
ids, componentIds, or site-wide), post-filters the response by slot
and trayIdx, and drives the downstream workflow with a ComponentTargets
spec built from the resolved UUIDs. The list handler skips Flow-side
pagination when a position filter is active and re-paginates after the
post-filter so the total count reflects the filtered set.

Signed-off-by: Kun Zhao <kunzhao@nvidia.com>
@kunzhao-nv kunzhao-nv force-pushed the feat/tray-location branch from 2ff17c9 to d23ad8b Compare May 18, 2026 08:08
@github-actions
Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-05-18 08:09:09 UTC | Commit: d23ad8b

Comment thread api/pkg/api/handler/tray.go Outdated
Drop trayIdx position filtering and rename slot to slotId so query/body
parameters align with TrayPosition.slotId. REST resolves slotId to
component UUIDs before driving Flow workflows, as before.

Signed-off-by: Kun Zhao <kunzhao@nvidia.com>
# Conflicts:
#	docs/index.html
@kunzhao-nv kunzhao-nv marked this pull request as ready for review May 22, 2026 06:17
Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (2)
api/pkg/api/model/tray_test.go (1)

449-473: ⚡ Quick win

Add negative slotId validation test cases

New slot-based tests cover valid compositions, but they do not assert rejection for invalid negative slotId. Please add one negative case for APITrayGetAllRequest.Validate() and one for TrayFilter.Validate().

Also applies to: 666-701

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/pkg/api/model/tray_test.go` around lines 449 - 473, Add negative test
cases asserting validation fails for negative slotId: in the
APITrayGetAllRequest table (tests exercising APITrayGetAllRequest.Validate) add
a case like name: "negative slotId invalid", req: APITrayGetAllRequest{SlotID:
int32Ptr(-1)}, wantErr: true; and in the TrayFilter table (tests for
TrayFilter.Validate) add a case like name: "negative slotId invalid", filter:
TrayFilter{SlotID: int32Ptr(-1)}, wantErr: true. Use the same int32Ptr helper
already in the file and ensure the test harness expects an error for these cases
so APITrayGetAllRequest.Validate and TrayFilter.Validate are exercised for
negative slot IDs.
api/pkg/api/handler/tray.go (1)

113-113: ⚡ Quick win

Split assign-and-condition for error handling

Please split the inline assignment into separate statements to match repository Go conventions.

Proposed fix
-	if err := we.Get(wfCtx, &resp); err != nil {
+	err = we.Get(wfCtx, &resp)
+	if err != nil {
 		var timeoutErr *tp.TimeoutError
 		if errors.As(err, &timeoutErr) || errors.Is(err, context.DeadlineExceeded) || wfCtx.Err() != nil {
 			return nil, fmt.Errorf("GetTrays workflow timed out: %w", err)
 		}
 		return nil, fmt.Errorf("get GetTrays result: %w", err)
 	}
As per coding guidelines: "Split assign-and-condition into two statements; prefer separate `derr := action()` and `if derr != nil` over `if derr := action(); derr != nil`."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/pkg/api/handler/tray.go` at line 113, The inline assignment in the if
statement using we.Get(wfCtx, &resp) should be split into two statements
following repo Go conventions: first call we.Get and store the error in a named
variable (e.g., derr or err) and then check that variable in a separate if
(e.g., derr := we.Get(wfCtx, &resp); if derr != nil -> split to derr :=
we.Get(wfCtx, &resp) on its own line, then if derr != nil { ... }). Update the
code around we.Get, wfCtx, and resp to use the separated assignment and
conditional check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/pkg/api/handler/tray.go`:
- Around line 61-69: The matches method can panic when comp is nil; add a nil
guard at the top of positionMatcher.matches to return false if comp == nil
before calling comp.GetPosition(). Keep the existing p.active() logic (which can
stay before or after the nil check), then safely call comp.GetPosition() and
compare slot IDs as before (referencing positionMatcher.matches,
comp.GetPosition, and p.SlotID).

In `@api/pkg/api/model/tray.go`:
- Around line 131-132: The new SlotID pointer field on API tray requests can be
negative and currently lacks validation; update both
APITrayGetAllRequest.Validate() and APITrayValidateAllRequest.Validate() to
reject negative values by adding the same check used elsewhere (e.g., if
r.SlotID != nil && *r.SlotID < 0 { return validation error }). Ensure the error
message clearly references SlotID and returns a validation error consistent with
existing patterns so negative slot IDs are rejected early.
- Around line 110-118: matchesRackSlot currently dereferences comp without
checking for nil which can cause a panic; add a nil guard at the top of the
function (check comp == nil) and return false if comp is nil, then proceed to
the existing slotID and position checks (use the existing comp.GetPosition() and
pos.GetSlotId() logic unchanged). Ensure you reference the same function name
matchesRackSlot and variables comp and slotID so callers' behavior stays
consistent.

---

Nitpick comments:
In `@api/pkg/api/handler/tray.go`:
- Line 113: The inline assignment in the if statement using we.Get(wfCtx, &resp)
should be split into two statements following repo Go conventions: first call
we.Get and store the error in a named variable (e.g., derr or err) and then
check that variable in a separate if (e.g., derr := we.Get(wfCtx, &resp); if
derr != nil -> split to derr := we.Get(wfCtx, &resp) on its own line, then if
derr != nil { ... }). Update the code around we.Get, wfCtx, and resp to use the
separated assignment and conditional check.

In `@api/pkg/api/model/tray_test.go`:
- Around line 449-473: Add negative test cases asserting validation fails for
negative slotId: in the APITrayGetAllRequest table (tests exercising
APITrayGetAllRequest.Validate) add a case like name: "negative slotId invalid",
req: APITrayGetAllRequest{SlotID: int32Ptr(-1)}, wantErr: true; and in the
TrayFilter table (tests for TrayFilter.Validate) add a case like name: "negative
slotId invalid", filter: TrayFilter{SlotID: int32Ptr(-1)}, wantErr: true. Use
the same int32Ptr helper already in the file and ensure the test harness expects
an error for these cases so APITrayGetAllRequest.Validate and
TrayFilter.Validate are exercised for negative slot IDs.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 49b2878c-e6b3-438f-af5b-3063832d21fb

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff17c9 and 2de950b.

📒 Files selected for processing (7)
  • api/pkg/api/handler/tray.go
  • api/pkg/api/model/tray.go
  • api/pkg/api/model/tray_test.go
  • docs/index.html
  • openapi/spec.yaml
  • sdk/standard/api_tray.go
  • sdk/standard/model_tray_filter.go
💤 Files with no reviewable changes (3)
  • sdk/standard/api_tray.go
  • openapi/spec.yaml
  • sdk/standard/model_tray_filter.go

Comment thread api/pkg/api/handler/tray.go Outdated
Comment thread api/pkg/api/model/tray.go Outdated
Comment thread api/pkg/api/model/tray.go Outdated
Copy link
Copy Markdown
Contributor

@thossain-nv thossain-nv left a comment

Choose a reason for hiding this comment

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

@kunzhao-nv Can ComponentTarget accept SlotID? Then we don't have to make an extra call to resolve TrayID.

Comment thread api/pkg/api/model/tray.go Outdated
Comment thread api/pkg/api/model/tray.go Outdated
Comment thread api/pkg/api/handler/tray.go Outdated
Comment thread api/pkg/api/handler/tray.go Outdated
Comment thread api/pkg/api/handler/tray.go Outdated
@kunzhao-nv
Copy link
Copy Markdown
Contributor Author

@kunzhao-nv Can ComponentTarget accept SlotID? Then we don't have to make an extra call to resolve TrayID.

Per earlier team discussion, we don't want to extend ComponentTarget for now.

Require rackId or rackName when slotId is set, reject negative slot IDs,
and consolidate slot matching under RackComponentSlotMatcher with clearer
handler resolver naming. Regenerate OpenAPI docs, SDK, and tests.

Signed-off-by: Kun Zhao <kunzhao@nvidia.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.

3 participants