Skip to content

feat(rest-api): Recovery of missing VPC Prefixes from Site inventory - #4913

Open
hwadekar-nv wants to merge 2 commits into
NVIDIA:mainfrom
hwadekar-nv:feat/vpc-prefix-auto-create
Open

feat(rest-api): Recovery of missing VPC Prefixes from Site inventory#4913
hwadekar-nv wants to merge 2 commits into
NVIDIA:mainfrom
hwadekar-nv:feat/vpc-prefix-auto-create

Conversation

@hwadekar-nv

@hwadekar-nv hwadekar-nv commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

Site inventory previously skipped VPC Prefixes that existed on Site but had no active REST database record, leaving Site and REST state inconsistent. This PR reconciles those VPC Prefixes using their controller ID

Soft-deleted VPC Prefixes are restored when reported by a newer inventory snapshot, while true orphans are auto-created after validating parent VPC ownership, a containing Ready tenant IPBlock, IPAM claim of the Site-reported CIDR, and name conflicts. Recovery writes are transactional and retry-safe.

Key behaviors:

  • Resolve the most specific Ready tenant IPBlock that contains the reported prefix (skip FullGrant blocks).
  • Claim the Site-reported CIDR via IPAM under the same tenant/IPBlock advisory lock as the REST create path.
  • Skip create/undelete when Site reports TERMINATING/TERMINATED so stale inventory cannot resurrect a user delete.
  • On undelete, clear soft-delete and refresh Status from Site inventory in the same transaction.
  • Reject non-canonical (host-bits) CIDRs and Prefix/VpcID/IPBlock drift before mutating IPAM.

Related issues

