Skip to content
Merged
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
11 changes: 11 additions & 0 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ use clap::ValueEnum;
use clap::builder::PossibleValue;
use clap::builder::PossibleValuesParser;
use clap::builder::TypedValueParser;
use db_metadata::DbMetadataArgs;
use db_metadata::DbMetadataCommands;
use db_metadata::cmd_db_metadata_list_nexus;
use diesel::BoolExpressionMethods;
use diesel::ExpressionMethods;
use diesel::JoinOnDsl;
Expand Down Expand Up @@ -170,6 +173,7 @@ use tabled::Tabled;
use uuid::Uuid;

mod alert;
mod db_metadata;
mod ereport;
mod saga;
mod user_data_export;
Expand Down Expand Up @@ -338,6 +342,8 @@ pub struct DbFetchOptions {
/// Subcommands that query or update the database
#[derive(Debug, Subcommand, Clone)]
enum DbCommands {
/// Commands for database metadata
DbMetadata(DbMetadataArgs),
/// Commands relevant to Crucible datasets
CrucibleDataset(CrucibleDatasetArgs),
/// Print any Crucible resources that are located on expunged physical disks
Expand Down Expand Up @@ -1128,6 +1134,11 @@ impl DbArgs {
self.db_url_opts.with_datastore(omdb, log, |opctx, datastore| {
async move {
match &self.command {
DbCommands::DbMetadata(DbMetadataArgs {
command: DbMetadataCommands::ListNexus,
}) => {
cmd_db_metadata_list_nexus(&opctx, &datastore).await
}
DbCommands::CrucibleDataset(CrucibleDatasetArgs {
command: CrucibleDatasetCommands::List,
}) => {
Expand Down
154 changes: 154 additions & 0 deletions dev-tools/omdb/src/bin/omdb/db/db_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 db db_metadata` subcommands

use super::display_option_blank;
use anyhow::Context;
use clap::Args;
use clap::Subcommand;
use nexus_db_model::DbMetadataNexusState;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::DataStore;
use nexus_types::deployment::Blueprint;
use nexus_types::deployment::BlueprintZoneDisposition;
use omicron_common::api::external::Generation;
use omicron_uuid_kinds::BlueprintUuid;
use omicron_uuid_kinds::OmicronZoneUuid;
use std::collections::BTreeMap;
use tabled::Tabled;

#[derive(Debug, Args, Clone)]
pub struct DbMetadataArgs {
#[command(subcommand)]
pub command: DbMetadataCommands,
}

#[derive(Debug, Subcommand, Clone)]
pub enum DbMetadataCommands {
#[clap(alias = "ls-nexus")]
ListNexus,
}

// DB Metadata

#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct DbMetadataNexusRow {
id: OmicronZoneUuid,
#[tabled(display_with = "display_option_blank")]
last_drained_blueprint: Option<BlueprintUuid>,

// Identifies the state we observe in the database
state: String,

// Identifies the state this Nexus is trying to achieve, based on the target
// blueprint, if it's different from the current state
#[tabled(display_with = "display_option_blank")]
transitioning_to: Option<String>,
}

fn get_intended_nexus_state(
bp_nexus_generation: Generation,
bp_nexus_generation_by_zone: &BTreeMap<OmicronZoneUuid, Generation>,
id: OmicronZoneUuid,
) -> Option<DbMetadataNexusState> {
let Some(gen) = bp_nexus_generation_by_zone.get(&id) else {
return None;
};

Some(if *gen < bp_nexus_generation {
// This Nexus is either quiescing, or has already quiesced
DbMetadataNexusState::Quiesced
} else if *gen == bp_nexus_generation {
// This Nexus is either active, or will become active once
// the prior generation has quiesced
DbMetadataNexusState::Active
} else {
// This Nexus is not ready to be run yet
DbMetadataNexusState::NotYet
})
}

fn get_nexus_state_transition(
observed: DbMetadataNexusState,
intended: Option<DbMetadataNexusState>,
) -> Option<String> {
match (observed, intended) {
(observed, Some(intended)) if observed == intended => None,
(_, Some(intended)) => Some(intended.to_string()),
(_, None) => Some("Unknown".to_string()),
}
}

async fn get_db_metadata_nexus_rows(
opctx: &OpContext,
datastore: &DataStore,
blueprint: &Blueprint,
) -> Result<Vec<DbMetadataNexusRow>, anyhow::Error> {
let states = vec![
DbMetadataNexusState::Active,
DbMetadataNexusState::NotYet,
DbMetadataNexusState::Quiesced,
];

let nexus_generation_by_zone = blueprint
.all_nexus_zones(BlueprintZoneDisposition::is_in_service)
.map(|(_, zone, nexus_zone)| (zone.id, nexus_zone.nexus_generation))
.collect::<BTreeMap<_, _>>();

Ok(datastore
.get_db_metadata_nexus_in_state(opctx, states)
.await?
.into_iter()
.map(|db_metadata_nexus| {
let id = db_metadata_nexus.nexus_id();
let last_drained_blueprint =
db_metadata_nexus.last_drained_blueprint_id();
let state = db_metadata_nexus.state().to_string();
let intended_state = get_intended_nexus_state(
blueprint.nexus_generation,
&nexus_generation_by_zone,
id,
);

let transitioning_to = get_nexus_state_transition(
db_metadata_nexus.state(),
intended_state,
);

DbMetadataNexusRow {
id,
last_drained_blueprint,
state,
transitioning_to,
}
})
.collect())
}

pub async fn cmd_db_metadata_list_nexus(
opctx: &OpContext,
datastore: &DataStore,
) -> Result<(), anyhow::Error> {
let (_, current_target_blueprint) = datastore
.blueprint_target_get_current_full(opctx)
.await
.context("loading current target blueprint")?;
println!(
"Target Blueprint {} @ nexus_generation: {}",
current_target_blueprint.id, current_target_blueprint.nexus_generation
);

let rows: Vec<_> =
get_db_metadata_nexus_rows(opctx, datastore, &current_target_blueprint)
.await?;
let table = tabled::Table::new(rows)
.with(tabled::settings::Style::psql())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();
println!("{}", table);

Ok(())
}
13 changes: 13 additions & 0 deletions dev-tools/omdb/tests/successes.out
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
EXECUTING COMMAND: omdb ["db", "db-metadata", "ls-nexus"]
termination: Exited(0)
---------------------------------------------
stdout:
Target Blueprint ......<REDACTED_BLUEPRINT_ID>....... @ nexus_generation: 1
ID |LAST_DRAINED_BLUEPRINT |STATE |TRANSITIONING_TO
-------------------------------------+-----------------------+-------+-----------------
..........<REDACTED_UUID>........... | |active |
---------------------------------------------
stderr:
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
note: database schema version matches expected (<redacted database version>)
=============================================
EXECUTING COMMAND: omdb ["db", "disks", "list"]
termination: Exited(0)
---------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions dev-tools/omdb/tests/test_all_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) {
let mut output = String::new();

let invocations: &[&[&str]] = &[
&["db", "db-metadata", "ls-nexus"],
&["db", "disks", "list"],
&["db", "dns", "show"],
&["db", "dns", "diff", "external", "2"],
Expand Down
2 changes: 2 additions & 0 deletions dev-tools/omdb/tests/usage_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Query the control plane database (CockroachDB)
Usage: omdb db [OPTIONS] <COMMAND>

Commands:
db-metadata Commands for database metadata
crucible-dataset Commands relevant to Crucible datasets
replacements-to-do Print any Crucible resources that are located on expunged physical
disks
Expand Down Expand Up @@ -174,6 +175,7 @@ Query the control plane database (CockroachDB)
Usage: omdb db [OPTIONS] <COMMAND>

Commands:
db-metadata Commands for database metadata
crucible-dataset Commands relevant to Crucible datasets
replacements-to-do Print any Crucible resources that are located on expunged physical
disks
Expand Down
11 changes: 11 additions & 0 deletions nexus/db-model/src/db_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use omicron_uuid_kinds::{
BlueprintKind, BlueprintUuid, OmicronZoneKind, OmicronZoneUuid,
};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Internal database metadata
#[derive(
Expand Down Expand Up @@ -52,6 +53,16 @@ impl_enum_type!(
Quiesced => b"quiesced"
);

impl fmt::Display for DbMetadataNexusState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
DbMetadataNexusState::Active => "active",
DbMetadataNexusState::NotYet => "not yet",
DbMetadataNexusState::Quiesced => "quiesced",
})
}
}

#[derive(
Queryable, Insertable, Debug, Clone, Selectable, Serialize, Deserialize,
)]
Expand Down
2 changes: 1 addition & 1 deletion nexus/db-queries/src/db/datastore/db_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ impl DataStore {
pub async fn get_db_metadata_nexus_in_state(
&self,
opctx: &OpContext,
states: &[DbMetadataNexusState],
states: Vec<DbMetadataNexusState>,
) -> Result<Vec<DbMetadataNexus>, Error> {
use nexus_db_schema::schema::db_metadata_nexus::dsl;

Expand Down
2 changes: 1 addition & 1 deletion nexus/reconfigurator/execution/src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1506,7 +1506,7 @@ mod test {
let active_nexus_zones = datastore
.get_db_metadata_nexus_in_state(
&opctx,
&[DbMetadataNexusState::Active],
vec![DbMetadataNexusState::Active],
)
.await
.internal_context("fetching active nexuses")
Expand Down
2 changes: 1 addition & 1 deletion nexus/reconfigurator/preparation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl PlanningInputFromDb<'_> {
datastore
.get_db_metadata_nexus_in_state(
opctx,
&[
vec![
DbMetadataNexusState::Active,
DbMetadataNexusState::NotYet,
],
Expand Down
Loading