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
9 changes: 8 additions & 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 Expand Up @@ -345,5 +345,12 @@ async fn cmd_db_sitrep_show(
}
}

if !cases.is_empty() {
println!("\n{:-<80}\n", "== CASES");
for case in cases {
println!("{}", case.display_indented(4, Some(id)));
}
}

Ok(())
}
16 changes: 14 additions & 2 deletions ereport/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct Ereport {
Serialize,
Deserialize,
JsonSchema,
Hash,
)]
#[repr(transparent)]
#[serde(from = "u64", into = "u64")]
Expand Down Expand Up @@ -102,15 +103,26 @@ 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,
Hash,
)]
pub struct EreportId {
pub restart_id: EreporterRestartUuid,
pub ena: Ena,
}

impl fmt::Display for EreportId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}:{:x}", self.restart_id, self.ena)
write!(f, "{}:{:x}", self.restart_id, self.ena.0)
}
}

Expand Down
37 changes: 37 additions & 0 deletions nexus/db-model/src/alert_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use super::impl_enum_type;
use nexus_types::external_api::views;
use omicron_common::api::external::Error;
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use std::fmt;
Expand All @@ -30,6 +31,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 +47,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 +81,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 +95,32 @@ 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 TryFrom<AlertClass> for nexus_types::fm::AlertClass {
type Error = Error;

fn try_from(input: AlertClass) -> Result<Self, Self::Error> {
use nexus_types::fm::AlertClass as Out;
match input {
AlertClass::PsuRemoved => Ok(Out::PsuRemoved),
AlertClass::PsuInserted => Ok(Out::PsuInserted),
class => Err(Error::invalid_value(
"alert_class",
format!("'{class}' is not a FM alert class"),
)),
}
}
}

impl fmt::Display for AlertClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
Expand Down
7 changes: 7 additions & 0 deletions nexus/db-model/src/fm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ use chrono::{DateTime, Utc};
use nexus_db_schema::schema::{fm_sitrep, fm_sitrep_history};
use omicron_uuid_kinds::{CollectionKind, OmicronZoneKind, SitrepKind};

mod alert_request;
pub use alert_request::*;
mod case;
pub use case::*;
mod diagnosis_engine;
pub use diagnosis_engine::*;

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_sitrep)]
pub struct SitrepMetadata {
Expand Down
55 changes: 55 additions & 0 deletions nexus/db-model/src/fm/alert_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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/.

//! Fault management alert requests.

use crate::AlertClass;
use crate::DbTypedUuid;
use nexus_db_schema::schema::fm_alert_request;
use nexus_types::fm;
use omicron_uuid_kinds::{
AlertKind, CaseKind, CaseUuid, SitrepKind, SitrepUuid,
};

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_alert_request)]
pub struct AlertRequest {
pub id: DbTypedUuid<AlertKind>,
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub requested_sitrep_id: DbTypedUuid<SitrepKind>,
pub case_id: DbTypedUuid<CaseKind>,
#[diesel(column_name = "class")]
pub class: AlertClass,
pub payload: serde_json::Value,
}

impl AlertRequest {
pub fn new(
current_sitrep_id: SitrepUuid,
case_id: CaseUuid,
req: fm::AlertRequest,
) -> Self {
let fm::AlertRequest { id, requested_sitrep_id, payload, class } = req;
AlertRequest {
id: id.into(),
sitrep_id: current_sitrep_id.into(),
requested_sitrep_id: requested_sitrep_id.into(),
case_id: case_id.into(),
class: class.into(),
payload,
}
}
}

impl TryFrom<AlertRequest> for fm::AlertRequest {
type Error = <fm::AlertClass as TryFrom<AlertClass>>::Error;
fn try_from(req: AlertRequest) -> Result<Self, Self::Error> {
Ok(fm::AlertRequest {
id: req.id.into(),
requested_sitrep_id: req.requested_sitrep_id.into(),
payload: req.payload,
class: req.class.try_into()?,
})
}
}
149 changes: 149 additions & 0 deletions nexus/db-model/src/fm/case.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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/.

