Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ members = [
"nexus/db-schema",
"nexus/defaults",
"nexus/external-api",
"nexus/fm",
"nexus/internal-api",
"nexus/inventory",
"nexus/lockstep-api",
Expand Down
2 changes: 1 addition & 1 deletion dev-tools/omdb/src/bin/omdb/db/sitrep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ async fn cmd_db_sitrep_show(
}
};

let fm::Sitrep { metadata } = sitrep;
let fm::Sitrep { metadata, cases } = sitrep;
let fm::SitrepMetadata {
id,
creator_id,
Expand Down
4 changes: 3 additions & 1 deletion ereport/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ impl TryFrom<i64> for Ena {
}

/// Unique identifier for an ereport.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord,
)]
pub struct EreportId {
pub restart_id: EreporterRestartUuid,
pub ena: Ena,
Expand Down
20 changes: 20 additions & 0 deletions nexus/db-model/src/alert_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ impl_enum_type!(
TestFooBaz => b"test.foo.baz"
TestQuuxBar => b"test.quux.bar"
TestQuuxBarBaz => b"test.quux.bar.baz"
PsuInserted => b"hw.insert.power.power_shelf.psu"
PsuRemoved => b"hw.remove.power.power_shelf.psu"
);

impl AlertClass {
Expand All @@ -44,6 +46,8 @@ impl AlertClass {
Self::TestFooBaz => "test.foo.baz",
Self::TestQuuxBar => "test.quux.bar",
Self::TestQuuxBarBaz => "test.quux.bar.baz",
Self::PsuInserted => "hw.insert.power.power_shelf.psu",
Self::PsuRemoved => "hw.remove.power.power_shelf.psu",
}
}

Expand Down Expand Up @@ -76,6 +80,12 @@ impl AlertClass {
| Self::TestQuuxBarBaz => {
"This is a test of the emergency alert system"
}
Self::PsuInserted => {
"A power supply unit (PSU) has been inserted into the power shelf"
}
Self::PsuRemoved => {
"A power supply unit (PSU) has been removed from the power shelf"
}
}
}

Expand All @@ -84,6 +94,16 @@ impl AlertClass {
<Self as strum::VariantArray>::VARIANTS;
}

impl From<nexus_types::fm::AlertClass> for AlertClass {
fn from(input: nexus_types::fm::AlertClass) -> Self {
use nexus_types::fm::AlertClass as In;
match input {
In::PsuRemoved => Self::PsuRemoved,
In::PsuInserted => Self::PsuInserted,
}
}
}

impl fmt::Display for AlertClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
Expand Down
17 changes: 16 additions & 1 deletion nexus/db-queries/src/db/datastore/fm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ impl DataStore {
// TODO(eliza): this is where we would read all the other sitrep data,
// if there was any.

Ok(Sitrep { metadata })
Ok(Sitrep {
metadata,
// TODO(eliza) read these
cases: Default::default(),
})
}

/// Insert the provided [`Sitrep`] into the database, and attempt to mark it
Expand Down Expand Up @@ -755,6 +759,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: None,
},
cases: Default::default(),
};

datastore.fm_sitrep_insert(&opctx, &sitrep).await.unwrap();
Expand Down Expand Up @@ -801,6 +806,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: None,
},
cases: Default::default(),
};
datastore.fm_sitrep_insert(&opctx, &sitrep1).await.unwrap();

Expand All @@ -814,6 +820,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: Some(sitrep1.id()),
},
cases: Default::default(),
};
datastore.fm_sitrep_insert(&opctx, &sitrep2).await.expect(
"inserting a sitrep whose parent is current should succeed",
Expand Down Expand Up @@ -854,6 +861,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: None,
},
cases: Default::default(),
};
datastore.fm_sitrep_insert(&opctx, &sitrep1).await.unwrap();

Expand All @@ -868,6 +876,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: Some(nonexistent_id),
},
cases: Default::default(),
};

let result = datastore.fm_sitrep_insert(&opctx, &sitrep2).await;
Expand Down Expand Up @@ -902,6 +911,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: None,
},
cases: Default::default(),
};
datastore.fm_sitrep_insert(&opctx, &sitrep1).await.unwrap();

Expand All @@ -915,6 +925,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: Some(sitrep1.id()),
},
cases: Default::default(),
};
datastore.fm_sitrep_insert(&opctx, &sitrep2).await.unwrap();

Expand All @@ -929,6 +940,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: Some(sitrep1.id()),
},
cases: Default::default(),
};
let result = datastore.fm_sitrep_insert(&opctx, &sitrep3).await;

