diff --git a/nexus/db-queries/src/db/datastore/ereport.rs b/nexus/db-queries/src/db/datastore/ereport.rs index c2456c3c871..78488f90f65 100644 --- a/nexus/db-queries/src/db/datastore/ereport.rs +++ b/nexus/db-queries/src/db/datastore/ereport.rs @@ -27,6 +27,7 @@ use nexus_db_errors::public_error_from_diesel; use nexus_db_lookup::DbConnection; use nexus_db_schema::schema::ereport::dsl; use nexus_types::fm::ereport as fm; +use nexus_types::fm::ereport::EreportFilters; use nexus_types::fm::ereport::EreportId; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; @@ -48,44 +49,6 @@ pub struct EreporterRestartBySerial { pub ereports: u32, } -/// A set of filters for fetching ereports. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct EreportFilters { - /// If present, include only ereports that were collected at the specified - /// timestamp or later. - /// - /// If `end_time` is also present, this value *must* be earlier than - /// `end_time`. - pub start_time: Option>, - /// If present, include only ereports that were collected at the specified - /// timestamp or before. - /// - /// If `start_time` is also present, this value *must* be later than - /// `start_time`. - pub end_time: Option>, - /// If this list is non-empty, include only ereports that were reported by - /// systems with the provided serial numbers. - pub only_serials: Vec, - /// If this list is non-empty, include only ereports with the provided class - /// strings. - // TODO(eliza): globbing could be nice to add here eventually... - pub only_classes: Vec, -} - -impl EreportFilters { - fn check_time_range(&self) -> Result<(), Error> { - if let (Some(start), Some(end)) = (self.start_time, self.end_time) { - if start > end { - return Err(Error::invalid_request( - "start time must be before end time", - )); - } - } - - Ok(()) - } -} - impl DataStore { /// Fetch an ereport by its restart ID and ENA. /// diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 37525b020b8..bca532e171d 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -141,7 +141,6 @@ pub use disk::LocalStorageAllocation; pub use disk::LocalStorageDisk; pub use dns::DataStoreDnsTest; pub use dns::DnsVersionUpdateBuilder; -pub use ereport::EreportFilters; pub use external_ip::FloatingIpAllocation; pub use external_subnet::ExternalSubnetBeginOpResult; pub use external_subnet::ExternalSubnetCompleteOpResult; diff --git a/nexus/src/app/background/tasks/support_bundle/request.rs b/nexus/src/app/background/tasks/support_bundle/request.rs index 89d43ecc73c..dac2fb237be 100644 --- a/nexus/src/app/background/tasks/support_bundle/request.rs +++ b/nexus/src/app/background/tasks/support_bundle/request.rs @@ -4,10 +4,12 @@ //! Support bundle request types and data selection -use nexus_db_queries::db::datastore::EreportFilters; +use nexus_types::fm::ereport::EreportFilters; +use nexus_types::support_bundle::{ + BundleData, BundleDataCategory, BundleDataSelection, SledSelection, +}; + use omicron_uuid_kinds::SledUuid; -use std::collections::HashMap; -use std::collections::HashSet; use std::num::NonZeroU64; /// We use "/var/tmp" to use Nexus' filesystem for temporary storage, @@ -18,121 +20,6 @@ pub const TEMPDIR: &str = "/var/tmp"; /// within a single streaming request. pub const CHUNK_SIZE: NonZeroU64 = NonZeroU64::new(1024 * 1024 * 1024).unwrap(); -/// Describes the category of support bundle data. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum BundleDataCategory { - /// Collects reconfigurator state (some of the latest blueprints, - /// information about the target blueprint). - Reconfigurator, - /// Collects info from sled agents, running a handful of - /// diagnostic commands (e.g., zoneadm, dladm, etc). - HostInfo, - /// Collects sled serial numbers, cubby numbers, and UUIDs. - SledCubbyInfo, - /// Saves task dumps from SPs. - SpDumps, - /// Collects ereports. - Ereports, -} - -/// Specifies what data to collect for a bundle data category. -/// -/// Each variant corresponds to a BundleDataCategory. -/// For categories without additional parameters, the variant is a unit variant. -/// For categories that can be filtered or configured, the variant contains -/// that configuration data. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum BundleData { - Reconfigurator, - HostInfo(HashSet), - SledCubbyInfo, - SpDumps, - Ereports(EreportFilters), -} - -impl BundleData { - fn category(&self) -> BundleDataCategory { - match self { - Self::Reconfigurator => BundleDataCategory::Reconfigurator, - Self::HostInfo(_) => BundleDataCategory::HostInfo, - Self::SledCubbyInfo => BundleDataCategory::SledCubbyInfo, - Self::SpDumps => BundleDataCategory::SpDumps, - Self::Ereports(_) => BundleDataCategory::Ereports, - } - } -} - -/// A collection of bundle data specifications. -/// -/// This wrapper ensures that categories and data always match - you can't -/// insert (BundleDataCategory::Reconfigurator, BundleData::SpDumps) -/// because each BundleData determines its own category. -#[derive(Debug, Clone)] -pub struct BundleDataSelection { - data: HashMap, -} - -impl BundleDataSelection { - pub fn new() -> Self { - Self { data: HashMap::new() } - } - - /// Inserts BundleData to be queried for a particular category within the - /// bundle. - /// - /// Each category of data can only be specified once (e.g., inserting - /// BundleData::HostInfo multiple times will only use the most-recently - /// inserted specification) - pub fn insert(&mut self, bundle_data: BundleData) { - self.data.insert(bundle_data.category(), bundle_data); - } - - pub fn contains(&self, category: BundleDataCategory) -> bool { - self.data.contains_key(&category) - } - - pub fn get(&self, category: BundleDataCategory) -> Option<&BundleData> { - self.data.get(&category) - } -} - -impl FromIterator for BundleDataSelection { - fn from_iter>(iter: T) -> Self { - let mut selection = Self::new(); - for bundle_data in iter { - selection.insert(bundle_data); - } - selection - } -} - -impl Default for BundleDataSelection { - fn default() -> Self { - [ - BundleData::Reconfigurator, - BundleData::HostInfo(HashSet::from([SledSelection::All])), - BundleData::SledCubbyInfo, - BundleData::SpDumps, - BundleData::Ereports(EreportFilters { - start_time: Some(chrono::Utc::now() - chrono::Days::new(7)), - ..EreportFilters::default() - }), - ] - .into_iter() - .collect() - } -} - -/// The set of sleds to include -/// -/// Multiple values of this enum are joined together into a HashSet. -/// Therefore "SledSelection::All" overrides specific sleds. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum SledSelection { - All, - Specific(SledUuid), -} - /// Specifies the data to be collected within the Support Bundle. #[derive(Clone)] pub struct BundleRequest { diff --git a/nexus/src/app/background/tasks/support_bundle/steps/ereports.rs b/nexus/src/app/background/tasks/support_bundle/steps/ereports.rs index 24d8272aefb..459be751b2f 100644 --- a/nexus/src/app/background/tasks/support_bundle/steps/ereports.rs +++ b/nexus/src/app/background/tasks/support_bundle/steps/ereports.rs @@ -6,6 +6,7 @@ use crate::app::background::tasks::support_bundle::collection::BundleCollection; use crate::app::background::tasks::support_bundle::step::CollectionStepOutput; +use nexus_types::fm::ereport::EreportFilters; use anyhow::Context; use camino::Utf8Path; @@ -13,7 +14,6 @@ use camino::Utf8PathBuf; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_db_queries::db::datastore; -use nexus_db_queries::db::datastore::EreportFilters; use nexus_db_queries::db::pagination::Paginator; use nexus_types::fm::Ereport; use nexus_types::internal_api::background::SupportBundleEreportStatus; diff --git a/nexus/src/app/background/tasks/support_bundle_collector.rs b/nexus/src/app/background/tasks/support_bundle_collector.rs index 76794dd6ebe..2d75c63e6b9 100644 --- a/nexus/src/app/background/tasks/support_bundle_collector.rs +++ b/nexus/src/app/background/tasks/support_bundle_collector.rs @@ -441,7 +441,6 @@ mod test { use super::*; use crate::app::background::tasks::support_bundle::perfetto; - use crate::app::background::tasks::support_bundle::request::BundleData; use crate::app::support_bundles::SupportBundleQueryType; use http_body_util::BodyExt; use illumos_utils::zpool::ZpoolHealth; @@ -456,6 +455,7 @@ mod test { use nexus_types::internal_api::background::SupportBundleCollectionStep; use nexus_types::internal_api::background::SupportBundleEreportStatus; use nexus_types::inventory::SpType; + use nexus_types::support_bundle::BundleData; use omicron_common::api::external::ByteCount; use omicron_common::api::internal::shared::DatasetKind; use omicron_common::disk::DatasetConfig; diff --git a/nexus/types/src/fm/ereport.rs b/nexus/types/src/fm/ereport.rs index 1ad263af15f..0e78743f549 100644 --- a/nexus/types/src/fm/ereport.rs +++ b/nexus/types/src/fm/ereport.rs @@ -6,6 +6,7 @@ use crate::inventory::SpType; use chrono::{DateTime, Utc}; +use omicron_common::api::external::Error; use omicron_uuid_kinds::EreporterRestartUuid; use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::SledUuid; @@ -214,3 +215,41 @@ fn get_sp_metadata_string( } } } + +/// A set of filters for fetching ereports. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct EreportFilters { + /// If present, include only ereports that were collected at the specified + /// timestamp or later. + /// + /// If `end_time` is also present, this value *must* be earlier than + /// `end_time`. + pub start_time: Option>, + /// If present, include only ereports that were collected at the specified + /// timestamp or before. + /// + /// If `start_time` is also present, this value *must* be later than + /// `start_time`. + pub end_time: Option>, + /// If this list is non-empty, include only ereports that were reported by + /// systems with the provided serial numbers. + pub only_serials: Vec, + /// If this list is non-empty, include only ereports with the provided class + /// strings. + // TODO(eliza): globbing could be nice to add here eventually... + pub only_classes: Vec, +} + +impl EreportFilters { + pub fn check_time_range(&self) -> Result<(), Error> { + if let (Some(start), Some(end)) = (self.start_time, self.end_time) { + if start > end { + return Err(Error::invalid_request( + "start time must be before end time", + )); + } + } + + Ok(()) + } +} diff --git a/nexus/types/src/lib.rs b/nexus/types/src/lib.rs index 555b74dc988..13978312f5a 100644 --- a/nexus/types/src/lib.rs +++ b/nexus/types/src/lib.rs @@ -41,4 +41,5 @@ pub mod multicast; pub mod quiesce; pub mod saga; pub mod silo; +pub mod support_bundle; pub mod trust_quorum; diff --git a/nexus/types/src/support_bundle.rs b/nexus/types/src/support_bundle.rs new file mode 100644 index 00000000000..ed53e426bb9 --- /dev/null +++ b/nexus/types/src/support_bundle.rs @@ -0,0 +1,135 @@ +// 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/. + +//! Support bundle data selection types. +//! +//! These types specify what data to collect in a support bundle. +//! They are shared between the support bundle collector and FM case types. + +use crate::fm::ereport::EreportFilters; +use omicron_uuid_kinds::SledUuid; +use std::collections::HashMap; +use std::collections::HashSet; + +/// Describes the category of support bundle data. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum BundleDataCategory { + /// Collects reconfigurator state (some of the latest blueprints, + /// information about the target blueprint). + Reconfigurator, + /// Collects info from sled agents, running a handful of + /// diagnostic commands (e.g., zoneadm, dladm, etc). + HostInfo, + /// Collects sled serial numbers, cubby numbers, and UUIDs. + SledCubbyInfo, + /// Saves task dumps from SPs. + SpDumps, + /// Collects ereports. + Ereports, +} + +/// Specifies what data to collect for a bundle data category. +/// +/// Each variant corresponds to a BundleDataCategory. +/// For categories without additional parameters, the variant is a unit variant. +/// For categories that can be filtered or configured, the variant contains +/// that configuration data. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BundleData { + Reconfigurator, + HostInfo(HashSet), + SledCubbyInfo, + SpDumps, + Ereports(EreportFilters), +} + +impl BundleData { + fn category(&self) -> BundleDataCategory { + match self { + Self::Reconfigurator => BundleDataCategory::Reconfigurator, + Self::HostInfo(_) => BundleDataCategory::HostInfo, + Self::SledCubbyInfo => BundleDataCategory::SledCubbyInfo, + Self::SpDumps => BundleDataCategory::SpDumps, + Self::Ereports(_) => BundleDataCategory::Ereports, + } + } +} + +/// A collection of bundle data specifications. +/// +/// This wrapper ensures that categories and data always match - you can't +/// insert (BundleDataCategory::Reconfigurator, BundleData::SpDumps) +/// because each BundleData determines its own category. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BundleDataSelection { + data: HashMap, +} + +impl BundleDataSelection { + /// Creates an empty selection with no data categories. + /// + /// This is distinct from [`Self::default`], which returns a selection + /// containing all categories (i.e. "collect everything"). + pub fn new() -> Self { + Self { data: HashMap::new() } + } + + /// Inserts BundleData to be queried for a particular category within the + /// bundle. + /// + /// Each category of data can only be specified once (e.g., inserting + /// BundleData::HostInfo multiple times will only use the most-recently + /// inserted specification) + pub fn insert(&mut self, bundle_data: BundleData) { + self.data.insert(bundle_data.category(), bundle_data); + } + + pub fn contains(&self, category: BundleDataCategory) -> bool { + self.data.contains_key(&category) + } + + pub fn get(&self, category: BundleDataCategory) -> Option<&BundleData> { + self.data.get(&category) + } +} + +impl FromIterator for BundleDataSelection { + fn from_iter>(iter: T) -> Self { + let mut selection = Self::new(); + for bundle_data in iter { + selection.insert(bundle_data); + } + selection + } +} + +impl Default for BundleDataSelection { + /// Returns a selection containing all data categories (i.e. "collect + /// everything"). This is distinct from [`Self::new`], which returns an + /// empty selection. + fn default() -> Self { + [ + BundleData::Reconfigurator, + BundleData::HostInfo(HashSet::from([SledSelection::All])), + BundleData::SledCubbyInfo, + BundleData::SpDumps, + BundleData::Ereports(EreportFilters { + start_time: Some(chrono::Utc::now() - chrono::Days::new(7)), + ..EreportFilters::default() + }), + ] + .into_iter() + .collect() + } +} + +/// The set of sleds to include. +/// +/// Multiple values of this enum are joined together into a HashSet. +/// Therefore "SledSelection::All" overrides specific sleds. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum SledSelection { + All, + Specific(SledUuid), +}