Skip to content
Open
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
36 changes: 36 additions & 0 deletions dev-tools/omdb/src/bin/omdb/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

mod quiesce;
mod reconfigurator_config;
mod support_bundle_config;
mod update_status;

use crate::Omdb;
Expand Down Expand Up @@ -70,6 +71,7 @@ use nexus_types::internal_api::background::RegionSnapshotReplacementStartStatus;
use nexus_types::internal_api::background::RegionSnapshotReplacementStepStatus;
use nexus_types::internal_api::background::SitrepGcStatus;
use nexus_types::internal_api::background::SitrepLoadStatus;
use nexus_types::internal_api::background::SupportBundleAutoDeletionReport;
use nexus_types::internal_api::background::SupportBundleCleanupReport;
use nexus_types::internal_api::background::SupportBundleCollectionReport;
use nexus_types::internal_api::background::SupportBundleCollectionStepStatus;
Expand Down Expand Up @@ -101,6 +103,8 @@ use std::os::unix::fs::PermissionsExt;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use support_bundle_config::SupportBundleConfigArgs;
use support_bundle_config::cmd_nexus_support_bundle_config;
use support_bundle_viewer::LocalFileAccess;
use support_bundle_viewer::SupportBundleAccessor;
use tabled::Tabled;
Expand Down Expand Up @@ -160,6 +164,8 @@ enum NexusCommands {
ReconfiguratorConfig(ReconfiguratorConfigArgs),
/// view sagas, create and complete demo sagas
Sagas(SagasArgs),
/// interact with support bundle auto-deletion config
SupportBundleConfig(SupportBundleConfigArgs),
/// interact with sleds
Sleds(SledsArgs),
/// interact with support bundles
Expand Down Expand Up @@ -783,6 +789,10 @@ impl NexusArgs {
cmd_nexus_reconfigurator_config(&omdb, &client, args).await
}

NexusCommands::SupportBundleConfig(args) => {
cmd_nexus_support_bundle_config(&omdb, log, args).await
}

NexusCommands::Sagas(SagasArgs { command }) => {
if self.nexus_internal_url.is_none() {
eprintln!(
Expand Down Expand Up @@ -2561,6 +2571,7 @@ fn print_task_service_firewall_rule_propagation(details: &serde_json::Value) {
fn print_task_support_bundle_collector(details: &serde_json::Value) {
#[derive(Deserialize)]
struct SupportBundleCollectionStatus {
auto_deletion_report: Option<SupportBundleAutoDeletionReport>,
cleanup_report: Option<SupportBundleCleanupReport>,
cleanup_err: Option<String>,
collection_report: Option<SupportBundleCollectionReport>,
Expand All @@ -2575,11 +2586,36 @@ fn print_task_support_bundle_collector(details: &serde_json::Value) {
error, details
),
Ok(SupportBundleCollectionStatus {
auto_deletion_report,
cleanup_report,
cleanup_err,
collection_report,
collection_err,
}) => {
// Print auto-deletion report first (since it runs first)
if let Some(SupportBundleAutoDeletionReport {
bundles_marked_for_deletion,
free_datasets,
total_datasets,
active_bundles,
errors,
}) = auto_deletion_report
{
println!(" Support Bundle Auto-Deletion Report:");
println!(" Total debug datasets: {total_datasets}");
println!(" Active bundles: {active_bundles}");
println!(" Free datasets: {free_datasets}");
println!(
" Bundles marked for deletion: {bundles_marked_for_deletion}"
);
if !errors.is_empty() {
println!(" Errors:");
for error in errors {
println!(" {error}");
}
}
}

if let Some(cleanup_err) = cleanup_err {
println!(" failed to perform cleanup: {cleanup_err}");
}
Expand Down
150 changes: 150 additions & 0 deletions dev-tools/omdb/src/bin/omdb/nexus/support_bundle_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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/.

//! omdb commands for support bundle auto-deletion configuration

use crate::Omdb;
use crate::check_allow_destructive::DestructiveOperationToken;
use crate::db::DbUrlOptions;
use anyhow::Context;
use clap::Args;
use clap::Subcommand;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::DataStore;
use std::sync::Arc;

#[derive(Debug, Args)]
pub struct SupportBundleConfigArgs {
#[clap(flatten)]
db_url_opts: DbUrlOptions,

#[command(subcommand)]
command: SupportBundleConfigCommands,
}

#[derive(Debug, Subcommand)]
pub enum SupportBundleConfigCommands {
/// Show current support bundle auto-deletion config
Show,

/// Set support bundle auto-deletion config
Set(SupportBundleConfigSetArgs),
}

#[derive(Debug, Clone, Args)]
pub struct SupportBundleConfigSetArgs {
/// Target percentage of datasets to keep free (0-100)
#[clap(long)]
target_free_percent: Option<u8>,

/// Minimum percentage of datasets to keep as bundles (0-100)
#[clap(long)]
min_keep_percent: Option<u8>,
}

pub async fn cmd_nexus_support_bundle_config(
omdb: &Omdb,
log: &slog::Logger,
args: &SupportBundleConfigArgs,
) -> Result<(), anyhow::Error> {
let datastore = args.db_url_opts.connect(omdb, log).await?;
let opctx = OpContext::for_tests(log.clone(), datastore.clone());

let result = match &args.command {
SupportBundleConfigCommands::Show => {
support_bundle_config_show(&opctx, &datastore).await
}
SupportBundleConfigCommands::Set(set_args) => {
let token = omdb.check_allow_destructive()?;
support_bundle_config_set(&opctx, &datastore, set_args, token).await
}
};

datastore.terminate().await;
result
}

async fn support_bundle_config_show(
opctx: &OpContext,
datastore: &Arc<DataStore>,
) -> Result<(), anyhow::Error> {
let config = datastore
.support_bundle_config_get(opctx)
.await
.context("failed to get support bundle config")?;

println!("Support Bundle Auto-Deletion Config:");
println!(
" Target free datasets: {}% (CEIL calculation)",
config.target_free_percent
);
println!(
" Minimum bundles to keep: {}% (CEIL calculation)",
config.min_keep_percent
);
println!(" Last modified: {}", config.time_modified);

Ok(())
}

async fn support_bundle_config_set(
opctx: &OpContext,
datastore: &Arc<DataStore>,
args: &SupportBundleConfigSetArgs,
_destruction_token: DestructiveOperationToken,
) -> Result<(), anyhow::Error> {
// Get current config
let current = datastore
.support_bundle_config_get(opctx)
.await
.context("failed to get current support bundle config")?;

// Apply changes, using current values as defaults
let new_target_free =
args.target_free_percent.unwrap_or(current.target_free_percent as u8);
let new_min_keep =
args.min_keep_percent.unwrap_or(current.min_keep_percent as u8);

// Check if anything changed
if i64::from(new_target_free) == current.target_free_percent
&& i64::from(new_min_keep) == current.min_keep_percent
{
println!("No changes to current config:");
println!(
" Target free datasets: {}% (CEIL calculation)",
current.target_free_percent
);
println!(
" Minimum bundles to keep: {}% (CEIL calculation)",
current.min_keep_percent
);
return Ok(());
}

// Apply the update
datastore
.support_bundle_config_set(opctx, new_target_free, new_min_keep)
.await
.context("failed to set support bundle config")?;

println!("Support bundle config updated:");
if i64::from(new_target_free) != current.target_free_percent {
println!(
" Target free datasets: {}% -> {}%",
current.target_free_percent, new_target_free
);
} else {
println!(" Target free datasets: {}% (unchanged)", new_target_free);
}
if i64::from(new_min_keep) != current.min_keep_percent {
println!(
" Minimum bundles to keep: {}% -> {}%",
current.min_keep_percent, new_min_keep
);
} else {
println!(" Minimum bundles to keep: {}% (unchanged)", new_min_keep);
}

Ok(())
}
10 changes: 10 additions & 0 deletions dev-tools/omdb/tests/successes.out
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,11 @@ task: "support_bundle_collector"
configured period: every <REDACTED_DURATION>days <REDACTED_DURATION>h <REDACTED_DURATION>m <REDACTED_DURATION>s
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
Support Bundle Auto-Deletion Report:
Total debug datasets: 0
Active bundles: 0
Free datasets: 0
Bundles marked for deletion: 0
Support Bundle Cleanup Report:
Bundles deleted from sleds: 0
Bundles not found on sleds: 0
Expand Down Expand Up @@ -1422,6 +1427,11 @@ task: "support_bundle_collector"
configured period: every <REDACTED_DURATION>days <REDACTED_DURATION>h <REDACTED_DURATION>m <REDACTED_DURATION>s
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
Support Bundle Auto-Deletion Report:
Total debug datasets: 0
Active bundles: 0
Free datasets: 0
Bundles marked for deletion: 0
Support Bundle Cleanup Report:
Bundles deleted from sleds: 0
Bundles not found on sleds: 0
Expand Down
1 change: 1 addition & 0 deletions dev-tools/omdb/tests/usage_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ Commands:
quiesce view or modify the quiesce status
reconfigurator-config interact with reconfigurator config
sagas view sagas, create and complete demo sagas
support-bundle-config interact with support bundle auto-deletion config
sleds interact with sleds
support-bundles interact with support bundles [aliases: sb]
update-status show running artifact versions
Expand Down
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(225, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(226, 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(226, "bundle-state-index"),
KnownVersion::new(225, "dual-stack-ephemeral-ip"),
KnownVersion::new(224, "add-external-subnets"),
KnownVersion::new(223, "ip-pool-range-by-pool-id-index"),
Expand Down
16 changes: 16 additions & 0 deletions nexus/db-model/src/support_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use super::impl_enum_type;
use crate::typed_uuid::DbTypedUuid;
use nexus_db_schema::schema::support_bundle;
use nexus_db_schema::schema::support_bundle_config;

use chrono::{DateTime, Utc};
use nexus_types::external_api::shared::SupportBundleInfo as SupportBundleView;
Expand Down Expand Up @@ -136,3 +137,18 @@ impl From<SupportBundle> for SupportBundleView {
}
}
}

/// Configuration for automatic support bundle deletion.
///
/// This table uses a singleton pattern - exactly one row exists, created by
/// the schema migration. The row is only updated, never inserted or deleted.
#[derive(Clone, Debug, Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = support_bundle_config)]
pub struct SupportBundleConfig {
pub singleton: bool,
/// Percentage (0-100) of total datasets to keep free for new allocations.
pub target_free_percent: i64,
/// Percentage (0-100) of total datasets to retain as bundles (minimum).
pub min_keep_percent: i64,
pub time_modified: DateTime<Utc>,
}
Loading
Loading