Expand Down Expand Up @@ -969,6 +981,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: None,
},
cases: Default::default(),
};
datastore
.fm_sitrep_insert(&opctx, &sitrep1)
Expand Down Expand Up @@ -1009,6 +1022,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id: Some(sitrep1.metadata.id),
},
cases: Default::default(),
};
datastore
.fm_sitrep_insert(&opctx, &sitrep2)
Expand Down Expand Up @@ -1072,6 +1086,7 @@ mod tests {
time_created: Utc::now(),
parent_sitrep_id,
},
cases: Default::default(),
};
match datastore.fm_sitrep_insert(&opctx, &sitrep).await {
Ok(_) => {
Expand Down
20 changes: 20 additions & 0 deletions nexus/fm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "nexus-fm"
version = "0.1.0"
edition = "2021"

[lints]
workspace = true

[dependencies]
anyhow.workspace = true
chrono.workspace = true
iddqd.workspace = true
nexus-types.workspace = true
omicron-uuid-kinds.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
slog.workspace = true

omicron-workspace-hack.workspace = true
22 changes: 22 additions & 0 deletions nexus/fm/src/alert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Alert messages.
use nexus_types::fm::AlertClass;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

pub mod power_shelf;

pub trait Alert: Serialize + JsonSchema + std::fmt::Debug {
const CLASS: AlertClass;
}

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct VpdIdentity {
pub part_number: Option<String>,
pub revision: Option<String>,
pub serial_number: Option<String>,
}
52 changes: 52 additions & 0 deletions nexus/fm/src/alert/power_shelf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Power shelf alerts.
use super::{Alert, VpdIdentity};
use nexus_types::fm::AlertClass;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "version", rename_all = "snake_case")]
pub enum PsuInserted {
V0 {
#[serde(flatten)]
psc_psu: PscPsu,
},
}

impl Alert for PsuInserted {
const CLASS: AlertClass = AlertClass::PsuInserted;
}

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "version", rename_all = "snake_case")]
pub enum PsuRemoved {
V0 {
#[serde(flatten)]
psc_psu: PscPsu,
},
}

impl Alert for PsuRemoved {
const CLASS: AlertClass = AlertClass::PsuInserted;
}

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct PscPsu {
pub psc_id: VpdIdentity,
pub psc_slot: u16,
pub psu_id: PsuIdentity,
pub psu_slot: u16,
}

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct PsuIdentity {
pub manufacturer: Option<String>,
pub part_number: Option<String>,
pub firmware_revision: Option<String>,
pub serial_number: Option<String>,
}
7 changes: 7 additions & 0 deletions nexus/fm/src/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Diagnosis engines

pub mod power_shelf;
76 changes: 76 additions & 0 deletions nexus/fm/src/de/power_shelf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Power shelf diagnosis

use crate::SitrepBuilder;
use crate::alert
use nexus_types::fm::AlertRequest;
use nexus_types::fm::DiagnosisEngine;
use nexus_types::fm::Ereport;
use nexus_types::fm::ereport;
use nexus_types::inventory::SpType;
use std::sync::Arc;

pub fn diagnose(
sitrep: &mut SitrepBuilder<'_>,
new_ereports: &[Arc<Ereport>],
) -> anyhow::Result<()> {
for ereport in new_ereports {
// Skip non-power shelf reports
let ereport::Reporter::Sp { sp_type: SpType::Power, slot, } = ereport.reporter else {
continue;
};

// TODO: check for existing cases tracked for this power shelf and see
// if the ereport is related to them...

match ereport.data.class.as_deref() {
// PSU inserted
Some("hw.insert.psu") => {
let mut case = sitrep.open_case(DiagnosisEngine::PowerShelf)?;
case.add_ereport(ereport);
case.comment = "PSU inserted".to_string();
let psu_id = match ereport.get("fruid") {
Some(serde_json::Value::Object(fruid)) => {
todo!()
},
None => {
todo!()
}
};
case.request_alert(alert::power_shelf::PsuInserted::V0 {
psc_psu: alert::power_shelf::PscPsu {
psc_id: alert::VpdIdentity {
serial_number: ereport.serial_number.clone(),
revision: ereport.report.get("baseboard_rev").map(ToString::to_string),
part_number: ereport.part_number.clone(),
},
psc_slot: slot,
psu_id,
psu_slot: ereport.report.get("slot").map(|s| todo!()),
}
})
}
Some("hw.remove.psu") => {}
Some(unknown) => {
slog::warn!(
&sitrep.log,
"ignoring unhandled PSC ereport class";
"ereport_class" => %unknown,
"ereport" => %ereport.id,
);
}
None => {
slog::warn!(
&sitrep.log,
"ignoring PSC ereport with no class";
"ereport" => %ereport.id,
);
}
}
}

Ok(())
}
Loading
Loading