Skip to content

Commit ab19029

Browse files
smkleincharliepark
authored andcommitted
Add omdb command to display db_metadata_nexus_state (#9034)
The viewing part of #9008
1 parent 7e8eca7 commit ab19029

File tree

9 files changed

+195
-3
lines changed

9 files changed

+195
-3
lines changed

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ use clap::ValueEnum;
4242
use clap::builder::PossibleValue;
4343
use clap::builder::PossibleValuesParser;
4444
use clap::builder::TypedValueParser;
45+
use db_metadata::DbMetadataArgs;
46+
use db_metadata::DbMetadataCommands;
47+
use db_metadata::cmd_db_metadata_list_nexus;
4548
use diesel::BoolExpressionMethods;
4649
use diesel::ExpressionMethods;
4750
use diesel::JoinOnDsl;
@@ -170,6 +173,7 @@ use tabled::Tabled;
170173
use uuid::Uuid;
171174

172175
mod alert;
176+
mod db_metadata;
173177
mod ereport;
174178
mod saga;
175179
mod user_data_export;
@@ -338,6 +342,8 @@ pub struct DbFetchOptions {
338342
/// Subcommands that query or update the database
339343
#[derive(Debug, Subcommand, Clone)]
340344
enum DbCommands {
345+
/// Commands for database metadata
346+
DbMetadata(DbMetadataArgs),
341347
/// Commands relevant to Crucible datasets
342348
CrucibleDataset(CrucibleDatasetArgs),
343349
/// Print any Crucible resources that are located on expunged physical disks
@@ -1128,6 +1134,11 @@ impl DbArgs {
11281134
self.db_url_opts.with_datastore(omdb, log, |opctx, datastore| {
11291135
async move {
11301136
match &self.command {
1137+
DbCommands::DbMetadata(DbMetadataArgs {
1138+
command: DbMetadataCommands::ListNexus,
1139+
}) => {
1140+
cmd_db_metadata_list_nexus(&opctx, &datastore).await
1141+
}
11311142
DbCommands::CrucibleDataset(CrucibleDatasetArgs {
11321143
command: CrucibleDatasetCommands::List,
11331144
}) => {
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
//! `omdb db db_metadata` subcommands
6+
7+
use super::display_option_blank;
8+
use anyhow::Context;
9+
use clap::Args;
10+
use clap::Subcommand;
11+
use nexus_db_model::DbMetadataNexusState;
12+
use nexus_db_queries::context::OpContext;
13+
use nexus_db_queries::db::DataStore;
14+
use nexus_types::deployment::Blueprint;
15+
use nexus_types::deployment::BlueprintZoneDisposition;
16+
use omicron_common::api::external::Generation;
17+
use omicron_uuid_kinds::BlueprintUuid;
18+
use omicron_uuid_kinds::OmicronZoneUuid;
19+
use std::collections::BTreeMap;
20+
use tabled::Tabled;
21+
22+
#[derive(Debug, Args, Clone)]
23+
pub struct DbMetadataArgs {
24+
#[command(subcommand)]
25+
pub command: DbMetadataCommands,
26+
}
27+
28+
#[derive(Debug, Subcommand, Clone)]
29+
pub enum DbMetadataCommands {
30+
#[clap(alias = "ls-nexus")]
31+
ListNexus,
32+
}
33+
34+
// DB Metadata
35+
36+
#[derive(Tabled)]
37+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
38+
struct DbMetadataNexusRow {
39+
id: OmicronZoneUuid,
40+
#[tabled(display_with = "display_option_blank")]
41+
last_drained_blueprint: Option<BlueprintUuid>,
42+
43+
// Identifies the state we observe in the database
44+
state: String,
45+
46+
// Identifies the state this Nexus is trying to achieve, based on the target
47+
// blueprint, if it's different from the current state
48+
#[tabled(display_with = "display_option_blank")]
49+
transitioning_to: Option<String>,
50+
}
51+
52+
fn get_intended_nexus_state(
53+
bp_nexus_generation: Generation,
54+
bp_nexus_generation_by_zone: &BTreeMap<OmicronZoneUuid, Generation>,
55+
id: OmicronZoneUuid,
56+
) -> Option<DbMetadataNexusState> {
57+
let Some(gen) = bp_nexus_generation_by_zone.get(&id) else {
58+
return None;
59+
};
60+
61+
Some(if *gen < bp_nexus_generation {
62+
// This Nexus is either quiescing, or has already quiesced
63+
DbMetadataNexusState::Quiesced
64+
} else if *gen == bp_nexus_generation {
65+
// This Nexus is either active, or will become active once
66+
// the prior generation has quiesced
67+
DbMetadataNexusState::Active
68+
} else {
69+
// This Nexus is not ready to be run yet
70+
DbMetadataNexusState::NotYet
71+
})
72+
}
73+
74+
fn get_nexus_state_transition(
75+
observed: DbMetadataNexusState,
76+
intended: Option<DbMetadataNexusState>,
77+
) -> Option<String> {
78+
match (observed, intended) {
79+
(observed, Some(intended)) if observed == intended => None,
80+
(_, Some(intended)) => Some(intended.to_string()),
81+
(_, None) => Some("Unknown".to_string()),
82+
}
83+
}
84+
85+
async fn get_db_metadata_nexus_rows(
86+
opctx: &OpContext,
87+
datastore: &DataStore,
88+
blueprint: &Blueprint,
89+
) -> Result<Vec<DbMetadataNexusRow>, anyhow::Error> {
90+
let states = vec![
91+
DbMetadataNexusState::Active,
92+
DbMetadataNexusState::NotYet,
93+
DbMetadataNexusState::Quiesced,
94+
];
95+
96+
let nexus_generation_by_zone = blueprint
97+
.all_nexus_zones(BlueprintZoneDisposition::is_in_service)
98+
.map(|(_, zone, nexus_zone)| (zone.id, nexus_zone.nexus_generation))
99+
.collect::<BTreeMap<_, _>>();
100+
101+
Ok(datastore
102+
.get_db_metadata_nexus_in_state(opctx, states)
103+
.await?
104+
.into_iter()
105+
.map(|db_metadata_nexus| {
106+
let id = db_metadata_nexus.nexus_id();
107+
let last_drained_blueprint =
108+
db_metadata_nexus.last_drained_blueprint_id();
109+
let state = db_metadata_nexus.state().to_string();
110+
let intended_state = get_intended_nexus_state(
111+
blueprint.nexus_generation,
112+
&nexus_generation_by_zone,
113+
id,
114+
);
115+
116+
let transitioning_to = get_nexus_state_transition(
117+
db_metadata_nexus.state(),
118+
intended_state,
119+
);
120+
121+
DbMetadataNexusRow {
122+
id,
123+
last_drained_blueprint,
124+
state,
125+
transitioning_to,
126+
}
127+
})
128+
.collect())
129+
}
130+
131+
pub async fn cmd_db_metadata_list_nexus(
132+
opctx: &OpContext,
133+
datastore: &DataStore,
134+
) -> Result<(), anyhow::Error> {
135+
let (_, current_target_blueprint) = datastore
136+
.blueprint_target_get_current_full(opctx)
137+
.await
138+
.context("loading current target blueprint")?;
139+
println!(
140+
"Target Blueprint {} @ nexus_generation: {}",
141+
current_target_blueprint.id, current_target_blueprint.nexus_generation
142+
);
143+
144+
let rows: Vec<_> =
145+
get_db_metadata_nexus_rows(opctx, datastore, &current_target_blueprint)
146+
.await?;
147+
let table = tabled::Table::new(rows)
148+
.with(tabled::settings::Style::psql())
149+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
150+
.to_string();
151+
println!("{}", table);
152+
153+
Ok(())
154+
}

dev-tools/omdb/tests/successes.out

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
EXECUTING COMMAND: omdb ["db", "db-metadata", "ls-nexus"]
2+
termination: Exited(0)
3+
---------------------------------------------
4+
stdout:
5+
Target Blueprint ......<REDACTED_BLUEPRINT_ID>....... @ nexus_generation: 1
6+
ID |LAST_DRAINED_BLUEPRINT |STATE |TRANSITIONING_TO
7+
-------------------------------------+-----------------------+-------+-----------------
8+
..........<REDACTED_UUID>........... | |active |
9+
---------------------------------------------
10+
stderr:
11+
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
12+
note: database schema version matches expected (<redacted database version>)
13+
=============================================
114
EXECUTING COMMAND: omdb ["db", "disks", "list"]
215
termination: Exited(0)
316
---------------------------------------------

dev-tools/omdb/tests/test_all_output.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) {
177177
let mut output = String::new();
178178

179179
let invocations: &[&[&str]] = &[
180+
&["db", "db-metadata", "ls-nexus"],
180181
&["db", "disks", "list"],
181182
&["db", "dns", "show"],
182183
&["db", "dns", "diff", "external", "2"],

dev-tools/omdb/tests/usage_errors.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Query the control plane database (CockroachDB)
115115
Usage: omdb db [OPTIONS] <COMMAND>
116116

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

176177
Commands:
178+
db-metadata Commands for database metadata
177179
crucible-dataset Commands relevant to Crucible datasets
178180
replacements-to-do Print any Crucible resources that are located on expunged physical
179181
disks

nexus/db-model/src/db_metadata.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use omicron_uuid_kinds::{
1212
BlueprintKind, BlueprintUuid, OmicronZoneKind, OmicronZoneUuid,
1313
};
1414
use serde::{Deserialize, Serialize};
15+
use std::fmt;
1516

1617
/// Internal database metadata
1718
#[derive(
@@ -52,6 +53,16 @@ impl_enum_type!(
5253
Quiesced => b"quiesced"
5354
);
5455

56+
impl fmt::Display for DbMetadataNexusState {
57+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58+
f.write_str(match self {
59+
DbMetadataNexusState::Active => "active",
60+
DbMetadataNexusState::NotYet => "not yet",
61+
DbMetadataNexusState::Quiesced => "quiesced",
62+
})
63+
}
64+
}
65+
5566
#[derive(
5667
Queryable, Insertable, Debug, Clone, Selectable, Serialize, Deserialize,
5768
)]

nexus/db-queries/src/db/datastore/db_metadata.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ impl DataStore {
305305
pub async fn get_db_metadata_nexus_in_state(
306306
&self,
307307
opctx: &OpContext,
308-
states: &[DbMetadataNexusState],
308+
states: Vec<DbMetadataNexusState>,
309309
) -> Result<Vec<DbMetadataNexus>, Error> {
310310
use nexus_db_schema::schema::db_metadata_nexus::dsl;
311311

nexus/reconfigurator/execution/src/dns.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ mod test {
15071507
let active_nexus_zones = datastore
15081508
.get_db_metadata_nexus_in_state(
15091509
&opctx,
1510-
&[DbMetadataNexusState::Active],
1510+
vec![DbMetadataNexusState::Active],
15111511
)
15121512
.await
15131513
.internal_context("fetching active nexuses")

nexus/reconfigurator/preparation/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl PlanningInputFromDb<'_> {
207207
datastore
208208
.get_db_metadata_nexus_in_state(
209209
opctx,
210-
&[
210+
vec![
211211
DbMetadataNexusState::Active,
212212
DbMetadataNexusState::NotYet,
213213
],

0 commit comments

Comments
 (0)