Skip to content

feat(rest-api): Add SKU management endpoints - #3633

Merged
nvlitagaki merged 18 commits into
NVIDIA:mainfrom
nvlitagaki:feat/sku-management-rest
Jul 31, 2026
Merged

feat(rest-api): Add SKU management endpoints#3633
nvlitagaki merged 18 commits into
NVIDIA:mainfrom
nvlitagaki:feat/sku-management-rest

Conversation

@nvlitagaki

@nvlitagaki nvlitagaki commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Provider Admins can create, partially update, and delete a SKU for a selected Site. The REST API sends the mutation through Temporal and the Site Agent to that Site's real NICo Core service. POST and PATCH return the Core-backed SKU shape; PATCH preserves fields omitted from the request.

This adds REST API support for managing SKUs.

Resolves #2810

What this PR does

Adds Site-scoped REST endpoints to create, partially update, and delete Core-backed SKUs. Mutations run through Temporal and the Site Agent to the selected Site's NICo Core. The writable schema includes SKU v5 storage size bounds and PCI patterns; ignored legacy write fields such as capacityMb are rejected.

@copy-pr-bot

copy-pr-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@nvlitagaki
nvlitagaki requested a review from thossain-nv July 16, 2026 23:45
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds REST API support for creating, updating, and deleting SKUs through validated request models, Core-backed handlers, route registration, OpenAPI contracts, component mappings, authorization checks, projection reconciliation, and tests.

Changes

SKU lifecycle operations

