Skip to content

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

Draft
kunzhao-nv wants to merge 1 commit into
mainfrom
feat/tray-location
Draft

feat: Support filter by location info in Flow Tray API#545
kunzhao-nv wants to merge 1 commit 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 slot and trayIdx 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

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0e152469-d512-41e3-a830-45b7ac8d2862

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces position-based filtering for tray endpoints via optional slot and trayIdx query parameters. The implementation adds validation constraints, REST-side position resolution through Flow's GetTrays workflow, filtered component processing with adjusted pagination, and comprehensive test coverage across list, validation, and batch operation handlers.

Changes

Tray Position Targeting

Layer / File(s) Summary
Position Filter Data Contracts
api/pkg/api/model/tray.go
TrayFilter, APITrayGetAllRequest, and APITrayValidateAllRequest now accept optional Slot and TrayIdx fields with strict validation: trayIdx requires slot, slot requires rack targeting, and slot is incompatible with component-id targeting. Each type provides HasPositionFilter and MatchesPosition helpers to support REST-side filtering and pagination re-calculation.
Handler Position Resolution and GetAllTray Filtering
api/pkg/api/handler/tray.go (lines 27–29, 52–187, 387–388, 501–505, 551–588)
New helper functions resolve rack slot/trayIdx to component UUIDs via Flow's GetTrays workflow and convert matched UUIDs into OperationTargetSpec. GetAllTrayHandler disables Flow-side pagination when position filters are present, applies REST-side filtering by MatchesPosition, recalculates offset/limit pagination after filtering, then converts the final slice to APITray objects.
Validation and Batch Operation Integration
api/pkg/api/handler/tray.go (lines 813–814, 898–917, 1200–1217, 1465–1482)
ValidateTraysHandler resolves position filters to component UUIDs before constructing the validation target spec, returning an empty result when no trays match. BatchUpdateTrayPowerStateHandler and BatchUpdateTrayFirmwareHandler resolve position-filtered requests into component TargetSpec, returning success with empty payloads when no UUIDs match.
Position Filter Validation and Matching Tests
api/pkg/api/model/tray_test.go
Test table for APITrayGetAllRequest_Validate extended with slot/trayIdx constraint cases. New TestTrayFilter_PositionFilter verifies HasPositionFilter and MatchesPosition semantics across nil filters, rack-scoped slots, and position mismatch rejection. New TestTrayFilter_Validate_PositionConstraints asserts valid/invalid Slot/TrayIdx configurations and enforcement of incompatibility with component-id targeting.
OpenAPI Specification Updates
openapi/spec.yaml
Documents slot and trayIdx optional query parameters for /v2/org/{org}/nico/tray and /v2/org/{org}/nico/tray/validation endpoints with constraints enforcing rack targeting requirement and trayIdx dependency. Adds slot and trayIdx (int32) properties to the TrayFilter schema component with updated constraint documentation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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
Title check ✅ Passed The title accurately captures the primary feature: adding slot and trayIdx location-based filtering to the Flow Tray API endpoints.
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.
Description check ✅ Passed The PR description clearly articulates the feature scope: adding optional slot and trayIdx parameters to enable position-based tray filtering, directly corresponding to the changeset across API models and handlers.

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

✨ Finishing Touches
🧪 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 416 tests  +22   9 416 ✅ +22   7m 22s ⏱️ +9s
  156 suites ± 0       0 💤 ± 0 
   14 files   ± 0       0 ❌ ± 0 

Results for commit d23ad8b. ± Comparison against base commit 538260d.

♻️ 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

// @Param componentId query string false "Filter by component ID (use repeated params for multiple values)"
// @Param id query string false "Filter by tray UUID (use repeated params for multiple values)"
// @Param slot query int false "Filter by rack slot ID. Requires rackId or rackName; mutually exclusive with id/componentId."
// @Param trayIdx query int false "Disambiguates trays sharing the same slot. Requires slot."
Copy link
Copy Markdown
Contributor

@zhaozhongn zhaozhongn May 19, 2026

Choose a reason for hiding this comment

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

Why? Slot number is per rack, tray index is per <rack, tray type>. These two things have nothing to do with each other. A tray is always staying in one slot and no trays share a same slot.

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