Skip to content
Closed
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
2 changes: 2 additions & 0 deletions nexus/db-model/src/fm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ mod case;
pub use case::*;
mod diagnosis_engine;
pub use diagnosis_engine::*;
mod support_bundle_request;
pub use support_bundle_request::*;

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_sitrep)]
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-model/src/fm/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use super::AlertRequest;
use super::DiagnosisEngine;
use super::SupportBundleRequest;
use crate::DbTypedUuid;
use crate::ereport;
use nexus_db_schema::schema::{fm_case, fm_ereport_in_case};
Expand Down Expand Up @@ -139,4 +140,5 @@ pub struct Case {
pub metadata: CaseMetadata,
pub ereports: Vec<CaseEreport>,
pub alerts_requested: Vec<AlertRequest>,
pub support_bundles_requested: Vec<SupportBundleRequest>,
}
241 changes: 241 additions & 0 deletions nexus/db-model/src/fm/support_bundle_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// 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 support bundle requests and data selection models.

use crate::DbTypedUuid;
use crate::impl_enum_type;
use anyhow::Context;
use chrono::{DateTime, Utc};
use nexus_db_schema::schema::{
fm_sb_req_data_selection, fm_support_bundle_request,
};
use nexus_types::fm;
use nexus_types::fm::ereport::EreportFilters;
use nexus_types::support_bundle as support_bundle_types;
use nexus_types::support_bundle::{
BundleData, BundleDataSelection, SledSelection,
};
use omicron_uuid_kinds::{
CaseKind, GenericUuid, SitrepKind, SledUuid, SupportBundleKind,
};
use serde::{Deserialize, Serialize};

impl_enum_type!(
BundleDataCategoryEnum:

#[derive(
Copy,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
AsExpression,
FromSqlRow,
)]
pub enum BundleDataCategory;

Reconfigurator => b"reconfigurator"
HostInfo => b"host_info"
SledCubbyInfo => b"sled_cubby_info"
SpDumps => b"sp_dumps"
Ereports => b"ereports"
);

impl From<&support_bundle_types::BundleData> for BundleDataCategory {
fn from(data: &support_bundle_types::BundleData) -> Self {
match data {
support_bundle_types::BundleData::Reconfigurator => {
BundleDataCategory::Reconfigurator
}
support_bundle_types::BundleData::HostInfo(_) => {
BundleDataCategory::HostInfo
}
support_bundle_types::BundleData::SledCubbyInfo => {
BundleDataCategory::SledCubbyInfo
}
support_bundle_types::BundleData::SpDumps => {
BundleDataCategory::SpDumps
}
support_bundle_types::BundleData::Ereports(_) => {
BundleDataCategory::Ereports
}
}
}
}

// --- SupportBundleRequest (parent row) ---

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_support_bundle_request)]
pub struct SupportBundleRequest {
pub id: DbTypedUuid<SupportBundleKind>,
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub requested_sitrep_id: DbTypedUuid<SitrepKind>,
pub case_id: DbTypedUuid<CaseKind>,
}

impl SupportBundleRequest {
pub fn from_sitrep(
sitrep_id: impl Into<DbTypedUuid<SitrepKind>>,
case_id: impl Into<DbTypedUuid<CaseKind>>,
req: fm::case::SupportBundleRequest,
) -> Self {
let fm::case::SupportBundleRequest {
id,
requested_sitrep_id,
data_selection: _,
} = req;
SupportBundleRequest {
id: id.into(),
sitrep_id: sitrep_id.into(),
requested_sitrep_id: requested_sitrep_id.into(),
case_id: case_id.into(),
}
}
}

// --- Data selection rows ---

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = fm_sb_req_data_selection)]
pub struct SbReqDataSelection {
pub sitrep_id: DbTypedUuid<SitrepKind>,
pub request_id: DbTypedUuid<SupportBundleKind>,
pub category: BundleDataCategory,
// HostInfo fields
pub all_sleds: Option<bool>,
pub sled_ids: Option<Vec<uuid::Uuid>>,
// Ereports fields
pub ereport_start_time: Option<DateTime<Utc>>,
pub ereport_end_time: Option<DateTime<Utc>>,
pub ereport_only_serials: Option<Vec<String>>,
pub ereport_only_classes: Option<Vec<String>>,
}

