use crate::bmc_quirks::BmcQuirks;
use crate::entity_link::FromLink;
use crate::hardware_id::HardwareIdRef;
use crate::hardware_id::Manufacturer as HardwareIdManufacturer;
use crate::hardware_id::Model as HardwareIdModel;
use crate::hardware_id::PartNumber as HardwareIdPartNumber;
use crate::hardware_id::SerialNumber as HardwareIdSerialNumber;
use crate::patch_support::JsonValue;
use crate::patch_support::Payload;
use crate::patch_support::ReadPatchFn;
use crate::schema::chassis::Chassis as ChassisSchema;
use crate::Error;
use crate::NvBmc;
use crate::Resource;
use crate::ResourceSchema;
use nv_redfish_core::bmc::Bmc;
use nv_redfish_core::NavProperty;
use std::future::Future;
use std::sync::Arc;
#[cfg(feature = "assembly")]
use crate::assembly::Assembly;
#[cfg(feature = "network-adapters")]
use crate::chassis::NetworkAdapter;
#[cfg(feature = "network-adapters")]
use crate::chassis::NetworkAdapterCollection;
#[cfg(feature = "power")]
use crate::chassis::Power;
#[cfg(feature = "power-supplies")]
use crate::chassis::PowerSupply;
#[cfg(feature = "thermal")]
use crate::chassis::Thermal;
#[cfg(feature = "log-services")]
use crate::log_service::LogService;
#[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
use crate::oem::liteon;
#[cfg(feature = "oem-nvidia-baseboard")]
use crate::oem::nvidia::baseboard::NvidiaCbcChassis;
#[cfg(feature = "pcie-devices")]
use crate::pcie_device::PcieDeviceCollection;
#[cfg(feature = "sensors")]
use crate::schema::sensor::Sensor as SchemaSensor;
#[cfg(feature = "sensors")]
use crate::sensor::extract_environment_sensors;
#[cfg(feature = "sensors")]
use crate::sensor::SensorLink;
#[cfg(feature = "oem-nvidia-baseboard")]
use std::convert::identity;
#[doc(hidden)]
pub enum ChassisTag {}
pub type Manufacturer<T> = HardwareIdManufacturer<T, ChassisTag>;
pub type Model<T> = HardwareIdModel<T, ChassisTag>;
pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
pub struct Config {
pub read_patch_fn: Option<ReadPatchFn>,
}
impl Config {
pub fn new(quirks: &BmcQuirks) -> Self {
let mut patches = Vec::new();
if quirks.bug_invalid_contained_by_fields() {
patches.push(remove_invalid_contained_by_fields as fn(JsonValue) -> JsonValue);
}
if quirks.bug_missing_chassis_type_field() {
patches.push(add_default_chassis_type);
}
if quirks.bug_missing_chassis_name_field() {
patches.push(add_default_chassis_name);
}
if quirks.bug_empty_uuid_field() {
patches.push(normalize_empty_uuid_field);
}
let read_patch_fn = (!patches.is_empty())
.then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
Self { read_patch_fn }
}
}
pub struct Chassis<B: Bmc> {
#[allow(dead_code)] bmc: NvBmc<B>,
data: Arc<ChassisSchema>,
#[allow(dead_code)] config: Arc<Config>,
}
impl<B: Bmc> Chassis<B> {
pub(crate) async fn new(
bmc: &NvBmc<B>,
nav: &NavProperty<ChassisSchema>,
) -> Result<Self, Error<B>> {
let config = Config::new(&bmc.quirks);
if let Some(read_patch_fn) = &config.read_patch_fn {
Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
} else {
nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
}
.map(|data| Self {
bmc: bmc.clone(),
data,
config: config.into(),
})
}
#[must_use]
pub fn raw(&self) -> Arc<ChassisSchema> {
self.data.clone()
}
#[must_use]
pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> {
HardwareIdRef {
manufacturer: self
.data
.manufacturer
.as_ref()
.and_then(Option::as_deref)
.map(Manufacturer::new),
model: self
.data
.model
.as_ref()
.and_then(Option::as_deref)
.map(Model::new),
part_number: self
.data
.part_number
.as_ref()
.and_then(Option::as_deref)
.map(PartNumber::new),
serial_number: self
.data
.serial_number
.as_ref()
.and_then(Option::as_deref)
.map(SerialNumber::new),
}
}
#[cfg(feature = "assembly")]
pub async fn assembly(&self) -> Result<Option<Assembly<B>>, Error<B>> {
if let Some(assembly_ref) = &self.data.assembly {
Assembly::new(&self.bmc, assembly_ref).await.map(Some)
} else {
Ok(None)
}
}
#[cfg(feature = "power-supplies")]
pub async fn power_supplies(&self) -> Result<Vec<PowerSupply<B>>, Error<B>> {
if let Some(ps) = &self.data.power_subsystem {
let ps = ps.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
if let Some(supplies) = &ps.power_supplies {
let supplies = &self.bmc.expand_property(supplies).await?.members;
let mut power_supplies = Vec::with_capacity(supplies.len());
for power_supply in supplies {
power_supplies.push(PowerSupply::new(&self.bmc, power_supply).await?);
}
return Ok(power_supplies);
}
}
Ok(Vec::new())
}
#[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
pub async fn oem_liteon_power_supply_links(
&self,
) -> Result<Option<Vec<liteon::power_supply::LiteonPowerSupplyLink<B>>>, Error<B>> {
liteon::power_supply::chassis_fetch_links(&self.bmc, self).await
}
#[cfg(feature = "power")]
pub async fn power(&self) -> Result<Option<Power<B>>, Error<B>> {
if let Some(power_ref) = &self.data.power {
Ok(Some(Power::new(&self.bmc, power_ref).await?))
} else {
Ok(None)
}
}
#[cfg(feature = "thermal")]
pub async fn thermal(&self) -> Result<Option<Thermal<B>>, Error<B>> {
if let Some(thermal_ref) = &self.data.thermal {
Thermal::new(&self.bmc, thermal_ref).await.map(Some)
} else {
Ok(None)
}
}
#[cfg(feature = "network-adapters")]
pub async fn network_adapters(&self) -> Result<Option<Vec<NetworkAdapter<B>>>, Error<B>> {
if let Some(network_adapters_collection_ref) = &self.data.network_adapters {
NetworkAdapterCollection::new(&self.bmc, network_adapters_collection_ref)
.await?
.members()
.await
.map(Some)
} else {
Ok(None)
}
}
#[cfg(feature = "log-services")]
pub async fn log_services(&self) -> Result<Option<Vec<LogService<B>>>, Error<B>> {
if let Some(log_services_ref) = &self.data.log_services {
let log_services_collection = log_services_ref
.get(self.bmc.as_ref())
.await
.map_err(Error::Bmc)?;
let mut log_services = Vec::new();
for m in &log_services_collection.members {
log_services.push(LogService::new(&self.bmc, m).await?);
}
Ok(Some(log_services))
} else {
Ok(None)
}
}
#[cfg(feature = "sensors")]
pub async fn environment_sensor_links(&self) -> Result<Vec<SensorLink<B>>, Error<B>> {
let sensor_refs = if let Some(env_ref) = &self.data.environment_metrics {
extract_environment_sensors(env_ref, self.bmc.as_ref()).await?
} else {
Vec::new()
};
Ok(sensor_refs
.into_iter()
.map(|r| SensorLink::new(&self.bmc, r))
.collect())
}
#[cfg(feature = "sensors")]
pub async fn sensor_links(&self) -> Result<Option<Vec<SensorLink<B>>>, Error<B>> {
if let Some(sensors_collection) = &self.data.sensors {
let sc = sensors_collection
.get(self.bmc.as_ref())
.await
.map_err(Error::Bmc)?;
let mut sensor_data = Vec::with_capacity(sc.members.len());
for sensor in &sc.members {
sensor_data.push(SensorLink::new(
&self.bmc,
NavProperty::<SchemaSensor>::new_reference(sensor.id().clone()),
));
}
Ok(Some(sensor_data))
} else {
Ok(None)
}
}
#[cfg(feature = "pcie-devices")]
pub async fn pcie_devices(&self) -> Result<Option<PcieDeviceCollection<B>>, crate::Error<B>> {
if let Some(p) = &self.data.pcie_devices {
PcieDeviceCollection::new(&self.bmc, p).await.map(Some)
} else {
Ok(None)
}
}
#[cfg(feature = "oem-nvidia-baseboard")]
pub fn oem_nvidia_baseboard_cbc(&self) -> Result<Option<NvidiaCbcChassis<B>>, Error<B>> {
self.data
.base
.base
.oem
.as_ref()
.map(NvidiaCbcChassis::new)
.transpose()
.map(|v| v.and_then(identity))
}
}
impl<B: Bmc> Resource for Chassis<B> {
fn resource_ref(&self) -> &ResourceSchema {
&self.data.as_ref().base
}
}
impl<B: Bmc> FromLink<B> for Chassis<B> {
type Schema = ChassisSchema;
fn from_link(
bmc: &NvBmc<B>,
nav: &NavProperty<Self::Schema>,
) -> impl Future<Output = Result<Self, Error<B>>> + Send {
Self::new(bmc, nav)
}
}
fn remove_invalid_contained_by_fields(mut v: JsonValue) -> JsonValue {
if let JsonValue::Object(ref mut obj) = v {
if let Some(JsonValue::Object(ref mut links_obj)) = obj.get_mut("Links") {
if let Some(JsonValue::Object(ref mut contained_by_obj)) =
links_obj.get_mut("ContainedBy")
{
contained_by_obj.retain(|k, _| k == "@odata.id");
}
}
}
v
}
fn add_default_chassis_type(v: JsonValue) -> JsonValue {
if let JsonValue::Object(mut obj) = v {
obj.entry("ChassisType")
.or_insert(JsonValue::String("Other".into()));
JsonValue::Object(obj)
} else {
v
}
}
fn add_default_chassis_name(v: JsonValue) -> JsonValue {
if let JsonValue::Object(mut obj) = v {
obj.entry("Name")
.or_insert(JsonValue::String("Unnamed chassis".into()));
JsonValue::Object(obj)
} else {
v
}
}
fn normalize_empty_uuid_field(mut v: JsonValue) -> JsonValue {
if let JsonValue::Object(ref mut obj) = v {
if let Some(uuid) = obj.get_mut("UUID") {
let is_empty = uuid.as_str().is_some_and(str::is_empty);
if is_empty {
*uuid = JsonValue::Null;
}
}
}
v
}