diff --git a/wicket/src/cli/inventory.rs b/wicket/src/cli/inventory.rs index 17b141a40e9..64b6ea44e62 100644 --- a/wicket/src/cli/inventory.rs +++ b/wicket/src/cli/inventory.rs @@ -8,13 +8,16 @@ use crate::cli::CommandOutput; use crate::wicketd::create_wicketd_client; use anyhow::Context; use anyhow::Result; +use anyhow::bail; use clap::{Subcommand, ValueEnum}; use owo_colors::OwoColorize; use slog::Logger; use std::fmt; use std::net::SocketAddrV6; use std::time::Duration; +use wicket_common::inventory::Transceiver; use wicket_common::rack_setup::BootstrapSledDescription; +use wicketd_client::types::{GetInventoryParams, GetInventoryResponse}; const WICKETD_TIMEOUT: Duration = Duration::from_secs(5); @@ -26,6 +29,17 @@ pub(crate) enum InventoryArgs { #[clap(long, default_value_t = OutputFormat::Table)] format: OutputFormat, }, + /// List the front-panel transceivers on each switch. + /// + /// This is the same data the TUI shows when a switch is selected, + /// including vendor identity, power state, and environmental monitors + /// (temperature, optical Rx/Tx power). It comes from the SP via wicketd + /// and so remains available when Nexus is not. + Transceivers { + /// Select output format + #[clap(long, default_value_t = OutputFormat::Table)] + format: OutputFormat, + }, } #[derive(Debug, ValueEnum, Clone)] @@ -90,12 +104,86 @@ impl InventoryArgs { } } + Ok(()) + } + InventoryArgs::Transceivers { format } => { + let params = GetInventoryParams { force_refresh: Vec::new() }; + let response = client + .get_inventory(¶ms) + .await + .context("failed to get inventory from wicketd")? + .into_inner(); + let inventory = match response { + GetInventoryResponse::Response { inventory } => inventory, + GetInventoryResponse::Unavailable => { + bail!("wicketd reports inventory as unavailable") + } + }; + let Some(snapshot) = inventory.transceivers else { + bail!( + "wicketd has no transceiver inventory yet \ + (still discovering switch slot?)" + ); + }; + + match format { + OutputFormat::Json => { + let json_str = serde_json::to_string_pretty(&snapshot) + .context("serializing transceiver data failed")?; + writeln!(output.stdout, "{}", json_str) + .expect("writing to stdout failed"); + } + OutputFormat::Table => { + let mut slots: Vec<_> = + snapshot.inventory.iter().collect(); + slots.sort_by_key(|(slot, _)| *slot); + for (slot, transceivers) in slots { + writeln!(output.stdout, "{slot:?}:") + .expect("writing to stdout failed"); + let mut transceivers: Vec<_> = + transceivers.iter().collect(); + transceivers.sort_by(|a, b| a.port.cmp(&b.port)); + for tr in transceivers { + print_transceiver_row(tr, &mut output); + } + } + writeln!( + output.stdout, + "(last updated {:?} ago; use --format json for \ + full monitor data)", + snapshot.last_seen + ) + .expect("writing to stdout failed"); + } + } + Ok(()) } } } } +fn print_transceiver_row(tr: &Transceiver, output: &mut CommandOutput<'_>) { + let vendor = match &tr.vendor { + Ok(v) => format!("{} {}", v.vendor.name.trim(), v.vendor.part.trim()), + Err(e) => format!("(vendor: {e})"), + }; + let power = match &tr.power { + Ok(p) => format!("{:?}", p.state), + Err(e) => format!("(power: {e})"), + }; + let monitors = match &tr.monitors { + Ok(_) => format!("{}", '✔'.green()), + Err(e) => format!("{} {e}", '⚠'.yellow()), + }; + writeln!( + output.stdout, + " {:<8} {:<32} {:<10} monitors={monitors}", + tr.port, vendor, power, + ) + .expect("writing to stdout failed"); +} + fn print_bootstrap_sled_data( desc: &BootstrapSledDescription, output: &mut CommandOutput<'_>,