diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index 7496a6255fa..be07a9aff1c 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -78,10 +78,9 @@ use nexus_types::internal_api::background::ServiceFirewallRuleStatus; use nexus_types::internal_api::background::SessionCleanupStatus; use nexus_types::internal_api::background::SitrepGcStatus; use nexus_types::internal_api::background::SitrepLoadStatus; +use nexus_types::internal_api::background::SupportBundleActivationReport; use nexus_types::internal_api::background::SupportBundleCleanupReport; -use nexus_types::internal_api::background::SupportBundleCollectionReport; use nexus_types::internal_api::background::SupportBundleCollectionStepStatus; -use nexus_types::internal_api::background::SupportBundleEreportStatus; use nexus_types::internal_api::background::SwitchPortPopulatorStatus; use nexus_types::internal_api::background::SwitchPortPopulatorStatusKind; use nexus_types::internal_api::background::TrustQuorumManagerStatus; @@ -2848,7 +2847,7 @@ fn print_task_support_bundle_collector(details: &serde_json::Value) { struct SupportBundleCollectionStatus { cleanup_report: Option, cleanup_err: Option, - collection_report: Option, + collection_report: Option, collection_err: Option, } @@ -2898,15 +2897,13 @@ fn print_task_support_bundle_collector(details: &serde_json::Value) { println!(" failed to perform collection: {collection_err}"); } - if let Some(SupportBundleCollectionReport { - bundle, + if let Some(SupportBundleActivationReport { + collection, activated_in_db_ok, - mut steps, - ereports, }) = collection_report { println!(" Support Bundle Collection Report:"); - println!(" Bundle ID: {bundle}"); + println!(" Bundle ID: {}", collection.bundle); #[derive(Tabled)] #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] @@ -2917,18 +2914,19 @@ fn print_task_support_bundle_collector(details: &serde_json::Value) { status: SupportBundleCollectionStepStatus, } + let mut steps = collection.steps; steps.sort_unstable_by_key(|s| s.start); let rows: Vec = steps - .into_iter() + .iter() .map(|step| { let duration = (step.end - step.start) .to_std() .unwrap_or(Duration::from_millis(0)); StepRow { - step_name: step.name, + step_name: step.name.clone(), start_time: step.start, duration: format!("{:.3}s", duration.as_secs_f64()), - status: step.status, + status: step.status.clone(), } }) .collect(); @@ -2936,38 +2934,19 @@ fn print_task_support_bundle_collector(details: &serde_json::Value) { if !rows.is_empty() { println!("\n{}", tabled::Table::new(rows)); } - println!( - " Bundle was activated in the database: {activated_in_db_ok}" - ); - match ereports { - None => { - println!(" ereport collection was not requested"); - } - Some(SupportBundleEreportStatus { - errors, - n_collected, - n_found, - }) if !errors.is_empty() => { - println!(" ereport collection failed:"); - println!( - " total matching ereports found: {n_found}" - ); + for step in &steps { + if let Some(details) = &step.details { + println!(" {} details:", step.name); println!( - " ereports collected successfully: {n_collected}" + "{}", + erebor::Displayer::new(details) + .with_initial_indent_spaces(8) ); - println!(" errors:"); - for error in errors { - println!(" {error}"); - } - } - Some(SupportBundleEreportStatus { - n_collected, .. - }) => { - // If ereport collection succeeded, n_found should be - // equal to n_collected. - println!(" ereports collected: {n_collected}"); } } + println!( + " Bundle was activated in the database: {activated_in_db_ok}" + ); } } } diff --git a/dev-tools/omdb/src/bin/omdb/support_bundle_collect.rs b/dev-tools/omdb/src/bin/omdb/support_bundle_collect.rs index 285bd45e2b6..4d9bd588e71 100644 --- a/dev-tools/omdb/src/bin/omdb/support_bundle_collect.rs +++ b/dev-tools/omdb/src/bin/omdb/support_bundle_collect.rs @@ -211,14 +211,13 @@ impl CollectArgs { step.status, step.name, ); - } - if let Some(ereports) = &report.ereports { - eprintln!( - "ereports: {} found, {} collected, {} errors", - ereports.n_found, - ereports.n_collected, - ereports.errors.len(), - ); + if let Some(details) = &step.details { + eprintln!( + "{}", + erebor::Displayer::new(details) + .with_initial_indent_spaces(6) + ); + } } Ok(()) } diff --git a/nexus/src/app/background/tasks/support_bundle_collector.rs b/nexus/src/app/background/tasks/support_bundle_collector.rs index 5c0e29b3232..467b7a6255d 100644 --- a/nexus/src/app/background/tasks/support_bundle_collector.rs +++ b/nexus/src/app/background/tasks/support_bundle_collector.rs @@ -17,8 +17,8 @@ use nexus_db_model::SupportBundleState; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; +use nexus_types::internal_api::background::SupportBundleActivationReport; use nexus_types::internal_api::background::SupportBundleCleanupReport; -use nexus_types::internal_api::background::SupportBundleCollectionReport; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::LookupType; @@ -345,7 +345,7 @@ impl SupportBundleCollector { async fn collect_bundle( &self, opctx: &OpContext, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let pagparams = DataPageParams::max_page(); let result = self .datastore @@ -440,9 +440,9 @@ impl SupportBundleCollector { cancel_task.abort(); let _ = cancel_task.await; - let mut report = collect_result?; + let collection = collect_result?; self.store_bundle_on_sled(&bundle_log, opctx, &bundle, dir).await?; - if let Err(err) = self + let activated_in_db_ok = match self .datastore .support_bundle_update( &opctx, @@ -451,15 +451,17 @@ impl SupportBundleCollector { ) .await { - if matches!(err, Error::InvalidRequest { .. }) { + Ok(()) => true, + Err(err) if matches!(err, Error::InvalidRequest { .. }) => { info!( &opctx.log, "SupportBundleCollector: Concurrent state change activating bundle"; "bundle" => %bundle.id, "err" => ?err, ); - return Ok(Some(report)); - } else { + false + } + Err(err) => { warn!( &opctx.log, "SupportBundleCollector: Unexpected error activating bundle"; @@ -468,9 +470,11 @@ impl SupportBundleCollector { ); anyhow::bail!("failed to activate bundle: {:#}", err); } - } - report.activated_in_db_ok = true; - Ok(Some(report)) + }; + Ok(Some(SupportBundleActivationReport { + collection, + activated_in_db_ok, + })) } // Zip the collected bundle directory and stream it to the sled-agent @@ -718,6 +722,7 @@ mod test { use nexus_types::fm::ereport::{EreportData, EreportId, Reporter}; use nexus_types::identity::Asset; use nexus_types::internal_api::background::SupportBundleCollectionStep; + use nexus_types::internal_api::background::SupportBundleCollectionStepStatus; use nexus_types::internal_api::background::SupportBundleEreportStatus; use nexus_types::inventory::SpType; use nexus_types::support_bundle::BundleDataSelection; @@ -1110,10 +1115,10 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Verify that we spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS) ); @@ -1122,13 +1127,28 @@ mod test { .contains(&SupportBundleCollectionStep::STEP_SPAWN_SP_DUMPS) ); assert!(report.activated_in_db_ok); + let ereport_step = report + .collection + .steps + .iter() + .find(|s| s.name == SupportBundleCollectionStep::STEP_EREPORTS) + .expect("should have ereports step"); + assert_eq!(ereport_step.status, SupportBundleCollectionStepStatus::Ok); + let ereport_details: SupportBundleEreportStatus = + serde_json::from_value( + ereport_step + .details + .clone() + .expect("ereports step should have details"), + ) + .expect("ereports step details should deserialize"); assert_eq!( - report.ereports, - Some(SupportBundleEreportStatus { + ereport_details, + SupportBundleEreportStatus { n_collected: 7, n_found: 7, - errors: Vec::new() - }) + errors: Vec::new(), + } ); let observed_bundle = datastore @@ -1245,7 +1265,7 @@ mod test { // Verify we have the same number of events as steps in the report assert_eq!( trace.trace_events.len(), - report.steps.len(), + report.collection.steps.len(), "Number of events should match number of steps" ); @@ -1253,7 +1273,7 @@ mod test { let trace_names: std::collections::HashSet<_> = trace.trace_events.iter().map(|e| e.name.as_str()).collect(); let report_names: std::collections::HashSet<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert_eq!( trace_names, report_names, "Trace event names should match report step names" @@ -1308,10 +1328,10 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Verify that we spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS) ); @@ -1411,10 +1431,10 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle1.id.into()); + assert_eq!(report.collection.bundle, bundle1.id.into()); // Verify that we spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS) ); @@ -1442,10 +1462,10 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle2.id.into()); + assert_eq!(report.collection.bundle, bundle2.id.into()); // Verify that we spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS) ); @@ -1583,10 +1603,10 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Verify that we spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS) ); @@ -1745,7 +1765,7 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Mark the bundle as "failing" - this should be triggered // automatically by the blueprint executor if the corresponding @@ -1833,7 +1853,7 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Mark the bundle as "failing" - this should be triggered // automatically by the blueprint executor if the corresponding @@ -1922,7 +1942,7 @@ mod test { .await .expect("Collection should have succeeded under test") .expect("Collecting the bundle should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); // Verify bundle is active let observed_bundle = datastore @@ -1984,8 +2004,6 @@ mod test { async fn test_per_bundle_data_selection( cptestctx: &ControlPlaneTestContext, ) { - use nexus_types::internal_api::background::SupportBundleCollectionStepStatus; - let nexus = &cptestctx.server.server_context().nexus; let datastore = nexus.datastore(); let resolver = nexus.resolver(); @@ -2027,11 +2045,12 @@ mod test { .await .expect("Collection should have succeeded") .expect("Should have generated a report"); - assert_eq!(report.bundle, bundle.id.into()); + assert_eq!(report.collection.bundle, bundle.id.into()); assert!(report.activated_in_db_ok); // Reconfigurator state should have run successfully. let reconfig_step = report + .collection .steps .iter() .find(|s| { @@ -2042,6 +2061,7 @@ mod test { // Ereports should be skipped since we didn't request them. let ereport_step = report + .collection .steps .iter() .find(|s| s.name == SupportBundleCollectionStep::STEP_EREPORTS) @@ -2053,6 +2073,7 @@ mod test { // Sled cubby info should be skipped. let cubby_step = report + .collection .steps .iter() .find(|s| { @@ -2066,6 +2087,7 @@ mod test { // SP dumps should be skipped. let sp_step = report + .collection .steps .iter() .find(|s| { @@ -2076,6 +2098,7 @@ mod test { // Sled queries should be skipped. let sled_step = report + .collection .steps .iter() .find(|s| s.name == SupportBundleCollectionStep::STEP_SPAWN_SLEDS) diff --git a/nexus/tests/integration_tests/support_bundles.rs b/nexus/tests/integration_tests/support_bundles.rs index 4ddd5365e16..4a2070fb3bd 100644 --- a/nexus/tests/integration_tests/support_bundles.rs +++ b/nexus/tests/integration_tests/support_bundles.rs @@ -21,9 +21,10 @@ use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::support_bundle::SupportBundleInfo; use nexus_types::external_api::support_bundle::SupportBundleState; +use nexus_types::internal_api::background::SupportBundleActivationReport; use nexus_types::internal_api::background::SupportBundleCleanupReport; -use nexus_types::internal_api::background::SupportBundleCollectionReport; use nexus_types::internal_api::background::SupportBundleCollectionStep; +use nexus_types::internal_api::background::SupportBundleCollectionStepStatus; use nexus_types::internal_api::background::SupportBundleEreportStatus; use omicron_common::api::external::LookupType; use omicron_uuid_kinds::SupportBundleUuid; @@ -335,7 +336,7 @@ struct TaskOutput { cleanup_err: Option, collection_err: Option, cleanup_report: Option, - collection_report: Option, + collection_report: Option, } async fn activate_bundle_collection_background_task( @@ -357,6 +358,23 @@ async fn activate_bundle_collection_background_task( ) } +fn assert_ereport_details_eq( + collection: &nexus_types::internal_api::background::SupportBundleCollectionReport, + expected: SupportBundleEreportStatus, +) { + let step = collection + .steps + .iter() + .find(|s| s.name == SupportBundleCollectionStep::STEP_EREPORTS) + .expect("should have ereports step"); + assert_eq!(step.status, SupportBundleCollectionStepStatus::Ok); + let details: SupportBundleEreportStatus = serde_json::from_value( + step.details.clone().expect("ereports step should have details"), + ) + .expect("ereports step details should deserialize"); + assert_eq!(details, expected); +} + // Test accessing support bundle interfaces when the bundle does not exist, // and when no U.2s exist on which to store support bundles. #[nexus_test] @@ -494,25 +512,28 @@ async fn test_support_bundle_lifecycle(cptestctx: &ControlPlaneTestContext) { ); let report = output.collection_report.as_ref().expect("Missing report"); - assert_eq!(report.bundle, bundle.id); + assert_eq!(report.collection.bundle, bundle.id); assert!(report.activated_in_db_ok); // This assertion expects 0 ereports in the database. This depends on // the sp_ereport_ingester background task being disabled in the test // config (config.test.toml). If that task runs before bundle collection, // it will ingest ereports from the simulated SPs into the database, // causing this assertion to fail nondeterministically. - assert_eq!( - report.ereports, - Some(SupportBundleEreportStatus { + assert_ereport_details_eq( + &report.collection, + SupportBundleEreportStatus { n_collected: 0, n_found: 0, - errors: Vec::new() - }) + errors: Vec::new(), + }, ); // Verify that steps were recorded with reasonable timing data - assert!(!report.steps.is_empty(), "Should have recorded some steps"); - for step in &report.steps { + assert!( + !report.collection.steps.is_empty(), + "Should have recorded some steps" + ); + for step in &report.collection.steps { assert!( step.end >= step.start, "Step '{}' end time should be >= start time", @@ -522,7 +543,7 @@ async fn test_support_bundle_lifecycle(cptestctx: &ControlPlaneTestContext) { // Verify that we successfully spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS), "Should have attempted to list in-service sleds" @@ -542,6 +563,7 @@ async fn test_support_bundle_lifecycle(cptestctx: &ControlPlaneTestContext) { assert_eq!(names.next(), Some("bundle_id.txt")); assert_eq!(names.next(), Some("meta/")); assert_eq!(names.next(), Some("meta/reason_for_creation.txt")); + assert_eq!(names.next(), Some("meta/report.json")); assert_eq!(names.next(), Some("meta/trace.json")); assert_eq!(names.next(), Some("rack/")); assert!(names.any(|n| n == "sp_task_dumps/")); @@ -624,22 +646,25 @@ async fn test_support_bundle_range_requests( let output = activate_bundle_collection_background_task(&cptestctx).await; assert_eq!(output.collection_err, None); let report = output.collection_report.as_ref().expect("Missing report"); - assert_eq!(report.bundle, bundle.id); + assert_eq!(report.collection.bundle, bundle.id); assert!(report.activated_in_db_ok); // See comment above — this depends on sp_ereport_ingester being disabled // in config.test.toml. - assert_eq!( - report.ereports, - Some(SupportBundleEreportStatus { + assert_ereport_details_eq( + &report.collection, + SupportBundleEreportStatus { n_collected: 0, n_found: 0, - errors: Vec::new() - }) + errors: Vec::new(), + }, ); // Verify that steps were recorded with reasonable timing data - assert!(!report.steps.is_empty(), "Should have recorded some steps"); - for step in &report.steps { + assert!( + !report.collection.steps.is_empty(), + "Should have recorded some steps" + ); + for step in &report.collection.steps { assert!( step.end >= step.start, "Step '{}' end time should be >= start time", @@ -649,7 +674,7 @@ async fn test_support_bundle_range_requests( // Verify that we successfully spawned steps to query sleds and SPs let step_names: Vec<_> = - report.steps.iter().map(|s| s.name.as_str()).collect(); + report.collection.steps.iter().map(|s| s.name.as_str()).collect(); assert!( step_names.contains(&SupportBundleCollectionStep::STEP_SPAWN_SLEDS), "Should have attempted to list in-service sleds" diff --git a/nexus/types/src/internal_api/background.rs b/nexus/types/src/internal_api/background.rs index d4fd3840c15..d04a863b683 100644 --- a/nexus/types/src/internal_api/background.rs +++ b/nexus/types/src/internal_api/background.rs @@ -271,21 +271,31 @@ pub struct SupportBundleCleanupReport { /// Identifies what we could or could not store within a support bundle. /// -/// This struct will get emitted as part of the background task infrastructure. +/// This struct describes facts known by the end of bundle collection: the +/// set of steps that ran and what they produced. Post-collection facts +/// (such as whether the bundle was successfully activated in the database) +/// live on [`SupportBundleActivationReport`], which wraps this struct. #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SupportBundleCollectionReport { pub bundle: SupportBundleUuid, - /// True iff the bundle was successfully made 'active' in the database. - pub activated_in_db_ok: bool, - /// All steps taken, alongside their timing information, when collecting the /// bundle. pub steps: Vec, +} + +/// Pairs a [`SupportBundleCollectionReport`] with facts known only after +/// collection finishes, such as whether the bundle was successfully made +/// 'active' in the database. +/// +/// This is what the Nexus support-bundle-collector background task emits as +/// its `collection_report`. +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct SupportBundleActivationReport { + pub collection: SupportBundleCollectionReport, - /// Status of ereport collection, or `None` if no ereports were requested - /// for this support bundle. - pub ereports: Option, + /// True iff the bundle was successfully made 'active' in the database. + pub activated_in_db_ok: bool, } #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] @@ -294,6 +304,13 @@ pub struct SupportBundleCollectionStep { pub start: DateTime, pub end: DateTime, pub status: SupportBundleCollectionStepStatus, + + /// Optional structured payload from the step. Steps that have only + /// pass/fail semantics leave this `None`; steps that can partially + /// succeed or want to surface counts/errors serialize their detail + /// struct here. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub details: Option, } impl SupportBundleCollectionStep { @@ -310,7 +327,7 @@ impl SupportBundleCollectionStep { pub const STEP_SPAWN_SLEDS: &'static str = "spawn steps to query all sleds"; } -#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum SupportBundleCollectionStepStatus { Ok, Skipped, @@ -346,12 +363,7 @@ pub struct SupportBundleEreportStatus { impl SupportBundleCollectionReport { pub fn new(bundle: SupportBundleUuid) -> Self { - Self { - bundle, - activated_in_db_ok: false, - steps: vec![], - ereports: None, - } + Self { bundle, steps: vec![] } } } diff --git a/support-bundle-collection/src/collection.rs b/support-bundle-collection/src/collection.rs index 818efd8d066..979d44e623c 100644 --- a/support-bundle-collection/src/collection.rs +++ b/support-bundle-collection/src/collection.rs @@ -226,7 +226,7 @@ impl BundleCollection { // Only finish if we've exhausted all possible steps and joined // all spawned work. if steps.is_empty() { - // Write trace file before returning + // Write trace and report files before returning. if let Err(err) = self.write_trace_file(output, &report).await { warn!( self.log, @@ -234,6 +234,14 @@ impl BundleCollection { "error" => ?err ); } + if let Err(err) = self.write_report_file(output, &report).await + { + warn!( + self.log, + "Failed to write report file"; + "error" => ?err + ); + } return report; } } @@ -325,6 +333,37 @@ impl BundleCollection { Ok(()) } + // Write the collection report as JSON into the bundle so anyone who + // unzips it later can see what was collected, including any per-step + // partial-success details. + async fn write_report_file( + &self, + output: &Utf8TempDir, + report: &SupportBundleCollectionReport, + ) -> anyhow::Result<()> { + let meta_dir = output.path().join("meta"); + tokio::fs::create_dir_all(&meta_dir).await.with_context(|| { + format!("Failed to create meta directory {meta_dir}") + })?; + + let report_path = meta_dir.join("report.json"); + let report_content = serde_json::to_string_pretty(report) + .context("Failed to serialize collection report")?; + + tokio::fs::write(&report_path, report_content).await.with_context( + || format!("Failed to write report file to {report_path}"), + )?; + + info!( + self.log, + "Wrote report file"; + "path" => %report_path, + "num_steps" => report.steps.len(), + ); + + Ok(()) + } + // Perform the work of collecting the support bundle into a temporary // directory. // diff --git a/support-bundle-collection/src/step.rs b/support-bundle-collection/src/step.rs index 576244d6580..49cb753c633 100644 --- a/support-bundle-collection/src/step.rs +++ b/support-bundle-collection/src/step.rs @@ -13,7 +13,6 @@ use futures::future::BoxFuture; use nexus_types::internal_api::background::SupportBundleCollectionReport; use nexus_types::internal_api::background::SupportBundleCollectionStep; use nexus_types::internal_api::background::SupportBundleCollectionStepStatus; -use nexus_types::internal_api::background::SupportBundleEreportStatus; use slog::warn; use slog_error_chain::InlineErrorChain; use std::sync::Arc; @@ -88,13 +87,14 @@ impl CompletedCollectionStep { ) { use SupportBundleCollectionStepStatus as Status; + let mut details = None; let status = match self.output { CollectionStepOutput::Skipped => Status::Skipped, CollectionStepOutput::Failed(err) => { Status::Failed(err.to_string()) } - CollectionStepOutput::Ereports(status) => { - report.ereports = Some(status); + CollectionStepOutput::Details(value) => { + details = Some(value); Status::Ok } CollectionStepOutput::Spawn { extra_steps } => { @@ -110,6 +110,7 @@ impl CompletedCollectionStep { start: self.start, end: self.end, status, + details, }; report.steps.push(step); } @@ -122,7 +123,10 @@ pub enum CollectionStepOutput { // // It may have still saved a partial set of data to the bundle. Failed(anyhow::Error), - Ereports(SupportBundleEreportStatus), + // The step completed successfully and wants to surface a structured + // summary in its report entry (counts, partial-success info, errors, + // etc.). Use any `Serialize` type that fits the step. + Details(serde_json::Value), // The step spawned additional steps to execute Spawn { extra_steps: Vec }, // The step completed with nothing to report, and no follow-up steps diff --git a/support-bundle-collection/src/steps/ereports.rs b/support-bundle-collection/src/steps/ereports.rs index 7b225b19175..82d8ec27641 100644 --- a/support-bundle-collection/src/steps/ereports.rs +++ b/support-bundle-collection/src/steps/ereports.rs @@ -61,7 +61,9 @@ pub async fn collect( status.errors.push(InlineErrorChain::new(err.as_ref()).to_string()); }; - Ok(CollectionStepOutput::Ereports(status)) + let details = serde_json::to_value(&status) + .context("failed to serialize ereport collection status")?; + Ok(CollectionStepOutput::Details(details)) } // Save ereports to disk, paginating through the database.