Skip to content

Commit fc3eb68

Browse files
committed
try to make impact lists usable
1 parent d1a7a8d commit fc3eb68

File tree

7 files changed

+358
-191
lines changed

7 files changed

+358
-191
lines changed

nexus/db-model/src/fm/diagnosis_engine.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,24 @@ impl_enum_type!(
2727

2828
);
2929

30-
impl From<DiagnosisEngine> for fm::DiagnosisEngine {
30+
impl From<DiagnosisEngine> for fm::DiagnosisEngineKind {
3131
fn from(de: DiagnosisEngine) -> Self {
3232
match de {
33-
DiagnosisEngine::PowerShelf => fm::DiagnosisEngine::PowerShelf,
33+
DiagnosisEngine::PowerShelf => fm::DiagnosisEngineKind::PowerShelf,
3434
}
3535
}
3636
}
3737

38-
impl From<fm::DiagnosisEngine> for DiagnosisEngine {
39-
fn from(fm_de: fm::DiagnosisEngine) -> Self {
38+
impl From<fm::DiagnosisEngineKind> for DiagnosisEngine {
39+
fn from(fm_de: fm::DiagnosisEngineKind) -> Self {
4040
match fm_de {
41-
fm::DiagnosisEngine::PowerShelf => DiagnosisEngine::PowerShelf,
41+
fm::DiagnosisEngineKind::PowerShelf => DiagnosisEngine::PowerShelf,
4242
}
4343
}
4444
}
4545

4646
impl fmt::Display for DiagnosisEngine {
4747
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48-
fm::DiagnosisEngine::from(*self).fmt(f)
48+
fm::DiagnosisEngineKind::from(*self).fmt(f)
4949
}
5050
}

nexus/fm/src/case.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use crate::alert;
6+
use anyhow::Context;
7+
use chrono::Utc;
8+
use iddqd::id_ord_map::{self, IdOrdMap};
9+
use nexus_types::fm;
10+
use nexus_types::inventory::SpType;
11+
use omicron_uuid_kinds::AlertUuid;
12+
use omicron_uuid_kinds::CaseUuid;
13+
use omicron_uuid_kinds::SitrepUuid;
14+
use std::collections::{HashMap, HashSet};
15+
use std::sync::Arc;
16+
17+
#[derive(Debug)]
18+
pub struct CaseBuilder {
19+
pub log: slog::Logger,
20+
pub case: fm::Case,
21+
pub sitrep_id: SitrepUuid,
22+
}
23+
24+
#[derive(Debug)]
25+
pub struct AllCases {
26+
log: slog::Logger,
27+
sitrep_id: SitrepUuid,
28+
pub cases: IdOrdMap<CaseBuilder>,
29+
}
30+
31+
impl AllCases {
32+
pub fn new(
33+
log: slog::Logger,
34+
sitrep_id: SitrepUuid,
35+
parent_sitrep: Option<&fm::Sitrep>,
36+
) -> (Self, ImpactLists) {
37+
// Copy forward any open cases from the parent sitrep.
38+
// If a case was closed in the parent sitrep, skip it.
39+
let mut cases_by_sp: HashMap<_, HashSet<CaseUuid>> = HashMap::new();
40+
let cases: IdOrdMap<_> = parent_sitrep
41+
.iter()
42+
.flat_map(|s| s.open_cases())
43+
.map(|case| {
44+
for sp in &case.impacted_sp_slots {
45+
cases_by_sp
46+
.entry((sp.sp_type, sp.slot))
47+
.or_default()
48+
.insert(case.id.clone());
49+
}
50+
CaseBuilder::new(&log, sitrep_id, case.clone())
51+
})
52+
.collect();
53+
54+
let cases = Self { log, sitrep_id, cases };
55+
let impact_lists = ImpactLists { cases_by_sp };
56+
(cases, impact_lists)
57+
}
58+
59+
pub fn open_case(
60+
&mut self,
61+
de: fm::DiagnosisEngineKind,
62+
) -> anyhow::Result<iddqd::id_ord_map::RefMut<'_, CaseBuilder>> {
63+
let id = CaseUuid::new_v4();
64+
let sitrep_id = self.sitrep_id;
65+
let case = match self.cases.entry(&id) {
66+
iddqd::id_ord_map::Entry::Occupied(_) => {
67+
panic!("generated a colliding UUID!")
68+
}
69+
iddqd::id_ord_map::Entry::Vacant(entry) => {
70+
let case = fm::Case {
71+
id,
72+
created_sitrep_id: self.sitrep_id,
73+
time_created: chrono::Utc::now(),
74+
closed_sitrep_id: None,
75+
time_closed: None,
76+
de,
77+
comment: String::new(),
78+
ereports: Default::default(),
79+
alerts_requested: Default::default(),
80+
impacted_sp_slots: Default::default(),
81+
};
82+
entry.insert(CaseBuilder::new(&self.log, sitrep_id, case))
83+
}
84+
};
85+
86+
slog::info!(
87+
self.log,
88+
"opened case {id:?}";
89+
"case_id" => ?id,
90+
"de" => %de
91+
);
92+
93+
Ok(case)
94+
}
95+
96+
pub fn case(&self, id: CaseUuid) -> Option<&CaseBuilder> {
97+
self.cases.get(&id)
98+
}
99+
100+
pub fn case_mut(
101+
&mut self,
102+
id: CaseUuid,
103+
) -> Option<id_ord_map::RefMut<'_, CaseBuilder>> {
104+
self.cases.get_mut(&id)
105+
}
106+
}
107+
108+
impl CaseBuilder {
109+
fn new(log: &slog::Logger, sitrep_id: SitrepUuid, case: fm::Case) -> Self {
110+
let log = log.new(slog::o!(
111+
"case_id" => format!("{:?}", case.id),
112+
"de" => case.de.to_string(),
113+
"created_sitrep_id" => format!("{:?}", case.created_sitrep_id),
114+
));
115+
Self { log, case, sitrep_id }
116+
}
117+
118+
pub fn request_alert<A: alert::Alert>(
119+
&mut self,
120+
alert: &A,
121+
) -> anyhow::Result<()> {
122+
let id = AlertUuid::new_v4();
123+
let class = A::CLASS;
124+
let req = fm::AlertRequest {
125+
id,
126+
class,
127+
requested_sitrep_id: self.sitrep_id,
128+
payload: serde_json::to_value(&alert).with_context(|| {
129+
format!(
130+
"failed to serialize payload for {class:?} alert {alert:?}"
131+
)
132+
})?,
133+
};
134+
self.case.alerts_requested.insert_unique(req).map_err(|_| {
135+
anyhow::anyhow!("an alert with ID {id:?} already exists")
136+
})?;
137+
138+
slog::info!(
139+
&self.log,
140+
"requested an alert";
141+
"alert_id" => ?id,
142+
"alert_class" => ?class,
143+
);
144+
145+
Ok(())
146+
}
147+
148+
pub fn close(&mut self) {
149+
self.case.time_closed = Some(Utc::now());
150+
self.case.closed_sitrep_id = Some(self.sitrep_id);
151+
152+
slog::info!(&self.log, "case closed");
153+
}
154+
155+
pub fn add_ereport(
156+
&mut self,
157+
report: &Arc<fm::Ereport>,
158+
comment: impl std::fmt::Display,
159+
) {
160+
match self.case.ereports.insert_unique(fm::case::CaseEreport {
161+
ereport: report.clone(),
162+
assigned_sitrep_id: self.sitrep_id,
163+
comment: comment.to_string(),
164+
}) {
165+
Ok(_) => {
166+
slog::info!(
167+
self.log,
168+
"assigned ereport {} to case", report.id();
169+
"ereport_id" => ?report.id(),
170+
"ereport_class" => ?report.class,
171+
);
172+
}
173+
Err(_) => {
174+
slog::warn!(
175+
self.log,
176+
"ereport {} already assigned to case", report.id();
177+
"ereport_id" => ?report.id(),
178+
"ereport_class" => ?report.class,
179+
);
180+
}
181+
}
182+
}
183+
184+
pub fn impacts_sp(
185+
&mut self,
186+
impact_lists: &mut ImpactLists,
187+
sp_type: SpType,
188+
slot: u16,
189+
comment: impl ToString,
190+
) -> anyhow::Result<()> {
191+
if self.impacted_sp_slots.contains_key(&(sp_type, slot)) {
192+
return Err(anyhow::anyhow!("case already impacts this SP"));
193+
}
194+
195+
impact_lists
196+
.cases_by_sp
197+
.entry((sp_type, slot))
198+
.or_default()
199+
.insert(self.id);
200+
201+
let comment = comment.to_string();
202+
slog::info!(
203+
&self.log,
204+
"case impacts SP";
205+
"sp_type" => %sp_type,
206+
"slot" => %slot,
207+
"comment" => %comment,
208+
);
209+
let created_sitrep_id = self.sitrep_id;
210+
self.impacted_sp_slots
211+
.insert_unique(fm::case::ImpactedSpSlot {
212+
sp_type,
213+
slot,
214+
created_sitrep_id,
215+
comment: comment.to_string(),
216+
})
217+
.expect(
218+
"we just checked that there wasn't already an entry for this \
219+
SP slot",
220+
);
221+
222+
Ok(())
223+
}
224+
}
225+
226+
impl From<CaseBuilder> for fm::Case {
227+
fn from(CaseBuilder { case, .. }: CaseBuilder) -> Self {
228+
case
229+
}
230+
}
231+
232+
impl core::ops::Deref for CaseBuilder {
233+
type Target = fm::Case;
234+
fn deref(&self) -> &Self::Target {
235+
&self.case
236+
}
237+
}
238+
239+
impl core::ops::DerefMut for CaseBuilder {
240+
fn deref_mut(&mut self) -> &mut Self::Target {
241+
&mut self.case
242+
}
243+
}
244+
245+
impl iddqd::IdOrdItem for CaseBuilder {
246+
type Key<'a> = &'a CaseUuid;
247+
fn key(&self) -> Self::Key<'_> {
248+
&self.case.id
249+
}
250+
251+
iddqd::id_upcast!();
252+
}
253+
254+
#[derive(Debug)]
255+
pub struct ImpactLists {
256+
cases_by_sp: HashMap<(SpType, u16), HashSet<CaseUuid>>,
257+
}
258+
259+
impl ImpactLists {
260+
pub fn cases_impacting_sp(
261+
&self,
262+
sp_type: SpType,
263+
slot: u16,
264+
) -> impl Iterator<Item = CaseUuid> + '_ {
265+
self.cases_by_sp
266+
.get(&(sp_type, slot))
267+
.into_iter()
268+
.flat_map(|ids| ids.iter().copied())
269+
}
270+
}

nexus/fm/src/de.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@
44

55
//! Diagnosis engines
66
7+
use crate::SitrepBuilder;
8+
use nexus_types::fm;
79
pub mod power_shelf;
10+
use std::sync::Arc;
11+
12+
pub trait DiagnosisEngine {
13+
fn kind(&self) -> fm::DiagnosisEngineKind;
14+
fn analyze_ereport(
15+
&mut self,
16+
sitrep: &mut SitrepBuilder<'_>,
17+
ereport: &Arc<fm::Ereport>,
18+
) -> anyhow::Result<()>;
19+
}

0 commit comments

Comments
 (0)