Skip to content

feat(api): persist DPU IPv6 loopback reservations - #3913

Merged
chet merged 1 commit into
NVIDIA:mainfrom
chet:gh-issue-2389
Jul 27, 2026
Merged

feat(api): persist DPU IPv6 loopback reservations#3913
chet merged 1 commit into
NVIDIA:mainfrom
chet:gh-issue-2389

Conversation

@chet

@chet chet commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

As it stood, the IPv6 underlay work had nowhere durable to keep one loopback address per DPU. #2390 needs that value before it can render the IPv6 underlay, but there was no pool-backed ManagedHostNetworkConfig.loopback_ip_v6 to provide one.

So, this adds the optional lo-ip-v6 pool and reserves one typed Ipv6Addr per DPU. New DPU rows allocate it in db::machine::create; existing rows get a bounded backfill during startup or AdminGrowResourcePool; discovery and MachineCreator fill any remaining gap; and admin_force_delete_machine returns the reservation to the pool.

Mixed-version deployments need a little extra care here: an older API pod replaces the complete network_config document without fields it doesn't know. The targeted preserve_machine_ipv6_loopback trigger restores only an omitted loopback_ip_v6, while an explicit null from a current writer can still clear it. Backfill reuses the DPU's existing reservation, retries compare-and-swap conflicts, and refuses to write once ForceDeletion wins; deletion asks the pool for the owner's current reservation instead of trusting its earlier machine snapshot.

While collecting the missing address fields into one compare-and-swap write, this also fixes an existing secondary-VTEP leak: discovery could reserve secondary_overlay_vtep_ip, make a second write with the now-stale network_config_version, ignore the false return, and commit the reservation without persisting it. A failed compare-and-swap now fails the transaction, which returns every new address to its pool.

Related issues

This supports #2389

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

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

The new coverage walks typed serialization, IPv6 pool listing, reservation reuse and exhaustion, mixed-version writes, startup/runtime backfill, compare-and-swap retries, force-deletion races, discovery, cleanup, and multi-DPU MachineCreator creation.

cargo make format-nightly
cargo make check-format-nightly
cargo make clippy
cargo make carbide-lints
cargo test -p carbide-api-model managed_host_network_config --lib -- --nocapture
cargo test -p carbide-api-db test_ipv6_pool_define_allocate_release --lib -- --nocapture
cargo test -p carbide-api-db ipv6_loopback --lib -- --nocapture
cargo test -p carbide-api-db find_owned_allocation_returns_typed_value_and_rejects_duplicates --lib -- --nocapture
cargo test -p carbide-api-core test_grow_ipv6_loopback_pool_backfills_existing_dpus --lib -- --nocapture
cargo test -p carbide-api-core test_discover_dpu_by_source_ip --lib -- --nocapture
cargo test -p carbide-api-core test_admin_force_delete_dpu_only --lib -- --nocapture
cargo test -p carbide-site-explorer --test integration test_machine_creator_creates_multi_dpu_managed_host

Tests added!

Additional Notes

This intentionally does NOT enable IPv6 FNN rendering by itself. lo-ip-v6 is optional, so sites without it keep their IPv4-only behavior; #2390 is the step that plumbs the persisted value into the underlay.

Deploy this change before configuring lo-ip-v6 or rolling out #2390, so every API pod knows how to preserve the field before it can be written.

The grow-time backfill sees committed DPU rows. A creation transaction that crosses the first lo-ip-v6 grow can land just after that scan; replaying the additive grow or restarting the API reruns the idempotent pass.

AdminGrowResourcePool commits the additive pool change before it starts per-DPU reconciliation. If reconciliation reports exhaustion or a persistent compare-and-swap conflict, the grow itself is already durable and the request can be replayed safely after correcting the pool or contention.

Startup uses the same per-DPU pass but logs a failure and continues, so one exhausted or busy DPU does not take the API offline while completed reservations remain persisted.

Closes #2389

@chet
chet requested a review from a team as a code owner July 23, 2026 05:43
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0db261ca-0190-40f8-a651-a4e4a1802273

📥 Commits

Reviewing files that changed from the base of the PR and between c635e11 and 71e9b24.

