Skip to content

feat: count firmware update outcomes across every target#3319

Open
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3175-firmware
Open

feat: count firmware update outcomes across every target#3319
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3175-firmware

Conversation

@chet

@chet chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Firmware updates run for three targets -- host BIOS/BMC, DPU NICs, and SuperNICs -- and none of their outcomes are countable today: starts, completions, and failures live only in log lines, including the "wrong version after attempted update" case that means an update silently didn't take. This makes all of it countable, with each event replacing its site's log line at the historical level (info for progress, warn for failures) and the same information as structured fields.

Two counters from carbide-instrument:

  • carbide_firmware_updates_total{target, phase} -- started and completed, per target. For hosts both phases fire, so the started-to-completed gap is the in-flight backlog; the DPU-NIC and SuperNIC targets emit started only, with completion visible in their own state flows.
  • carbide_firmware_update_failures_total{target, cause} -- no_update_match (a DPU left ready between snapshot and trigger) and wrong_version_after_update (the update ran and the firmware version still doesn't match -- the silent-failure case).

Notable details:

  • Started counts only committed triggers: the DPU-NIC emit fires after txn.commit(), so an update whose trigger rolled back is a no_update_match failure, never a started update, and the started-minus-completed math stays truthful.
  • Host completion emits once per machine as its update markers clear, replacing one Vec-debug log line with per-machine lines.
  • A SuperNIC re-counts one started per scout pass while a device stays mismatched -- each pass issues a real apply command, and the sustained rate is the stuck-device signal.

Tests added!

This supports #3175

@chet chet requested review from a team and polarweasel as code owners July 9, 2026 17:15
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! I'll perform a full review of the PR now.

(๑>ᴗ<๑)

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

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: 7e20d98c-95f7-4541-b2e1-01e4a0b48cea

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa5b3b and 1c7a0c3.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Summary by CodeRabbit

  • Observability

    • Added structured metrics for firmware update progress and failures across host, DPU NIC, and SuperNIC updates.
    • Firmware update events now include status, target, machine details, and failure causes.
    • Prevented duplicate reporting of repeated incorrect firmware versions.
  • Documentation

    • Documented the new firmware update progress and failure metrics.
  • Tests

    • Added coverage for metric emission and one-time failure counting.

Walkthrough

This PR adds structured firmware-update metrics for host, DPU NIC, and SuperNic update paths, including progress and failure counters, wrong-version deduplication, validation tests, and observability documentation.

Changes

Firmware update instrumentation

Layer / File(s) Summary
Metrics event types and counters
crates/api-core/src/machine_update_manager/metrics.rs
Defines firmware-update targets, phases, failure causes, progress and failure events, and tests their logs, labels, context, and counter increments.
Host and SuperNic progress emission
crates/api-core/src/handlers/host_reprovisioning.rs, crates/api-core/src/handlers/svpc.rs, crates/api-core/src/machine_update_manager/host_firmware.rs
Emits structured host and SuperNic progress events after update actions, including host completion events.
DPU NIC update progress and failures
crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs, crates/api-core/src/tests/dpu_nic_firmware.rs
Emits DPU NIC start and failure events, deduplicates wrong-version failures by machine and firmware version, and validates counter behavior.
Metrics documentation
docs/observability/core_metrics.md
Documents the firmware-update progress and failure counters.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HostReprovisioning
  participant HostFirmwareUpdate
  participant DpuNicFirmwareUpdate
  participant SvpcHandler
  participant MetricsModule

  HostReprovisioning->>MetricsModule: emit FirmwareUpdateProgress(Host, Started)
  HostFirmwareUpdate->>MetricsModule: emit FirmwareUpdateProgress(Host, Started/Completed)
  DpuNicFirmwareUpdate->>MetricsModule: emit FirmwareUpdateProgress(DpuNic, Started)
  DpuNicFirmwareUpdate->>MetricsModule: emit FirmwareUpdateFailed(DpuNic, NoUpdateMatch/WrongVersionAfterUpdate)
  SvpcHandler->>MetricsModule: emit FirmwareUpdateProgress(SuperNic, Started)
Loading

Possibly related PRs

  • NVIDIA/infra-controller#3192: Introduces the unified carbide-instrument event and emission infrastructure used by these firmware-update metrics.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: counting firmware update outcomes across all targets.
Description check ✅ Passed The description matches the changeset and explains the new firmware update metrics, failure causes, and tests.
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.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/api-core/src/machine_update_manager/host_firmware.rs (1)

70-115: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move the Started emit after the batch commit.
FirmwareUpdateProgress { phase: Started, .. } is emitted before txn.commit().await?, so a later error rolls back the DB work but leaves the counter incremented. Emit from updates_started only after the commit succeeds, matching the DPU path and keeping the series aligned with committed triggers.

🤖 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/machine_update_manager/host_firmware.rs` around lines 70
- 115, The `FirmwareUpdateProgress` with phase `Started` is emitted too early in
`host_firmware::check_for_updates`, before `txn.commit().await?`, which can
leave progress counters out of sync if the transaction later fails. Move the
`carbide_instrument::emit` call so it happens only after `txn.commit()`
succeeds, and emit using the `updates_started` set after the batch is durably
committed; keep `db::host_machine_update::trigger_host_reprovisioning_request`,
`updates_started`, and the commit boundary aligned with the DPU 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.

Inline comments:
In `@crates/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 117-137: The `clear_completed_updates` path in
`MachineUpdateManager` emits `FirmwareUpdateProgress` with
`FirmwareUpdatePhase::Completed` before the surrounding transaction is
committed, so move those emits out of the transaction boundary. Collect the
affected machine IDs in `clear_completed_updates` while still calling
`remove_health_report` and `update_update_complete`, then have
`MachineUpdateManager::run` emit the `Completed` events only after
`txn.commit().await?` succeeds.

In `@docs/observability/core_metrics.md`:
- Line 44: Update the documentation row for carbide_firmware_updates_total in
the core metrics table to include the phase label alongside target. The current
description only mentions the update target, so clarify that the counter is
split by both target and phase, and note the started/completed series in the
metric description.

---

Outside diff comments:
In `@crates/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 70-115: The `FirmwareUpdateProgress` with phase `Started` is
emitted too early in `host_firmware::check_for_updates`, before
`txn.commit().await?`, which can leave progress counters out of sync if the
transaction later fails. Move the `carbide_instrument::emit` call so it happens
only after `txn.commit()` succeeds, and emit using the `updates_started` set
after the batch is durably committed; keep
`db::host_machine_update::trigger_host_reprovisioning_request`,
`updates_started`, and the commit boundary aligned with the DPU path.
🪄 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: 77a83aa7-0cb7-4e16-9f30-ca4a55aa12ac

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and 8188e48.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Comment thread crates/api-core/src/machine_update_manager/host_firmware.rs
Comment thread docs/observability/core_metrics.md Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/api-core/src/machine_update_manager/host_firmware.rs (1)

89-114: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Emit Started after the batch commit, not per-iteration inside it.

txn.commit().await? at line 113 covers the whole loop. If a later trigger_host_reprovisioning_request in the same batch fails and returns Err, the transaction rolls back — undoing earlier machines' DB writes — but their FirmwareUpdateProgress{Started} events already fired. Those machines get re-triggered (and re-counted) on the next pass, inflating carbide_firmware_updates_total{target=host,phase=started} beyond real distinct starts. dpu_nic_firmware.rs's start_updates avoids this exact pitfall by committing before emitting; apply the same ordering here.

🛡️ Proposed fix: commit before emitting
             db::host_machine_update::trigger_host_reprovisioning_request(
                 &mut txn,
                 "Automated",
                 machine_update,
             )
             .await?;

-            // Counted after the trigger succeeds; the commit below spans the
-            // whole batch, so a later DB error can re-count machines on the
-            // next pass -- the same repeat-on-retry the old log line had.
-            carbide_instrument::emit(FirmwareUpdateProgress {
-                target: FirmwareUpdateTarget::Host,
-                phase: FirmwareUpdatePhase::Started,
-                machine_id: *machine_update,
-                detail: String::new(),
-            });
             updates_started.insert(*machine_update);
         }

         txn.commit().await?;
+        for machine_update in &updates_started {
+            carbide_instrument::emit(FirmwareUpdateProgress {
+                target: FirmwareUpdateTarget::Host,
+                phase: FirmwareUpdatePhase::Started,
+                machine_id: *machine_update,
+                detail: String::new(),
+            });
+        }
         Ok(updates_started)
🤖 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/machine_update_manager/host_firmware.rs` around lines 89
- 114, Emitting FirmwareUpdateProgress::Started inside the loop in
host_firmware::start_updates can overcount when the surrounding transaction
rolls back; move the start-event emission to after txn.commit().await? succeeds,
so only committed host reprovision requests are counted. Keep the
trigger_host_reprovisioning_request loop and updates_started collection as-is,
but defer carbide_instrument::emit for each machine until after the batch
commit, following the same commit-before-emit ordering used in
dpu_nic_firmware::start_updates.
🤖 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.

Outside diff comments:
In `@crates/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 89-114: Emitting FirmwareUpdateProgress::Started inside the loop
in host_firmware::start_updates can overcount when the surrounding transaction
rolls back; move the start-event emission to after txn.commit().await? succeeds,
so only committed host reprovision requests are counted. Keep the
trigger_host_reprovisioning_request loop and updates_started collection as-is,
but defer carbide_instrument::emit for each machine until after the batch
commit, following the same commit-before-emit ordering used in
dpu_nic_firmware::start_updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0c1245b1-1e70-47dc-ac43-0cb7f7f534e3

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and 8188e48.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

@chet chet force-pushed the gh-issue-3175-firmware branch from 8188e48 to b22549d Compare July 9, 2026 18:05
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! I'll kick off a full review of the PR now.

✅ 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/machine_update_manager/metrics.rs (1)

113-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider Option<String> instead of empty-string sentinels for optional context.

unmatched_dpu_machine_id, firmware_version, and detail all rely on an empty String to mean "not applicable for this cause/target." This is a workable convention documented in the field comments, but it's a magic value that a future caller could misuse (e.g., accidentally logging a genuinely empty version string as "n/a"). Since these are #[context] fields (log-only, not labels), an Option<String> would let carbide_instrument render None explicitly and remove the ambiguity, at the cost of updating the three call sites that currently pass String::new().

♻️ Sketch of the alternative
-    #[context]
-    pub unmatched_dpu_machine_id: String,
+    #[context]
+    pub unmatched_dpu_machine_id: Option<String>,

Also applies to: 150-167

🤖 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/machine_update_manager/metrics.rs` around lines 113 -
127, The `FirmwareUpdateProgress` context fields are using empty strings as
sentinel values for “not applicable,” which is ambiguous and easy to misuse.
Update the `FirmwareUpdateProgress` struct fields (`machine_id`/`detail` and the
related optional context fields used in the same module) to use `Option<String>`
for log-only `#[context]` data, then adjust the call sites that currently pass
`String::new()` to pass `None` when a value is not relevant. Keep the
`FirmwareUpdateProgress` and `carbide_instrument` usage aligned so the logged
context renders the absence explicitly.
🤖 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/machine_update_manager/metrics.rs`:
- Around line 113-127: The `FirmwareUpdateProgress` context fields are using
empty strings as sentinel values for “not applicable,” which is ambiguous and
easy to misuse. Update the `FirmwareUpdateProgress` struct fields
(`machine_id`/`detail` and the related optional context fields used in the same
module) to use `Option<String>` for log-only `#[context]` data, then adjust the
call sites that currently pass `String::new()` to pass `None` when a value is
not relevant. Keep the `FirmwareUpdateProgress` and `carbide_instrument` usage
aligned so the logged context renders the absence explicitly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 729a6855-20f3-4989-a465-b1f415714e55

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and b22549d.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 271 13 34 91 7 126
machine-validation-runner 804 40 237 296 36 195
machine_validation 804 40 237 296 36 195
machine_validation-aarch64 804 40 237 296 36 195
nvmetal-carbide 804 40 237 296 36 195
TOTAL 3493 173 982 1281 151 906

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks! (External Codex round: a persistent wrong-version condition now counts once per outcome instead of once per manager pass -- an in-memory reported-set, cleared when the DPU's markers clear so a fresh attempt re-reports; and operator-initiated host reprovisions emit the paired started, so the started-to-completed gap stays truthful for every initiator.)

@chet chet force-pushed the gh-issue-3175-firmware branch from b22549d to f1b2160 Compare July 9, 2026 20:24
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet Understood. I’ll run a fresh full review with specific attention to the corrected semantics: deduplicating persistent wrong-version reporting to once per outcome until DPU markers clear, and ensuring operator-initiated host reprovisions emit the paired started signal so the started-to-completed gap remains accurate for every initiator.

(ᵔᴥᵔ)


✏️ Learnings added
Learnt from: chet
URL: https://github.com/NVIDIA/infra-controller/pull/3319

Timestamp: 2026-07-09T20:25:21.005Z
Learning: In the firmware update flow for this repository, a persistent wrong-version-after-update condition should be counted once per outcome rather than once per manager pass. The implementation uses an in-memory reported set that is cleared when the DPU's markers clear, so a fresh attempt can be reported again.

Learnt from: chet
URL: https://github.com/NVIDIA/infra-controller/pull/3319

Timestamp: 2026-07-09T20:25:21.005Z
Learning: In the host firmware reprovision flow for this repository, operator-initiated host reprovisions must emit the paired `started` firmware-update metric so that the `started` to `completed` gap remains truthful across every initiation path.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/api-core/src/handlers/host_reprovisioning.rs (1)

76-92: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Emit occurs before the transaction commits.

The carbide_instrument::emit call at Lines 85-92 fires immediately after trigger_host_reprovisioning_request, but the enclosing transaction is not committed until Line 100. If txn.commit() fails, a Started event and counter increment will have already been recorded for a reprovisioning request that was never actually persisted — undermining the started-to-completed gap accuracy that this PR is explicitly designed to preserve.

Move the emit after the successful txn.commit() (or only within the Mode::Set branch, after commit) so metrics only reflect durably-committed state.

🐛 Proposed fix
     match req.mode() {
         Mode::Set => {
             let initiator = req.initiator().as_str_name();
             db::host_machine_update::trigger_host_reprovisioning_request(
                 &mut txn,
                 initiator,
                 &machine_id,
             )
             .await?;
-            // Manual initiations pair with the same completion emit the
-            // update manager's automatic path gets, keeping the
-            // started-to-completed gap truthful for every initiator.
-            carbide_instrument::emit(
-                crate::machine_update_manager::metrics::FirmwareUpdateProgress {
-                    target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
-                    phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
-                    machine_id,
-                    detail: initiator.to_string(),
-                },
-            );
         }
         Mode::Clear => {
             db::host_machine_update::clear_host_reprovisioning_request(&mut txn, &machine_id)
                 .await?;
         }
     }
 
     txn.commit().await?;
+
+    if matches!(req.mode(), Mode::Set) {
+        carbide_instrument::emit(
+            crate::machine_update_manager::metrics::FirmwareUpdateProgress {
+                target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
+                phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
+                machine_id,
+                detail: String::new(),
+            },
+        );
+    }
 
     Ok(Response::new(()))
🤖 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/host_reprovisioning.rs` around lines 76 - 92,
The Started metrics emit in host reprovisioning is happening before the
transaction is durably committed, so a failed commit can still record a request
that never persisted. Update host_reprovisioning.rs in the flow around
trigger_host_reprovisioning_request and txn.commit() so carbide_instrument::emit
runs only after a successful commit, or guard it so it executes only in the
committed Mode::Set path. Keep the metrics emission aligned with the committed
state by referencing the existing FirmwareUpdateProgress,
FirmwareUpdateTarget::Host, and FirmwareUpdatePhase::Started logic.
🤖 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/host_reprovisioning.rs`:
- Line 90: The `HostReprovisioning` handler is populating
`FirmwareUpdateProgress::detail` with `initiator.to_string()`, which conflicts
with the Host contract and existing Host test expectations. Update the
`host_reprovisioning` flow to keep `detail` empty for Host emissions, using the
`FirmwareUpdateProgress` construction in this handler; if initiator info must be
preserved, first update the documented Host schema and related tests to match
the new contract.

---

Outside diff comments:
In `@crates/api-core/src/handlers/host_reprovisioning.rs`:
- Around line 76-92: The Started metrics emit in host reprovisioning is
happening before the transaction is durably committed, so a failed commit can
still record a request that never persisted. Update host_reprovisioning.rs in
the flow around trigger_host_reprovisioning_request and txn.commit() so
carbide_instrument::emit runs only after a successful commit, or guard it so it
executes only in the committed Mode::Set path. Keep the metrics emission aligned
with the committed state by referencing the existing FirmwareUpdateProgress,
FirmwareUpdateTarget::Host, and FirmwareUpdatePhase::Started logic.
🪄 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: f2b4cedb-6c86-46c3-a6da-dc8653885574

📥 Commits

Reviewing files that changed from the base of the PR and between b22549d and f1b2160.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md
✅ Files skipped from review due to trivial changes (1)
  • docs/observability/core_metrics.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs

Comment thread crates/api-core/src/handlers/host_reprovisioning.rs Outdated

@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 (2)
crates/api-core/src/handlers/host_reprovisioning.rs (1)

85-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a top-level use over repeated fully-qualified paths.

Other call sites (host_firmware.rs, dpu_nic_firmware.rs, svpc.rs) import FirmwareUpdateProgress/FirmwareUpdateTarget/FirmwareUpdatePhase at the top of the file; this handler instead spells out the full module path three times inline, which is more verbose than necessary.

♻️ Proposed refactor
+use crate::machine_update_manager::metrics::{
+    FirmwareUpdatePhase, FirmwareUpdateProgress, FirmwareUpdateTarget,
+};
+
 pub(crate) async fn trigger_host_reprovisioning(
     ...
-            carbide_instrument::emit(
-                crate::machine_update_manager::metrics::FirmwareUpdateProgress {
-                    target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
-                    phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
-                    machine_id,
-                    detail: initiator.to_string(),
-                },
-            );
+            carbide_instrument::emit(FirmwareUpdateProgress {
+                target: FirmwareUpdateTarget::Host,
+                phase: FirmwareUpdatePhase::Started,
+                machine_id,
+                detail: String::new(),
+            });
🤖 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/host_reprovisioning.rs` around lines 85 - 92,
The host reprovisioning handler is using repeated fully-qualified paths for
firmware update metrics, making the `emit` call more verbose than the other
firmware handlers. Add top-level `use` imports for `FirmwareUpdateProgress`,
`FirmwareUpdateTarget`, and `FirmwareUpdatePhase` in `host_reprovisioning.rs`,
then update the `carbide_instrument::emit` call to use those imported symbols
consistently like the other call sites.
crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs (1)

262-282: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: Default::default() avoids repeating the mutex boilerplate.

reported_wrong_versions: std::sync::Mutex::new(HashSet::new()) is spelled out identically here and in five test struct literals in tests/dpu_nic_firmware.rs. Mutex<HashSet<T>> implements Default, so Default::default() would be slightly more idiomatic and DRY.

♻️ Optional tweak
         Some(DpuNicFirmwareUpdate {
-            reported_wrong_versions: std::sync::Mutex::new(HashSet::new()),
+            reported_wrong_versions: Default::default(),
             metrics: Some(metrics),
             config,
             dpf,
         })
🤖 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/machine_update_manager/dpu_nic_firmware.rs` around lines
262 - 282, The `DpuNicFirmwareUpdate::new` initializer repeats the mutex
construction boilerplate for `reported_wrong_versions`, and the same pattern
appears in the related test struct literals; switch this field to use
`Default::default()` since `Mutex<HashSet<_>>` implements `Default`, keeping the
constructor and the `tests/dpu_nic_firmware.rs` fixtures consistent and more
idiomatic.
🤖 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/handlers/host_reprovisioning.rs`:
- Around line 85-92: The host reprovisioning handler is using repeated
fully-qualified paths for firmware update metrics, making the `emit` call more
verbose than the other firmware handlers. Add top-level `use` imports for
`FirmwareUpdateProgress`, `FirmwareUpdateTarget`, and `FirmwareUpdatePhase` in
`host_reprovisioning.rs`, then update the `carbide_instrument::emit` call to use
those imported symbols consistently like the other call sites.

In `@crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs`:
- Around line 262-282: The `DpuNicFirmwareUpdate::new` initializer repeats the
mutex construction boilerplate for `reported_wrong_versions`, and the same
pattern appears in the related test struct literals; switch this field to use
`Default::default()` since `Mutex<HashSet<_>>` implements `Default`, keeping the
constructor and the `tests/dpu_nic_firmware.rs` fixtures consistent and more
idiomatic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: afb23cac-cb53-4ce3-9659-6cca1cb36892

📥 Commits

Reviewing files that changed from the base of the PR and between e7a297c and f1b2160.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

@chet chet force-pushed the gh-issue-3175-firmware branch from f1b2160 to 9d4bc54 Compare July 9, 2026 20:52
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! Kicking off a full review of the PR now.

(๑>ᴗ<๑)

✅ Action performed

Full review finished.

@chet

chet commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks! (Rebased over the merged #3272 -- clean, no conflicts; integration regen re-verified the catalogue stable.)

@chet chet force-pushed the gh-issue-3175-firmware branch from 9d4bc54 to 4f50ead Compare July 10, 2026 06:07
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@chet, understood — I'll run a fresh full review now on the rebased branch, confirming the catalogue stability and re-checking the wrong-version dedup logic and the operator-initiated host reprovision started metric emission we discussed earlier.

(๑>ᴗ<๑)

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

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/host_reprovisioning.rs`:
- Around line 82-92: Move the Started FirmwareUpdateProgress emission from
before txn.commit().await? to immediately after the commit succeeds in the host
reprovisioning handler. Preserve the existing event fields and ensure no metric
is emitted when the transaction fails.

In `@crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs`:
- Around line 184-203: Add a focused test alongside test_clear_completed_updates
that configures a DPU with an unexpected firmware version, invokes
clear_completed_updates twice, and verifies that WrongVersionAfterUpdate is
emitted only once. Also assert reported_wrong_versions contains the marker after
the first call and prevents duplicate emission on the second.
🪄 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: 476b9aed-621c-4169-9442-edbb3de8124a

📥 Commits

Reviewing files that changed from the base of the PR and between fde34a4 and 4f50ead.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Comment thread crates/api-core/src/handlers/host_reprovisioning.rs Outdated
Comment thread crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
@chet chet force-pushed the gh-issue-3175-firmware branch from 4f50ead to 85d59ad Compare July 10, 2026 07:26
@chet

chet commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! Kicking off another full review of the PR now.

(๑>ᴗ<๑)

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

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 `@docs/observability/core_metrics.md`:
- Line 45: Update the carbide_firmware_updates_total description in
core_metrics.md to document phase coverage by target: DpuNic and SuperNic emit
only started, while Host emits both started and completed.
🪄 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: 199cee19-b72f-49c7-b6ea-09b691d517bc

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa5b3b and 85d59ad.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Comment thread docs/observability/core_metrics.md Outdated
Firmware updates run for three targets -- host BIOS/BMC, DPU NICs, and SuperNICs -- and none of their outcomes are countable today: starts, completions, and failures live only in log lines, including the "wrong version after attempted update" case that means an update silently didn't take. This makes all of it countable, with each event replacing its site's log line at the historical level (info for progress, warn for failures) and the same information as structured fields.

Two counters from `carbide-instrument`:

- `carbide_firmware_updates_total{target, phase}` -- started and completed, per target. For hosts both phases fire, so the started-to-completed gap is the in-flight backlog; the DPU-NIC and SuperNIC targets emit started only, with completion visible in their own state flows.
- `carbide_firmware_update_failures_total{target, cause}` -- `no_update_match` (a DPU left ready between snapshot and trigger) and `wrong_version_after_update` (the update ran and the firmware version still doesn't match -- the silent-failure case).

Notable details:

- Started counts only committed triggers: the DPU-NIC emit fires after `txn.commit()`, so an update whose trigger rolled back is a `no_update_match` failure, never a started update, and the started-minus-completed math stays truthful.
- Host completion emits once per machine as its update markers clear, replacing one Vec-debug log line with per-machine lines.
- A SuperNIC re-counts one started per scout pass while a device stays mismatched -- each pass issues a real apply command, and the sustained rate is the stuck-device signal.

Tests added!

This supports NVIDIA#3175

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
@chet chet force-pushed the gh-issue-3175-firmware branch from 85d59ad to 1c7a0c3 Compare July 10, 2026 08:34
@chet

chet commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! Kicking off another full review of the PR now.

(๑>ᴗ<๑)

✅ Action performed

Full review finished.

✅ Action performed

Full review finished.

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