Skip to content

fix(api-core): scope transaction lint exemptions#3872

Merged
poroh merged 1 commit into
NVIDIA:mainfrom
poroh:fix/api-core-transaction-lint-scope
Jul 22, 2026
Merged

fix(api-core): scope transaction lint exemptions#3872
poroh merged 1 commit into
NVIDIA:mainfrom
poroh:fix/api-core-transaction-lint-scope

Conversation

@poroh

@poroh poroh commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Restore custom transaction-lint coverage for production api-core code. The API crate split in #2054 unintentionally made the test-support exemption crate-wide, while carbide-lints runs with --all-features; as a result, enabling test-support suppressed transaction warnings in production modules.

Changes:

The UEFI and force-delete behavior is intentionally preserved. The prefix and secrets allowances document cases where the custom lint cannot distinguish database-only savepoint work or detached session-lock connections from transactions.

Related issues

#2054
#2904
#2991
#3456
#2665

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

Additional Notes

@poroh
poroh requested a review from a team as a code owner July 22, 2026 22:16
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • Bug Fixes
    • Improved error reporting when Astra configuration reads fail during final transaction commit.
    • Improved reliability and safety of forced machine deletion for BIOS unlock and host UEFI credential clearing.
    • Preserved the success behavior of BIOS unlock and UEFI password clearing while making commit/credential steps handle failures more cleanly.
  • Maintenance
    • Tightened transaction-handling practices and clarified where transactions are intentionally held across awaits in credential and secret flows.

Walkthrough

The change narrows transaction lint exemptions, documents intentional cross-await transaction usage, centralizes host UEFI credential resolution for machine deletion, and adds explicit Astra transaction commit error mapping.

Changes

Transaction and credential updates

Layer / File(s) Summary
Transaction lint policy and documented exceptions
crates/api-core/src/lib.rs, crates/api-core/src/handlers/uefi.rs, crates/api-core/src/instance/mod.rs, crates/api-core/src/run.rs, crates/api-core/src/secrets/re_wrap.rs
Crate-wide exemptions now apply only to tests; intentional transaction and session-lock flows use local allowances with explanatory comments.
UEFI credential resolution helper
crates/api-core/src/handlers/machine.rs
Adds a reusable credential-resolution transaction helper and uses it from admin_force_delete_machine.
Astra transaction commit errors
crates/api-core/src/handlers/astra.rs
Maps Astra configuration transaction commit failures to an internal error with contextual messaging.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • NVIDIA/infra-controller#3864: Both changes touch the one-time Vault import workflow in crates/api-core/src/run.rs; this PR adjusts transaction-lint handling there.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: scoping transaction lint exemptions in api-core.
Description check ✅ Passed The description accurately matches the changeset and its lint-scope and transaction fixes.
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.

@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

🤖 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 `@crates/api-core/src/handlers/machine.rs`:
- Around line 38-53: Refactor resolve_host_uefi_clear_credentials and the
underlying host_uefi_clear_credentials flow so the UEFI device version is read
in a short transaction, that transaction is committed before any credential
lookup, and Vault credential resolution occurs outside the transaction. Remove
the local txn_held_across_await allowance and preserve the existing
Option<Credentials> behavior and error handling.
- Around line 46-55: Update the transaction handling in the api.txn_begin
success branch so txn.commit().await is checked before returning credentials. Do
not discard commit failures; return None (or the existing failure result) when
the commit fails, and only return credentials.ok() after a successful commit.
🪄 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: 84740f61-11dc-4437-84a5-3abbefbf0b3e

📥 Commits

Reviewing files that changed from the base of the PR and between b8c30d3 and 1a2fa42.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/astra.rs
  • crates/api-core/src/handlers/machine.rs
  • crates/api-core/src/handlers/uefi.rs
  • crates/api-core/src/instance/mod.rs
  • crates/api-core/src/lib.rs
  • crates/api-core/src/run.rs
  • crates/api-core/src/secrets/re_wrap.rs

