|
| 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 | +//! Fault management support bundle requests and data selection models. |
| 6 | +
|
| 7 | +use crate::DbTypedUuid; |
| 8 | +use chrono::{DateTime, Utc}; |
| 9 | +use nexus_db_schema::schema::{ |
| 10 | + fm_support_bundle_request, |
| 11 | + fm_support_bundle_request_data_selection_ereports, |
| 12 | + fm_support_bundle_request_data_selection_flags, |
| 13 | + fm_support_bundle_request_data_selection_host_info, |
| 14 | +}; |
| 15 | +use nexus_types::fm; |
| 16 | +use nexus_types::fm::ereport::{EreportFilters, EreportFiltersParams}; |
| 17 | +use nexus_types::support_bundle::{ |
| 18 | + BundleData, BundleDataCategory, SledSelection, |
| 19 | +}; |
| 20 | +use omicron_uuid_kinds::{ |
| 21 | + CaseKind, GenericUuid, SitrepKind, SledUuid, SupportBundleKind, |
| 22 | +}; |
| 23 | + |
| 24 | +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] |
| 25 | +#[diesel(table_name = fm_support_bundle_request)] |
| 26 | +pub struct SupportBundleRequest { |
| 27 | + pub id: DbTypedUuid<SupportBundleKind>, |
| 28 | + pub sitrep_id: DbTypedUuid<SitrepKind>, |
| 29 | + pub requested_sitrep_id: DbTypedUuid<SitrepKind>, |
| 30 | + pub case_id: DbTypedUuid<CaseKind>, |
| 31 | +} |
| 32 | + |
| 33 | +impl SupportBundleRequest { |
| 34 | + pub fn from_sitrep( |
| 35 | + sitrep_id: impl Into<DbTypedUuid<SitrepKind>>, |
| 36 | + case_id: impl Into<DbTypedUuid<CaseKind>>, |
| 37 | + req: fm::case::SupportBundleRequest, |
| 38 | + ) -> Self { |
| 39 | + let fm::case::SupportBundleRequest { |
| 40 | + id, |
| 41 | + requested_sitrep_id, |
| 42 | + data_selection: _, |
| 43 | + } = req; |
| 44 | + SupportBundleRequest { |
| 45 | + id: id.into(), |
| 46 | + sitrep_id: sitrep_id.into(), |
| 47 | + requested_sitrep_id: requested_sitrep_id.into(), |
| 48 | + case_id: case_id.into(), |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +/// Flags table row — tracks which payload-less data categories are selected. |
| 54 | +/// Always inserted alongside the parent bundle request. |
| 55 | +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] |
| 56 | +#[diesel(table_name = fm_support_bundle_request_data_selection_flags)] |
| 57 | +pub struct DataSelectionFlags { |
| 58 | + pub sitrep_id: DbTypedUuid<SitrepKind>, |
| 59 | + pub request_id: DbTypedUuid<SupportBundleKind>, |
| 60 | + pub include_reconfigurator: bool, |
| 61 | + pub include_sled_cubby_info: bool, |
| 62 | + pub include_sp_dumps: bool, |
| 63 | +} |
| 64 | + |
| 65 | +impl DataSelectionFlags { |
| 66 | + pub fn from_sitrep( |
| 67 | + sitrep_id: impl Into<DbTypedUuid<SitrepKind>>, |
| 68 | + request_id: impl Into<DbTypedUuid<SupportBundleKind>>, |
| 69 | + data_selection: &nexus_types::support_bundle::BundleDataSelection, |
| 70 | + ) -> Self { |
| 71 | + DataSelectionFlags { |
| 72 | + sitrep_id: sitrep_id.into(), |
| 73 | + request_id: request_id.into(), |
| 74 | + include_reconfigurator: data_selection |
| 75 | + .contains(BundleDataCategory::Reconfigurator), |
| 76 | + include_sled_cubby_info: data_selection |
| 77 | + .contains(BundleDataCategory::SledCubbyInfo), |
| 78 | + include_sp_dumps: data_selection |
| 79 | + .contains(BundleDataCategory::SpDumps), |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] |
| 85 | +#[diesel(table_name = fm_support_bundle_request_data_selection_host_info)] |
| 86 | +pub struct HostInfo { |
| 87 | + pub sitrep_id: DbTypedUuid<SitrepKind>, |
| 88 | + pub request_id: DbTypedUuid<SupportBundleKind>, |
| 89 | + pub all_sleds: bool, |
| 90 | + pub sled_ids: Vec<uuid::Uuid>, |
| 91 | +} |
| 92 | + |
| 93 | +impl HostInfo { |
| 94 | + pub fn from_sitrep( |
| 95 | + sitrep_id: impl Into<DbTypedUuid<SitrepKind>>, |
| 96 | + request_id: impl Into<DbTypedUuid<SupportBundleKind>>, |
| 97 | + sleds: SledSelection, |
| 98 | + ) -> Self { |
| 99 | + let (all_sleds, sled_ids) = match sleds { |
| 100 | + SledSelection::All => (true, Vec::new()), |
| 101 | + SledSelection::Specific(ids) => ( |
| 102 | + false, |
| 103 | + ids.into_iter().map(|id| id.into_untyped_uuid()).collect(), |
| 104 | + ), |
| 105 | + }; |
| 106 | + HostInfo { |
| 107 | + sitrep_id: sitrep_id.into(), |
| 108 | + request_id: request_id.into(), |
| 109 | + all_sleds, |
| 110 | + sled_ids, |
| 111 | + } |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +impl From<HostInfo> for BundleData { |
| 116 | + fn from(row: HostInfo) -> Self { |
| 117 | + let HostInfo { sitrep_id: _, request_id: _, all_sleds, sled_ids } = row; |
| 118 | + let selection = if all_sleds { |
| 119 | + SledSelection::All |
| 120 | + } else { |
| 121 | + SledSelection::Specific( |
| 122 | + sled_ids.into_iter().map(SledUuid::from_untyped_uuid).collect(), |
| 123 | + ) |
| 124 | + }; |
| 125 | + BundleData::HostInfo(selection) |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +#[derive(Queryable, Insertable, Clone, Debug, Selectable)] |
| 130 | +#[diesel(table_name = fm_support_bundle_request_data_selection_ereports)] |
| 131 | +pub struct Ereports { |
| 132 | + pub sitrep_id: DbTypedUuid<SitrepKind>, |
| 133 | + pub request_id: DbTypedUuid<SupportBundleKind>, |
| 134 | + pub start_time: Option<DateTime<Utc>>, |
| 135 | + pub end_time: Option<DateTime<Utc>>, |
| 136 | + pub only_serials: Vec<String>, |
| 137 | + pub only_classes: Vec<String>, |
| 138 | +} |
| 139 | + |
| 140 | +impl Ereports { |
| 141 | + pub fn from_sitrep( |
| 142 | + sitrep_id: impl Into<DbTypedUuid<SitrepKind>>, |
| 143 | + request_id: impl Into<DbTypedUuid<SupportBundleKind>>, |
| 144 | + filters: EreportFilters, |
| 145 | + ) -> Self { |
| 146 | + Ereports { |
| 147 | + sitrep_id: sitrep_id.into(), |
| 148 | + request_id: request_id.into(), |
| 149 | + start_time: filters.start_time(), |
| 150 | + end_time: filters.end_time(), |
| 151 | + only_serials: filters.only_serials().to_vec(), |
| 152 | + only_classes: filters.only_classes().to_vec(), |
| 153 | + } |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +impl TryFrom<Ereports> for BundleData { |
| 158 | + type Error = omicron_common::api::external::Error; |
| 159 | + |
| 160 | + fn try_from(row: Ereports) -> Result<Self, Self::Error> { |
| 161 | + let Ereports { |
| 162 | + sitrep_id: _, |
| 163 | + request_id: _, |
| 164 | + start_time, |
| 165 | + end_time, |
| 166 | + only_serials, |
| 167 | + only_classes, |
| 168 | + } = row; |
| 169 | + EreportFiltersParams { |
| 170 | + start_time, |
| 171 | + end_time, |
| 172 | + only_serials, |
| 173 | + only_classes, |
| 174 | + } |
| 175 | + .try_into() |
| 176 | + .map(BundleData::Ereports) |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +/// Joined query result: flags + optional host_info + optional ereports. |
| 181 | +/// All fields use `#[diesel(embed)]` so no `table_name` is needed. |
| 182 | +#[derive(Queryable, Selectable)] |
| 183 | +pub struct BundleDataSelection { |
| 184 | + #[diesel(embed)] |
| 185 | + pub flags: DataSelectionFlags, |
| 186 | + #[diesel(embed)] |
| 187 | + pub host_info: Option<HostInfo>, |
| 188 | + #[diesel(embed)] |
| 189 | + pub ereports: Option<Ereports>, |
| 190 | +} |
| 191 | + |
| 192 | +impl TryFrom<BundleDataSelection> |
| 193 | + for nexus_types::support_bundle::BundleDataSelection |
| 194 | +{ |
| 195 | + type Error = omicron_common::api::external::Error; |
| 196 | + |
| 197 | + fn try_from(row: BundleDataSelection) -> Result<Self, Self::Error> { |
| 198 | + let mut selection = |
| 199 | + nexus_types::support_bundle::BundleDataSelection::new(); |
| 200 | + if row.flags.include_reconfigurator { |
| 201 | + selection.insert(BundleData::Reconfigurator); |
| 202 | + } |
| 203 | + if row.flags.include_sled_cubby_info { |
| 204 | + selection.insert(BundleData::SledCubbyInfo); |
| 205 | + } |
| 206 | + if row.flags.include_sp_dumps { |
| 207 | + selection.insert(BundleData::SpDumps); |
| 208 | + } |
| 209 | + if let Some(host_info) = row.host_info { |
| 210 | + selection.insert(host_info.into()); |
| 211 | + } |
| 212 | + if let Some(ereports) = row.ereports { |
| 213 | + selection.insert(ereports.try_into()?); |
| 214 | + } |
| 215 | + Ok(selection) |
| 216 | + } |
| 217 | +} |
0 commit comments