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
28 changes: 25 additions & 3 deletions dev-tools/omdb/src/bin/omdb/reconfigurator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ enum ReconfiguratorCommands {

#[derive(Debug, Args, Clone)]
struct ExportArgs {
/// maximum number of blueprints to save
#[clap(long, default_value_t = 1000)]
nmax_blueprints: usize,

/// where to save the output
output_file: Utf8PathBuf,
}
Expand Down Expand Up @@ -147,21 +151,30 @@ async fn cmd_reconfigurator_export(
) -> anyhow::Result<UnstableReconfiguratorState> {
// See Nexus::blueprint_planning_context().
eprint!("assembling reconfigurator state ... ");
let limit = export_args.nmax_blueprints;
let state = nexus_reconfigurator_preparation::reconfigurator_state_load(
opctx, datastore,
opctx, datastore, limit,
)
.await?;
eprintln!("done");

if state.blueprints.len() >= limit {
eprintln!(
"warning: reached limit of {limit} while fetching blueprints"
);
eprintln!("warning: saving only the most recent {limit}");
}

let output_path = &export_args.output_file;
eprint!("saving to {} ... ", output_path);
let file = std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&output_path)
.with_context(|| format!("open {:?}", output_path))?;
serde_json::to_writer_pretty(&file, &state)
.with_context(|| format!("write {:?}", output_path))?;
eprintln!("wrote {}", output_path);
eprintln!("done");
Ok(state)
}

Expand Down Expand Up @@ -199,7 +212,7 @@ async fn cmd_reconfigurator_archive(

let mut ndeleted = 0;

eprintln!("removing non-target blueprints ...");
eprintln!("removing saved, non-target blueprints ...");
for blueprint in &saved_state.blueprints {
if blueprint.id == target_blueprint_id {
continue;
Expand Down Expand Up @@ -245,6 +258,15 @@ async fn cmd_reconfigurator_archive(
eprintln!("done ({ndeleted} blueprint{plural} deleted)",);
}

if saved_state.blueprints.len() >= archive_args.nmax_blueprints {
eprintln!(
"warning: Only tried deleting the most recent {} blueprints\n\
warning: because that's all that was fetched and saved.\n\
warning: You may want to run this tool again to archive more.",
saved_state.blueprints.len(),
);
}

Ok(())
}

Expand Down
23 changes: 23 additions & 0 deletions nexus/db-queries/src/db/datastore/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4078,6 +4078,29 @@ impl DataStore {
internal_dns_generation_status,
})
}

pub async fn inventory_collections_latest(
&self,
opctx: &OpContext,
count: u8,
) -> anyhow::Result<Vec<InvCollection>> {
let limit: i64 = i64::from(count);
let conn = self
.pool_connection_authorized(opctx)
.await
.context("getting connection")?;

use nexus_db_schema::schema::inv_collection::dsl;
let collections = dsl::inv_collection
.select(InvCollection::as_select())
.order_by(dsl::time_started.desc())
.limit(limit)
.load_async(&*conn)
.await
.context("failed to list collections")?;

Ok(collections)
}
}

#[derive(Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async fn test_blueprint_edit(cptestctx: &ControlPlaneTestContext) {

// Assemble state that we can load into reconfigurator-cli.
let state1 = nexus_reconfigurator_preparation::reconfigurator_state_load(
&opctx, datastore,
&opctx, datastore, 20,
)
.await
.expect("failed to assemble reconfigurator state");
Expand Down
21 changes: 17 additions & 4 deletions nexus/reconfigurator/preparation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use nexus_db_model::Generation;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::DataStore;
use nexus_db_queries::db::datastore::DataStoreDnsTest;
use nexus_db_queries::db::datastore::DataStoreInventoryTest;
use nexus_db_queries::db::datastore::Discoverability;
use nexus_db_queries::db::datastore::SQL_BATCH_SIZE;
use nexus_db_queries::db::pagination::Paginator;
Expand Down Expand Up @@ -57,6 +56,7 @@ use omicron_uuid_kinds::OmicronZoneUuid;
use slog::Logger;
use slog::error;
use slog_error_chain::InlineErrorChain;
use std::cmp::Reverse;
use std::collections::BTreeMap;
use std::collections::BTreeSet;

Expand Down Expand Up @@ -406,12 +406,13 @@ async fn fetch_all_service_ip_pool_ranges(
Ok(ranges)
}

/// Loads state for import into `reconfigurator-cli`
/// Loads state for debugging or import into `reconfigurator-cli`
///
/// This is only to be used in omdb or tests.
/// This is used in omdb, tests, and in Nexus to collect support bundles
pub async fn reconfigurator_state_load(
opctx: &OpContext,
datastore: &DataStore,
nmax_blueprints: usize,
) -> Result<UnstableReconfiguratorState, anyhow::Error> {
opctx.check_complex_operations_allowed()?;
let planner_config = datastore
Expand All @@ -420,8 +421,11 @@ pub async fn reconfigurator_state_load(
.map_or_else(PlannerConfig::default, |c| c.config.planner_config);
let planning_input =
PlanningInputFromDb::assemble(opctx, datastore, planner_config).await?;

// We'll grab the most recent several inventory collections.
const NCOLLECTIONS: u8 = 5;
let collection_ids = datastore
.inventory_collections()
.inventory_collections_latest(opctx, NCOLLECTIONS)
.await
.context("listing collections")?
.into_iter()
Expand All @@ -439,11 +443,13 @@ pub async fn reconfigurator_state_load(
.collect::<Vec<Collection>>()
.await;

// Grab the latest target blueprint.
let target_blueprint = datastore
.blueprint_target_get_current(opctx)
.await
.context("failed to read current target blueprint")?;

// Paginate through the list of all blueprints.
let mut blueprint_ids = Vec::new();
let mut paginator = Paginator::new(
SQL_BATCH_SIZE,
Expand All @@ -460,6 +466,13 @@ pub async fn reconfigurator_state_load(
blueprint_ids.extend(batch.into_iter());
}

// We'll only grab the most recent blueprints that fit within the limit that
// we were given. This is a heuristic intended to grab what's most likely
// to be useful even when the system has a large number of blueprints. But
// the intent is that callers provide a limit that should be large enough to
// cover everything.
blueprint_ids.sort_by_key(|bpm| Reverse(bpm.time_created));
let blueprint_ids = blueprint_ids.into_iter().take(nmax_blueprints);
let blueprints = futures::stream::iter(blueprint_ids)
.filter_map(|bpm| async move {
let blueprint_id = bpm.id.into_untyped_uuid();
Expand Down
Loading