Skip to content

feat(api): enforce REST semantics for worker endpoints - #875

Merged
CatherineSue merged 7 commits into
mainfrom
fix/worker-api-rest-endpoints
Mar 24, 2026
Merged

CatherineSue merged 7 commits into
mainfrom
fix/worker-api-rest-endpoints

Conversation

@CatherineSue

@CatherineSue CatherineSue commented Mar 24, 2026 •

Copy link
Copy Markdown
Member

Description

Part of the worker API REST fix series: #836 (registry refactor) → this PR (REST endpoints).

Problem

POST /workers silently upserts when the same URL is registered twice. PUT /workers/{id} only does partial updates. There's no way to do a full worker replacement (e.g., new API key with model re-discovery).

Solution

Enforce proper REST semantics:

Method Endpoint Behavior
POST /workers Create only. Returns 409 Conflict if URL already exists.
PUT /workers/{id} Full replace. Takes WorkerSpec, re-runs registration workflow (model discovery, etc.) via register_or_replace().
PATCH /workers/{id} Partial update. Same WorkerUpdateRequest as before (moved from PUT).
DELETE /workers/{id} Unchanged.

Breaking change

Clients using PUT /workers/{id} with WorkerUpdateRequest must switch to PATCH /workers/{id}. PUT now expects a full WorkerSpec.

Changes

  • Add 409 Conflict check in WorkerService::create_worker() — rejects duplicate URLs with actionable error message
  • Add WorkerServiceError::Conflict variant
  • Add WorkerService::replace_worker() — submits AddWorker job (same workflow, uses register_or_replace() internally)
  • Add replace_worker handler in server.rs
  • Route PUT /workers/{id} to replace_worker, PATCH /workers/{id} to update_worker
  • Remove stale TODO on reserve_id_for_url

Test Plan

  • cargo test -p smg --lib — all 437 tests pass, 0 failures
  • Pre-commit hooks pass (rustfmt, clippy, codespell, DCO)
  • Manual test: POST /workers with duplicate URL returns 409
Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

Summary by CodeRabbit

  • New Features

    • Added endpoint to fully replace a worker configuration (PUT).
    • Creation now detects duplicate worker URLs and returns 409 Conflict.
  • API Changes

    • PUT /workers/{id} now performs full replacement; PATCH /workers/{id} now handles partial updates.
    • PUT with a mismatched URL returns 400 Bad Request.
  • Tests

    • Added end-to-end tests for duplicate-URL handling, PATCH partial updates, PUT replace, and URL-mismatch errors.

Summary by CodeRabbit

- POST /workers now returns 409 Conflict if a worker with the same
  URL already exists, with a message directing to PUT/PATCH
- PUT /workers/{id} does full replace (re-runs registration workflow
  with model discovery via register_or_replace)
- PATCH /workers/{id} does partial update (moved from PUT, same
  WorkerUpdateRequest behavior)
- DELETE /workers/{id} unchanged
- Add Conflict variant to WorkerServiceError
- Add replace_worker() to WorkerService

Signed-off-by: Chang Su <chang.s.su@oracle.com>
The 409 check now happens in the service layer (create_worker).

Signed-off-by: Chang Su <chang.s.su@oracle.com>
@CatherineSue
CatherineSue requested a review from slin1237 as a code owner March 24, 2026 01:06
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the worker API to align with standard REST principles, ensuring that POST is for creation, PUT is for full replacement, and PATCH is for partial updates. This change clarifies the behavior of worker management endpoints, improves error handling for duplicate registrations, and provides a robust mechanism for fully replacing worker configurations, enhancing the API's predictability and usability.

Highlights

  • REST Semantics Enforcement: The API now strictly enforces REST semantics for worker endpoints, distinguishing between creating, replacing, and partially updating workers.
  • Duplicate Worker Creation Prevention: Attempting to create a worker with a URL that already exists will now result in a 409 Conflict error, preventing silent upserts.
  • Full Worker Replacement: A new PUT /workers/{id} endpoint has been introduced for full worker replacement, which re-runs the entire registration workflow.
  • Partial Worker Update: Partial worker updates, previously handled by PUT, are now explicitly routed to a new PATCH /workers/{id} endpoint.
  • Error Handling: A new WorkerServiceError::Conflict variant was added to provide clear error messages for duplicate worker registrations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@coderabbitai

