Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[omdb] Add disks, historical VMMs to omdb db instance show #6935

Merged
merged 4 commits into from
Oct 26, 2024
Merged
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
275 changes: 228 additions & 47 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,14 @@ struct InstanceInfoArgs {
/// the UUID of the instance to show details for
#[clap(value_name = "UUID")]
id: InstanceUuid,

/// include a list of VMMs and migrations previously associated with this
/// instance.
///
/// note that this is not exhaustive, as some VMM or migration records may
/// have been hard-deleted.
#[arg(short = 'i', long)]
history: bool,
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -1213,21 +1221,31 @@ async fn lookup_project(

// Disks

#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct DiskIdentity {
name: String,
id: Uuid,
size: String,
state: String,
}

impl From<&'_ db::model::Disk> for DiskIdentity {
fn from(disk: &db::model::Disk) -> Self {
Self {
name: disk.name().to_string(),
id: disk.id(),
size: disk.size.to_string(),
state: disk.runtime().disk_state,
}
}
}

/// Run `omdb db disk list`.
async fn cmd_db_disk_list(
datastore: &DataStore,
fetch_opts: &DbFetchOptions,
) -> Result<(), anyhow::Error> {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct DiskRow {
name: String,
id: String,
size: String,
state: String,
attached_to: String,
}

let ctx = || "listing disks".to_string();

use db::schema::disk::dsl;
Expand All @@ -1236,6 +1254,26 @@ async fn cmd_db_disk_list(
query = query.filter(dsl::time_deleted.is_null());
}

#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct DiskRow {
#[tabled(inline)]
identity: DiskIdentity,
attached_to: String,
}

impl From<&'_ db::model::Disk> for DiskRow {
fn from(disk: &db::model::Disk) -> Self {
Self {
identity: disk.into(),
attached_to: match disk.runtime().attach_instance_id {
Some(uuid) => uuid.to_string(),
None => "-".to_string(),
},
}
}
}