📒 Files selected for processing (15)
  • crates/api-core/src/handlers/dpu.rs
  • crates/api-core/src/handlers/machine.rs
  • crates/api-core/src/handlers/machine_discovery.rs
  • crates/api-core/src/handlers/resource_pool.rs
  • crates/api-core/src/setup.rs
  • crates/api-core/src/tests/machine_admin_force_delete.rs
  • crates/api-core/src/tests/machine_discovery.rs
  • crates/api-core/src/tests/resource_pool.rs
  • crates/api-db/migrations/20260722120000_preserve_machine_ipv6_loopback.sql
  • crates/api-db/src/machine.rs
  • crates/api-db/src/resource_pool.rs
  • crates/api-model/src/machine/mod.rs
  • crates/api-model/src/machine/network.rs
  • crates/api-model/src/resource_pool/common.rs
  • crates/api-model/src/test_support/machine_snapshot.rs
🚧 Files skipped from review as they are similar to previous changes (15)
  • crates/api-db/migrations/20260722120000_preserve_machine_ipv6_loopback.sql
  • crates/api-core/src/setup.rs
  • crates/api-core/src/handlers/dpu.rs
  • crates/api-core/src/handlers/machine.rs
  • crates/api-core/src/tests/machine_discovery.rs
  • crates/api-model/src/test_support/machine_snapshot.rs
  • crates/api-core/src/handlers/machine_discovery.rs
  • crates/api-model/src/resource_pool/common.rs
  • crates/api-model/src/machine/mod.rs
  • crates/api-core/src/handlers/resource_pool.rs
  • crates/api-core/src/tests/resource_pool.rs
  • crates/api-core/src/tests/machine_admin_force_delete.rs
  • crates/api-model/src/machine/network.rs
  • crates/api-db/src/resource_pool.rs
  • crates/api-db/src/machine.rs

Summary by CodeRabbit

  • New Features
    • Added IPv6 loopback address support across DPUs, managed hosts, and machine creation/discovery.
    • Introduced an IPv6 loopback resource pool (lo-ip-v6) with automatic backfill for existing DPUs when the pool is created/expanded.
  • Bug Fixes
    • Improved network configuration update behavior to reduce stale/concurrent overwrites via batched updates and safer version handling.
    • Force deletion now best-effort releases IPv6 loopback allocations.
  • Maintenance
    • Added a database trigger to preserve existing IPv6 loopback values when updating network_config.

Walkthrough

Adds optional IPv6 loopback configuration and resource-pool support for DPUs, including allocation, backfill, CAS-safe updates, startup and pool-growth reconciliation, stale-snapshot cleanup, persistence compatibility, and expanded integration coverage.

Changes

IPv6 DPU loopback lifecycle

Layer / File(s) Summary
Configuration and pool contracts
crates/api-model/..., crates/api-db/migrations/..., crates/test-harness/..., crates/api-db/src/resource_pool.rs
Adds the IPv6 loopback field, public accessor, resource-pool wiring, persistence trigger, fixtures, builders, snapshots, and serialization coverage.
Allocation and CAS backfill
crates/api-db/src/machine.rs, crates/api-db/src/resource_pool.rs
Adds owned-allocation lookup, IPv6 allocation and reuse, force-deletion-aware updates, and retrying DPU backfill with database tests.
Lifecycle reconciliation and cleanup
crates/api-core/..., crates/site-explorer/...
Integrates allocation into discovery and DPU creation, reconciles startup and pool growth, releases stale force-deletion allocations, and validates provisioning and accounting.

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

Sequence Diagram(s)

sequenceDiagram
  participant DPUProvisioning
  participant IPv6LoopbackPool
  participant MachineDatabase
  participant Reconciliation
  DPUProvisioning->>IPv6LoopbackPool: allocate loopback_ip_v6
  IPv6LoopbackPool-->>DPUProvisioning: reserved IPv6 address
  DPUProvisioning->>MachineDatabase: persist network configuration
  Reconciliation->>MachineDatabase: find DPUs missing IPv6 loopback
  Reconciliation->>IPv6LoopbackPool: allocate missing reservations
  Reconciliation->>MachineDatabase: CAS-update loopback_ip_v6
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly captures the main change: persisting DPU IPv6 loopback reservations.
Description check ✅ Passed The description is detailed and directly matches the implemented IPv6 loopback persistence and backfill changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@chet

