Skip to content

Commit 148ab9c

Browse files
committed
feat: per-machine BMC vendor override
Pin a Redfish BMC vendor per machine, forced into libredfish instead of automatic detection. The value is a plain string matched against libredfish's RedfishVendor enum at client creation, and an unknown name warns and falls back, so NICo keeps no vendor list. It rides on BmcAccessInfo so every client_by_info caller honors it, and the direct instance power and force delete paths pass it too. Adds the machines.bmc_vendor_override column and migration, the UpdateMachineBmcVendorOverride RPC and a field on Machine, the forced vendor in client_by_info, and a machine vendor override set, clear, and show CLI command. Signed-off-by: s3rj1k <evasive.gyron@gmail.com>
1 parent a8580eb commit 148ab9c

26 files changed

Lines changed: 5541 additions & 4833 deletions

File tree

crates/admin-cli/src/machine/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod nvlink_info;
2727
pub mod positions;
2828
pub mod reboot;
2929
pub mod show;
30+
pub mod vendor_override;
3031

3132
#[cfg(test)]
3233
mod tests;
@@ -89,4 +90,9 @@ pub enum Cmd {
8990
Positions(positions::Args),
9091
#[clap(subcommand, about = "Update/show NVLink info for an MNNVL machine")]
9192
NvlinkInfo(nvlink_info::Args),
93+
#[clap(
94+
subcommand,
95+
about = "Pin or clear the Redfish BMC vendor override for a machine"
96+
)]
97+
VendorOverride(vendor_override::Args),
9298
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
use carbide_uuid::machine::MachineId;
19+
use clap::Parser;
20+
21+
#[derive(Parser, Debug, Clone)]
22+
pub enum Args {
23+
#[clap(about = "Pin the Redfish BMC vendor for a machine")]
24+
Set(VendorOverrideSet),
25+
#[clap(about = "Clear the Redfish BMC vendor override for a machine")]
26+
Clear(VendorOverrideClear),
27+
#[clap(about = "Show the Redfish BMC vendor override for a machine")]
28+
Show(VendorOverrideShow),
29+
}
30+
31+
#[derive(Parser, Debug, Clone)]
32+
#[command(after_long_help = "\
33+
EXAMPLES:
34+
35+
Force a machine's BMC vendor to Dell:
36+
$ nico-admin-cli machine vendor-override set 12345678-1234-5678-90ab-cdef01234567 \
37+
--vendor Dell
38+
39+
")]
40+
pub struct VendorOverrideSet {
41+
#[clap(help = "The machine whose BMC vendor should be pinned")]
42+
pub machine: MachineId,
43+
#[clap(
44+
long,
45+
help = "RedfishVendor to force (e.g. Dell, Supermicro, NvidiaDpu, Hpe, Lenovo)"
46+
)]
47+
pub vendor: String,
48+
}
49+
50+
#[derive(Parser, Debug, Clone)]
51+
#[command(after_long_help = "\
52+
EXAMPLES:
53+
54+
Clear a machine's BMC vendor override (return to automatic detection):
55+
$ nico-admin-cli machine vendor-override clear 12345678-1234-5678-90ab-cdef01234567
56+
57+
")]
58+
pub struct VendorOverrideClear {
59+
#[clap(help = "The machine whose BMC vendor override should be cleared")]
60+
pub machine: MachineId,
61+
}
62+
63+
#[derive(Parser, Debug, Clone)]
64+
#[command(after_long_help = "\
65+
EXAMPLES:
66+
67+
Show a machine's pinned BMC vendor (or that none is set):
68+
$ nico-admin-cli machine vendor-override show 12345678-1234-5678-90ab-cdef01234567
69+
70+
")]
71+
pub struct VendorOverrideShow {
72+
#[clap(help = "The machine whose BMC vendor override should be shown")]
73+
pub machine: MachineId,
74+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
use carbide_uuid::machine::MachineId;
19+
use rpc::Machine;
20+
21+
use super::args::{Args, VendorOverrideClear, VendorOverrideSet, VendorOverrideShow};
22+
use crate::errors::{CarbideCliError, CarbideCliResult};
23+
use crate::rpc::ApiClient;
24+
25+
pub async fn vendor_override(api_client: &ApiClient, cmd: Args) -> CarbideCliResult<()> {
26+
match cmd {
27+
Args::Set(cmd) => set(api_client, cmd).await,
28+
Args::Clear(cmd) => clear(api_client, cmd).await,
29+
Args::Show(cmd) => show(api_client, cmd).await,
30+
}
31+
}
32+
33+
async fn fetch_machine(api_client: &ApiClient, machine_id: MachineId) -> CarbideCliResult<Machine> {
34+
let mut machines = api_client
35+
.get_machines_by_ids(&[machine_id])
36+
.await?
37+
.machines;
38+
machines.pop().ok_or_else(|| {
39+
CarbideCliError::GenericError(format!("Machine with ID {machine_id} was not found"))
40+
})
41+
}
42+
43+
async fn set(api_client: &ApiClient, cmd: VendorOverrideSet) -> CarbideCliResult<()> {
44+
api_client
45+
.update_machine_bmc_vendor_override(cmd.machine, Some(cmd.vendor))
46+
.await
47+
}
48+
49+
async fn clear(api_client: &ApiClient, cmd: VendorOverrideClear) -> CarbideCliResult<()> {
50+
api_client
51+
.update_machine_bmc_vendor_override(cmd.machine, None)
52+
.await
53+
}
54+
55+
async fn show(api_client: &ApiClient, cmd: VendorOverrideShow) -> CarbideCliResult<()> {
56+
let machine = fetch_machine(api_client, cmd.machine).await?;
57+
match machine.bmc_vendor_override.as_deref() {
58+
Some(vendor) => println!("{vendor}"),
59+
None => println!("not set (automatic detection)"),
60+
}
61+
Ok(())
62+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
pub mod args;
19+
pub mod cmd;
20+
21+
pub use args::Args;
22+
23+
use crate::cfg::run::Run;
24+
use crate::cfg::runtime::RuntimeContext;
25+
use crate::errors::CarbideCliResult;
26+
27+
impl Run for Args {
28+
async fn run(self, ctx: &mut RuntimeContext) -> CarbideCliResult<()> {
29+
cmd::vendor_override(&ctx.api_client, self).await?;
30+
Ok(())
31+
}
32+
}

crates/admin-cli/src/rpc.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,6 +2139,18 @@ impl ApiClient {
21392139
Ok(self.0.update_machine_metadata(request).await?)
21402140
}
21412141

2142+
pub async fn update_machine_bmc_vendor_override(
2143+
&self,
2144+
machine_id: MachineId,
2145+
bmc_vendor_override: Option<String>,
2146+
) -> CarbideCliResult<()> {
2147+
let request = ::rpc::forge::MachineBmcVendorOverrideUpdateRequest {
2148+
machine_id: Some(machine_id),
2149+
bmc_vendor_override,
2150+
};
2151+
Ok(self.0.update_machine_bmc_vendor_override(request).await?)
2152+
}
2153+
21422154
pub async fn update_rack_metadata(
21432155
&self,
21442156
rack_id: RackId,

crates/api-core/src/api.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,13 @@ impl Forge for Api {
11801180
crate::handlers::machine::update_machine_metadata(self, request).await
11811181
}
11821182

1183+
async fn update_machine_bmc_vendor_override(
1184+
&self,
1185+
request: Request<rpc::MachineBmcVendorOverrideUpdateRequest>,
1186+
) -> std::result::Result<Response<()>, Status> {
1187+
crate::handlers::machine::update_machine_bmc_vendor_override(self, request).await
1188+
}
1189+
11831190
async fn update_rack_metadata(
11841191
&self,
11851192
request: Request<rpc::RackMetadataUpdateRequest>,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,10 @@ pub(crate) async fn invoke_power(
10841084
// but instead queue it for the state handler. That will avoid racing
10851085
// with other internal reboot requests from the state handler.
10861086
let bmc_ip = bmc_ip.to_string();
1087+
let vendor_override = carbide_redfish::libredfish::conv::redfish_vendor_override(
1088+
&bmc_ip,
1089+
snapshot.host_snapshot.bmc_vendor_override.as_deref(),
1090+
);
10871091
let client = api
10881092
.redfish_pool
10891093
.create_client(
@@ -1092,7 +1096,7 @@ pub(crate) async fn invoke_power(
10921096
RedfishAuth::Key(CredentialKey::BmcCredentials {
10931097
credential_type: BmcCredentialType::BmcRoot { bmc_mac_address },
10941098
}),
1095-
None,
1099+
vendor_override,
10961100
)
10971101
.await
10981102
.map_err(|e| CarbideError::internal(e.to_string()))?;

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,40 @@ pub(crate) async fn update_machine_metadata(
302302
Ok(tonic::Response::new(()))
303303
}
304304

305+
pub(crate) async fn update_machine_bmc_vendor_override(
306+
api: &Api,
307+
request: Request<rpc::MachineBmcVendorOverrideUpdateRequest>,
308+
) -> std::result::Result<tonic::Response<()>, tonic::Status> {
309+
log_request_data(&request);
310+
let request = request.into_inner();
311+
let machine_id = convert_and_log_machine_id(request.machine_id.as_ref())?;
312+
313+
let mut txn = api.txn_begin().await?;
314+
if db::machine::find_one(&mut txn, &machine_id, MachineSearchConfig::default())
315+
.await?
316+
.is_none()
317+
{
318+
return Err(CarbideError::NotFoundError {
319+
kind: "machine",
320+
id: machine_id.to_string(),
321+
}
322+
.into());
323+
}
324+
325+
// Store the override as a plain string, empty or absent clears it. libredfish
326+
// matches the vendor when the client is built, so the API keeps no vendor list.
327+
let bmc_vendor_override = match request.bmc_vendor_override {
328+
Some(name) if !name.is_empty() => Some(name),
329+
_ => None,
330+
};
331+
332+
db::machine::update_bmc_vendor_override(&mut txn, &machine_id, bmc_vendor_override).await?;
333+
334+
txn.commit().await?;
335+
336+
Ok(tonic::Response::new(()))
337+
}
338+
305339
pub(crate) async fn admin_force_delete_machine(
306340
api: &Api,
307341
request: Request<rpc::AdminForceDeleteMachineRequest>,
@@ -472,6 +506,11 @@ pub(crate) async fn admin_force_delete_machine(
472506
"BMC IP and MAC address for machine was found. Trying to perform Bios unlock",
473507
);
474508

509+
let vendor_override = carbide_redfish::libredfish::conv::redfish_vendor_override(
510+
&ip_address,
511+
machine.bmc_vendor_override.as_deref(),
512+
);
513+
475514
match api
476515
.redfish_pool
477516
.create_client(
@@ -480,7 +519,7 @@ pub(crate) async fn admin_force_delete_machine(
480519
RedfishAuth::Key(CredentialKey::BmcCredentials {
481520
credential_type: BmcCredentialType::BmcRoot { bmc_mac_address },
482521
}),
483-
None,
522+
vendor_override,
484523
)
485524
.await
486525
{
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Add bmc_vendor_override to machines so an operator can pin the Redfish BMC
2+
-- vendor for a machine. NULL means automatic detection. The value is a
3+
-- RedfishVendor variant name passed down into libredfish as the forced vendor.
4+
ALTER TABLE machines ADD COLUMN bmc_vendor_override text;

crates/api-db/src/machine.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,23 @@ pub async fn update_metadata(
926926
}
927927
}
928928

929+
/// Set or clear the operator pinned Redfish BMC vendor override for a machine.
930+
/// Passing None clears it.
931+
pub async fn update_bmc_vendor_override(
932+
txn: &mut PgConnection,
933+
machine_id: &MachineId,
934+
bmc_vendor_override: Option<String>,
935+
) -> Result<(), DatabaseError> {
936+
let query = "UPDATE machines SET bmc_vendor_override = $1 WHERE id = $2";
937+
sqlx::query(query)
938+
.bind(bmc_vendor_override)
939+
.bind(machine_id)
940+
.execute(txn)
941+
.await
942+
.map_err(|e| DatabaseError::query(query, e))?;
943+
Ok(())
944+
}
945+
929946
/// Only does the update if the passed observation is newer than any existing one
930947
pub async fn update_network_status_observation(
931948
txn: &mut PgConnection,

0 commit comments

Comments
 (0)