@@ -8,10 +8,9 @@ use crate::fm::Ereport;
88use crate :: inventory:: SpType ;
99use chrono:: { DateTime , Utc } ;
1010use iddqd:: { IdOrdItem , IdOrdMap } ;
11- use omicron_uuid_kinds:: {
12- CaseUuid , CollectionUuid , OmicronZoneUuid , SitrepUuid ,
13- } ;
11+ use omicron_uuid_kinds:: { CaseUuid , SitrepUuid } ;
1412use serde:: { Deserialize , Serialize } ;
13+ use std:: fmt;
1514use std:: sync:: Arc ;
1615
1716#[ derive( Clone , Debug , Eq , PartialEq , Deserialize , Serialize ) ]
@@ -25,10 +24,8 @@ pub struct Case {
2524
2625 pub de : DiagnosisEngine ,
2726
28- pub ereports : IdOrdMap < Arc < Ereport > > ,
29-
27+ pub ereports : IdOrdMap < CaseEreport > ,
3028 pub alerts_requested : IdOrdMap < AlertRequest > ,
31-
3229 pub impacted_sp_slots : IdOrdMap < ImpactedSpSlot > ,
3330
3431 pub comment : String ,
@@ -38,6 +35,16 @@ impl Case {
3835 pub fn is_open ( & self ) -> bool {
3936 self . time_closed . is_none ( )
4037 }
38+
39+ pub fn display_indented ( & self , indent : usize ) -> impl fmt:: Display + ' _ {
40+ DisplayCase { case : self , indent }
41+ }
42+ }
43+
44+ impl fmt:: Display for Case {
45+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
46+ self . display_indented ( 0 ) . fmt ( f)
47+ }
4148}
4249
4350impl IdOrdItem for Case {
@@ -49,6 +56,22 @@ impl IdOrdItem for Case {
4956 iddqd:: id_upcast!( ) ;
5057}
5158
59+ #[ derive( Clone , Debug , Eq , PartialEq , Deserialize , Serialize ) ]
60+ pub struct CaseEreport {
61+ pub ereport : Arc < Ereport > ,
62+ pub assigned_sitrep_id : SitrepUuid ,
63+ pub comment : String ,
64+ }
65+
66+ impl IdOrdItem for CaseEreport {
67+ type Key < ' a > = <Arc < Ereport > as IdOrdItem >:: Key < ' a > ;
68+ fn key ( & self ) -> Self :: Key < ' _ > {
69+ self . ereport . key ( )
70+ }
71+
72+ iddqd:: id_upcast!( ) ;
73+ }
74+
5275#[ derive( Clone , Debug , Eq , PartialEq , Deserialize , Serialize ) ]
5376pub struct ImpactedSpSlot {
5477 pub sp_type : SpType ,
@@ -65,3 +88,226 @@ impl IdOrdItem for ImpactedSpSlot {
6588
6689 iddqd:: id_upcast!( ) ;
6790}
91+
92+ struct DisplayCase < ' a > {
93+ case : & ' a Case ,
94+ indent : usize ,
95+ }
96+
97+ impl fmt:: Display for DisplayCase < ' _ > {
98+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
99+ const BULLET : & str = "* " ;
100+ const LIST_INDENT : usize = 4 ;
101+
102+ let & Self {
103+ case :
104+ Case {
105+ ref id,
106+ ref created_sitrep_id,
107+ ref time_created,
108+ ref closed_sitrep_id,
109+ ref time_closed,
110+ ref de,
111+ ref ereports,
112+ ref alerts_requested,
113+ ref impacted_sp_slots,
114+ ref comment,
115+ } ,
116+ indent,
117+ } = self ;
118+ writeln ! (
119+ f,
120+ "{:>indent$}case: {id:?}" ,
121+ if indent > 0 { BULLET } else { "" }
122+ ) ?;
123+ writeln ! ( f, "{:>indent$}comment: {comment}" , "" ) ?;
124+ writeln ! ( f, "{:>indent$}diagnosis engine: {de}" , "" ) ?;
125+ writeln ! ( f, "{:>indent$}created in sitrep: {created_sitrep_id}" , "" ) ?;
126+ writeln ! ( f, "{:>indent$} at: {time_created}" , "" ) ?;
127+ if let Some ( closed_id) = closed_sitrep_id {
128+ writeln ! ( f, "{:>indent$}closed in sitrep: {closed_id}" , "" ) ?;
129+ if let Some ( time_closed) = time_closed {
130+ writeln ! ( f, "{:>indent$} at: {time_closed}" , "" ) ?;
131+ } else {
132+ writeln ! ( f, "{:>indent$} at: <MISSING TIME CLOSED>" , "" ) ?;
133+ }
134+ }
135+
136+ if !ereports. is_empty ( ) {
137+ writeln ! ( f, "\n {:>indent$}ereports:" , "" ) ?;
138+ let indent = indent + LIST_INDENT ;
139+ for CaseEreport { ereport, assigned_sitrep_id, comment } in ereports
140+ {
141+ writeln ! ( f, "{BULLET:>indent$}{}" , ereport. id( ) ) ?;
142+ writeln ! ( f, "{:>indent$}class: {:?}" , "" , ereport. class) ?;
143+ writeln ! ( f, "{:>indent$}reporter: {}" , "" , ereport. reporter) ?;
144+ writeln ! (
145+ f,
146+ "{:>indent$}added in sitrep: {assigned_sitrep_id}" ,
147+ ""
148+ ) ?;
149+ writeln ! ( f, "{:>indent$}comment: {comment}" , "" ) ?;
150+ }
151+ }
152+
153+ if !impacted_sp_slots. is_empty ( ) {
154+ writeln ! ( f, "\n {:>indent$}SP slots impacted:" , "" ) ?;
155+ let indent = indent + LIST_INDENT ;
156+ for ImpactedSpSlot { sp_type, slot, created_sitrep_id, comment } in
157+ impacted_sp_slots
158+ {
159+ writeln ! ( f, "{BULLET:>indent$}{sp_type:<6} {slot:02}" ) ?;
160+ writeln ! (
161+ f,
162+ "{:>indent$}added in sitrep: {created_sitrep_id}" ,
163+ ""
164+ ) ?;
165+ writeln ! ( f, "{:>indent$}comment: {comment}" , "" ) ?;
166+ }
167+ }
168+
169+ if !alerts_requested. is_empty ( ) {
170+ writeln ! ( f, "\n {:>indent$}alerts requested:" , "" ) ?;
171+ let indent = indent + LIST_INDENT ;
172+ for AlertRequest { id, class, requested_sitrep_id, .. } in
173+ alerts_requested
174+ {
175+ writeln ! ( f, "{BULLET:>indent$}{id:?}" ) ?;
176+ writeln ! ( f, "{:>indent$}class: {class:?}" , "" ) ?;
177+ writeln ! (
178+ f,
179+ "{:>indent$}requested in sitrep: {requested_sitrep_id}" ,
180+ ""
181+ ) ?;
182+ }
183+ }
184+
185+ writeln ! ( f) ?;
186+
187+ Ok ( ( ) )
188+ }
189+ }
190+
191+ #[ cfg( test) ]
192+ mod tests {
193+ use super :: * ;
194+ use crate :: fm:: { AlertClass , AlertRequest , DiagnosisEngine } ;
195+ use chrono:: Utc ;
196+ use ereport_types:: { Ena , EreportId } ;
197+ use omicron_uuid_kinds:: {
198+ AlertUuid , CaseUuid , EreporterRestartUuid , OmicronZoneUuid , SitrepUuid ,
199+ } ;
200+ use std:: sync:: Arc ;
201+
202+ #[ test]
203+ fn test_case_display ( ) {
204+ // Create UUIDs for the case
205+ let case_id = CaseUuid :: new_v4 ( ) ;
206+ let created_sitrep_id = SitrepUuid :: new_v4 ( ) ;
207+ let closed_sitrep_id = SitrepUuid :: new_v4 ( ) ;
208+ let time_created = Utc :: now ( ) ;
209+ let time_closed = Utc :: now ( ) ;
210+
211+ // Create some ereports
212+ let mut ereports = IdOrdMap :: new ( ) ;
213+
214+ let ereport1 = CaseEreport {
215+ ereport : Arc :: new ( Ereport {
216+ data : crate :: fm:: ereport:: EreportData {
217+ id : EreportId {
218+ restart_id : EreporterRestartUuid :: new_v4 ( ) ,
219+ ena : Ena :: from ( 2u64 ) ,
220+ } ,
221+ time_collected : time_created,
222+ collector_id : OmicronZoneUuid :: new_v4 ( ) ,
223+ serial_number : Some ( "BRM6900420" . to_string ( ) ) ,
224+ part_number : Some ( "913-0000037" . to_string ( ) ) ,
225+ class : Some ( "hw.pwr.remove.psu" . to_string ( ) ) ,
226+ report : serde_json:: json!( { } ) ,
227+ } ,
228+ reporter : crate :: fm:: ereport:: Reporter :: Sp {
229+ sp_type : SpType :: Power ,
230+ slot : 0 ,
231+ } ,
232+ } ) ,
233+ assigned_sitrep_id : created_sitrep_id,
234+ comment : "PSU removed" . to_string ( ) ,
235+ } ;
236+ ereports. insert_unique ( ereport1) . unwrap ( ) ;
237+
238+ let ereport2 = CaseEreport {
239+ ereport : Arc :: new ( Ereport {
240+ data : crate :: fm:: ereport:: EreportData {
241+ id : EreportId {
242+ restart_id : EreporterRestartUuid :: new_v4 ( ) ,
243+ ena : Ena :: from ( 3u64 ) ,
244+ } ,
245+ time_collected : time_created,
246+ collector_id : OmicronZoneUuid :: new_v4 ( ) ,
247+ serial_number : Some ( "BRM6900420" . to_string ( ) ) ,
248+ part_number : Some ( "913-0000037" . to_string ( ) ) ,
249+ class : Some ( "hw.pwr.insert.psu" . to_string ( ) ) ,
250+ report : serde_json:: json!( { "link" : "eth0" , "status" : "down" } ) ,
251+ } ,
252+ reporter : crate :: fm:: ereport:: Reporter :: Sp {
253+ sp_type : SpType :: Power ,
254+ slot : 0 ,
255+ } ,
256+ } ) ,
257+ assigned_sitrep_id : closed_sitrep_id,
258+ comment : "PSU inserted, closing this case" . to_string ( ) ,
259+ } ;
260+ ereports. insert_unique ( ereport2) . unwrap ( ) ;
261+
262+ // Create some alerts
263+ let mut alerts_requested = IdOrdMap :: new ( ) ;
264+
265+ let alert1 = AlertRequest {
266+ id : AlertUuid :: new_v4 ( ) ,
267+ class : AlertClass :: PsuRemoved ,
268+ payload : serde_json:: json!( { } ) ,
269+ requested_sitrep_id : created_sitrep_id,
270+ } ;
271+ alerts_requested. insert_unique ( alert1) . unwrap ( ) ;
272+
273+ let alert2 = AlertRequest {
274+ id : AlertUuid :: new_v4 ( ) ,
275+ class : AlertClass :: PsuInserted ,
276+ payload : serde_json:: json!( { } ) ,
277+ requested_sitrep_id : closed_sitrep_id,
278+ } ;
279+ alerts_requested. insert_unique ( alert2) . unwrap ( ) ;
280+
281+ let mut impacted_sp_slots = IdOrdMap :: new ( ) ;
282+ let slot2 = ImpactedSpSlot {
283+ sp_type : SpType :: Power ,
284+ slot : 0 ,
285+ created_sitrep_id,
286+ comment : "Power shelf 0 reduced redundancy" . to_string ( ) ,
287+ } ;
288+ impacted_sp_slots. insert_unique ( slot2) . unwrap ( ) ;
289+
290+ // Create the case
291+ let case = Case {
292+ id : case_id,
293+ created_sitrep_id,
294+ time_created,
295+ closed_sitrep_id : Some ( closed_sitrep_id) ,
296+ time_closed : Some ( time_closed) ,
297+ de : DiagnosisEngine :: PowerShelf ,
298+ ereports,
299+ alerts_requested,
300+ impacted_sp_slots,
301+ comment : "Power shelf rectifier added and removed here :-)"
302+ . to_string ( ) ,
303+ } ;
304+
305+ eprintln ! ( "example case display:" ) ;
306+ eprintln ! ( "=====================" ) ;
307+ eprintln ! ( "{case}" ) ;
308+
309+ eprintln ! ( "example case display (indented by 4):" ) ;
310+ eprintln ! ( "======================================" ) ;
311+ eprintln ! ( "{}" , case. display_indented( 4 ) ) ;
312+ }
313+ }
0 commit comments