chet commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@chet: Starting a complete re-review of #3913.

✅ Action performed

Full review finished.

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

🧹 Nitpick comments (1)
crates/api-core/src/setup.rs (1)

1028-1033: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider aligning the log severity with the adjacent best-effort reconciliation. This backfill is intentionally non-fatal and its most likely failure (ConcurrentModificationError) is transient and self-healing on the next startup or AdminGrowResourcePool. Emitting it at error while the immediately following update_dpu_asns best-effort step uses warn risks spurious alerts for a benign condition. A warn level would keep the two swallow-and-continue paths consistent.

♻️ Suggested alignment
     if let Err(error) = update_dpu_loopback_ips_v6(db_pool, common_pools).await {
-        tracing::error!(
+        tracing::warn!(
             error = %error,
             "Failed to update IPv6 loopback IPs for DPUs",
         );
     }
🤖 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 `@crates/api-core/src/setup.rs` around lines 1028 - 1033, Change the tracing
severity in the update_dpu_loopback_ips_v6 error branch from error to warn,
keeping the existing error context and message unchanged so this non-fatal
reconciliation matches the adjacent update_dpu_asns best-effort path.
🤖 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 `@crates/api-core/src/setup.rs`:
- Around line 1028-1033: Change the tracing severity in the
update_dpu_loopback_ips_v6 error branch from error to warn, keeping the existing
error context and message unchanged so this non-fatal reconciliation matches the
adjacent update_dpu_asns best-effort path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5b963dff-dbf4-4443-a848-fbfa4f3ae8be

📥 Commits

Reviewing files that changed from the base of the PR and between 6db91ec and c635e11.

📒 Files selected for processing (18)
  • crates/api-core/src/handlers/dpu.rs
  • crates/api-core/src/handlers/machine.rs
  • crates/api-core/src/handlers/machine_discovery.rs
  • crates/api-core/src/handlers/resource_pool.rs
  • crates/api-core/src/setup.rs
  • crates/api-core/src/tests/machine_admin_force_delete.rs
  • crates/api-core/src/tests/machine_discovery.rs
  • crates/api-core/src/tests/resource_pool.rs
  • crates/api-db/migrations/20260722120000_preserve_machine_ipv6_loopback.sql
  • crates/api-db/src/machine.rs
  • crates/api-db/src/resource_pool.rs
  • crates/api-model/src/machine/mod.rs
  • crates/api-model/src/machine/network.rs
  • crates/api-model/src/resource_pool/common.rs
  • crates/api-model/src/test_support/machine_snapshot.rs
  • crates/site-explorer/src/machine_creator.rs
  • crates/site-explorer/tests/integration/machine_creator.rs
  • crates/test-harness/src/resource_pool.rs

As it stood, the IPv6 underlay work had nowhere durable to keep one loopback address per DPU. NVIDIA#2390 needs that value before it can render the IPv6 underlay, but there was no pool-backed `ManagedHostNetworkConfig.loopback_ip_v6` to provide one.

So, this adds the optional `lo-ip-v6` pool and reserves one typed `Ipv6Addr` per DPU. New DPU rows allocate it in `db::machine::create`; existing rows get a bounded backfill during startup or `AdminGrowResourcePool`; discovery and `MachineCreator` fill any remaining gap; and `admin_force_delete_machine` returns the reservation to the pool.

Mixed-version deployments need a little extra care here: an older API pod replaces the complete `network_config` document without fields it doesn't know. The targeted `preserve_machine_ipv6_loopback` trigger restores only an omitted `loopback_ip_v6`, while an explicit `null` from a current writer can still clear it. Backfill reuses the DPU's existing reservation, retries compare-and-swap conflicts, and refuses to write once `ForceDeletion` wins; deletion asks the pool for the owner's current reservation instead of trusting its earlier machine snapshot.

While collecting the missing address fields into one compare-and-swap write, this also fixes an existing secondary-VTEP leak: discovery could reserve `secondary_overlay_vtep_ip`, make a second write with the now-stale `network_config_version`, ignore the `false` return, and commit the reservation without persisting it. A failed compare-and-swap now fails the transaction, which returns every new address to its pool.

This intentionally does **NOT** enable IPv6 FNN rendering by itself. `lo-ip-v6` is optional, so sites without it keep their IPv4-only behavior; NVIDIA#2390 is the step that plumbs the persisted value into the underlay. Deploy this change before configuring `lo-ip-v6` or rolling out NVIDIA#2390, so every API pod knows how to preserve the field before it can be written.

The grow-time backfill sees committed DPU rows. A creation transaction that crosses the first `lo-ip-v6` grow can land just after that scan; replaying the additive grow or restarting the API reruns the idempotent pass.

The new coverage walks typed serialization, IPv6 pool listing, reservation reuse and exhaustion, mixed-version writes, startup/runtime backfill, compare-and-swap retries, force-deletion races, discovery, cleanup, and multi-DPU `MachineCreator` creation.

Tests added!

This supports NVIDIA#2389

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
@chet
chet enabled auto-merge (squash) July 27, 2026 20:25
@chet
chet merged commit dd3a48f into NVIDIA:main Jul 27, 2026
62 checks passed
@chet
chet deleted the gh-issue-2389 branch July 28, 2026 00:32
chet added a commit that referenced this pull request Jul 29, 2026
With #2389, each DPU can reserve an optional IPv6 loopback, but the
managed-host response still stops at the IPv4 address. That leaves FNN's
already-enabled `ipv6-unicast` session without an IPv6 address on `lo`
to use as its next hop.

So, this plumbs `loopback_ip_v6` through `ManagedHostNetworkConfig`,
parses it as `Ipv6Addr` at the agent boundary, renders it as a `/128` on
the FNN loopback, and permits that route through the IPv6 underlay
export policy.

In practice:

- `get_managed_host_network_config` uses the requesting DPU's
reservation, so two DPUs on one host do not collapse onto one address.
- ETV and sites without `lo-ip-v6` stay IPv4-only.
- The existing IPv4 loopback remains the router ID and VXLAN VTEP.
- `CurrentNetworkVersion` hashes the nested managed-host config so a
missed machine-group version fan-out cannot leave a DPU-specific
loopback hidden behind the agent cache.
- The manual `write nvue` path accepts `--loopback-ip-v6` so operators
can reproduce the generated FNN config.

## Related issues

This supports #2390

## Type of Change

- [x] **Add** - New feature or capability
- [ ] **Change** - Changes in existing functionality
- [ ] **Fix** - Bug fixes
- [ ] **Remove** - Removed features or deprecated functionality
- [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.)

## Breaking Changes

- [ ] **This PR contains breaking changes**

## Testing

- [x] Unit tests added/updated
- [x] Integration tests added/updated
- [ ] Manual testing performed
- [ ] No testing required (docs, internal refactor, etc.)

- `cargo test -p carbide-agent --lib`
- `cargo test -p carbide-api-core
handlers::dpu::consolidated_network_config_tests::dpu_ipv6_loopback_carries_through_independently
--lib -- --exact --nocapture`
- `cargo test -p carbide-api-core
tests::machine_network::test_managed_host_network_config --lib --
--exact --nocapture`
- `cargo test -p carbide-api-core
tests::machine_network::test_managed_host_network_config_multi_dpu --lib
-- --exact --nocapture`
- `cargo test -p carbide-api-core
tests::machine_network::test_managed_host_network_config_multi_dpu_fnn_ipv6_loopbacks
--lib -- --exact --nocapture`
- `cargo make format-nightly`
- `cargo make clippy`
- `cargo make carbide-lints`
- `cargo make --no-workspace generate-rest-core-proto`

## Additional Notes

FNN only: ETV intentionally does not receive the optional IPv6 address,
and the existing IPv4 loopback remains the router ID and VXLAN VTEP. The
optional protobuf field is wire-compatible; older agents ignore it,
while newer agents treat its absence as the existing IPv4-only behavior.

The REST Core protobuf snapshot and Go binding are regenerated for
repository sync, but the curated public REST/OpenAPI model intentionally
remains unchanged.

The per-DPU reservation dependency landed through #3913.

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
chet added a commit that referenced this pull request Jul 31, 2026
…4432)