Layer / File(s) Summary
SKU mutation contracts and component mapping
rest-api/api/pkg/api/model/sku.go, rest-api/api/pkg/api/model/sku_test.go
Adds validated create/update requests, protobuf conversion and patching, mutation responses, expanded component fields, storage constraints, and model tests.
SKU description persistence and reconciliation
rest-api/db/pkg/db/model/sku.go, rest-api/db/pkg/db/model/sku_test.go, rest-api/db/pkg/migrations/..., rest-api/workflow/pkg/activity/sku/*
Adds persisted SKU descriptions, DAO mapping, migration support, inventory reconciliation, and description-focused tests.
Core-backed SKU mutation handlers
rest-api/api/pkg/api/handler/sku.go, rest-api/api/pkg/api/handler/sku_test.go
Adds authorized create, update, and delete handlers, projection upsert and retry logic, Core RPC routing, fallback responses, and comprehensive handler coverage.
SKU routes and OpenAPI contracts
rest-api/api/pkg/api/routes.go, rest-api/api/pkg/api/routes_test.go, rest-api/openapi/spec.yaml
Registers POST, PATCH, and DELETE SKU endpoints, verifies route registration, and defines mutation operations and schemas.
Duplicate-ID persistence handling
crates/api-db/src/sku.rs, crates/api-core/src/handlers/sku.rs, crates/api-core/src/tests/sku.rs
Maps duplicate SKU inserts to structured already-found errors, explicitly converts handler errors, and verifies duplicate creation behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SKUHandlers
  participant Core
  participant RESTDatabase
  Client->>SKUHandlers: Submit SKU mutation
  SKUHandlers->>SKUHandlers: Validate request and authorize Site
  SKUHandlers->>Core: Create, update, or delete SKU
  Core-->>SKUHandlers: Return mutation result
  SKUHandlers->>RESTDatabase: Reconcile or remove SKU projection
  RESTDatabase-->>SKUHandlers: Persist projection result
  SKUHandlers-->>Client: Return HTTP 201, 200, or 204
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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 is concise and accurately summarizes the main change: adding SKU management endpoints.
Description check ✅ Passed The description is clearly aligned with the SKU create, update, and delete work in this PR.
Linked Issues check ✅ Passed The PR implements the requested POST, PATCH, and DELETE SKU endpoints from #2810.
Out of Scope Changes check ✅ Passed The additional schema, model, and projection updates support the SKU lifecycle feature and are not out of scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@nvlitagaki
nvlitagaki marked this pull request as ready for review July 20, 2026 14:58
@nvlitagaki
nvlitagaki requested a review from a team as a code owner July 20, 2026 14:58
@github-actions

Copy link
Copy Markdown

@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-07-20 15:02:10 UTC | Commit: fafa06e

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rest-api/openapi/spec.yaml (1)

167-171: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale tag description now contradicts the new SKU mutation endpoints.

This description still asserts "SKUs are automatically derived from machine hardware characteristics and used to group similar machines. SKUs are read-only and managed by the system." This PR adds POST, PATCH, and DELETE under the SKU tag (create-sku, update-sku, delete-sku), so the tag-level claim of "read-only" is now inaccurate and will mislead SDK consumers and documentation readers about the resource's actual mutability.

📝 Proposed fix
   - name: SKU
     description: |-
       SKU (Stock Keeping Unit) defines one or more hardware configurations or Machine Bill of Materials (BOM).

-      SKUs are automatically derived from machine hardware characteristics and used to group similar machines. SKUs are read-only and managed by the system.
+      SKUs are automatically derived from machine hardware characteristics and used to group similar machines. Most SKUs are managed by the system, but Provider Admins can create, update, and delete SKUs on a Site's NICo Core service via the mutation endpoints below.

As per path instructions, "Review OpenAPI docs and examples for accuracy, deprecation clarity, client-facing compatibility, spelling, and consistency with spec.yaml."

🤖 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 `@rest-api/openapi/spec.yaml` around lines 167 - 171, Update the SKU tag
description in spec.yaml to remove the claim that SKUs are read-only and
system-managed, and revise it to accurately reflect that SKUs can be created,
updated, and deleted through the documented mutation endpoints while preserving
the valid description of their purpose.

Source: Path instructions

🧹 Nitpick comments (4)
rest-api/api/pkg/api/handler/sku_management_test.go (1)

55-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for update/delete against a non-existent SKU ID.

No test exercises the 404 path through findSkuByIDViaCore for UpdateSkuHandler or the equivalent not-found path for DeleteSkuHandler. Given the OpenAPI contract explicitly documents 404 -> NotFoundError for both operations, this is a critical path worth locking down with a regression test.

🤖 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 `@rest-api/api/pkg/api/handler/sku_management_test.go` around lines 55 - 97,
Add regression tests alongside TestUpdateSkuHandler_MergesPatchBeforeReplace and
TestDeleteSkuHandler_ProxiesDelete that invoke each handler with a non-existent
SKU ID, assert HTTP 404, and verify the expected not-found error response
without issuing the update or delete core request. Reuse the existing
newSkuManagementFixture and request helpers.
rest-api/api/pkg/api/model/sku.go (1)

106-122: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Refactor APISkuUpdateRequest.Validate() to compose via ozzo instead of early-returning.

The manual if checks bypass ValidateStruct's error aggregation: if SiteID is invalid and SchemaVersion == 0, only the schemaVersion error is returned to the client — the SiteID error is silently swallowed by the early return. As per path instructions, "validation should prefer ozzo-validation built-in rules and composition over reinvented custom validation helpers," and as per coding guidelines, cross-field checks should use named receiver methods with validation.By.

♻️ Suggested refactor
 func (r APISkuUpdateRequest) Validate() error {
-	if r.SchemaVersion != nil && *r.SchemaVersion == 0 {
-		return validation.Errors{"schemaVersion": validation.NewError("validation_min", "schemaVersion must be greater than zero")}
-	}
-	if err := validation.ValidateStruct(&r,
+	return validation.ValidateStruct(&r,
 		validation.Field(&r.SiteID,
 			validation.Required.Error(validationErrorValueRequired),
 			validationis.UUID.Error(validationErrorInvalidUUID)),
-	); err != nil {
-		return err
-	}
-	if r.Description == nil && r.SchemaVersion == nil && r.DeviceType == nil && r.Components == nil {
-		return validation.Errors{"request": validation.NewError("validation_required", "at least one mutable field is required")}
-	}
-	return nil
+		validation.Field(&r.SchemaVersion, validation.By(r.validateSchemaVersion)),
+		validation.Field(&r, validation.By(r.validateHasMutableField)),
+	)
+}
+
+func (r APISkuUpdateRequest) validateSchemaVersion(value interface{}) error {
+	v, _ := value.(*uint32)
+	if v != nil && *v == 0 {
+		return validation.NewError("validation_min", "schemaVersion must be greater than zero")
+	}
+	return nil
+}
+
+func (r APISkuUpdateRequest) validateHasMutableField(interface{}) error {
+	if r.Description == nil && r.SchemaVersion == nil && r.DeviceType == nil && r.Components == nil {
+		return validation.NewError("validation_required", "at least one mutable field is required")
+	}
+	return 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 `@rest-api/api/pkg/api/model/sku.go` around lines 106 - 122, Refactor
APISkuUpdateRequest.Validate to compose all checks through ozzo-validation so
schemaVersion, SiteID, and mutable-field requirements are aggregated instead of
returned early. Move the cross-field rules into named receiver methods and
attach them with validation.By, while preserving the existing validation
messages and requiring schemaVersion to be greater than zero and at least one
mutable field.

Sources: Coding guidelines, Path instructions

rest-api/openapi/spec.yaml (2)

22656-22699: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

SkuMutationResponse omits updated, unlike the sibling Sku schema.

Sku (line ~22578) exposes both created and updated as read-only timestamps, but SkuMutationResponse only carries a nullable created. For the update-sku (PATCH) response in particular, callers have no way to confirm the Core-side update timestamp from the response body — they'd need a follow-up GET just to see updated.

Consider adding a nullable updated field to SkuMutationResponse for parity with Sku and to spare clients an extra round-trip after PATCH.

🤖 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 `@rest-api/openapi/spec.yaml` around lines 22656 - 22699, Add a nullable
date-time updated property to the SkuMutationResponse schema, matching the
existing updated field definition and read-only semantics from the sibling Sku
schema. Keep it optional, consistent with created, and do not alter the existing
required fields.

4637-4662: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing examples: for the new SKU mutation contracts.

None of the new SkuCreateRequest, SkuUpdateRequest, SkuDeleteRequest, or SkuMutationResponse schemas — nor the create-sku/update-sku/delete-sku request bodies — include an examples: block. Virtually every other mutation schema and requestBody in this file (e.g., TenantAccountCreateRequest, SiteCreateRequest, HostFirmwareConfigCreateOrUpdateRequest) includes at least one example. This inconsistency degrades generated SDK docs and Redoc/Swagger UI previews for the new endpoints.

Please add representative examples: blocks to the four new schemas (or at minimum to the requestBody/responses in the three new operations), consistent with the rest of the file.

As per path instructions, when model attributes are added "verify the OpenAPI spec is updated with matching schema, required/nullable semantics, and examples when applicable."

Also applies to: 4780-4831, 22586-22699

🤖 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 `@rest-api/openapi/spec.yaml` around lines 4637 - 4662, Add representative
examples for the new SKU mutation contracts: SkuCreateRequest, SkuUpdateRequest,
SkuDeleteRequest, and SkuMutationResponse, and ensure the create-sku,
update-sku, and delete-sku request/response definitions expose them where
needed. Match the existing schema property names, required fields, and nullable
semantics, following the example structure used by nearby mutation contracts.

Source: Path instructions

🤖 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 `@rest-api/api/pkg/api/handler/sku_management.go`:
- Around line 73-88: The post-create lookup failure in the handler currently
turns a successful Core mutation into an error response. Update the
findSkuByIDViaCore failure path to return a successful creation response built
from the validated apiReq and returned ids.Ids[0], while preserving the existing
fetched-SKU response when lookup succeeds and the existing create-call error
handling.
- Around line 141-152: Update UpdateSkuHandler’s read-modify-replace flow to
carry the version observed in current as an explicit compare-and-swap
precondition on updatedReq or the Core request, rather than relying on
schema_version in the mutable payload. Ensure ExecuteCoreGRPC propagates that
precondition so Core rejects stale concurrent updates, preserving the existing
API error handling for rejected replacements.

---

Outside diff comments:
In `@rest-api/openapi/spec.yaml`:
- Around line 167-171: Update the SKU tag description in spec.yaml to remove the
claim that SKUs are read-only and system-managed, and revise it to accurately
reflect that SKUs can be created, updated, and deleted through the documented
mutation endpoints while preserving the valid description of their purpose.

---

Nitpick comments:
In `@rest-api/api/pkg/api/handler/sku_management_test.go`:
- Around line 55-97: Add regression tests alongside
TestUpdateSkuHandler_MergesPatchBeforeReplace and
TestDeleteSkuHandler_ProxiesDelete that invoke each handler with a non-existent
SKU ID, assert HTTP 404, and verify the expected not-found error response
without issuing the update or delete core request. Reuse the existing
newSkuManagementFixture and request helpers.

In `@rest-api/api/pkg/api/model/sku.go`:
- Around line 106-122: Refactor APISkuUpdateRequest.Validate to compose all
checks through ozzo-validation so schemaVersion, SiteID, and mutable-field
requirements are aggregated instead of returned early. Move the cross-field
rules into named receiver methods and attach them with validation.By, while
preserving the existing validation messages and requiring schemaVersion to be
greater than zero and at least one mutable field.

In `@rest-api/openapi/spec.yaml`:
- Around line 22656-22699: Add a nullable date-time updated property to the
SkuMutationResponse schema, matching the existing updated field definition and
read-only semantics from the sibling Sku schema. Keep it optional, consistent
with created, and do not alter the existing required fields.
- Around line 4637-4662: Add representative examples for the new SKU mutation
contracts: SkuCreateRequest, SkuUpdateRequest, SkuDeleteRequest, and
SkuMutationResponse, and ensure the create-sku, update-sku, and delete-sku
request/response definitions expose them where needed. Match the existing schema
property names, required fields, and nullable semantics, following the example
structure used by nearby mutation contracts.
🪄 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: 034348da-93bf-47db-9932-3b6a9b36c675

📥 Commits

Reviewing files that changed from the base of the PR and between 5002c1c and fafa06e.

⛔ Files ignored due to path filters (9)
  • rest-api/sdk/standard/api_sku.go is excluded by !rest-api/sdk/standard/api_*.go
  • rest-api/sdk/standard/model_sku_chassis.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_components.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_create_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_delete_request.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_ethernet_device.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_infiniband_device.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_mutation_response.go is excluded by !rest-api/sdk/standard/model_*.go
  • rest-api/sdk/standard/model_sku_update_request.go is excluded by !rest-api/sdk/standard/model_*.go
📒 Files selected for processing (7)
  • rest-api/api/pkg/api/handler/sku_management.go
  • rest-api/api/pkg/api/handler/sku_management_test.go
  • rest-api/api/pkg/api/model/sku.go
  • rest-api/api/pkg/api/model/sku_management_test.go
  • rest-api/api/pkg/api/routes.go
  • rest-api/api/pkg/api/routes_test.go
  • rest-api/openapi/spec.yaml

Comment thread rest-api/api/pkg/api/handler/sku_management.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku_management.go Outdated
@ajf

ajf commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

@nvlitagaki moved the PR description text to this comment since it doesn't really need to show up in the final commit message:

Verification

How we verified it

Verified revision 3e1fdcbdbea4fcb42969396499adfbe195aabbab on macOS/Apple Silicon with Docker Desktop, kind cluster nico-rest-local, PostgreSQL, Vault, Temporal, and a real local NICo Core listening on 127.0.0.1:1079.

The default local API image contained an amd64 Go binary despite being tagged as arm64 because the local Dockerfile defaults TARGETARCH=amd64. To exercise the native runtime path, the same PR revision was rebuilt with explicit TARGETOS=linux and TARGETARCH=arm64; the extracted /app/api binary reported ELF aarch64 and GOARCH=arm64.

Hands-on results through REST → Temporal → Site Agent → real Core:

  • POST /v2/org/test-org/nico/sku returned 201 and the Core-backed SKU.
  • PATCH /v2/org/test-org/nico/sku/{id} returned 200, changed only the description, and preserved schemaVersion, deviceType, storage bounds/PCI patterns, and chassis data.
  • A legacy create payload containing capacityMb returned 400 Invalid request body.
  • DELETE /v2/org/test-org/nico/sku/{id} returned 204.
  • A PATCH after deletion returned 404 Could not find SKU with the specified ID.
  • Site Agent logs recorded successful CreateSku, FindSkusByIds, ReplaceSku, and DeleteSku Core RPCs.
  • The temporary verification SKU was deleted.

Supporting check:

go test ./api/pkg/api/handler -run '^TestCreateSkuHandler_ProxiesCreateAndReturnsCreatedSku$' -count=1 -v
PASS

How to reproduce the verification

Prerequisites:

  • macOS/Apple Silicon with Docker Desktop, Go, kubectl, kind, jq, and the repository's local REST stack dependencies.
  • A real local NICo Core with PostgreSQL and Vault, listening on 127.0.0.1:1079. When using dev/mac-local-dev/run-nico-api.sh, point firmware_directory at a writable temporary directory rather than /opt/carbide/firmware.
  • A clean checkout of this revision.
  1. Check out the verified revision and start the REST test database:
git checkout --detach 3e1fdcbdbea4fcb42969396499adfbe195aabbab
cd rest-api
make ensure-postgres
  1. Build the API image natively for Apple Silicon, load it into kind, and deploy it:
docker build --platform linux/arm64 \
  --build-arg TARGETOS=linux \
  --build-arg TARGETARCH=arm64 \
  --build-arg VERSION=3e1fdcbd \
  -t localhost:5000/nico-rest-api:sku-rest-arm64 \
  -f docker/local/Dockerfile.nico-rest-api .
kind load docker-image localhost:5000/nico-rest-api:sku-rest-arm64 --name nico-rest-local
kubectl --context kind-nico-rest-local -n nico-rest \
  set image deployment/nico-rest-api api=localhost:5000/nico-rest-api:sku-rest-arm64
kubectl --context kind-nico-rest-local -n nico-rest \
  rollout status deployment/nico-rest-api --timeout=240s
  1. Point the Site Agent at real Core and confirm the connection:
KUBECONFIG=/tmp/do-work-kind-nico-rest-local.kubeconfig \
  make configure-local-core-site-agent
kubectl --context kind-nico-rest-local -n nico-rest \
  rollout status statefulset/nico-rest-site-agent --timeout=240s
kubectl --context kind-nico-rest-local -n nico-rest \
  logs statefulset/nico-rest-site-agent --tail=100 | grep 'forge.Forge/Version.*code=Ok'

Expected checkpoint: the Site Agent reports a successful Core Version RPC and the selected Site is Registered.

  1. Obtain a token from the local Keycloak instance, then create a unique SKU with schemaVersion: 5, one storage group containing minSizeMb, maxSizeMb, and pciPatterns, and chassis data:
TOKEN="$(curl -fsS -X POST http://localhost:8082/realms/nico/protocol/openid-connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=nico-rest-api' \
  --data-urlencode 'username=admin@example.com' \
  --data-urlencode 'password=adminpassword' \
  --data-urlencode 'grant_type=password' | jq -r .access_token)"
SITE_ID="$(curl -fsS http://localhost:8388/v2/org/test-org/nico/site \
  -H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')"
SKU_ID="sku-rest-verification-$(date +%s)"

curl -i -X POST http://localhost:8388/v2/org/test-org/nico/sku \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg id "$SKU_ID" --arg site "$SITE_ID" '{
    id: $id,
    siteId: $site,
    description: "REST Core verification",
    schemaVersion: 5,
    deviceType: "verification-node",
    storage: [{
      model: "TEST-NVME",
      count: 2,
      minSizeMb: 3600000,
      maxSizeMb: 3900000,
      pciPatterns: ["^/devices/pci.*nvme[0-1]$"]
    }],
    chassis: {
      manufacturer: "NVIDIA",
      model: "Verification Chassis",
      architecture: "aarch64"
    }
  }')"

Expected checkpoint: HTTP 201 and a response containing the same v5 storage and chassis fields.

  1. PATCH only the description and confirm the omitted fields are preserved:
curl -i -X PATCH "http://localhost:8388/v2/org/test-org/nico/sku/$SKU_ID?siteId=$SITE_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"description":"REST Core verification updated"}'

Expected checkpoint: HTTP 200; only the description changes.

  1. Verify legacy write-field rejection, then delete the test SKU and confirm it is gone:
curl -i -X POST http://localhost:8388/v2/org/test-org/nico/sku \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg id "$SKU_ID-legacy" --arg site "$SITE_ID" '{
    id: $id,
    siteId: $site,
    schemaVersion: 5,
    storage: [{model: "TEST-NVME", count: 1, capacityMb: 3600000}]
  }')"

curl -i -X DELETE \
  "http://localhost:8388/v2/org/test-org/nico/sku/$SKU_ID?siteId=$SITE_ID" \
  -H "Authorization: Bearer $TOKEN"

curl -i -X PATCH \
  "http://localhost:8388/v2/org/test-org/nico/sku/$SKU_ID?siteId=$SITE_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"description":"must not exist"}'

Expected results: legacy POST 400, DELETE 204, post-delete PATCH 404.

@kensimon

Copy link
Copy Markdown
Contributor

@nvlitagaki moved the PR description text to this comment since it doesn't really need to show up in the final commit message:

(IMO the "What this PR does" should probably stay, but yeah the rest should move)

@nvlitagaki
nvlitagaki marked this pull request as draft July 21, 2026 17:29
Comment thread rest-api/api/pkg/api/handler/sku_management.go Outdated
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch 2 times, most recently from c0f658d to 1335142 Compare July 23, 2026 23:23
@nvlitagaki
nvlitagaki marked this pull request as ready for review July 27, 2026 14:18
@nvlitagaki
nvlitagaki marked this pull request as draft July 27, 2026 14:29
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch 2 times, most recently from 48fcac3 to 507093a Compare July 27, 2026 19:42
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch from 507093a to 859868e Compare July 27, 2026 21:04
@nvlitagaki

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nvlitagaki

Copy link
Copy Markdown
Contributor Author

What this PR does

The current revision writes the Core-authoritative SKU projection into the REST database immediately after successful create and update operations, and removes it immediately after delete. REST reads therefore reflect the mutation without waiting for the periodic inventory sync.

How we verified it

Revision: 7f579e824830c6656d49a69a04f4184160750132

Environment: macOS on Apple Silicon, Docker Desktop, kind cluster nico-rest-local, the revision-built linux/arm64 REST image, Temporal Site Agent, PostgreSQL, and a locally running real Core API over mTLS. The REST binary inside the image was confirmed as ELF aarch64 before deployment.

At 2026-07-28T08:38:55Z, I exercised the real REST → Temporal → Site Agent → Core path with a unique SKU:

POST create                         201
immediate GET after create          200 in 0.013419s (stored minSizeMb=512)
PATCH metadata                      200
PATCH component replacement         200
immediate GET after replacement     200 in 0.011425s (stored minSizeMb=1536, count=2)
POST with legacy storage capacityMb 400
DELETE                              204
immediate GET after delete          404 in 0.009755s
PATCH after delete                  404

The Site Agent recorded successful real-Core RPCs (code=Ok) for CreateSku, FindSkusByIds, UpdateSkuMetadata, ReplaceSku, and DeleteSku. The immediate reads occurred well before the three-minute inventory schedule.

Supporting handler verification also passed:

cd rest-api
go test ./api/pkg/api/handler -run '^(TestCreateSkuHandler|TestUpdateSkuHandler|TestDeleteSkuHandler)$' -count=1 -v

How to reproduce the verification

Prerequisites: Docker Desktop, kind, kubectl, jq, Go, Rust, a local REST stack, and the local Core prerequisites documented by dev/mac-local-dev/run-nico-api.sh.

  1. Start from the tested revision and run real Core:

    git checkout 7f579e824830c6656d49a69a04f4184160750132
    ./dev/mac-local-dev/run-nico-api.sh
  2. In another terminal, build an architecture-correct REST image, load it, deploy it, and point the Site Agent at local Core:

    cd rest-api
    docker build --platform linux/arm64 \
      --build-arg TARGETOS=linux --build-arg TARGETARCH=arm64 \
      --build-arg VERSION=7f579e82 \
      -t localhost:5000/nico-rest-api:pr3633-7f579e82-arm64 \
      -f docker/local/Dockerfile.nico-rest-api .
    kind load docker-image localhost:5000/nico-rest-api:pr3633-7f579e82-arm64 \
      --name nico-rest-local
    kubectl --context kind-nico-rest-local -n nico-rest set image \
      deployment/nico-rest-api api=localhost:5000/nico-rest-api:pr3633-7f579e82-arm64
    KUBECONFIG="$HOME/.kube/config" make configure-local-core-site-agent
    kubectl --context kind-nico-rest-local -n nico-rest rollout status \
      deployment/nico-rest-api statefulset/nico-rest-site-agent --timeout=240s
  3. Port-forward API and Keycloak, obtain a local development token, and set the base URL:

    kubectl --context kind-nico-rest-local -n nico-rest port-forward service/nico-rest-api 8388:8388 &
    kubectl --context kind-nico-rest-local -n nico-rest port-forward service/keycloak 8082:8082 &
    export TOKEN='<local Keycloak token>'
    export BASE='http://localhost:8388/v2/org/test-org/nico'
    export SITE_ID=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/site" | \
      jq -r '.[] | select(.status=="Registered") | .id' | head -1)
    export SKU_ID="sku-projection-repro-$(date +%s)"
  4. Create the SKU, then read it immediately. Expect 201, followed immediately by 200 with minSizeMb: 512:

    curl -i -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      --data "{\"siteId\":\"$SITE_ID\",\"id\":\"$SKU_ID\",\"description\":\"projection-create\",\"components\":{\"storage\":[{\"model\":\"M1\",\"count\":1,\"minSizeMb\":512,\"maxSizeMb\":2048}],\"chassis\":{\"vendor\":\"NVIDIA\",\"model\":\"DGX\",\"architecture\":\"x86_64\"}}}" \
      "$BASE/sku"
    curl -i -H "Authorization: Bearer $TOKEN" "$BASE/sku/$SKU_ID"
  5. Replace the components, then read immediately. Expect 200, followed by 200 with minSizeMb: 1536, count: 2, and chassis model DGX-H200:

    curl -i -X PATCH -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      --data '{"components":{"storage":[{"model":"M2","count":2,"minSizeMb":1536,"maxSizeMb":4096}],"chassis":{"vendor":"NVIDIA","model":"DGX-H200","architecture":"x86_64"}}}' \
      "$BASE/sku/$SKU_ID"
    curl -i -H "Authorization: Bearer $TOKEN" "$BASE/sku/$SKU_ID"
  6. Delete, then read immediately. Expect 204, then 404:

    curl -i -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/sku/$SKU_ID"
    curl -i -H "Authorization: Bearer $TOKEN" "$BASE/sku/$SKU_ID"

@nvlitagaki
nvlitagaki marked this pull request as ready for review July 28, 2026 08:42
@nvlitagaki
nvlitagaki marked this pull request as draft July 28, 2026 13:38
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch from a90633b to 615c781 Compare July 28, 2026 15:15
@nvlitagaki

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch from 5039819 to 060bb6a Compare July 29, 2026 17:38

@thossain-nv thossain-nv left a comment

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.

Thanks for the implementation @nvlitagaki, added a few notes.

Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
@nvlitagaki
nvlitagaki requested a review from thossain-nv July 29, 2026 22:25
Signed-off-by: Leah Itagaki <litagaki@nvidia.com>
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch from 55b5bdf to 6d4f2a7 Compare July 29, 2026 23:13
@nvlitagaki
nvlitagaki force-pushed the feat/sku-management-rest branch from eae95a1 to 01a2286 Compare July 30, 2026 17:40
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Signed-off-by: Leah Itagaki <litagaki@nvidia.com>
@nvlitagaki
nvlitagaki requested a review from thossain-nv July 30, 2026 19:07

@thossain-nv thossain-nv left a comment

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.

Thanks for the updates @nvlitagaki, added some notes regarding the models.

Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/handler/sku.go Outdated
Signed-off-by: Leah Itagaki <litagaki@nvidia.com>
@nvlitagaki
nvlitagaki requested a review from thossain-nv July 30, 2026 20:20

@thossain-nv thossain-nv left a comment

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.

@nvlitagaki sorry if I didn't write it correctly in my previous comment - APIActionResourceRequest is only needed if it differs from the original object. Usually request objects are different from response object, but in case SKU, they seem to be usable both as request and response.

Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/api/pkg/api/model/sku.go
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
Comment thread rest-api/db/pkg/db/model/sku.go
Comment thread rest-api/api/pkg/api/model/sku.go Outdated
// Vendor is retained for response compatibility.
//
// Deprecated: Core returns an empty string and does not use this field for matching.
Vendor string `json:"vendor"`

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.

We should turn the deprecated attributes into *string Also it might be a good idea to issue a deprecation notice for them? 2.1 will be released September 30, we can set that as deprecation date.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've realized deprecation is the wrong call here. Existing sites that have SKUs with schema version 4 will continue to have and rely on these fields until anyone sees fit to update them. We can't know when that will happen (might be never), as there is nothing forcing a migration.

Signed-off-by: Leah Itagaki <litagaki@nvidia.com>
@nvlitagaki
nvlitagaki merged commit ae4f003 into NVIDIA:main Jul 31, 2026
121 checks passed
@thossain-nv

Copy link
Copy Markdown
Contributor

Thanks for the changes @nvlitagaki

polarweasel pushed a commit to polarweasel/infra-controller that referenced this pull request Jul 31, 2026
Provider Admins can create, partially update, and delete a SKU for a
selected Site. The REST API sends the mutation through Temporal and the
Site Agent to that Site's real NICo Core service. POST and PATCH return
the Core-backed SKU shape; PATCH preserves fields omitted from the
request.

This adds REST API support for managing SKUs.

Resolves NVIDIA#2810

### What this PR does

Adds Site-scoped REST endpoints to create, partially update, and delete
Core-backed SKUs. Mutations run through Temporal and the Site Agent to
the selected Site's NICo Core. The writable schema includes SKU v5
storage size bounds and PCI patterns; ignored legacy write fields such
as `capacityMb` are rejected.

---------

Signed-off-by: Leah Itagaki <litagaki@nvidia.com>
Signed-off-by: Alex Ball <aball@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rest-api Add this label when an issue or PR concerns NICo REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add REST API endpoints to created, update and delete SKUs

5 participants