Repository navigation
feat(global-router): add a crate for types the Global Router components share - #17330
Conversation
…ts share dynamo-global-common holds the types that more than one Global Router component needs, so each is defined once: - a pool's identity and location (PoolKey, PoolId and its deriver, PoolLocation), from the shared crate on the global-router-v2 test branch; - the JSON of the Global Discovery Service's HTTP API: the POST /pools body and its lease reply, and the pool and router lists. Today the service and its client each define these; - how a router names a PoolRelay on a trusted network (address_identity), which the service writes into each pool's relay_identity; - Global Router replicas, and list-and-watch events and snapshots, which the service and the client in #17208 both define today. The JSON does not change. PoolKey now reads and writes {"site_id", "namespace", "dgd_name"} and checks each field while reading. A lease reply parses only when the heartbeat interval is above zero and below the lease, as in the client in #17208, and the lease is at most a day. Co-authored-by: Sachal Malick <s@chal.ai> Co-authored-by: Jacob Ioffe <jioffe@nvidia.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Crate setup and pool identity .github/codeowners/areas.yaml, CODEOWNERS, Cargo.toml, lib/global-common/Cargo.toml, lib/global-common/src/pool.rs, lib/global-common/src/lib.rs |
The workspace and ownership rules include the new crate. The crate defines validated pool coordinates, pool locations, and deterministic pool IDs, and re-exports its shared types. |
Watch and replica types lib/global-common/src/watch.rs, lib/global-common/src/replicas.rs |
Adds watch events, snapshots, completeness values, replica IDs and endpoints, and a replica event alias. |
Discovery API types and validation lib/global-common/src/discovery.rs |
Adds pool registration and listing types, lease validation, address identity formatting, router responses, and tests for serialization and validation. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Merge Risk: 
Minimal · up to 9e368
-
No actionable issue currently blocks merging. Confirm the discovery API’s wire format when its service implementation is available.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description is thorough and follows the template's intent. It has a Summary (Overview), a Key Changes list covering the new crate, the moved client types, JSON changes, and renames, and a Validation section listing the cargo test, clippy, doc, and fmt runs. It is not vague or off-topic. It has no 'Issues and Ongoing Work' section with a 'Closes
#XXXX' issue link, and the template's Pull Request Checklist item ('I used Dynamo's /pr-checklist skill') is not shown as checked. The description also does not use the template's section headings: it uses 'Summary' and 'Validation' rather than 'Overview', 'Key Changes', and 'Issues and Ongoing Work'. The PR links related work (#17354,#17208,#17371,#15295,#17355) but does not give a closing issue reference. The template notes the PR Issue Link check is advisory until release/1.7.0 is cut, so the missing link is not a hard blocker.
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
✅ Dynamo PR CI passed — run 40614797175 (attempt 1) on
|
Review feedback: the comments on `round_trip`, `PoolLocation` and `Snapshot` repeated the name or the fields right below them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
…c comments - A `POST /pools` body with an empty or blank `model` no longer parses, as with an invalid pool key, so the PoolRelay gets a 422 instead of registering a pool that no request can match. - The crate docs said the service writes the API's JSON and its clients read it; the registration body goes the other way. - `pool.rs` no longer refers to a source adapter that is not on main, or calls the V1 derivation the POC one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
There was a problem hiding this comment.
Previously reported defects still present:
- Original discussion: The redundant
LeaseErrordoc comment remains atlib/global-common/src/discovery.rs:94; it only restates thatLease::newreturns this error, while the validation and detailed error message already preserve the constraints.
The one-day cap made the shared `Lease` stricter than the client in #17208: once that client reads the shared type, a reply with a longer lease would fail to parse. The cap only protects the service's own clock arithmetic, so the service now bounds the lease it grants (#17371), and `Lease` keeps the checks a client needs: the interval is above zero and below the lease. Also drops `LeaseError`'s doc comment, which restated its use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
A pool's listing named its PoolRelay by one address. On a dual-stack cluster the PoolRelay's pod has two, and it connects to a router from whichever family the router's address uses, so a router matching one address could turn the right PoolRelay away. `relay_identities` now lists each of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
round_trip fails unless the parsed value serializes back to the same JSON, so the field assertions after it could not catch anything more. A lease of u64::MAX seconds already shows that a lease has no upper bound, so the 48-hour case goes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
|
Naming points, since this crate is where the shared types will live. Checked against head c7ee161. None of these change the JSON unless noted, and the first is the only one I would hold the PR on. 1. 2. Smaller, take or leave:
🤖 Generated with Claude Code, reviewed and posted by @nnshah1. |
Main now has #17355, which adds empty lib/global-* crates, lib/global-common among them, and routes lib/global-*/** to the Global Router team. Where both sides changed the same lines, main's version is kept: the workspace members, the CODEOWNERS glob, and the crate's package metadata and summary line. This branch keeps its modules, dependencies, workspace dependency entry and Cargo.lock entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
There was a problem hiding this comment.
No P0 or P1 found. This review read head c6efecb, which merges main at 2f296b5.
Finding from this review:
- [P3]
lib/global-common/src/discovery.rs:4-6: no test holds the promise that a reader skips unknown fields. It is inline, with a tested test.
Open items from other reviewers:
discovery.rs:140(dynamo-review-agent):relay_identitiesreplaces the singularrelay_identityof theglobal-router-v2service and client. A reply on that thread has measurements.discovery.rs:214andpool.rs:159(dynamo-review-agent): two duplicate test assertions.- nnshah1 holds the PR on the name
PoolKey.site_idagainst the relay'scluster_id(issue comment of 2026-10-27).
What this review measured.
- Scope: 8 files, +550/-0 against
main. The rootCargo.tomlline addsdynamo-global-commonto[workspace.dependencies].lib/global-commonwas already a workspace member.Cargo.lockadds no package. It adds 5 edges fromdynamo-global-commonto crates thatmainalready locks: blake3 1.8.7, serde 1.0.228, serde_json 1.0.150, thiserror 2.0.18 and url 2.5.8. - Tests: at c6efecb and on a merge with
mainat 457fba9,cargo test --locked -p dynamo-global-commonpasses 8 tests. The merge has no conflict. Clippy with-D warnings,cargo fmt --checkand rustdoc with-D warningsare clean, with cargo 1.96.1. On the merge, the tests ofdynamo-global-discovery-clientfrom #17208 also pass. A merge withmainat 9be9538 also has no conflict. The 3 newer commits onmainchange noCargo.toml,Cargo.lockorlib/global-*file. - Lease reply and router list: this crate, the #17208 client and the
global-router-v2client accept and reject the same 16 lease bodies and 7 router bodies. This crate and theglobal-router-v2service write the same JSON for both. - Registration body: the differences are the ones that the description names. This crate rejects an empty or blank
modeland a bad key field. This review ran the #17208 client against a local axum service that takes these types. The client gets the lease for a valid body and a 422 for a badmodelor key. This crate also accepts a body withoutcapabilities,frontendsorcapacity, which the #17208 type rejects. The #17208 client always sends all three. - Pool list: apart from the relay identity field, one pool with a bad key makes this crate reject the whole list. The
global-router-v2client accepts that list, and its router skips that one pool. - Pool ID: the derivers of this PR, of
global-router-v2and of #15295 give the same ID for 7 inputs. The pinned ID is one of them. - Checks: each of 10 deliberate breaks of the parse checks fails an existing test. The breaks remove each
validateclause, the key check, themodelcheck and each lease check. They also add back a one-day lease cap and dropto_canonical. - Release staging:
.github/scripts/stage_crates.pyselects the same 29 crates in the same order at base and head.dynamo-global-commonwas in that set before this PR. - Description: the claims that this review measured hold. These are the 8 tests, clippy, the pinned ID, the 422, the lease checks and the one-day bound in #17371.
- CI on c6efecb: PR run 40614797175 passed. Pre Merge run 40614792813 failed only
rust-tests (.), on thedynamo-mockertestgeneralized_live::tests::cancellation_does_not_end_a_pass_with_unrelated_pending_output. This crate's 8 tests passed in that job. The diff does not reach that test. The dependency tree ofdynamo-mockeris the same at base and head. The same test failed in Pre Merge run 40635050407 of pull request 17074, which changes onlylib/llm/src/preprocessor.rs. Each skipped job needs a filter that this diff does not match. Those filters are operator, deploy tests, snapshots, DGDR, dev and EFA images, power agent, planner gym, docs and examples. The one exception isallure-report, which is off for every pull request (if: false).
Main now has the Global Discovery Service client from #17208 in lib/global-discovery-client, which the next commits move onto this crate's types, and the PoolRelay rename from #17215. No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
From Neelay Shah's review: - PoolKey's first field is cluster_id, not site_id, in Rust and in JSON. It is the cluster ID the pool's PoolRelay runs with (--cluster-id, DYN_CLUSTER_ID), so a pool key and its relay share one name. Where MCS also reports the pool, that ID must be the cluster's name in the ClusterSet, and the doc says so. Pool IDs do not change: the deriver hashes the values, not the field names. - PoolLocation drops cluster, which the key carries. - ReplicaEndpoint, ReplicaId and ReplicaEvent become RouterReplica, RouterReplicaId and RouterReplicaEvent, since relays and workers have replicas too. Their JSON does not change. - WatchEvent::Added becomes Upserted, Lease::lease_duration() becomes duration(), and Capacity.count becomes workers, in JSON too. Lease is now #[must_use], as the client's copy was, so the client keeps that check when it moves onto this type. From the review threads: a test, from Dmitry Tokarev's review, that a reader skips unknown fields and fills missing lists, and two duplicate assertions removed. A pre-push review added a test that a reply without its list does not parse, so an empty body never reads as "no routers". Co-authored-by: Dmitry Tokarev <191198572+dmitry-tokarev-nv@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
Neelay Shah and Jacob Ioffe asked on #17208 that its client move onto dynamo-global-common in this PR, so two PoolKey definitions never sit on main together. lib/global-discovery-client drops its own copies of the JSON types, router replicas and watch events. It re-exports the shared types its calls send and return, with their error types; the watch types come from dynamo-global-common. Its calls and errors do not change. What it sends follows the shared types: the key's cluster_id, a location without cluster, and capacity in workers. A PoolKey is built with PoolKey::new, which checks its fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
|
@nnshah1 thanks, all of it is in 4323f2a and 5aadafa:
As you and @jioffe502 asked on #17208, the client moves onto these types in this PR: @dmitry-tokarev-nv your unknown-field test is in, moved to the new names, and both duplicate asserts are gone. The relay identity thread has a reply. |
…obal-discovery-http The base merged main (#17330 is merged, so the shared types and the discovery client come from main), documented the stores' watch streams, and fixed Karmada's Service name limit: 55, not 57. No conflicts. CODEOWNERS still matches areas.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
…very-pool-registrations The base took #17331's changes: main (with #17330's shared types), doc comments on the stores' watch streams, and Karmada's Service name limit, 55, not 57. McsReplicaDirectory, which also reads the peer Service here, now logs an error for a Service name Karmada cannot import. No conflicts. CODEOWNERS still matches areas.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
…very-register-router The base took #17331's changes: main (with #17330's shared types), doc comments on the stores' watch streams, and Karmada's Service name limit, 55, not 57. No conflicts. CODEOWNERS still matches areas.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
Summary
Adds
lib/global-common(dynamo-global-common), the crate the Global Router coding standards ask for: the types that more than one Global Router component needs, each defined once. The name follows #17354, which lists it among the Global Router crates. It also moves the Global Discovery Service client from #17208 onto these types.PoolKey(cluster_id,namespace,dgd_name),PoolIdand its deriver, andPoolLocation. Taken from the shared crate on theglobal-router-v2test branch (Sachal's code, credited in the commit).POST /poolsbody and its lease reply, and the pool and router lists.address_identity). The service writes one for each address of the PoolRelay's pod into each pool'srelay_identities(two on a dual-stack cluster), and a router compares a connecting PoolRelay against them.RouterReplica), and list-and-watch events and snapshots (WatchEvent,Snapshot,Completeness).lib/global-discovery-clientdrops its own copies and re-exports the types its calls use, so onePoolKeyexists on main, as Neelay and Jacob asked on feat(router): add a Global Discovery Service client crate #17208. Its calls and errors do not change.Compared with the JSON that client sent before this PR:
cluster_id, notsite_id. It is the cluster ID the pool's PoolRelay runs with (--cluster-id,DYN_CLUSTER_ID), so a pool key and its relay use one name. Where MCS also reports the pool, that ID must be the cluster's name in the ClusterSet. Pool IDs do not change: the deriver hashes the values, not the field names.PoolLocationhas nocluster, since the key carries it.workers, notcount.relay_identities.PoolKeychecks each field while reading. A registration'smodelgets the same check, so a PoolRelay with an empty model gets a 422 instead of registering a pool that no request can match. The lease reply keeps the check from the client in #17208 (Jacob's, credited in the commit): a zero interval, or one not below the lease, does not parse. As in that client, there is no upper bound: the service bounds the lease it grants (#17371).Leaseis#[must_use], as the client's copy was.Renamed in Rust only, after Neelay's review:
RouterReplica,RouterReplicaIdandRouterReplicaEvent(wereReplicaEndpoint,ReplicaIdandReplicaEvent),WatchEvent::Upserted(wasAdded) andLease::duration()(waslease_duration()).The Global Discovery Service (#17371) uses these types. The PRs stacked on this one pick up the new names when they merge it.
Overlap with #15295: that draft defines the same pool identity types (
PoolKey,PoolId,V1PoolIdDeriver,PoolLocation) inkv_router::global_view. Whichever lands second should use the other's, so there is one definition. The derived IDs are the same, and a test here pins one.#17355 is on main: it added an empty
lib/global-commonfor #17354, and alib/global-*/**code-owner rule that covers this crate. This PR fills that crate, so it adds no workspace member and no code-owner line.Validation
cargo test -p dynamo-global-common: 10 passed. They cover pool identity (with one ID pinned), the key's JSON and its checks, the API's JSON (and a blankmodel), that a reader skips unknown fields and fills missing lists (Dmitry's test), that a reply without its list does not parse, the relay identity, and the lease checks.cargo test -p dynamo-global-discovery-client: 7 passed, including the exact JSON the client sends withregister_pool.cargo clippy --all-targets -- -D warnings,cargo docwith-D warningsandcargo fmt --checkfor both crates: clean, with--locked.cluster_iddoc to state the MCS requirement, trimmed the client's re-exports to what its calls use (addingLeaseError), and added the test that a reply without its list does not parse.🤖 Generated with Claude Code
Summary by CodeRabbit