PR #4389 and PR #4428 independently fixed the same duplicate migration
version from opposite sides. The loopback preservation migration first
reached `main` in #3913 as `20260722120000`, while the BMC suppression
migration came later. #4428 correctly moved the newer BMC migration to
`20260722120001`, but #4389 had already renamed the older loopback
migration to that same version on an earlier base. Since #4428 merged
first, #4389 left `main` with two `20260722120001` migrations and
removed the identity existing databases may already have in
`_sqlx_migrations`.

So, restore `20260722120000_preserve_machine_ipv6_loopback.sql` and
leave `20260722120001_bmc_suppressions.sql` where it is. The loopback
migration SQL is byte-for-byte identical to #3913, existing database
histories keep matching the published version and checksum, and SQLx
sees each migration version exactly once again.

## Related issues

None -- urgent post-merge repair for #4389 and #4428.

## Type of Change

- [ ] **Add** - New feature or capability
- [ ] **Change** - Changes in existing functionality
- [x] **Fix** - Bug fixes
- [ ] **Remove** - Removed features or deprecated functionality
- [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.)

## Breaking Changes

- [ ] **This PR contains breaking changes**

## Testing

- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [x] Manual testing performed
- [ ] No testing required (docs, internal refactor, etc.)

## Additional Notes

- `cargo test -p carbide-api-db migrations::tests --lib` (4 passed)
- `cargo make format-nightly`
- `cargo make clippy`
- `cargo make carbide-lints`
- `git diff --check`
- Restored migration blob matches the original #3913 blob exactly
(`eaff208e4ea6d3956bdb158a84c45ee6c1362ecf`)
- This restores the migration identities intended by #4428. A database
first initialized during either brief duplicate-version window may have
recorded the wrong checksum at `20260722120000` or `20260722120001`;
inspect its schema and migration history and perform site-specific
repair before retrying rather than blindly replaying these
non-idempotent migrations

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
polarweasel pushed a commit to polarweasel/infra-controller that referenced this pull request Jul 31, 2026
…VIDIA#4432)