This PR is part of the issue (#3436)

Type of Change

  • Add - New feature or capability

Testing

  • Unit tests added/updated

Tests executed:
go test ./workflow/pkg/activity/vpcprefix/ -count=1
go test ./db/pkg/db/model/ -run VpcPrefix -count=1

Additional Notes

  • VPC Prefix identity matching uses controller IDs, not names.
  • Empty inventory names become recovered-<8hex>; name conflicts append -recovered-<8hex>.
  • Adds VpcPrefixDAO.Clear / IncludeDeleted and AcquireSpecificChildIpamEntryForIPBlock for FullGrant-safe IPAM claims.
  • No database schema migration is required.

@hwadekar-nv
hwadekar-nv requested a review from a team as a code owner August 13, 2026 02:03
@coderabbitai

coderabbitai Bot commented Aug 13, 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: 5ae93a92-073c-41c0-8938-88b0ec315f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 45e0f0a and e789ac2.

📒 Files selected for processing (4)
  • rest-api/db/pkg/db/ipam/ipam.go
  • rest-api/db/pkg/db/ipam/ipam_test.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • rest-api/db/pkg/db/ipam/ipam.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go

Summary by CodeRabbit

  • New Features

    • Added support for acquiring a specific child CIDR within an IP block.
    • Added automatic creation and restoration of VPC prefixes from inventory data.
    • Soft-deleted VPC prefixes can now be restored when inventory data is valid.
    • Added an option to include soft-deleted VPC prefixes in results.
  • Bug Fixes

    • Improved validation for CIDRs, ownership, tenant alignment, and IP block eligibility.
    • Prevented unsafe deletion when required IPAM cleanup cannot be completed.
    • Improved handling of incomplete or conflicting inventory records.

Walkthrough

The change adds exact child-CIDR acquisition, soft-delete clearing, and Site inventory recovery for VPC Prefix records. Recovery validates ownership and CIDRs, performs transactional IPAM allocation, restores matching records, and strengthens deletion cleanup handling.

Changes

VPC Prefix Recovery

Layer / File(s) Summary
Soft-delete persistence contract
rest-api/db/pkg/db/model/vpcprefix.go, rest-api/db/pkg/db/model/vpcprefix_test.go
The DAO can include soft-deleted rows and clear their deletion marker. Tests cover restoration and unknown-prefix errors.
Inventory recovery and exact allocation
rest-api/db/pkg/db/ipam/ipam.go, rest-api/db/pkg/db/ipam/ipam_test.go, rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go, rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go
Site inventory processing validates ownership, status, identifiers, and CIDRs. It creates or restores records and acquires exact IPAM child CIDRs transactionally. Tests cover replay, restoration, invalid inputs, full grants, mismatches, and site isolation.
Deletion cleanup safeguards
rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go, rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go
Deletion handles missing IP block identifiers and rejects soft deletion when an expected IP block relation is unavailable. Tests verify that the prefix remains active.

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

Mergeability Score: 🟡 Moderate · up to e789a

VPC Prefix recovery may mishandle IPAM claims when the reported prefix matches a FullGrant block at equal length, potentially creating incorrect REST/IPAM state. Merge should wait for this bounded correctness issue to be fixed or explicitly accepted.

Sequence Diagram(s)

sequenceDiagram
  participant SiteInventory
  participant VpcPrefixWorkflow
  participant VpcPrefixDAO
  participant IPAM
  SiteInventory->>VpcPrefixWorkflow: Report controller prefix and CIDR
  VpcPrefixWorkflow->>VpcPrefixDAO: Find active or soft-deleted VPC Prefix
  VpcPrefixWorkflow->>IPAM: Acquire or claim exact child CIDR
  IPAM-->>VpcPrefixWorkflow: Return allocation result
  VpcPrefixWorkflow->>VpcPrefixDAO: Create or clear deletion marker
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: recovery of missing VPC Prefixes from Site inventory.
Description check ✅ Passed The description directly explains the recovery behavior, validation rules, transactional design, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

@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-08-13 02:05:41 UTC | Commit: 45e0f0a

@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

🧹 Nitpick comments (4)
rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go (2)

527-534: 🩺 Stability & Availability | 🔵 Trivial

Plan an escape hatch for a permanently blocked cleanup.

Returning an error when IPBlockID is set but the relation is nil correctly prevents a leaked child CIDR. It also creates a terminal state: if the parent IP Block row is soft-deleted, the relation never loads again, and the caller at lines 184-215 retries the same failing delete on every inventory cycle. The VPC Prefix then stays in Deleting forever with no operator signal beyond a repeating error log.

Recommended operational follow-up: emit a distinct metric or alert for this branch so the stuck prefix is visible, and provide a documented remediation path, for example an administrative reconciliation that releases the CIDR using the stored Prefix and the soft-deleted block record.

🤖 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/workflow/pkg/activity/vpcprefix/vpcprefix.go` around lines 527 -
534, Add a distinct operational signal in the default branch of the VPC Prefix
deletion flow when IPBlockID is set but the relation is missing, such as a
dedicated metric or alert, while preserving the existing error return that
prevents CIDR leakage. Document an administrative remediation path that can
release the CIDR using the stored Prefix and soft-deleted IP block record.

242-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider decomposing createOrUpdateVpcPrefixFromSite.

The function spans roughly 235 lines and mixes proto validation, CIDR canonicalization, IP Block resolution, drift checks, IPAM allocation, undelete, and creation. Three cohesive helpers would isolate the invariants and make the transaction body readable: resolveContainingIPBlock, restoreSoftDeletedVpcPrefix, and createRecoveredVpcPrefix. Behavior stays identical.

Note also line 472: logger.Warn().Err(err).Msg(err.Error()) records the same text twice, once as the error field and once as the message. A static message such as "failed to recover VPC Prefix from Site inventory" reads better in structured logs.

Also applies to: 470-476

🤖 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/workflow/pkg/activity/vpcprefix/vpcprefix.go` around lines 242 -
246, Refactor createOrUpdateVpcPrefixFromSite into cohesive helpers for
resolveContainingIPBlock, restoreSoftDeletedVpcPrefix, and
createRecoveredVpcPrefix while preserving all existing validation,
canonicalization, drift, allocation, undelete, and creation behavior. In the
recovery logging near the undelete path, update logger.Warn().Err(err) to use a
static descriptive message instead of duplicating err.Error().
rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go (1)

856-880: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the precondition explicit instead of branching on prior subtest state.

This subtest reads the current row and then branches: it soft-deletes the prefix if it is active, or asserts a status if it is already deleted. The branch encodes a dependency on whether the preceding TERMINATING subtest ran. Running this subtest alone with -run takes the other branch, so the test does not verify a fixed scenario, and a regression in the preceding subtest can silently change what this one exercises.

Assert the required starting state, then perform the setup unconditionally.

💚 Suggested precondition handling
-		if existing[0].Deleted == nil {
-			_, err = vpcPrefixDAO.Update(ctx, nil, cdbm.VpcPrefixUpdateInput{
-				VpcPrefixID:     controllerVpcPrefixID,
-				Status:          cutil.GetPtr(cdbm.VpcPrefixStatusDeleting),
-				IsMissingOnSite: cutil.GetPtr(true),
-			})
-			require.NoError(t, err)
-			require.NoError(t, ipam.DeleteChildIpamEntryFromCidr(
-				ctx, nil, dbSession, ipamStorage, ipBlock, controllerVpcPrefix.Config.Prefix,
-			))
-			require.NoError(t, vpcPrefixDAO.Delete(ctx, nil, controllerVpcPrefixID))
-		} else {
-			assert.Equal(t, cdbm.VpcPrefixStatusDeleting, existing[0].Status)
-		}
+		// The preceding TERMINATING subtest leaves the row soft-deleted with Status=Deleting.
+		require.NotNil(t, existing[0].Deleted, "expected the prefix to be soft-deleted by the preceding subtest")
+		require.Equal(t, cdbm.VpcPrefixStatusDeleting, existing[0].Status)
🤖 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/workflow/pkg/activity/vpcprefix/vpcprefix_test.go` around lines 856
- 880, Update the “inventory restores soft-deleted VPC Prefix” subtest to
require that the fetched prefix is active/not deleted with the expected initial
state, rather than branching on existing[0].Deleted. After asserting that
precondition, perform the status update, child IPAM deletion, and
vpcPrefixDAO.Delete setup unconditionally so the test is independent of the
preceding TERMINATING subtest.
rest-api/db/pkg/db/ipam/ipam.go (1)

186-208: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Reject an equal-length childCidr explicitly.

CreateChildIpamEntryForIPBlock treats childBlockSize == parentIPBlock.PrefixLength as a full grant and records FullGrant = true in the REST database. This helper has no such branch. If a caller passes a childCidr whose length equals the parent length, the allocation happens in IPAM while FullGrant stays false. The comment block correctly states that FullGrant exists only in the REST database, so this asymmetry is exactly the kind of divergence the guard above is meant to prevent.

The current caller in rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go line 399 branches on prefix length before calling this helper, so no live path is affected. A guard keeps the exported contract self-enforcing.

♻️ Suggested guard
 	if parentIPBlock.FullGrant {
 		return nil, errors.New(fmt.Sprintf("parent IPBlock : %s already has a full-grant", parentIPBlock.ID.String()))
 	}
 	ipamer := cipam.NewWithStorage(ipamDB)
 	namespace := GetIpamNamespaceForIPBlock(ctx, parentIPBlock.RoutingType, parentIPBlock.InfrastructureProviderID.String(), parentIPBlock.SiteID.String())
 	ipamer.SetNamespace(namespace)
 	parentCidr := GetCidrForIPBlock(ctx, parentIPBlock.Prefix, parentIPBlock.PrefixLength)
+	// A child equal to the parent is a full grant. That path must go through
+	// CreateChildIpamEntryForIPBlock so the REST DB FullGrant flag stays consistent.
+	if childCidr == parentCidr {
+		return nil, errors.New(fmt.Sprintf("childCidr: %s equals parentCidr for IPBlock: %s, use CreateChildIpamEntryForIPBlock", childCidr, parentIPBlock.ID.String()))
+	}
 	childPrefix, err := ipamer.AcquireSpecificChildPrefix(ctx, parentCidr, childCidr)
🤖 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/db/pkg/db/ipam/ipam.go` around lines 186 - 208, Update
AcquireSpecificChildIpamEntryForIPBlock to reject childCidr values whose prefix
length equals parentIPBlock.PrefixLength, before invoking IPAM, using the
existing full-grant error behavior or an equivalent error. Keep the existing
nil-parent and already-FullGrant checks unchanged, and ensure equal-length
allocations are never persisted in IPAM without the REST FullGrant state.
🤖 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/workflow/pkg/activity/vpcprefix/vpcprefix.go`:
- Around line 316-326: Align the cross-site duplicate handling in the VPC prefix
lookup: use a global primary-key lookup by removing the SiteIDs filter from
vpcPrefixDAO.GetAll, then detect an existing match whose SiteID differs from
site.ID and return nil, nil before resolving IP blocks or mutating IPAM. Update
the surrounding comment if needed to accurately describe this behavior.
- Around line 390-395: Use the tenant/IPBlock advisory-lock key consistently in
both VPC-prefix deletion paths, matching the key used by REST create and
inventory recovery. In each deletion flow, acquire that shared lock before
mutating IPAM or IPBlock.FullGrant, then load or refresh IPBlock.FullGrant while
the lock is held before applying changes.

---

Nitpick comments:
In `@rest-api/db/pkg/db/ipam/ipam.go`:
- Around line 186-208: Update AcquireSpecificChildIpamEntryForIPBlock to reject
childCidr values whose prefix length equals parentIPBlock.PrefixLength, before
invoking IPAM, using the existing full-grant error behavior or an equivalent
error. Keep the existing nil-parent and already-FullGrant checks unchanged, and
ensure equal-length allocations are never persisted in IPAM without the REST
FullGrant state.

In `@rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go`:
- Around line 856-880: Update the “inventory restores soft-deleted VPC Prefix”
subtest to require that the fetched prefix is active/not deleted with the
expected initial state, rather than branching on existing[0].Deleted. After
asserting that precondition, perform the status update, child IPAM deletion, and
vpcPrefixDAO.Delete setup unconditionally so the test is independent of the
preceding TERMINATING subtest.

In `@rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go`:
- Around line 527-534: Add a distinct operational signal in the default branch
of the VPC Prefix deletion flow when IPBlockID is set but the relation is
missing, such as a dedicated metric or alert, while preserving the existing
error return that prevents CIDR leakage. Document an administrative remediation
path that can release the CIDR using the stored Prefix and soft-deleted IP block
record.
- Around line 242-246: Refactor createOrUpdateVpcPrefixFromSite into cohesive
helpers for resolveContainingIPBlock, restoreSoftDeletedVpcPrefix, and
createRecoveredVpcPrefix while preserving all existing validation,
canonicalization, drift, allocation, undelete, and creation behavior. In the
recovery logging near the undelete path, update logger.Warn().Err(err) to use a
static descriptive message instead of duplicating err.Error().
🪄 Autofix

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: aabb8957-8a1f-4e54-97a9-e6950bf942ff

📥 Commits

Reviewing files that changed from the base of the PR and between 4af48d2 and 45e0f0a.

📒 Files selected for processing (5)
  • rest-api/db/pkg/db/ipam/ipam.go
  • rest-api/db/pkg/db/model/vpcprefix.go
  • rest-api/db/pkg/db/model/vpcprefix_test.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go
  • rest-api/workflow/pkg/activity/vpcprefix/vpcprefix_test.go

Comment thread rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go
Comment thread rest-api/workflow/pkg/activity/vpcprefix/vpcprefix.go
@thossain-nv thossain-nv added the rest-api Add this label when an issue or PR concerns NICo REST API label Aug 13, 2026 — with ChatGPT Codex Connector
@hwadekar-nv hwadekar-nv added this to the v2.0 milestone Aug 13, 2026
@AlexGridnev

Copy link
Copy Markdown
Contributor

@hwadekar-nv why this PR has milestone 2.0?

@hwadekar-nv hwadekar-nv modified the milestones: v2.0, v2.2 Aug 17, 2026
@hwadekar-nv

hwadekar-nv commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@AlexGridnev Removing it and updating it to v2.2

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.

3 participants