//! Fault management cases.

use super::AlertRequest;
use super::DiagnosisEngine;
use crate::DbTypedUuid;
use crate::SpMgsSlot;
use crate::SpType;
use crate::ereport;
use chrono::{DateTime, Utc};
use nexus_db_schema::schema::{
fm_case, fm_case_impacts_sp_slot, fm_ereport_in_case,
};
use nexus_types::fm;
use omicron_uuid_kinds::{
CaseKind, EreporterRestartKind, SitrepKind, SitrepUuid,
};

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_case)]
pub struct CaseMetadata {
pub id: DbTypedUuid<CaseKind>,
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub de: DiagnosisEngine,

pub created_sitrep_id: DbTypedUuid<SitrepKind>,
pub time_created: DateTime<Utc>,

pub time_closed: Option<DateTime<Utc>>,
pub closed_sitrep_id: Option<DbTypedUuid<SitrepKind>>,

pub comment: String,
}

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_ereport_in_case)]
pub struct CaseEreport {
pub restart_id: DbTypedUuid<EreporterRestartKind>,
pub ena: ereport::DbEna,
pub case_id: DbTypedUuid<CaseKind>,
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub assigned_sitrep_id: DbTypedUuid<SitrepKind>,
pub comment: String,
}

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_case_impacts_sp_slot)]
pub struct CaseImpactsSp {
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub case_id: DbTypedUuid<CaseKind>,
pub sp_type: SpType,
pub sp_slot: SpMgsSlot,
pub created_sitrep_id: DbTypedUuid<SitrepKind>,
pub comment: String,
}

#[derive(Clone, Debug)]
pub struct Case {
pub metadata: CaseMetadata,
pub ereports: Vec<CaseEreport>,
pub impacted_sp_slots: Vec<CaseImpactsSp>,
pub alerts_requested: Vec<AlertRequest>,
}

impl Case {
pub fn from_sitrep(sitrep_id: SitrepUuid, case: fm::Case) -> Self {
let sitrep_id = sitrep_id.into();
let case_id = case.id.into();
let ereports = case
.ereports
.into_iter()
.map(
|fm::case::CaseEreport {
ereport,
assigned_sitrep_id,
comment,
}| {
let restart_id = ereport.id().restart_id.into();
let ena = ereport.id().ena.into();
CaseEreport {
case_id,
restart_id,
ena,
comment,
sitrep_id,
assigned_sitrep_id: assigned_sitrep_id.into(),
}
},
)
.collect();
let impacted_sp_slots = case
.impacted_sp_slots
.into_iter()
.map(
|fm::case::ImpactedSpSlot {
sp_type,
slot,
comment,
created_sitrep_id,
}| CaseImpactsSp {
sitrep_id,
case_id,
sp_type: sp_type.into(),
sp_slot: SpMgsSlot::from(slot as u16),
created_sitrep_id: created_sitrep_id.into(),
comment,
},
)
.collect();
let alerts_requested = case
.alerts_requested
.into_iter()
.map(
|fm::AlertRequest {
id,
class,
payload,
requested_sitrep_id,
}| AlertRequest {
sitrep_id,
case_id,
class: class.into(),
id: id.into(),
payload,
requested_sitrep_id: requested_sitrep_id.into(),
},
)
.collect();

Self {
metadata: CaseMetadata {
id: case_id,
sitrep_id,
de: case.de.into(),
created_sitrep_id: case.created_sitrep_id.into(),
time_created: case.time_created.into(),
time_closed: case.time_closed.map(Into::into),
closed_sitrep_id: case.closed_sitrep_id.map(Into::into),
comment: case.comment,
},
ereports,
impacted_sp_slots,
alerts_requested,
}
}
}
Loading
Loading