55//! Power shelf diagnosis
66
77use crate :: SitrepBuilder ;
8- use crate :: alert
9- use nexus_types:: fm:: AlertRequest ;
8+ use crate :: alert;
109use nexus_types:: fm:: DiagnosisEngine ;
1110use nexus_types:: fm:: Ereport ;
1211use nexus_types:: fm:: ereport;
1312use nexus_types:: inventory:: SpType ;
13+ use serde:: de:: DeserializeOwned ;
14+ use serde_json:: Value ;
1415use std:: sync:: Arc ;
1516
1617pub fn diagnose (
@@ -19,7 +20,9 @@ pub fn diagnose(
1920) -> anyhow:: Result < ( ) > {
2021 for ereport in new_ereports {
2122 // Skip non-power shelf reports
22- let ereport:: Reporter :: Sp { sp_type : SpType :: Power , slot, } = ereport. reporter else {
23+ let ereport:: Reporter :: Sp { sp_type : SpType :: Power , slot } =
24+ ereport. reporter
25+ else {
2326 continue ;
2427 } ;
2528
@@ -29,31 +32,30 @@ pub fn diagnose(
2932 match ereport. data . class . as_deref ( ) {
3033 // PSU inserted
3134 Some ( "hw.insert.psu" ) => {
35+ let psc_psu = extract_psc_psu ( & ereport, slot, & sitrep. log ) ;
3236 let mut case = sitrep. open_case ( DiagnosisEngine :: PowerShelf ) ?;
3337 case. add_ereport ( ereport) ;
34- case. comment = "PSU inserted" . to_string ( ) ;
35- let psu_id = match ereport. get ( "fruid" ) {
36- Some ( serde_json:: Value :: Object ( fruid) ) => {
37- todo ! ( )
38- } ,
39- None => {
40- todo ! ( )
41- }
42- } ;
43- case. request_alert ( alert:: power_shelf:: PsuInserted :: V0 {
44- psc_psu : alert:: power_shelf:: PscPsu {
45- psc_id : alert:: VpdIdentity {
46- serial_number : ereport. serial_number . clone ( ) ,
47- revision : ereport. report . get ( "baseboard_rev" ) . map ( ToString :: to_string) ,
48- part_number : ereport. part_number . clone ( ) ,
49- } ,
50- psc_slot : slot,
51- psu_id,
52- psu_slot : ereport. report . get ( "slot" ) . map ( |s| todo ! ( ) ) ,
53- }
54- } )
38+ case. comment =
39+ format ! ( "PSC {slot} PSU {:?} inserted" , psc_psu. psu_slot) ;
40+ case. request_alert ( & alert:: power_shelf:: PsuInserted :: V0 {
41+ psc_psu,
42+ } ) ?;
43+ // Nothing else to do at this time.
44+ case. close ( ) ;
45+ }
46+ Some ( "hw.remove.psu" ) => {
47+ let psc_psu = extract_psc_psu ( & ereport, slot, & sitrep. log ) ;
48+ let mut case = sitrep. open_case ( DiagnosisEngine :: PowerShelf ) ?;
49+ case. add_ereport ( ereport) ;
50+ case. comment =
51+ format ! ( "PSC {slot} PSU {:?} removed" , psc_psu. psu_slot) ;
52+ case. request_alert ( & alert:: power_shelf:: PsuRemoved :: V0 {
53+ psc_psu,
54+ } ) ?;
55+
56+ // Nothing else to do at this time.
57+ case. close ( ) ;
5558 }
56- Some ( "hw.remove.psu" ) => { }
5759 Some ( unknown) => {
5860 slog:: warn!(
5961 & sitrep. log,
@@ -74,3 +76,82 @@ pub fn diagnose(
7476
7577 Ok ( ( ) )
7678}
79+
80+ fn extract_psc_psu (
81+ ereport : & Ereport ,
82+ psc_slot : u16 ,
83+ log : & slog:: Logger ,
84+ ) -> alert:: power_shelf:: PscPsu {
85+ let psc_id = extract_psc_id ( ereport, log) ;
86+ let psu_id = extract_psu_id ( ereport, log) ;
87+ let psu_slot = grab_json_value ( ereport, "slot" , & ereport. report , log) ;
88+ alert:: power_shelf:: PscPsu { psc_id, psc_slot, psu_id, psu_slot }
89+ }
90+
91+ fn extract_psc_id ( ereport : & Ereport , log : & slog:: Logger ) -> alert:: VpdIdentity {
92+ let serial_number = ereport. serial_number . clone ( ) ;
93+ let revision =
94+ grab_json_value ( ereport, "baseboard_rev" , & ereport. report , log) ;
95+ let part_number = ereport. part_number . clone ( ) ;
96+ alert:: VpdIdentity { serial_number, revision, part_number }
97+ }
98+
99+ fn extract_psu_id (
100+ ereport : & Ereport ,
101+ log : & slog:: Logger ,
102+ ) -> alert:: power_shelf:: PsuIdentity {
103+ // These are the same field names that Hubris uses in the ereport. See:
104+ // https://github.com/oxidecomputer/hubris/blob/ec18e4f11aaa14600c61f67335c32b250ef38269/drv/psc-seq-server/src/main.rs#L1107-L1117
105+ #[ derive( serde:: Deserialize , Default ) ]
106+ struct Fruid {
107+ mfr : Option < String > ,
108+ mpn : Option < String > ,
109+ serial : Option < String > ,
110+ fw_rev : Option < String > ,
111+ }
112+
113+ let Fruid { mfr, mpn, serial, fw_rev } =
114+ grab_json_value ( ereport, "fruid" , & ereport. report , log)
115+ . unwrap_or_default ( ) ;
116+
117+ alert:: power_shelf:: PsuIdentity {
118+ serial_number : serial,
119+ part_number : mpn,
120+ firmware_revision : fw_rev,
121+ manufacturer : mfr,
122+ }
123+ }
124+
125+ fn grab_json_value < T : DeserializeOwned > (
126+ ereport : & Ereport ,
127+ key : & str ,
128+ obj : & Value ,
129+ log : & slog:: Logger ,
130+ ) -> Option < T > {
131+ let v = match obj. get ( "key" ) {
132+ Some ( v) => v,
133+ None => {
134+ slog:: warn!(
135+ log,
136+ "expected ereport to contain a '{key}' field" ;
137+ "ereport_id" => %ereport. id,
138+ "ereport_class" => ?ereport. class,
139+ ) ;
140+ return None ;
141+ }
142+ } ;
143+ match serde_json:: from_value ( v. clone ( ) ) {
144+ Ok ( v) => Some ( v) ,
145+ Err ( e) => {
146+ slog:: warn!(
147+ log,
148+ "expected ereport '{key}' field to deserialize as a {}" ,
149+ std:: any:: type_name:: <T >( ) ;
150+ "ereport_id" => %ereport. id,
151+ "ereport_class" => ?ereport. class,
152+ "error" => %e,
153+ ) ;
154+ None
155+ }
156+ }
157+ }
0 commit comments