impl SbReqDataSelection {
/// Create rows from a `BundleDataSelection` for a given request.
pub fn from_data_selection(
sitrep_id: impl Into<DbTypedUuid<SitrepKind>> + Copy,
request_id: impl Into<DbTypedUuid<SupportBundleKind>> + Copy,
selection: &BundleDataSelection,
) -> Vec<Self> {
selection
.iter()
.map(|data| Self::from_bundle_data(sitrep_id, request_id, data))
.collect()
}

fn from_bundle_data(
sitrep_id: impl Into<DbTypedUuid<SitrepKind>>,
request_id: impl Into<DbTypedUuid<SupportBundleKind>>,
data: &BundleData,
) -> Self {
let sitrep_id = sitrep_id.into();
let request_id = request_id.into();
let base = Self {
sitrep_id,
request_id,
category: BundleDataCategory::from(data),
all_sleds: None,
sled_ids: None,
ereport_start_time: None,
ereport_end_time: None,
ereport_only_serials: None,
ereport_only_classes: None,
};
match data {
BundleData::Reconfigurator
| BundleData::SledCubbyInfo
| BundleData::SpDumps => base,
BundleData::HostInfo(sled_selection) => match sled_selection {
SledSelection::All => Self {
all_sleds: Some(true),
sled_ids: Some(vec![]),
..base
},
SledSelection::Specific(set) => Self {
all_sleds: Some(false),
sled_ids: Some(
set.iter().map(|id| id.into_untyped_uuid()).collect(),
),
..base
},
},
BundleData::Ereports(filters) => Self {
ereport_start_time: filters.start_time(),
ereport_end_time: filters.end_time(),
ereport_only_serials: Some(filters.only_serials().to_vec()),
ereport_only_classes: Some(filters.only_classes().to_vec()),
..base
},
}
}

/// Convert a set of data selection rows back into a `BundleDataSelection`.
/// Empty input produces the default selection (collect everything).
pub fn into_data_selection(
rows: Vec<Self>,
) -> anyhow::Result<BundleDataSelection> {
rows.into_iter()
.map(Self::into_bundle_data)
.collect::<anyhow::Result<BundleDataSelection>>()
}

fn into_bundle_data(self) -> anyhow::Result<BundleData> {
match self.category {
BundleDataCategory::Reconfigurator => {
Ok(BundleData::Reconfigurator)
}
BundleDataCategory::SledCubbyInfo => Ok(BundleData::SledCubbyInfo),
BundleDataCategory::SpDumps => Ok(BundleData::SpDumps),
BundleDataCategory::HostInfo => {
let all_sleds = self.all_sleds.context(
"illegal database state (CHECK constraint broken?!): \
all_sleds is NULL for host_info row",
)?;
if all_sleds {
Ok(BundleData::HostInfo(SledSelection::All))
} else {
let ids = self.sled_ids.context(
"illegal database state (CHECK constraint broken?!): \
sled_ids is NULL for host_info row",
)?;
Ok(BundleData::HostInfo(SledSelection::Specific(
ids.into_iter()
.map(SledUuid::from_untyped_uuid)
.collect(),
)))
}
}
BundleDataCategory::Ereports => {
let mut filters = EreportFilters::new();
if let Some(t) = self.ereport_start_time {
filters = filters.with_start_time(t).context(
"illegal database state (CHECK constraint broken?!): \
start_time > end_time",
)?;
}
if let Some(t) = self.ereport_end_time {
filters = filters.with_end_time(t).context(
"illegal database state (CHECK constraint broken?!): \
start_time > end_time",
)?;
}
let serials = self.ereport_only_serials.context(
"illegal database state (CHECK constraint broken?!): \
ereport_only_serials is NULL for ereports row",
)?;
filters = filters.with_serials(serials);
let classes = self.ereport_only_classes.context(
"illegal database state (CHECK constraint broken?!): \
ereport_only_classes is NULL for ereports row",
)?;
filters = filters.with_classes(classes);
Ok(BundleData::Ereports(filters))
}
}
}
}
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
///
/// This must be updated when you change the database schema. Refer to
/// schema/crdb/README.adoc in the root of this repository for details.
pub const SCHEMA_VERSION: Version = Version::new(241, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(242, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
// | leaving the first copy as an example for the next person.
// v
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
KnownVersion::new(242, "fm-support-bundle-request"),
KnownVersion::new(241, "audit-log-incomplete-timeout"),
KnownVersion::new(240, "multicast-drop-mvlan"),
KnownVersion::new(239, "fm-alert-request"),
Expand Down
3 changes: 3 additions & 0 deletions nexus/db-model/src/support_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use nexus_db_schema::schema::support_bundle;

use chrono::{DateTime, Utc};
use nexus_types::external_api::support_bundle as support_bundle_types;
use omicron_uuid_kinds::CaseKind;
use omicron_uuid_kinds::DatasetKind;
use omicron_uuid_kinds::DatasetUuid;
use omicron_uuid_kinds::OmicronZoneKind;
Expand Down Expand Up @@ -95,6 +96,7 @@ pub struct SupportBundle {
pub dataset_id: DbTypedUuid<DatasetKind>,
pub assigned_nexus: Option<DbTypedUuid<OmicronZoneKind>>,
pub user_comment: Option<String>,
pub fm_case_id: Option<DbTypedUuid<CaseKind>>,
}

impl SupportBundle {
Expand All @@ -115,6 +117,7 @@ impl SupportBundle {
dataset_id: dataset_id.into(),
assigned_nexus: Some(nexus_id.into()),
user_comment,
fm_case_id: None,
}
}

Expand Down
3 changes: 2 additions & 1 deletion nexus/db-schema/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ define_enums! {
BlockSizeEnum => "block_size",
BpDatasetDispositionEnum => "bp_dataset_disposition",
BpPhysicalDiskDispositionEnum => "bp_physical_disk_disposition",
BpSourceEnum => "bp_source",
BpSledMeasurementsEnum => "bp_sled_measurements",
BpSourceEnum => "bp_source",
BpZoneDispositionEnum => "bp_zone_disposition",
BpZoneImageSourceEnum => "bp_zone_image_source",
BundleDataCategoryEnum => "bundle_data_category",
CabooseWhichEnum => "caboose_which",
ClickhouseModeEnum => "clickhouse_mode",
DatasetKindEnum => "dataset_kind",
Expand Down
36 changes: 36 additions & 0 deletions nexus/db-schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,7 @@ table! {

assigned_nexus -> Nullable<Uuid>,
user_comment -> Nullable<Text>,
fm_case_id -> Nullable<Uuid>,
}
}

Expand Down Expand Up @@ -3183,6 +3184,41 @@ table! {
}
}

// FM support bundle requests, stored per-sitrep like alert requests.
table! {
fm_support_bundle_request (sitrep_id, id) {
id -> Uuid,
sitrep_id -> Uuid,
requested_sitrep_id -> Uuid,
case_id -> Uuid,
}
}

// Per-category data selection for support bundle requests.
// One row per selected BundleData category. Row existence means
// "include this category in the bundle." No rows for a request means
// "collect everything."
table! {
fm_sb_req_data_selection (sitrep_id, request_id, category) {
sitrep_id -> Uuid,
request_id -> Uuid,
category -> crate::enums::BundleDataCategoryEnum,
// HostInfo fields (non-null iff category = 'host_info')
all_sleds -> Nullable<Bool>,
sled_ids -> Nullable<Array<Uuid>>,
// Ereports fields (non-null iff category = 'ereports')
ereport_start_time -> Nullable<Timestamptz>,
ereport_end_time -> Nullable<Timestamptz>,
ereport_only_serials -> Nullable<Array<Text>>,
ereport_only_classes -> Nullable<Array<Text>>,
}
}

allow_tables_to_appear_in_same_query!(
fm_support_bundle_request,
fm_sb_req_data_selection,
);

table! {
trust_quorum_configuration (rack_id, epoch) {
rack_id -> Uuid,
Expand Down
Loading
Loading