Comment on lines +38 to +53
// TODO(#2991, @spydaNVIDIA): avoid holding this transaction while the
// credential reader may perform a remote Vault request. Keep the allowance on
// this small helper rather than the full force-delete handler.
#[allow(txn_held_across_await)]
async fn resolve_host_uefi_clear_credentials(
api: &Api,
bmc_mac_address: mac_address::MacAddress,
) -> Option<Credentials> {
match api.txn_begin().await {
Ok(mut txn) => {
let credentials = crate::handlers::uefi::host_uefi_clear_credentials(
&mut txn,
api.redfish_pool.credential_reader(),
bmc_mac_address,
)
.await;

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Do not hold the database transaction across the Vault request.

host_uefi_clear_credentials awaits credential resolution while holding &mut txn; its implementation in crates/api-core/src/handlers/uefi.rs:132-141 performs the remote lookup after the database read. This can retain database connections and locks during slow external I/O, reducing pool capacity under concurrent force deletes.

Resolve the UEFI device version in a short transaction, commit it, then perform the credential lookup outside the transaction and remove the local allowance.

As per coding guidelines, transactions must not be held across awaits; as per path instructions, transaction lifetime and resource behavior should be reviewed explicitly.

🤖 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/handlers/machine.rs` around lines 38 - 53, Refactor
resolve_host_uefi_clear_credentials and the underlying
host_uefi_clear_credentials flow so the UEFI device version is read in a short
transaction, that transaction is committed before any credential lookup, and
Vault credential resolution occurs outside the transaction. Remove the local
txn_held_across_await allowance and preserve the existing Option<Credentials>
behavior and error handling.

Sources: Coding guidelines, Path instructions

Comment on lines +46 to +55
match api.txn_begin().await {
Ok(mut txn) => {
let credentials = crate::handlers::uefi::host_uefi_clear_credentials(
&mut txn,
api.redfish_pool.credential_reader(),
bmc_mac_address,
)
.await;
let _ = txn.commit().await;
credentials.ok()

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Honor transaction commit failures before using the credentials.

let _ = txn.commit().await allows credentials.ok() to return Some even when the transaction failed to commit. This changes the previous best-effort behavior and can issue the device-side clear using credentials resolved from an unsuccessful transaction.

-            let _ = txn.commit().await;
-            credentials.ok()
+            if txn.commit().await.is_err() {
+                return None;
+            }
+            credentials.ok()

As per coding guidelines, transaction results must not be discarded with let _ = ...; handle failures explicitly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match api.txn_begin().await {
Ok(mut txn) => {
let credentials = crate::handlers::uefi::host_uefi_clear_credentials(
&mut txn,
api.redfish_pool.credential_reader(),
bmc_mac_address,
)
.await;
let _ = txn.commit().await;
credentials.ok()
match api.txn_begin().await {
Ok(mut txn) => {
let credentials = crate::handlers::uefi::host_uefi_clear_credentials(
&mut txn,
api.redfish_pool.credential_reader(),
bmc_mac_address,
)
.await;
if txn.commit().await.is_err() {
return None;
}
credentials.ok()
🤖 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/handlers/machine.rs` around lines 46 - 55, Update the
transaction handling in the api.txn_begin success branch so txn.commit().await
is checked before returning credentials. Do not discard commit failures; return
None (or the existing failure result) when the commit fails, and only return
credentials.ok() after a successful commit.

Source: Coding guidelines

@poroh
poroh enabled auto-merge (squash) July 22, 2026 22:26
Signed-off-by: Dmitry Porokh <dporokh@nvidia.com>
@poroh
poroh force-pushed the fix/api-core-transaction-lint-scope branch from 1a2fa42 to 38c72d7 Compare July 22, 2026 22:31

@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: 1

🤖 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 `@crates/api-core/src/handlers/uefi.rs`:
- Around line 129-131: Remove transaction-across-await behavior from the UEFI
credential flows: in crates/api-core/src/handlers/uefi.rs lines 129-131, commit
the device-version lookup before resolving clear credentials; in
crates/api-core/src/handlers/uefi.rs lines 113-115, separate set-flow database
work from credential retrieval; and in crates/api-core/src/handlers/machine.rs
lines 38-41, remove the helper-level allowance after moving Vault lookup outside
the transaction.
🪄 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: 13094f32-2c56-42aa-8d2f-1e9a603133e1

📥 Commits

Reviewing files that changed from the base of the PR and between 1a2fa42 and 38c72d7.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/astra.rs
  • crates/api-core/src/handlers/machine.rs
  • crates/api-core/src/handlers/uefi.rs
  • crates/api-core/src/instance/mod.rs
  • crates/api-core/src/lib.rs
  • crates/api-core/src/run.rs
  • crates/api-core/src/secrets/re_wrap.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/api-core/src/instance/mod.rs
  • crates/api-core/src/secrets/re_wrap.rs
  • crates/api-core/src/lib.rs

Comment on lines +129 to +131
// TODO(#2991, @spydaNVIDIA): do not hold the database connection while the
// credential reader may perform a remote Vault request.
#[allow(txn_held_across_await)]

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.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Remove the UEFI transaction-across-Vault awaits.

All three paths retain database connections during remote credential resolution, so Vault latency can consume API database-pool capacity.

  • crates/api-core/src/handlers/uefi.rs#L129-L131: commit the device-version lookup before resolving clear credentials.
  • crates/api-core/src/handlers/uefi.rs#L113-L115: separate set-flow database work from credential retrieval.
  • crates/api-core/src/handlers/machine.rs#L38-L41: remove the helper-level allowance after moving Vault lookup outside the transaction.
📍 Affects 2 files
  • crates/api-core/src/handlers/uefi.rs#L129-L131 (this comment)
  • crates/api-core/src/handlers/uefi.rs#L113-L115
  • crates/api-core/src/handlers/machine.rs#L38-L41
🤖 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/handlers/uefi.rs` around lines 129 - 131, Remove
transaction-across-await behavior from the UEFI credential flows: in
crates/api-core/src/handlers/uefi.rs lines 129-131, commit the device-version
lookup before resolving clear credentials; in
crates/api-core/src/handlers/uefi.rs lines 113-115, separate set-flow database
work from credential retrieval; and in crates/api-core/src/handlers/machine.rs
lines 38-41, remove the helper-level allowance after moving Vault lookup outside
the transaction.

Sources: Coding guidelines, Path instructions

@poroh
poroh merged commit 51bab11 into NVIDIA:main Jul 22, 2026
61 checks passed
chet added a commit to chet/bare-metal-manager-core that referenced this pull request Jul 23, 2026
Vault import and secret re-wrap each need to hold a lock for their whole run -- one importer at a time, one re-wrap at a time -- while they await Vault/KMS and open a transaction per batch. Both do it the same way: take a Postgres *session* advisory lock on a connection they `detach()` from the pool, and let dropping that connection release the lock. No transaction is ever open on it. It's the right design, but NVIDIA#3872 (which restored `txn_held_across_await` on `api-core`) can't tell a bare `PgConnection` holding only a session lock from a real transaction, so both had been parked behind `#[allow(txn_held_across_await)]`.

So, this gives the pattern a name. `db::secrets::SessionLock` -- living next to the lock primitives it wraps -- owns the detached connection and releases on drop, with `acquire` (waits) and `try_acquire` (returns `None` when another session holds it). It's `#[must_use]`, so you can't accidentally drop the lock the instant you take it. The value held across the awaits is now a `SessionLock`, a lock holder rather than a connection, so the lint is satisfied for the true reason: nothing here is a transaction.

Primary callouts are:
- `import_vault_secrets_once` and `re_wrap_stale` each collapse to a one-line `SessionLock::{acquire,try_acquire}`, and both `#[allow(txn_held_across_await)]` go away.
- The "why we detach a dedicated connection" explanation moves to the guard's doc, instead of being re-explained at each call site.

No behavior change -- the guard does exactly what the hand-rolled `acquire().detach()` + lock did, releasing on drop the same way (including on an early `?` return). The existing `re_wrap_stale` tests exercise the new `try_acquire` path.

This supports NVIDIA#4002

Follow-up to NVIDIA#3872.

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

2 participants