manta_server/server/common/
hw_inventory_utils.rs

1//! Pure helpers for parsing raw HSM hardware-inventory JSON into the
2//! component aggregations the hw_cluster service operates on.
3
4use serde_json::Value;
5
6/// Extract memory DIMM capacities (MiB) from a node's
7/// hardware inventory JSON.
8pub fn get_list_memory_capacity_from_hw_inventory_value(
9  hw_inventory: &Value,
10) -> Option<Vec<u64>> {
11  hw_inventory
12    .pointer("/Nodes/0/Memory")
13    .and_then(Value::as_array)
14    .map(|memory_list| {
15      memory_list
16        .iter()
17        .map(|memory| {
18          memory
19            .pointer("/PopulatedFRU/MemoryFRUInfo/CapacityMiB")
20            .unwrap_or(&serde_json::json!(0))
21            .as_u64()
22            .unwrap_or(0)
23        })
24        .collect::<Vec<u64>>()
25    })
26}
27
28/// Extract processor model names from a node's hardware
29/// inventory JSON.
30pub fn get_list_processor_model_from_hw_inventory_value(
31  hw_inventory: &Value,
32) -> Option<Vec<String>> {
33  hw_inventory
34    .pointer("/Nodes/0/Processors")
35    .and_then(Value::as_array)
36    .map(|processor_list| {
37      processor_list
38        .iter()
39        .filter_map(|processor| {
40          processor
41            .pointer("/PopulatedFRU/ProcessorFRUInfo/Model")
42            .and_then(Value::as_str)
43            .map(std::string::ToString::to_string)
44        })
45        .collect::<Vec<String>>()
46    })
47}
48
49/// Extract accelerator (GPU) model names from a node's
50/// hardware inventory JSON.
51pub fn get_list_accelerator_model_from_hw_inventory_value(
52  hw_inventory: &Value,
53) -> Option<Vec<String>> {
54  hw_inventory
55    .pointer("/Nodes/0/NodeAccels")
56    .and_then(Value::as_array)
57    .map(|accelerator_list| {
58      accelerator_list
59        .iter()
60        .filter_map(|accelerator| {
61          accelerator
62            .pointer("/PopulatedFRU/NodeAccelFRUInfo/Model")
63            .and_then(Value::as_str)
64            .map(std::string::ToString::to_string)
65        })
66        .collect::<Vec<String>>()
67    })
68}
69
70#[cfg(test)]
71mod tests {
72  use super::*;
73  use serde_json::json;
74
75  // ── get_list_memory_capacity_from_hw_inventory_value ──
76
77  #[test]
78  fn memory_capacity_returns_values_for_valid_json() {
79    let hw = json!({
80      "Nodes": [{
81        "Memory": [
82          {"PopulatedFRU": {"MemoryFRUInfo": {"CapacityMiB": 16384}}},
83          {"PopulatedFRU": {"MemoryFRUInfo": {"CapacityMiB": 32768}}}
84        ]
85      }]
86    });
87    let result = get_list_memory_capacity_from_hw_inventory_value(&hw).unwrap();
88    assert_eq!(result, vec![16384, 32768]);
89  }
90
91  #[test]
92  fn memory_capacity_returns_none_when_no_nodes() {
93    let hw = json!({});
94    assert!(get_list_memory_capacity_from_hw_inventory_value(&hw).is_none());
95  }
96
97  #[test]
98  fn memory_capacity_returns_none_when_no_memory_key() {
99    let hw = json!({"Nodes": [{"Processors": []}]});
100    assert!(get_list_memory_capacity_from_hw_inventory_value(&hw).is_none());
101  }
102
103  #[test]
104  fn memory_capacity_returns_empty_vec_for_empty_memory_array() {
105    let hw = json!({"Nodes": [{"Memory": []}]});
106    let result = get_list_memory_capacity_from_hw_inventory_value(&hw).unwrap();
107    assert!(result.is_empty());
108  }
109
110  #[test]
111  fn memory_capacity_defaults_to_zero_when_field_missing() {
112    let hw = json!({
113      "Nodes": [{
114        "Memory": [
115          {"PopulatedFRU": {"MemoryFRUInfo": {}}},
116          {"PopulatedFRU": {"MemoryFRUInfo": {"CapacityMiB": 8192}}}
117        ]
118      }]
119    });
120    let result = get_list_memory_capacity_from_hw_inventory_value(&hw).unwrap();
121    assert_eq!(result, vec![0, 8192]);
122  }
123
124  // ── get_list_processor_model_from_hw_inventory_value ──
125
126  #[test]
127  fn processor_model_returns_values_for_valid_json() {
128    let hw = json!({
129      "Nodes": [{
130        "Processors": [
131          {"PopulatedFRU": {"ProcessorFRUInfo": {"Model": "AMD EPYC 7742"}}},
132          {"PopulatedFRU": {"ProcessorFRUInfo": {"Model": "Intel Xeon Gold 6248"}}}
133        ]
134      }]
135    });
136    let result = get_list_processor_model_from_hw_inventory_value(&hw).unwrap();
137    assert_eq!(result, vec!["AMD EPYC 7742", "Intel Xeon Gold 6248"]);
138  }
139
140  #[test]
141  fn processor_model_returns_none_when_no_nodes() {
142    let hw = json!({});
143    assert!(get_list_processor_model_from_hw_inventory_value(&hw).is_none());
144  }
145
146  #[test]
147  fn processor_model_skips_entries_without_model_field() {
148    let hw = json!({
149      "Nodes": [{
150        "Processors": [
151          {"PopulatedFRU": {"ProcessorFRUInfo": {"Model": "AMD EPYC 7742"}}},
152          {"PopulatedFRU": {"ProcessorFRUInfo": {}}},
153          {"PopulatedFRU": {}}
154        ]
155      }]
156    });
157    let result = get_list_processor_model_from_hw_inventory_value(&hw).unwrap();
158    assert_eq!(result, vec!["AMD EPYC 7742"]);
159  }
160
161  #[test]
162  fn processor_model_returns_empty_vec_for_empty_array() {
163    let hw = json!({"Nodes": [{"Processors": []}]});
164    let result = get_list_processor_model_from_hw_inventory_value(&hw).unwrap();
165    assert!(result.is_empty());
166  }
167
168  // ── get_list_accelerator_model_from_hw_inventory_value ──
169
170  #[test]
171  fn accelerator_model_returns_values_for_valid_json() {
172    let hw = json!({
173      "Nodes": [{
174        "NodeAccels": [
175          {"PopulatedFRU": {"NodeAccelFRUInfo": {"Model": "NVIDIA A100"}}},
176          {"PopulatedFRU": {"NodeAccelFRUInfo": {"Model": "NVIDIA H100"}}}
177        ]
178      }]
179    });
180    let result =
181      get_list_accelerator_model_from_hw_inventory_value(&hw).unwrap();
182    assert_eq!(result, vec!["NVIDIA A100", "NVIDIA H100"]);
183  }
184
185  #[test]
186  fn accelerator_model_returns_none_when_no_nodes() {
187    let hw = json!({});
188    assert!(get_list_accelerator_model_from_hw_inventory_value(&hw).is_none());
189  }
190
191  #[test]
192  fn accelerator_model_skips_entries_without_model_field() {
193    let hw = json!({
194      "Nodes": [{
195        "NodeAccels": [
196          {"PopulatedFRU": {"NodeAccelFRUInfo": {"Model": "NVIDIA A100"}}},
197          {"PopulatedFRU": {"NodeAccelFRUInfo": {}}},
198          {"SomethingElse": {}}
199        ]
200      }]
201    });
202    let result =
203      get_list_accelerator_model_from_hw_inventory_value(&hw).unwrap();
204    assert_eq!(result, vec!["NVIDIA A100"]);
205  }
206
207  #[test]
208  fn accelerator_model_returns_empty_vec_for_empty_array() {
209    let hw = json!({"Nodes": [{"NodeAccels": []}]});
210    let result =
211      get_list_accelerator_model_from_hw_inventory_value(&hw).unwrap();
212    assert!(result.is_empty());
213  }
214}