coderabbitai Bot commented Mar 24, 2026 •

Copy link
Copy Markdown

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

Walkthrough

Adds REST semantics: POST now returns 409 for duplicate worker URLs, PUT /workers/{id} is a full replace (validates worker-id ↔ URL) that enqueues AddWorker, PATCH remains for partial updates, and a docstring TODO was removed from WorkerRegistry::reserve_id_for_url.

Changes

Cohort / File(s) Summary
Worker Service Core
model_gateway/src/core/worker_service.rs
Added WorkerServiceError::BadRequest and ::Conflict (HTTP 400/409); create_worker detects duplicate reserved IDs and returns 409; added pub async fn replace_worker(...) to validate ID↔URL and enqueue Job::AddWorker.
API Route Handling
model_gateway/src/server.rs
Added replace_worker handler; remapped /workers/{worker_id} so PUT → replace_worker (full replace) and PATCH → update_worker (partial); GET/DELETE unchanged.
Registry Docstring
model_gateway/src/core/worker_registry.rs
Removed a multi-line TODO from WorkerRegistry::reserve_id_for_url docstring; no behavioral or signature changes.
End-to-end Tests
e2e_test/router/test_worker_api.py
Added TestWorkerAPIRestSemantics with tests asserting POST duplicate-URL → 409, PATCH partial updates apply, PUT full replace succeeds and preserves ID, and PUT with URL mismatch → 400.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as "Server (HTTP)"
    participant Service as "WorkerService"
    participant Registry as "WorkerRegistry"
    participant Queue as "Job Queue"

    Client->>Server: PUT /workers/{worker_id} (WorkerSpec)
    Server->>Service: replace_worker(worker_id, config)
    Service->>Service: parse & normalize worker_id
    Service->>Registry: get_worker_by_id(worker_id)
    Registry-->>Service: existing_worker_url / NotFound
    alt NotFound
        Service-->>Server: 404 Not Found
        Server-->>Client: 404
    else Found
        Service->>Service: validate config.url == existing_worker_url
        alt URL mismatch
            Service-->>Server: BadRequest (400)
            Server-->>Client: 400
        else Match
            Service->>Queue: submit Job::AddWorker(config)
            Queue-->>Service: accepted
            Service-->>Server: UpdateWorkerResult{worker_id, url}
            Server-->>Client: 200/202 OK
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • key4ng
  • slin1237
  • XinyueZhang369

Poem

🐇 I hopped through lines both neat and spry,

Reserved an ID beneath the sky,
PUT replaces whole, PATCH nibbles a part,
Queue hums softly as changes start,
Hop—deploy with a rabbit's heart!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(api): enforce REST semantics for worker endpoints' accurately summarizes the main change—the PR enforces proper REST semantics (POST creates, PUT replaces fully, PATCH updates partially) for the worker API endpoints.

✏️ 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 fix/worker-api-rest-endpoints

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

Check for active worker using the reserved ID instead of calling
get_by_url then reserve_id_for_url separately.

Signed-off-by: Chang Su <chang.s.su@oracle.com>

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors the worker API endpoints to align with REST semantics, introducing PUT for full replacement and PATCH for partial updates, and making POST create-only. The changes are well-structured, but I've identified two high-severity issues. First, there's a race condition in the create_worker function that could lead to an upsert on POST, defeating the create-only goal. Second, the new replace_worker function for PUT requests has a bug where it doesn't validate the worker URL, potentially creating a new worker instead of replacing the intended one. I've provided detailed comments and a code suggestion for the replace_worker bug.

