Skip to content

Commit 427d5b0

Browse files
Merge remote-tracking branch 'remotes/origin/main' into sdmweave
2 parents 78f310b + f40026a commit 427d5b0

12 files changed

Lines changed: 449 additions & 303 deletions

File tree

crates/admin-cli/src/switch/show/cmd.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,12 @@ fn switch_details_text(switch: &Switch) -> CarbideCliResult<String> {
275275
writeln!(&mut lines, "\nConfig:")?;
276276
if let Some(config) = &switch.config {
277277
writeln!(&mut lines, "\tName : {}", config.name)?;
278-
writeln!(&mut lines, "\tEnable NMX-C : {}", config.enable_nmxc)?;
278+
writeln!(&mut lines, "\tNMX-C Configured : {}", config.enable_nmxc)?;
279+
writeln!(
280+
&mut lines,
281+
"\tNMX-C Effective : {}",
282+
config.enable_nmxc || switch.is_primary
283+
)?;
279284
if let Some(fm_config) = &config.fabric_manager_config
280285
&& !fm_config.config_map.is_empty()
281286
{
@@ -490,7 +495,7 @@ mod tests {
490495
slot_number: Some(13),
491496
tray_index: Some(8),
492497
}),
493-
is_primary: false,
498+
is_primary: true,
494499
controller_state:
495500
r#"{"state":"reprovisioning","reprovisioning_state":"WaitingForNVOSUpgrade"}"#
496501
.to_string(),
@@ -513,7 +518,10 @@ mod tests {
513518

514519
for expected in [
515520
"ID : sw100nsner0op5osl6n85t7772j010jmhafm934n7oej4mlome3okrn9b60",
521+
"Primary : Yes",
516522
"\tName : MT2519600UD6",
523+
"\tNMX-C Configured : false",
524+
"\tNMX-C Effective : true",
517525
"\tPower State : on",
518526
"\tFirmware Version : 1.3.5-GA",
519527
"\tNAME: sw100nsner0op5osl6n85t7772j010jmhafm934n7oej4mlome3okrn9b60",

crates/api-core/src/handlers/component_manager.rs

Lines changed: 65 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3295,7 +3295,7 @@ enum FirmwareStatusRouting {
32953295
/// requested IDs are sent to the compute-tray backend.
32963296
DirectDispatch,
32973297
/// The CM is in state-controller mode: the request is split by whether each
3298-
/// machine has a persisted `backend_firmware_object_job_id`.
3298+
/// machine has an in-flight direct-dispatch firmware-object job.
32993299
Partitioned {
33003300
/// IDs with a persisted backend job — route to `compute_tray` so callers
33013301
/// can poll the live in-flight state.
@@ -3305,92 +3305,59 @@ enum FirmwareStatusRouting {
33053305
},
33063306
}
33073307

3308-
/// Partition `machine_ids` into those that have a persisted backend
3309-
/// firmware-object job ID on their machine row and those that do not.
3308+
/// Partition `machine_ids` by whether their BMC MAC has a persisted
3309+
/// direct-dispatch firmware-update job (`bmc_macs_with_direct_fw_updates`).
33103310
///
33113311
/// Returns `(persisted_ids, fallback_ids)`:
3312-
/// - `persisted_ids` — IDs where the loaded machine has a non-null
3313-
/// `backend_firmware_object_job_id`; route to `compute_tray`.
3312+
/// - `persisted_ids` — the machine's BMC MAC is in
3313+
/// `bmc_macs_with_direct_fw_updates`; route to `compute_tray` to poll the live
3314+
/// in-flight job.
33143315
/// - `fallback_ids` — remaining IDs; route to `machine_firmware_statuses`.
33153316
///
3316-
/// A tray flashed before ingestion persists its job to `explored_endpoints`
3317-
/// rather than the machine row, so the caller augments the split with
3318-
/// [`reclassify_preingestion_jobs_from_explored_endpoints`] before routing.
3317+
/// Keyed by BMC MAC because a job dispatched before ingestion is recorded under
3318+
/// the tray's MAC, and stays reachable there after ingestion creates the machine
3319+
/// row — so a tray flashed pre-ingestion still polls the live backend.
33193320
fn partition_by_backend_job_id(
33203321
machine_ids: &[MachineId],
33213322
machines_by_id: &HashMap<MachineId, Machine>,
3323+
bmc_macs_with_direct_fw_updates: &HashSet<MacAddress>,
33223324
) -> (Vec<MachineId>, Vec<MachineId>) {
33233325
machine_ids.iter().copied().partition(|id| {
33243326
machines_by_id
33253327
.get(id)
3326-
.is_some_and(|m| m.status.backend_firmware_object_job_id.is_some())
3328+
.and_then(|m| m.status.bmc_info.mac)
3329+
.is_some_and(|mac| bmc_macs_with_direct_fw_updates.contains(&mac))
33273330
})
33283331
}
33293332

33303333
/// Choose how to route a batch of `machine_ids` for firmware-status retrieval.
33313334
///
33323335
/// When `use_state_controller` is `false` (direct-dispatch mode) all IDs are
33333336
/// forwarded to the compute-tray backend. Otherwise the IDs are partitioned by
3334-
/// the presence of a persisted `backend_firmware_object_job_id`: those with a job
3335-
/// go to the live backend; the rest fall back to the DB-only path.
3337+
/// whether their BMC MAC has a persisted direct-dispatch job
3338+
/// (`bmc_macs_with_direct_fw_updates`): those with a job poll the live backend;
3339+
/// the rest fall back to the DB-only path.
33363340
fn select_firmware_status_routing(
33373341
use_state_controller: bool,
33383342
machine_ids: &[MachineId],
33393343
machines_by_id: &HashMap<MachineId, Machine>,
3344+
bmc_macs_with_direct_fw_updates: &HashSet<MacAddress>,
33403345
) -> FirmwareStatusRouting {
33413346
if !use_state_controller {
33423347
FirmwareStatusRouting::DirectDispatch
33433348
} else {
3344-
let (persisted, fallback) = partition_by_backend_job_id(machine_ids, machines_by_id);
3349+
let (persisted, fallback) = partition_by_backend_job_id(
3350+
machine_ids,
3351+
machines_by_id,
3352+
bmc_macs_with_direct_fw_updates,
3353+
);
33453354
FirmwareStatusRouting::Partitioned {
33463355
persisted,
33473356
fallback,
33483357
}
33493358
}
33503359
}
33513360

3352-
/// Move `fallback` machines whose BMC IP still carries a pre-ingestion firmware
3353-
/// job in `explored_endpoints` into `persisted`, so they poll the live backend.
3354-
///
3355-
/// A tray flashed before ingestion persists its job id to `explored_endpoints`
3356-
/// (keyed by BMC IP), not the machine row. Once ingestion creates the row its
3357-
/// `backend_firmware_object_job_id` stays NULL, so the machine-row partition
3358-
/// alone would misroute it to the DB-only path and lose live status. One batched
3359-
/// query over the fallback set's BMC IPs reclassifies those still tracked.
3360-
async fn reclassify_preingestion_jobs_from_explored_endpoints(
3361-
api: &Api,
3362-
persisted: &mut Vec<MachineId>,
3363-
fallback: &mut Vec<MachineId>,
3364-
machines_by_id: &HashMap<MachineId, Machine>,
3365-
) -> Result<(), Status> {
3366-
let fallback_ips: Vec<IpAddr> = fallback
3367-
.iter()
3368-
.filter_map(|id| machines_by_id.get(id).and_then(|m| m.status.bmc_info.ip))
3369-
.collect();
3370-
let ips_with_job = db::explored_endpoints::find_ips_with_backend_firmware_object_job_id(
3371-
api.pg_pool(),
3372-
&fallback_ips,
3373-
)
3374-
.await
3375-
.map_err(|e| Status::internal(format!("db error: {e}")))?;
3376-
3377-
if ips_with_job.is_empty() {
3378-
return Ok(());
3379-
}
3380-
3381-
fallback.retain(|id| {
3382-
let has_explored_job = machines_by_id
3383-
.get(id)
3384-
.and_then(|m| m.status.bmc_info.ip)
3385-
.is_some_and(|ip| ips_with_job.contains(&ip));
3386-
if has_explored_job {
3387-
persisted.push(*id);
3388-
}
3389-
!has_explored_job
3390-
});
3391-
Ok(())
3392-
}
3393-
33943361
pub(crate) async fn get_component_firmware_status(
33953362
api: &Api,
33963363
request: Request<rpc::GetComponentFirmwareStatusRequest>,
@@ -3479,38 +3446,42 @@ pub(crate) async fn get_component_firmware_status(
34793446
}
34803447

34813448
// In direct-dispatch mode all IDs go to the compute-tray backend.
3482-
// In state-controller mode the batch is partitioned: IDs with a
3483-
// persisted backend_firmware_object_job_id (set when a firmware update
3484-
// was dispatched via --bypass-state-controller) are polled from the
3485-
// live backend; the rest use the DB-only machine_firmware_statuses()
3486-
// path.
3449+
// In state-controller mode the batch is partitioned by whether each
3450+
// tray's BMC MAC has an in-flight direct-dispatch firmware-object job
3451+
// in compute_firmware_object_jobs (set when a firmware update was
3452+
// dispatched via --bypass-state-controller, before or after
3453+
// ingestion): those are polled from the live backend; the rest use
3454+
// the DB-only machine_firmware_statuses() path.
34873455
if let Some(cm) = api.component_manager.as_ref() {
34883456
let machines_by_id = load_machines_by_id(api, &list.machine_ids).await?;
34893457

3458+
let bmc_macs_with_direct_fw_updates = if cm.compute_tray_use_state_controller {
3459+
let bmc_macs: Vec<MacAddress> = list
3460+
.machine_ids
3461+
.iter()
3462+
.filter_map(|id| machines_by_id.get(id).and_then(|m| m.status.bmc_info.mac))
3463+
.collect();
3464+
db::direct_dispatch_firmware_job::find_macs_with_job(api.pg_pool(), &bmc_macs)
3465+
.await
3466+
.map_err(|e| Status::internal(format!("db error: {e}")))?
3467+
} else {
3468+
HashSet::new()
3469+
};
3470+
34903471
match select_firmware_status_routing(
34913472
cm.compute_tray_use_state_controller,
34923473
&list.machine_ids,
34933474
&machines_by_id,
3475+
&bmc_macs_with_direct_fw_updates,
34943476
) {
34953477
FirmwareStatusRouting::DirectDispatch => {
34963478
compute_tray_firmware_statuses(cm, api, &machines_by_id, &list.machine_ids)
34973479
.await?
34983480
}
34993481
FirmwareStatusRouting::Partitioned {
3500-
mut persisted,
3501-
mut fallback,
3482+
persisted,
3483+
fallback,
35023484
} => {
3503-
// A tray flashed before ingestion keeps its job on
3504-
// explored_endpoints, not the machine row, so reclassify
3505-
// those still tracked there back onto the live-poll path.
3506-
reclassify_preingestion_jobs_from_explored_endpoints(
3507-
api,
3508-
&mut persisted,
3509-
&mut fallback,
3510-
&machines_by_id,
3511-
)
3512-
.await?;
3513-
35143485
let mut statuses = Vec::with_capacity(list.machine_ids.len());
35153486
if !persisted.is_empty() {
35163487
statuses.extend(
@@ -5216,22 +5187,26 @@ mod tests {
52165187
let id_b = dpu_machine_id(0);
52175188
let id_c = dpu_machine_id(1);
52185189

5219-
let mut machine_with_rms_job = machine_with_id(standalone_machine(), id_a);
5220-
machine_with_rms_job.status.backend_firmware_object_job_id =
5221-
Some("backend-job-bypass-abc".to_string());
5190+
let mac_a: MacAddress = "AA:BB:CC:DD:EE:01".parse().unwrap();
5191+
let mac_b: MacAddress = "AA:BB:CC:DD:EE:02".parse().unwrap();
5192+
let mac_c: MacAddress = "AA:BB:CC:DD:EE:03".parse().unwrap();
5193+
5194+
let mut machine_a = machine_with_id(standalone_machine(), id_a);
5195+
machine_a.status.bmc_info.mac = Some(mac_a);
5196+
let mut machine_b = machine_with_id(standalone_machine(), id_b.into());
5197+
machine_b.status.bmc_info.mac = Some(mac_b);
5198+
let mut machine_c = machine_with_id(standalone_machine(), id_c.into());
5199+
machine_c.status.bmc_info.mac = Some(mac_c);
52225200

52235201
let machines = HashMap::from([
5224-
(id_a, machine_with_rms_job),
5225-
(
5226-
id_b.into(),
5227-
machine_with_id(standalone_machine(), id_b.into()),
5228-
),
5229-
(
5230-
id_c.into(),
5231-
machine_with_id(standalone_machine(), id_c.into()),
5232-
),
5202+
(id_a, machine_a),
5203+
(id_b.into(), machine_b),
5204+
(id_c.into(), machine_c),
52335205
]);
52345206

5207+
// Only machine A has an in-flight direct-dispatch firmware job.
5208+
let bmc_macs_with_direct_fw_updates: HashSet<MacAddress> = HashSet::from([mac_a]);
5209+
52355210
struct Case {
52365211
scenario: &'static str,
52375212
use_state_controller: bool,
@@ -5283,8 +5258,12 @@ mod tests {
52835258
.map(|&i| all_ids[i])
52845259
.collect();
52855260

5286-
let routing =
5287-
select_firmware_status_routing(case.use_state_controller, &ids, &machines);
5261+
let routing = select_firmware_status_routing(
5262+
case.use_state_controller,
5263+
&ids,
5264+
&machines,
5265+
&bmc_macs_with_direct_fw_updates,
5266+
);
52885267
match routing {
52895268
FirmwareStatusRouting::DirectDispatch => {
52905269
assert!(
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Unify direct-dispatch (--bypass-state-controller) firmware-update job IDs in
2+
-- one table keyed by the device's BMC MAC, its stable identity across
3+
-- ingestion. Recording the backend job here lets get_firmware_status recover it
4+
-- after a nico-api restart clears the in-memory job map, for both ingested and
5+
-- pre-ingestion devices, without splitting the state across the machines and
6+
-- explored_endpoints rows.
7+
CREATE TABLE direct_dispatch_firmware_update_jobs (
8+
bmc_mac macaddr PRIMARY KEY,
9+
job_id text NOT NULL,
10+
created timestamp with time zone DEFAULT now() NOT NULL
11+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- Backfill the unified table from the two legacy columns this release stops
2+
-- writing, so a direct-dispatch firmware job already in flight at upgrade stays
3+
-- pollable under its BMC MAC. The columns themselves are dropped in a later
4+
-- release, once no running instance still writes them.
5+
--
6+
-- Pre-ingestion jobs live on explored_endpoints keyed by BMC IP; resolve the
7+
-- MAC through the BMC interface that owns that address. Insert these first so a
8+
-- later machines row can overwrite them.
9+
INSERT INTO direct_dispatch_firmware_update_jobs (bmc_mac, job_id)
10+
SELECT DISTINCT ON (mi.mac_address) mi.mac_address, ee.backend_firmware_object_job_id
11+
FROM explored_endpoints ee
12+
JOIN machine_interface_addresses mia ON mia.address = ee.address
13+
JOIN machine_interfaces mi
14+
ON mi.id = mia.interface_id AND mi.interface_type = 'Bmc'
15+
WHERE ee.backend_firmware_object_job_id IS NOT NULL
16+
ORDER BY mi.mac_address, ee.address
17+
ON CONFLICT (bmc_mac) DO NOTHING;
18+
19+
-- Ingested jobs live on machines keyed by machine id; resolve the MAC through
20+
-- the machine's BMC interface. A tray flashed pre-ingestion and then ingested
21+
-- can hold a stale explored_endpoints entry alongside a current machines entry
22+
-- for the same MAC, so a machines job always wins over an explored one.
23+
INSERT INTO direct_dispatch_firmware_update_jobs (bmc_mac, job_id)
24+
SELECT DISTINCT ON (mi.mac_address) mi.mac_address, m.backend_firmware_object_job_id
25+
FROM machines m
26+
JOIN machine_interfaces mi
27+
ON mi.machine_id = m.id AND mi.interface_type = 'Bmc'
28+
WHERE m.backend_firmware_object_job_id IS NOT NULL
29+
ORDER BY mi.mac_address, m.updated DESC
30+
ON CONFLICT (bmc_mac) DO UPDATE SET job_id = EXCLUDED.job_id;

0 commit comments

Comments
 (0)