let disks = query
.limit(i64::from(u32::from(fetch_opts.fetch_limit)))
.select(Disk::as_select())
Expand All @@ -1245,16 +1283,7 @@ async fn cmd_db_disk_list(

check_limit(&disks, fetch_opts.fetch_limit, ctx);

let rows = disks.into_iter().map(|disk| DiskRow {
name: disk.name().to_string(),
id: disk.id().to_string(),
size: disk.size.to_string(),
state: disk.runtime().disk_state,
attached_to: match disk.runtime().attach_instance_id {
Some(uuid) => uuid.to_string(),
None => "-".to_string(),
},
});
let rows = disks.iter().map(DiskRow::from);
let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
Expand Down Expand Up @@ -2552,6 +2581,7 @@ async fn cmd_db_region_replacement_list(
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct Row {
pub id: Uuid,
#[tabled(display_with = "datetime_rfc3339_concise")]
pub request_time: DateTime<Utc>,
pub replacement_state: String,
}
Expand Down Expand Up @@ -2677,6 +2707,7 @@ async fn cmd_db_region_replacement_info(
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct Row {
#[tabled(display_with = "datetime_rfc3339_concise")]
pub time: DateTime<Utc>,

pub repair_id: String,
Expand Down Expand Up @@ -2752,6 +2783,7 @@ async fn cmd_db_region_replacement_info(
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct StepRow {
#[tabled(display_with = "datetime_rfc3339_concise")]
pub time: DateTime<Utc>,
pub step_type: String,
pub details: String,
Expand Down Expand Up @@ -2888,14 +2920,14 @@ async fn cmd_db_instance_info(
args: &InstanceInfoArgs,
) -> Result<(), anyhow::Error> {
use nexus_db_model::schema::{
instance::dsl as instance_dsl, migration::dsl as migration_dsl,
vmm::dsl as vmm_dsl,
disk::dsl as disk_dsl, instance::dsl as instance_dsl,
migration::dsl as migration_dsl, vmm::dsl as vmm_dsl,
};
use nexus_db_model::{
Instance, InstanceKarmicStatus, InstanceRuntimeState, Migration,
Reincarnatability, Vmm,
};
let InstanceInfoArgs { id } = args;
let &InstanceInfoArgs { ref id, history } = args;

let instance = instance_dsl::instance
.filter(instance_dsl::id.eq(id.into_untyped_uuid()))
Expand Down Expand Up @@ -3164,40 +3196,170 @@ async fn cmd_db_instance_info(
}
}
}
let past_migrations = migration_dsl::migration
.filter(migration_dsl::instance_id.eq(id.into_untyped_uuid()))

let ctx = || "listing attached disks";
let mut query = disk_dsl::disk
.filter(disk_dsl::attach_instance_id.eq(id.into_untyped_uuid()))
.limit(i64::from(u32::from(fetch_opts.fetch_limit)))
.order_by(migration_dsl::time_created)
// This is just to prove to CRDB that it can use the
// migrations-by-time-created index, it doesn't actually do anything.
.filter(migration_dsl::time_created.gt(chrono::DateTime::UNIX_EPOCH))
.select(Migration::as_select())
.order_by(disk_dsl::time_created.desc())
.into_boxed();
if !fetch_opts.include_deleted {
query = query.filter(disk_dsl::time_deleted.is_null());
}

let disks = query
.select(Disk::as_select())
.load_async(&*datastore.pool_connection_for_tests().await?)
.await
.context("listing migrations")?;
.with_context(ctx)?;

check_limit(&past_migrations, fetch_opts.fetch_limit, || {
"listing migrations"
});
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct DiskRow {
#[tabled(display_with = "display_option_blank")]
slot: Option<u8>,
#[tabled(inline)]
identity: DiskIdentity,
}

if !past_migrations.is_empty() {
let rows =
past_migrations.into_iter().map(|m| SingleInstanceMigrationRow {
created: m.time_created,
vmms: MigrationVmms::from(&m),
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct MaybeDeletedDiskRow {
#[tabled(inline)]
r: DiskRow,
#[tabled(display_with = "datetime_opt_rfc3339_concise")]
time_deleted: Option<DateTime<Utc>>,
}

impl From<&'_ db::model::Disk> for DiskRow {
fn from(disk: &db::model::Disk) -> Self {
Self { slot: disk.slot.map(|s| s.into()), identity: disk.into() }
}
}

impl From<&'_ db::model::Disk> for MaybeDeletedDiskRow {
fn from(disk: &db::model::Disk) -> Self {
Self { r: disk.into(), time_deleted: disk.time_deleted() }
}
}

if !disks.is_empty() {
println!("\n{:=<80}\n", "== ATTACHED DISKS");

check_limit(&disks, fetch_opts.fetch_limit, ctx);
let table = if fetch_opts.include_deleted {
tabled::Table::new(disks.iter().map(MaybeDeletedDiskRow::from))
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string()
} else {
tabled::Table::new(disks.iter().map(DiskRow::from))
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string()
};
println!("{table}");
}

if history {
let ctx = || "listing migrations";
let past_migrations = migration_dsl::migration
.filter(migration_dsl::instance_id.eq(id.into_untyped_uuid()))
.limit(i64::from(u32::from(fetch_opts.fetch_limit)))
.order_by(migration_dsl::time_created.desc())
// This is just to prove to CRDB that it can use the
// migrations-by-time-created index, it doesn't actually do anything.
.filter(
migration_dsl::time_created.gt(chrono::DateTime::UNIX_EPOCH),
)
.select(Migration::as_select())
.load_async(&*datastore.pool_connection_for_tests().await?)
.await
.with_context(ctx)?;

if !past_migrations.is_empty() {
println!("\n{:=<80}\n", "== MIGRATION HISTORY");

check_limit(&past_migrations, fetch_opts.fetch_limit, ctx);

let rows = past_migrations.into_iter().map(|m| {
SingleInstanceMigrationRow {
created: m.time_created,
vmms: MigrationVmms::from(&m),
}
});

let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(4, 1, 0, 0))
.to_string();
let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();

println!("\n{:=<80}\n\n{table}", "== MIGRATION HISTORY");
println!("{table}");
}

let ctx = || "listing past VMMs";
let vmms = vmm_dsl::vmm
.filter(vmm_dsl::instance_id.eq(id.into_untyped_uuid()))
.limit(i64::from(u32::from(fetch_opts.fetch_limit)))
.order_by(vmm_dsl::time_created.desc())
.select(Vmm::as_select())
.load_async(&*datastore.pool_connection_for_tests().await?)
.await
.with_context(ctx)?;

if !vmms.is_empty() {
println!("\n{:=<80}\n", "== VMM HISTORY");

check_limit(&vmms, fetch_opts.fetch_limit, ctx);

let table = tabled::Table::new(vmms.iter().map(VmmStateRow::from))
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();
println!("{table}");
}
}

Ok(())
}

#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct VmmStateRow {
id: Uuid,
state: db::model::VmmState,
#[tabled(rename = "GEN")]
generation: u64,
sled_id: Uuid,
#[tabled(display_with = "datetime_rfc3339_concise")]
time_created: chrono::DateTime<Utc>,
#[tabled(display_with = "datetime_opt_rfc3339_concise")]
time_deleted: Option<chrono::DateTime<Utc>>,
}

impl From<&'_ Vmm> for VmmStateRow {
fn from(vmm: &Vmm) -> Self {
let &Vmm {
id,
time_created,
time_deleted,
sled_id,
propolis_ip: _,
propolis_port: _,
instance_id: _,
runtime:
db::model::VmmRuntimeState { time_state_updated: _, r#gen, state },
} = vmm;
Self {
id,
state,
time_created,
time_deleted,
generation: r#gen.0.into(),
sled_id,
}
}
}
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct CustomerInstanceRow {
Expand Down Expand Up @@ -3929,6 +4091,7 @@ async fn cmd_db_region_snapshot_replacement_list(
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct Row {
pub id: Uuid,
#[tabled(display_with = "datetime_rfc3339_concise")]
pub request_time: DateTime<Utc>,
pub replacement_state: String,
}
Expand Down Expand Up @@ -5345,16 +5508,17 @@ async fn cmd_db_migrations_list(
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct VerboseMigrationRow {
#[tabled(display_with = "datetime_rfc3339_concise")]
created: chrono::DateTime<Utc>,
id: Uuid,
instance: Uuid,
#[tabled(inline)]
vmms: MigrationVmms,
#[tabled(display_with = "display_option_blank")]
#[tabled(display_with = "datetime_opt_rfc3339_concise")]
src_updated: Option<chrono::DateTime<Utc>>,
#[tabled(display_with = "display_option_blank")]
#[tabled(display_with = "datetime_opt_rfc3339_concise")]
tgt_updated: Option<chrono::DateTime<Utc>>,
#[tabled(display_with = "display_option_blank")]
#[tabled(display_with = "datetime_opt_rfc3339_concise")]
deleted: Option<chrono::DateTime<Utc>>,
}

Expand Down Expand Up @@ -5390,6 +5554,7 @@ async fn cmd_db_migrations_list(
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct MigrationRow {
#[tabled(display_with = "datetime_rfc3339_concise")]
created: chrono::DateTime<Utc>,
instance: Uuid,
#[tabled(inline)]
Expand All @@ -5416,6 +5581,7 @@ async fn cmd_db_migrations_list(
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct SingleInstanceMigrationRow {
#[tabled(display_with = "datetime_rfc3339_concise")]
created: chrono::DateTime<Utc>,
#[tabled(inline)]
vmms: MigrationVmms,
Expand Down Expand Up @@ -5452,3 +5618,18 @@ impl From<&'_ Migration> for MigrationVmms {
fn display_option_blank<T: Display>(opt: &Option<T>) -> String {
opt.as_ref().map(|x| x.to_string()).unwrap_or_else(|| "".to_string())
}

// Format a `chrono::DateTime` in RFC3339 with milliseconds precision and using
// `Z` rather than the UTC offset for UTC timestamps, to save a few characters
// of line width in tabular output.
fn datetime_rfc3339_concise(t: &DateTime<Utc>) -> String {
t.to_rfc3339_opts(chrono::format::SecondsFormat::Millis, true)
}

// Format an optional `chrono::DateTime` in RFC3339 with milliseconds precision
// and using `Z` rather than the UTC offset for UTC timestamps, to save a few
// characters of line width in tabular output.
fn datetime_opt_rfc3339_concise(t: &Option<DateTime<Utc>>) -> String {
t.map(|t| t.to_rfc3339_opts(chrono::format::SecondsFormat::Millis, true))
.unwrap_or_else(|| "-".to_string())
}
Loading