Comment thread model_gateway/src/core/worker_service.rs
Comment thread model_gateway/src/core/worker_service.rs
@github-actions github-actions Bot added the model-gateway Model gateway crate changes label Mar 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/core/worker_service.rs`:
- Around line 277-305: In replace_worker, ensure the URL used for the
replacement is consistent with the registry: after retrieving the existing url
via worker_registry.get_url_by_id(&worker_id) and before creating Job::AddWorker
(which uses config.url during register_or_replace), either validate that
config.url == existing url and return a BadRequest/WorkerServiceError if they
differ, or overwrite config.url with the existing url so the job will target the
current worker; update the code around the Job::AddWorker creation and ensure
UpdateWorkerResult still returns the existing url for clarity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 165e207f-ebd1-4120-881c-5042ad138ab7

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd1f85 and 5920802.

📒 Files selected for processing (3)
  • model_gateway/src/core/worker_registry.rs
  • model_gateway/src/core/worker_service.rs
  • model_gateway/src/server.rs
💤 Files with no reviewable changes (1)
  • model_gateway/src/core/worker_registry.rs

Comment thread model_gateway/src/core/worker_service.rs
Reject PUT /workers/{id} if the URL in the request body doesn't match
the existing worker's URL. URL changes are not supported via replace
— use DELETE + POST instead.

Signed-off-by: Chang Su <chang.s.su@oracle.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/core/worker_service.rs`:
- Around line 295-302: Replace the incorrect use of
WorkerServiceError::InvalidId when the parsed worker_id_raw matches but the
request body URL differs: add a new error variant (e.g.,
WorkerServiceError::UrlMismatch or a generic BadRequest { message }) and return
that variant in the branch that currently constructs InvalidId, using a clear
message about the URL mismatch (reusing the existing formatted string). Update
the code that constructs the Err(...) in the branch (the expression that
currently builds WorkerServiceError::InvalidId with raw:
worker_id_raw.to_string()) to instead construct the new UrlMismatch/BadRequest
variant so the HTTP 400 response reflects a URL mismatch rather than an invalid
UUID.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7ec96cfd-4215-4794-bf0b-a265c92b4aa9

📥 Commits

Reviewing files that changed from the base of the PR and between 5920802 and cfedb04.

📒 Files selected for processing (1)
  • model_gateway/src/core/worker_service.rs

Comment thread model_gateway/src/core/worker_service.rs Outdated
Add TestWorkerAPIRestSemantics e2e test class:
- test_post_duplicate_url_returns_409: POST same URL twice, verify 409
- test_patch_partial_update: PATCH updates priority/labels
- test_put_full_replace: PUT replaces worker with model re-discovery
- test_put_url_mismatch_returns_400: PUT with different URL rejected

Signed-off-by: Chang Su <chang.s.su@oracle.com>
@github-actions github-actions Bot added the tests Test changes label Mar 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
e2e_test/router/test_worker_api.py (1)

3-6: ⚠️ Potential issue | 🟡 Minor

Stale file docstring: update to reflect new REST endpoints.

The file docstring still references legacy endpoints (POST /add_worker, POST /remove_worker) but the new TestWorkerAPIRestSemantics class tests POST /workers, PUT /workers/{id}, and PATCH /workers/{id}. Consider updating the docstring to document all tested endpoints.

