Skip to content

Commit b54cba3

Browse files
committed
nv-redfish: support NvSwitch wrong Chassis part location type
Signed-off-by: Dmitry Porokh <dporokh@nvidia.com>
1 parent 1673749 commit b54cba3

File tree

6 files changed

+149
-13
lines changed

6 files changed

+149
-13
lines changed

redfish/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ assembly = []
5858
accounts = ["patch-payload-get", "patch-payload-update", "patch-collection-create"]
5959
bios = []
6060
boot-options = []
61-
chassis = ["patch-payload-get", "nv-bmc-expand"]
61+
chassis = ["patch-payload-get", "patch-collection", "nv-bmc-expand"]
6262
computer-systems = ["patch-payload-get", "patch-collection"]
6363
ethernet-interfaces = []
6464
host-interfaces = []

redfish/src/bmc_quirks.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ enum Platform {
3434
AmiViking,
3535
Nvidia,
3636
Anonymous1_9_0,
37+
NvSwitch,
3738
}
3839

3940
impl BmcQuirks {
4041
pub fn new(root: &ServiceRoot) -> Self {
4142
let vendor_str = root.vendor.as_ref().and_then(Option::as_deref);
4243
let redfish_version_str = root.redfish_version.as_deref();
44+
let product_str = root.product.as_ref().and_then(Option::as_deref);
4345
let platform = match vendor_str {
4446
Some("HPE") => Some(Platform::Hpe),
4547
Some("Dell") => Some(Platform::Dell),
4648
Some("AMI") if redfish_version_str == Some("1.11.0") => Some(Platform::AmiViking),
49+
Some("NVIDIA") if product_str == Some("P3809") => Some(Platform::NvSwitch),
4750
Some("NVIDIA") => Some(Platform::Nvidia),
4851
None if redfish_version_str == Some("1.9.0") => Some(Platform::Anonymous1_9_0),
4952
_ => None,
@@ -175,6 +178,14 @@ impl BmcQuirks {
175178
self.platform == Some(Platform::Anonymous1_9_0)
176179
}
177180

181+
/// NVSwitch provides invalid (Unknown) Location/PartLocation/LocationType in Chassis.
182+
#[cfg(feature = "chassis")]
183+
pub(crate) fn wrong_resource_part_location_type(&self) -> bool {
184+
// Note that Liteon prefer not to tell about itself. So we
185+
// apply patches for all platforms that are not identified.
186+
self.platform == Some(Platform::NvSwitch)
187+
}
188+
178189
/// In some cases we expand is not working according to spec,
179190
/// if it is the case for specific chassis, we would disable
180191
/// expand api

redfish/src/chassis/item.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::hardware_id::SerialNumber as HardwareIdSerialNumber;
2222
use crate::patch_support::JsonValue;
2323
use crate::patch_support::Payload;
2424
use crate::patch_support::ReadPatchFn;
25+
use crate::patches::remove_invalid_resource_part_location_type;
2526
use crate::patches::remove_invalid_resource_state;
2627
use crate::schema::redfish::chassis::Chassis as ChassisSchema;
2728
use crate::Error;
@@ -75,7 +76,7 @@ pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
7576
pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
7677

7778
pub struct Config {
78-
read_patch_fn: Option<ReadPatchFn>,
79+
pub read_patch_fn: Option<ReadPatchFn>,
7980
}
8081

8182
impl Config {
@@ -93,6 +94,9 @@ impl Config {
9394
if quirks.wrong_resource_status_state() {
9495
patches.push(remove_invalid_resource_state);
9596
}
97+
if quirks.wrong_resource_part_location_type() {
98+
patches.push(remove_invalid_resource_part_location_type);
99+
}
96100
let read_patch_fn = (!patches.is_empty())
97101
.then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
98102
Self { read_patch_fn }

redfish/src/chassis/mod.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ pub use power_supply::PowerSupply;
6666
pub use thermal::Thermal;
6767

6868
use crate::core::NavProperty;
69+
use crate::patch_support::CollectionWithPatch;
6970
use crate::resource::Resource as _;
71+
use crate::schema::redfish::chassis::Chassis as ChassisSchema;
7072
use crate::schema::redfish::chassis_collection::ChassisCollection as ChassisCollectionSchema;
71-
use crate::{Error, NvBmc, ServiceRoot};
73+
use crate::schema::redfish::resource::ResourceCollection;
74+
use crate::Error;
75+
use crate::NvBmc;
76+
use crate::ServiceRoot;
7277

7378
/// Chassis collection.
7479
///
@@ -84,8 +89,11 @@ impl<B: Bmc> ChassisCollection<B> {
8489
bmc: &NvBmc<B>,
8590
root: &ServiceRoot<B>,
8691
) -> Result<Option<Self>, Error<B>> {
92+
let item_config = item::Config::new(&bmc.quirks);
8793
if let Some(collection_ref) = &root.root.chassis {
88-
bmc.expand_property(collection_ref).await.map(Some)
94+
Self::expand_collection(bmc, collection_ref, item_config.read_patch_fn.as_ref())
95+
.await
96+
.map(Some)
8997
} else if bmc.quirks.bug_missing_root_nav_properties() {
9098
bmc.expand_property(&NavProperty::new_reference(
9199
format!("{}/Chassis", root.odata_id()).into(),
@@ -96,13 +104,10 @@ impl<B: Bmc> ChassisCollection<B> {
96104
Ok(None)
97105
}
98106
.map(|c| {
99-
c.map(|collection| {
100-
let item_config = item::Config::new(&bmc.quirks).into();
101-
Self {
102-
bmc: bmc.clone(),
103-
collection,
104-
item_config,
105-
}
107+
c.map(|collection| Self {
108+
bmc: bmc.clone(),
109+
collection,
110+
item_config: item_config.into(),
106111
})
107112
})
108113
}
@@ -121,3 +126,14 @@ impl<B: Bmc> ChassisCollection<B> {
121126
Ok(chassis_members)
122127
}
123128
}
129+
130+
impl<B: Bmc> CollectionWithPatch<ChassisCollectionSchema, ChassisSchema, B>
131+
for ChassisCollection<B>
132+
{
133+
fn convert_patched(
134+
base: ResourceCollection,
135+
members: Vec<NavProperty<ChassisSchema>>,
136+
) -> ChassisCollectionSchema {
137+
ChassisCollectionSchema { base, members }
138+
}
139+
}

redfish/src/patches.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
//! Sometimes Redfish implementations do not perfectly match the CSDL
1717
//! specification. This module provides helpers to deal with that.
1818
19-
/// Redfish collection related patches.
19+
use crate::schema::redfish::resource::State as ResourceStateSchema;
2020
use serde_json::Value;
2121

22-
use crate::schema::redfish::resource::State as ResourceStateSchema;
22+
#[cfg(feature = "chassis")]
23+
use crate::schema::redfish::resource::LocationType as ResourceLocationTypeSchema;
2324

2425
/// Remove unsupported `Status.State` enum values from a resource payload.
2526
///
@@ -40,3 +41,26 @@ pub fn remove_invalid_resource_state(mut resource: Value) -> Value {
4041
}
4142
resource
4243
}
44+
45+
/// Remove unsupported `Location.PartLocation.LocationType` enum values from a resource payload.
46+
///
47+
/// Some BMCs return state values outside Redfish schema enum constraints
48+
/// (for example `"Unknown"`). This helper drops only invalid state values
49+
/// and keeps all other payload fields unchanged.
50+
#[cfg(feature = "chassis")]
51+
#[must_use]
52+
pub fn remove_invalid_resource_part_location_type(mut resource: Value) -> Value {
53+
if let Value::Object(ref mut obj) = resource {
54+
if let Some(Value::Object(ref mut location)) = obj.get_mut("Location") {
55+
if let Some(Value::Object(ref mut part_location)) = location.get_mut("PartLocation") {
56+
let location_type_is_invalid = part_location.get("LocationType").is_some_and(|v| {
57+
serde_json::from_value::<ResourceLocationTypeSchema>(v.clone()).is_err()
58+
});
59+
if location_type_is_invalid {
60+
part_location.remove("LocationType");
61+
}
62+
}
63+
}
64+
}
65+
resource
66+
}

tests/tests/test-chassis.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use nv_redfish::ServiceRoot;
2020
use nv_redfish_core::ODataId;
2121
use nv_redfish_tests::ami_viking_service_root;
2222
use nv_redfish_tests::anonymous_1_9_service_root;
23+
use nv_redfish_tests::json_merge;
2324
use nv_redfish_tests::Bmc;
2425
use nv_redfish_tests::Expect;
2526
use nv_redfish_tests::ODATA_ID;
@@ -187,6 +188,58 @@ async fn anonymous_1_9_0_wrong_chassis_status_state_workaround() -> Result<(), B
187188
Ok(())
188189
}
189190

191+
#[test]
192+
async fn nvswitch_wrong_location_part_location_type_workaround() -> Result<(), Box<dyn StdError>> {
193+
// Platform under test: NVSwitch (`Vendor=NVIDIA`, `Product=P3809`).
194+
// Quirk under test: invalid Location.PartLocation.LocationType="Unknown".
195+
let bmc = Arc::new(Bmc::default());
196+
let ids = ids();
197+
let root = expect_nvswitch_service_root(
198+
bmc.clone(),
199+
&ids,
200+
json!({
201+
"Chassis": { ODATA_ID: &ids.chassis_collection_id }
202+
}),
203+
)
204+
.await?;
205+
bmc.expect(Expect::expand(
206+
&ids.chassis_collection_id,
207+
json!({
208+
ODATA_ID: &ids.chassis_collection_id,
209+
ODATA_TYPE: CHASSIS_COLLECTION_DATA_TYPE,
210+
"Id": "Chassis",
211+
"Name": "Chassis Collection",
212+
"Members": [
213+
{
214+
ODATA_ID: &ids.chassis_id
215+
}
216+
]
217+
}),
218+
));
219+
220+
let collection = root.chassis().await?.unwrap();
221+
expect_chassis_get(
222+
bmc.clone(),
223+
&ids,
224+
json!({ // Real id: CPLD_0
225+
ODATA_ID: &ids.chassis_id,
226+
ODATA_TYPE: CHASSIS_DATA_TYPE,
227+
"Id": "1",
228+
"Name": "Chassis",
229+
"ChassisType": "Module",
230+
"Location": {
231+
"PartLocation": {
232+
"LocationType": "Unknown"
233+
}
234+
}
235+
}),
236+
);
237+
let members = collection.members().await?;
238+
assert_eq!(members.len(), 1);
239+
240+
Ok(())
241+
}
242+
190243
async fn expect_viking_service_root(
191244
bmc: Arc<Bmc>,
192245
ids: &Ids,
@@ -211,6 +264,34 @@ async fn expect_anonymous_1_9_service_root(
211264
ServiceRoot::new(bmc).await.map_err(Into::into)
212265
}
213266

267+
async fn expect_nvswitch_service_root(
268+
bmc: Arc<Bmc>,
269+
ids: &Ids,
270+
fields: Value,
271+
) -> Result<ServiceRoot<Bmc>, Box<dyn StdError>> {
272+
bmc.expect(Expect::get(
273+
&ids.root_id,
274+
json_merge([
275+
&json!({
276+
ODATA_ID: &ids.root_id,
277+
ODATA_TYPE: "#ServiceRoot.v1_13_0.ServiceRoot",
278+
"Id": "RootService",
279+
"Name": "RootService",
280+
"Vendor": "NVIDIA",
281+
"Product": "P3809",
282+
"ProtocolFeaturesSupported": {
283+
"ExpandQuery": {
284+
"NoLinks": true
285+
}
286+
},
287+
"Links": {}
288+
}),
289+
&fields,
290+
]),
291+
));
292+
ServiceRoot::new(bmc).await.map_err(Into::into)
293+
}
294+
214295
fn expect_chassis_collection(bmc: Arc<Bmc>, ids: &Ids) {
215296
bmc.expect(Expect::get(
216297
&ids.chassis_collection_id,

0 commit comments

Comments
 (0)