PR NVIDIA#4389 and PR NVIDIA#4428 independently fixed the same duplicate migration
version from opposite sides. The loopback preservation migration first
reached `main` in NVIDIA#3913 as `20260722120000`, while the BMC suppression
migration came later. NVIDIA#4428 correctly moved the newer BMC migration to
`20260722120001`, but NVIDIA#4389 had already renamed the older loopback
migration to that same version on an earlier base. Since NVIDIA#4428 merged
first, NVIDIA#4389 left `main` with two `20260722120001` migrations and
removed the identity existing databases may already have in
`_sqlx_migrations`.

So, restore `20260722120000_preserve_machine_ipv6_loopback.sql` and
leave `20260722120001_bmc_suppressions.sql` where it is. The loopback
migration SQL is byte-for-byte identical to NVIDIA#3913, existing database
histories keep matching the published version and checksum, and SQLx
sees each migration version exactly once again.

## Related issues

None -- urgent post-merge repair for NVIDIA#4389 and NVIDIA#4428.

## Type of Change

- [ ] **Add** - New feature or capability
- [ ] **Change** - Changes in existing functionality
- [x] **Fix** - Bug fixes
- [ ] **Remove** - Removed features or deprecated functionality
- [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.)

## Breaking Changes

- [ ] **This PR contains breaking changes**

## Testing

- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [x] Manual testing performed
- [ ] No testing required (docs, internal refactor, etc.)

## Additional Notes

- `cargo test -p carbide-api-db migrations::tests --lib` (4 passed)
- `cargo make format-nightly`
- `cargo make clippy`
- `cargo make carbide-lints`
- `git diff --check`
- Restored migration blob matches the original NVIDIA#3913 blob exactly
(`eaff208e4ea6d3956bdb158a84c45ee6c1362ecf`)
- This restores the migration identities intended by NVIDIA#4428. A database
first initialized during either brief duplicate-version window may have
recorded the wrong checksum at `20260722120000` or `20260722120001`;
inspect its schema and migration history and perform site-specific
repair before retrying rather than blindly replaying these
non-idempotent migrations

Signed-off-by: Chet Nichols III <chetn@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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Underlay/00 — IPv6 loopback allocation & persistence source

3 participants