📝 Suggested docstring update
 """Tests for gateway worker management APIs.
 
 Tests the gateway's worker management endpoints:
 - GET /workers - List all workers
-- POST /add_worker - Add a worker dynamically
-- POST /remove_worker - Remove a worker dynamically
+- POST /workers - Add a worker (returns 409 on duplicate URL)
+- PUT /workers/{id} - Full replace of a worker
+- PATCH /workers/{id} - Partial update of a worker
+- DELETE /workers/{id} - Remove a worker dynamically
 - GET /v1/models - List available models
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e_test/router/test_worker_api.py` around lines 3 - 6, The module-level
docstring is stale and still lists legacy endpoints; update it to reflect the
endpoints exercised by the TestWorkerAPIRestSemantics test class by replacing
references to POST /add_worker and POST /remove_worker with the current REST
endpoints: POST /workers (create), PUT /workers/{id} (replace/update), and PATCH
/workers/{id} (partial update), and describe that GET /workers lists workers so
the docstring accurately documents the tested API surface and behavior in the
file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e_test/router/test_worker_api.py`:
- Around line 552-589: The test test_put_full_replace only checks the worker
still exists after PUT; update it to assert that model re-discovery occurred by
either (A) waiting for the worker to become healthy/ready after the PUT (e.g.,
poll the gateway until the worker's status is healthy) to ensure
register_or_replace() completed, or (B) inspecting the PUT response or a
subsequent GET /workers/{worker_id} (via Gateway.list_workers() or a dedicated
Gateway.get_worker() call) for fields that change on re-registration (model
metadata, version, or a re-registration timestamp) and asserting those changed;
locate the HTTP PUT call in test_put_full_replace and add the polling/assertion
against gateway.list_workers() or the PUT response to confirm re-discovery.
- Around line 519-550: Update test_patch_partial_update to actually verify the
PATCH changes: after calling gateway.add_worker and performing the httpx.patch
to /workers/{worker_id}, issue a GET to
f"{gateway.base_url}/workers/{worker_id}" (or use gateway.get_worker if
available) and assert the returned worker JSON contains the updated "priority"
== 100 and "labels" contains {"env": "test"}; if you intend to test "cost" also,
include it in the PATCH payload and assert it on the GET result. Also update the
test's docstring to match the fields you're modifying (remove "cost" if not
testing it, or include it if you add it to the payload). Ensure assertions
reference the test function name test_patch_partial_update and the Gateway
add_worker flow so the change is easy to locate.

---

Outside diff comments:
In `@e2e_test/router/test_worker_api.py`:
- Around line 3-6: The module-level docstring is stale and still lists legacy
endpoints; update it to reflect the endpoints exercised by the
TestWorkerAPIRestSemantics test class by replacing references to POST
/add_worker and POST /remove_worker with the current REST endpoints: POST
/workers (create), PUT /workers/{id} (replace/update), and PATCH /workers/{id}
(partial update), and describe that GET /workers lists workers so the docstring
accurately documents the tested API surface and behavior in the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e2f38ff-9e2d-4cd8-b320-6884b5c14e87

📥 Commits

Reviewing files that changed from the base of the PR and between cfedb04 and 144417a.

📒 Files selected for processing (1)
  • e2e_test/router/test_worker_api.py

Comment thread e2e_test/router/test_worker_api.py
Comment thread e2e_test/router/test_worker_api.py
Add WorkerServiceError::BadRequest variant for general bad request
errors. Use it for URL mismatch in replace_worker() instead of
reusing InvalidId which misleadingly claims the UUID is malformed.

Signed-off-by: Chang Su <chang.s.su@oracle.com>
Poll GET /workers/{id} after PATCH to verify priority, cost, and
labels were actually applied, not just accepted.

Signed-off-by: Chang Su <chang.s.su@oracle.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
e2e_test/router/test_worker_api.py (1)

577-614: 🧹 Nitpick | 🔵 Trivial

Consider verifying model re-discovery occurred.

The docstring claims the test verifies "full replace with model re-discovery," but the test only confirms the worker still exists after PUT. To strengthen this test, consider waiting for the worker to become healthy again after the PUT, which would confirm the register_or_replace() workflow completed successfully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e_test/router/test_worker_api.py` around lines 577 - 614, The
test_put_full_replace currently only asserts the worker still exists after PUT;
update it to verify model re-discovery completed by polling the Gateway for the
worker's health/model metadata after the PUT. After calling httpx.put on
/workers/{worker_id}, repeatedly call Gateway.list_workers() (or a
Gateway.get_worker(worker_id) helper) and wait until the worker's health/status
indicates healthy and/or its model info reflects re-discovery (i.e., compare
model fields or a last_registered timestamp), failing the test if a timeout
elapses; reference Gateway, add_worker, list_workers, and the
register_or_replace workflow when implementing the polling check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@e2e_test/router/test_worker_api.py`:
- Around line 577-614: The test_put_full_replace currently only asserts the
worker still exists after PUT; update it to verify model re-discovery completed
by polling the Gateway for the worker's health/model metadata after the PUT.
After calling httpx.put on /workers/{worker_id}, repeatedly call
Gateway.list_workers() (or a Gateway.get_worker(worker_id) helper) and wait
until the worker's health/status indicates healthy and/or its model info
reflects re-discovery (i.e., compare model fields or a last_registered
timestamp), failing the test if a timeout elapses; reference Gateway,
add_worker, list_workers, and the register_or_replace workflow when implementing
the polling check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f1c59094-d21a-415e-bc5b-e6dd821f595a

📥 Commits

Reviewing files that changed from the base of the PR and between a51b5dd and e61ad05.

📒 Files selected for processing (1)
  • e2e_test/router/test_worker_api.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-gateway Model gateway crate changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant