From 840aa98e35ac4b93295c3d4d3a65c0079af441d3 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Tue, 26 Sep 2023 13:38:39 -0700 Subject: [PATCH 1/6] list external ips with parents --- Cargo.lock | 1 + dev-tools/omdb/Cargo.toml | 1 + dev-tools/omdb/src/bin/omdb/db.rs | 150 ++++++++++++++++++ dev-tools/omdb/tests/usage_errors.out | 2 + .../src/db/datastore/external_ip.rs | 14 ++ 5 files changed, 168 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3a45dcb381..c4385bf694 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5127,6 +5127,7 @@ dependencies = [ "expectorate", "humantime", "internal-dns 0.1.0", + "ipnetwork", "nexus-client 0.1.0", "nexus-db-model", "nexus-db-queries", diff --git a/dev-tools/omdb/Cargo.toml b/dev-tools/omdb/Cargo.toml index 5b2adde1b2..5a05e93db9 100644 --- a/dev-tools/omdb/Cargo.toml +++ b/dev-tools/omdb/Cargo.toml @@ -33,6 +33,7 @@ textwrap.workspace = true tokio = { workspace = true, features = [ "full" ] } uuid.workspace = true omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } +ipnetwork.workspace = true [dev-dependencies] expectorate.workspace = true diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 93e5ef4301..535e3f5427 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -12,6 +12,9 @@ //! would be the only consumer -- and in that case it's okay to query the //! database directly. +// NOTE: eminates from Tabled macros +#![allow(clippy::useless_vec)] + use crate::Omdb; use anyhow::anyhow; use anyhow::bail; @@ -31,6 +34,7 @@ use nexus_db_model::DnsName; use nexus_db_model::DnsVersion; use nexus_db_model::DnsZone; use nexus_db_model::Instance; +use nexus_db_model::Project; use nexus_db_model::Region; use nexus_db_model::Sled; use nexus_db_model::Zpool; @@ -86,6 +90,8 @@ enum DbCommands { Sleds, /// Print information about customer instances Instances, + /// Print information about the network + Network(NetworkArgs), } #[derive(Debug, Args)] @@ -170,6 +176,21 @@ enum ServicesCommands { ListBySled, } +#[derive(Debug, Args)] +struct NetworkArgs { + #[command(subcommand)] + command: NetworkCommands, + + #[clap(long)] + verbose: bool, +} + +#[derive(Debug, Subcommand)] +enum NetworkCommands { + /// List external IPs + ListEips, +} + impl DbArgs { /// Run a `omdb db` subcommand. pub(crate) async fn run_cmd( @@ -269,6 +290,13 @@ impl DbArgs { DbCommands::Instances => { cmd_db_instances(&datastore, self.fetch_limit).await } + DbCommands::Network(NetworkArgs { + command: NetworkCommands::ListEips, + verbose, + }) => { + cmd_db_eips(&opctx, &datastore, self.fetch_limit, *verbose) + .await + } } } } @@ -1098,6 +1126,128 @@ async fn cmd_db_dns_names( Ok(()) } +async fn cmd_db_eips( + opctx: &OpContext, + datastore: &DataStore, + limit: NonZeroU32, + verbose: bool, +) -> Result<(), anyhow::Error> { + let ips = datastore + .lookup_external_ips(&opctx) + .await + .context("listing external ips")?; + + check_limit(&ips, limit, || String::from("listing external ips")); + + struct PortRange { + first: u16, + last: u16, + } + + impl Display for PortRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", self.first, self.last) + } + } + + #[derive(Tabled)] + enum Owner { + Instance { project: String, name: String }, + Service { kind: String }, + None, + } + + impl Display for Owner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Instance { project, name } => { + write!(f, "Instance {project}/{name}") + } + Self::Service { kind } => write!(f, "Service {kind}"), + Self::None => write!(f, "None"), + } + } + } + + #[derive(Tabled)] + struct IpRow { + ip: ipnetwork::IpNetwork, + ports: PortRange, + kind: String, + owner: Owner, + } + + let mut rows = Vec::new(); + + for ip in &ips { + if verbose { + println!("{ip:#?}"); + continue; + } + + let owner = if let Some(owner_id) = ip.parent_id { + if ip.is_service { + let service = LookupPath::new(opctx, datastore) + .service_id(owner_id) + .fetch() + .await?; + Owner::Service { kind: format!("{:?}", service.1.kind) } + } else { + use db::schema::instance::dsl as instance_dsl; + let instance = instance_dsl::instance + .filter(instance_dsl::id.eq(owner_id)) + .limit(1) + .select(Instance::as_select()) + .load_async(datastore.pool_for_tests().await?) + .await + .context("loading requested instance")? + .pop() + .context("loading requested instance")?; + + use db::schema::project::dsl as project_dsl; + let project = project_dsl::project + .filter(project_dsl::id.eq(instance.project_id)) + .limit(1) + .select(Project::as_select()) + .load_async(datastore.pool_for_tests().await?) + .await + .context("loading requested project")? + .pop() + .context("loading requested instance")?; + + Owner::Instance { + project: project.name().to_string(), + name: instance.name().to_string(), + } + } + } else { + Owner::None + }; + + let row = IpRow { + ip: ip.ip, + ports: PortRange { + first: ip.first_port.into(), + last: ip.last_port.into(), + }, + kind: format!("{:?}", ip.kind), + owner, + }; + rows.push(row); + } + + if !verbose { + rows.sort_by(|a, b| a.ip.cmp(&b.ip)); + let table = tabled::Table::new(rows) + .with(tabled::settings::Style::empty()) + .to_string(); + + println!("{}", table); + } + + Ok(()) +} + fn print_name( prefix: &str, name: &str, diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index 136a631e80..cebbb261ea 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -91,6 +91,7 @@ Commands: services Print information about control plane services sleds Print information about sleds instances Print information about customer instances + network Print information about the network help Print this message or the help of the given subcommand(s) Options: @@ -112,6 +113,7 @@ Commands: services Print information about control plane services sleds Print information about sleds instances Print information about customer instances + network Print information about the network help Print this message or the help of the given subcommand(s) Options: diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index 268b284a0a..7633c31af3 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -282,4 +282,18 @@ impl DataStore { .await .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) } + + /// Fetch all external IP addresses of any kind for the provided instance + pub async fn lookup_external_ips( + &self, + opctx: &OpContext, + ) -> LookupResult> { + use db::schema::external_ip::dsl; + dsl::external_ip + .filter(dsl::time_deleted.is_null()) + .select(ExternalIp::as_select()) + .get_results_async(self.pool_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + } } From e5a2b5a7ed0e2bd671a80e2e789eda850dad8109 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Tue, 26 Sep 2023 17:09:54 -0700 Subject: [PATCH 2/6] fix tests, cleanup --- dev-tools/omdb/src/bin/omdb/db.rs | 31 ++++++++++++++----------- dev-tools/omdb/tests/successes.out | 18 ++++++++++++++ dev-tools/omdb/tests/test_all_output.rs | 1 + 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 535e3f5427..2b8d2512f1 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -181,6 +181,7 @@ struct NetworkArgs { #[command(subcommand)] command: NetworkCommands, + /// Print out raw data structures from the data store. #[clap(long)] verbose: bool, } @@ -1177,14 +1178,18 @@ async fn cmd_db_eips( owner: Owner, } + if verbose { + for ip in &ips { + if verbose { + println!("{ip:#?}"); + } + } + return Ok(()); + } + let mut rows = Vec::new(); for ip in &ips { - if verbose { - println!("{ip:#?}"); - continue; - } - let owner = if let Some(owner_id) = ip.parent_id { if ip.is_service { let service = LookupPath::new(opctx, datastore) @@ -1202,7 +1207,7 @@ async fn cmd_db_eips( .await .context("loading requested instance")? .pop() - .context("loading requested instance")?; + .context("requested instance not found")?; use db::schema::project::dsl as project_dsl; let project = project_dsl::project @@ -1213,7 +1218,7 @@ async fn cmd_db_eips( .await .context("loading requested project")? .pop() - .context("loading requested instance")?; + .context("requested project not found")?; Owner::Instance { project: project.name().to_string(), @@ -1236,14 +1241,12 @@ async fn cmd_db_eips( rows.push(row); } - if !verbose { - rows.sort_by(|a, b| a.ip.cmp(&b.ip)); - let table = tabled::Table::new(rows) - .with(tabled::settings::Style::empty()) - .to_string(); + rows.sort_by(|a, b| a.ip.cmp(&b.ip)); + let table = tabled::Table::new(rows) + .with(tabled::settings::Style::empty()) + .to_string(); - println!("{}", table); - } + println!("{}", table); Ok(()) } diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index b1464cb824..ef806fb87f 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -84,6 +84,24 @@ stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable note: database schema version matches expected (5.0.0) ============================================= +EXECUTING COMMAND: omdb ["db", "network"] +termination: Exited(2) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +Print information about the network + +Usage: omdb db network [OPTIONS] + +Commands: + list-eips List external IPs + help Print this message or the help of the given subcommand(s) + +Options: + --verbose Print out raw data structures from the data store + -h, --help Print help +============================================= EXECUTING COMMAND: omdb ["nexus", "background-tasks", "doc"] termination: Exited(0) --------------------------------------------- diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index 0eddcb492c..7028ec5710 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -69,6 +69,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { &["db", "services", "list-instances"], &["db", "services", "list-by-sled"], &["db", "sleds"], + &["db", "network"], &["nexus", "background-tasks", "doc"], &["nexus", "background-tasks", "show"], // We can't easily test the sled agent output because that's only From 28d4b25868cd96a2b360f61877906b437559b4e5 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Mon, 2 Oct 2023 17:43:18 -0700 Subject: [PATCH 3/6] review feedback --- dev-tools/omdb/src/bin/omdb/db.rs | 32 ++++++++++++++++--- dev-tools/omdb/tests/successes.out | 18 ----------- dev-tools/omdb/tests/test_all_output.rs | 2 +- dev-tools/omdb/tests/usage_errors.out | 18 +++++++++++ .../src/db/datastore/external_ip.rs | 14 -------- 5 files changed, 47 insertions(+), 37 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 2b8d2512f1..53d83eb2af 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -33,6 +33,7 @@ use nexus_db_model::DnsGroup; use nexus_db_model::DnsName; use nexus_db_model::DnsVersion; use nexus_db_model::DnsZone; +use nexus_db_model::ExternalIp; use nexus_db_model::Instance; use nexus_db_model::Project; use nexus_db_model::Region; @@ -1133,10 +1134,18 @@ async fn cmd_db_eips( limit: NonZeroU32, verbose: bool, ) -> Result<(), anyhow::Error> { + /* let ips = datastore .lookup_external_ips(&opctx) .await .context("listing external ips")?; + */ + use db::schema::external_ip::dsl; + let ips: Vec = dsl::external_ip + .filter(dsl::time_deleted.is_null()) + .select(ExternalIp::as_select()) + .get_results_async(datastore.pool_for_tests().await?) + .await?; check_limit(&ips, limit, || String::from("listing external ips")); @@ -1199,7 +1208,7 @@ async fn cmd_db_eips( Owner::Service { kind: format!("{:?}", service.1.kind) } } else { use db::schema::instance::dsl as instance_dsl; - let instance = instance_dsl::instance + let instance = match instance_dsl::instance .filter(instance_dsl::id.eq(owner_id)) .limit(1) .select(Instance::as_select()) @@ -1207,10 +1216,16 @@ async fn cmd_db_eips( .await .context("loading requested instance")? .pop() - .context("requested instance not found")?; + { + Some(instance) => instance, + None => { + eprintln!("instance with id {owner_id} not found"); + continue; + } + }; use db::schema::project::dsl as project_dsl; - let project = project_dsl::project + let project = match project_dsl::project .filter(project_dsl::id.eq(instance.project_id)) .limit(1) .select(Project::as_select()) @@ -1218,7 +1233,16 @@ async fn cmd_db_eips( .await .context("loading requested project")? .pop() - .context("requested project not found")?; + { + Some(instance) => instance, + None => { + eprintln!( + "project with id {} not found", + instance.project_id + ); + continue; + } + }; Owner::Instance { project: project.name().to_string(), diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index ef806fb87f..b1464cb824 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -84,24 +84,6 @@ stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable note: database schema version matches expected (5.0.0) ============================================= -EXECUTING COMMAND: omdb ["db", "network"] -termination: Exited(2) ---------------------------------------------- -stdout: ---------------------------------------------- -stderr: -Print information about the network - -Usage: omdb db network [OPTIONS] - -Commands: - list-eips List external IPs - help Print this message or the help of the given subcommand(s) - -Options: - --verbose Print out raw data structures from the data store - -h, --help Print help -============================================= EXECUTING COMMAND: omdb ["nexus", "background-tasks", "doc"] termination: Exited(0) --------------------------------------------- diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index 7028ec5710..d757369ead 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -41,6 +41,7 @@ async fn test_omdb_usage_errors() { &["db", "dns", "diff"], &["db", "dns", "names"], &["db", "services"], + &["db", "network"], &["nexus"], &["nexus", "background-tasks"], &["sled-agent"], @@ -69,7 +70,6 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { &["db", "services", "list-instances"], &["db", "services", "list-by-sled"], &["db", "sleds"], - &["db", "network"], &["nexus", "background-tasks", "doc"], &["nexus", "background-tasks", "show"], // We can't easily test the sled agent output because that's only diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index cebbb261ea..b5421b76af 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -188,6 +188,24 @@ Commands: Options: -h, --help Print help ============================================= +EXECUTING COMMAND: omdb ["db", "network"] +termination: Exited(2) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +Print information about the network + +Usage: omdb db network [OPTIONS] + +Commands: + list-eips List external IPs + help Print this message or the help of the given subcommand(s) + +Options: + --verbose Print out raw data structures from the data store + -h, --help Print help +============================================= EXECUTING COMMAND: omdb ["nexus"] termination: Exited(2) --------------------------------------------- diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index 7633c31af3..268b284a0a 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -282,18 +282,4 @@ impl DataStore { .await .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) } - - /// Fetch all external IP addresses of any kind for the provided instance - pub async fn lookup_external_ips( - &self, - opctx: &OpContext, - ) -> LookupResult> { - use db::schema::external_ip::dsl; - dsl::external_ip - .filter(dsl::time_deleted.is_null()) - .select(ExternalIp::as_select()) - .get_results_async(self.pool_authorized(opctx).await?) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) - } } From 5f84818dba5fdae4574b6c0f7b97dab48e13f5f5 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Mon, 2 Oct 2023 21:10:19 -0700 Subject: [PATCH 4/6] rebase fixup --- dev-tools/omdb/src/bin/omdb/db.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 53d83eb2af..ae468a8b81 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -1144,7 +1144,7 @@ async fn cmd_db_eips( let ips: Vec = dsl::external_ip .filter(dsl::time_deleted.is_null()) .select(ExternalIp::as_select()) - .get_results_async(datastore.pool_for_tests().await?) + .get_results_async(&*datastore.pool_connection_for_tests().await?) .await?; check_limit(&ips, limit, || String::from("listing external ips")); @@ -1212,7 +1212,7 @@ async fn cmd_db_eips( .filter(instance_dsl::id.eq(owner_id)) .limit(1) .select(Instance::as_select()) - .load_async(datastore.pool_for_tests().await?) + .load_async(&*datastore.pool_connection_for_tests().await?) .await .context("loading requested instance")? .pop() @@ -1229,7 +1229,7 @@ async fn cmd_db_eips( .filter(project_dsl::id.eq(instance.project_id)) .limit(1) .select(Project::as_select()) - .load_async(datastore.pool_for_tests().await?) + .load_async(&*datastore.pool_connection_for_tests().await?) .await .context("loading requested project")? .pop() From 1e9179b23277f8cfed178d8ac3ee2cb785b6c65a Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Mon, 2 Oct 2023 21:23:36 -0700 Subject: [PATCH 5/6] print error and continue if service id not found --- dev-tools/omdb/src/bin/omdb/db.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index ae468a8b81..47a2961b0f 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -1201,10 +1201,19 @@ async fn cmd_db_eips( for ip in &ips { let owner = if let Some(owner_id) = ip.parent_id { if ip.is_service { - let service = LookupPath::new(opctx, datastore) + let service = match LookupPath::new(opctx, datastore) .service_id(owner_id) .fetch() - .await?; + .await + { + Ok(instance) => instance, + Err(e) => { + eprintln!( + "error looking up service with id {owner_id}: {e}" + ); + continue; + } + }; Owner::Service { kind: format!("{:?}", service.1.kind) } } else { use db::schema::instance::dsl as instance_dsl; From 8d09e04e25fdda9c22c99af1bcb087ffbae37c67 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Tue, 3 Oct 2023 10:37:10 -0700 Subject: [PATCH 6/6] remove stray comment --- dev-tools/omdb/src/bin/omdb/db.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 47a2961b0f..10e5546b6d 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -1134,12 +1134,6 @@ async fn cmd_db_eips( limit: NonZeroU32, verbose: bool, ) -> Result<(), anyhow::Error> { - /* - let ips = datastore - .lookup_external_ips(&opctx) - .await - .context("listing external ips")?; - */ use db::schema::external_ip::dsl; let ips: Vec = dsl::external_ip .filter(dsl::time_deleted.is_null())