diff --git a/Cargo.lock b/Cargo.lock index 8b4915bbc6..02f61b98b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5070,6 +5070,7 @@ dependencies = [ "regex", "serde", "serde_json", + "sled-agent-client", "slog", "strum", "subprocess", diff --git a/omdb/Cargo.toml b/omdb/Cargo.toml index 4b3ecd6e0e..0bf4ee17b7 100644 --- a/omdb/Cargo.toml +++ b/omdb/Cargo.toml @@ -24,6 +24,7 @@ omicron-common.workspace = true pq-sys = "*" serde.workspace = true serde_json.workspace = true +sled-agent-client.workspace = true slog.workspace = true strum.workspace = true tabled.workspace = true diff --git a/omdb/src/bin/omdb/main.rs b/omdb/src/bin/omdb/main.rs index 861df47b51..d0c47f8e2a 100644 --- a/omdb/src/bin/omdb/main.rs +++ b/omdb/src/bin/omdb/main.rs @@ -25,6 +25,7 @@ use clap::Subcommand; mod db; mod nexus; +mod sled_agent; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { @@ -35,8 +36,9 @@ async fn main() -> Result<(), anyhow::Error> { .context("failed to create logger")?; match args.command { - OmdbCommands::Nexus(nexus) => nexus.run_cmd(&log).await, OmdbCommands::Db(db) => db.run_cmd(&log).await, + OmdbCommands::Nexus(nexus) => nexus.run_cmd(&log).await, + OmdbCommands::SledAgent(sled) => sled.run_cmd(&log).await, } } @@ -67,6 +69,8 @@ enum OmdbCommands { Db(db::DbArgs), /// Debug a specific Nexus instance Nexus(nexus::NexusArgs), + /// Debug a specific Sled + SledAgent(sled_agent::SledAgentArgs), } fn parse_dropshot_log_level( diff --git a/omdb/src/bin/omdb/nexus.rs b/omdb/src/bin/omdb/nexus.rs index 704abf4af3..531158ab16 100644 --- a/omdb/src/bin/omdb/nexus.rs +++ b/omdb/src/bin/omdb/nexus.rs @@ -59,7 +59,7 @@ impl NexusArgs { &self, log: &slog::Logger, ) -> Result<(), anyhow::Error> { - // This is a little goofy. The database URL is required, but can come + // This is a little goofy. The nexus URL is required, but can come // from the environment, in which case it won't be on the command line. let Some(nexus_url) = &self.nexus_internal_url else { bail!( diff --git a/omdb/src/bin/omdb/sled_agent.rs b/omdb/src/bin/omdb/sled_agent.rs new file mode 100644 index 0000000000..09f34936ca --- /dev/null +++ b/omdb/src/bin/omdb/sled_agent.rs @@ -0,0 +1,110 @@ +// 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 commands that query or update specific Sleds + +use anyhow::bail; +use anyhow::Context; +use clap::Args; +use clap::Subcommand; + +/// Arguments to the "omdb sled" subcommand +#[derive(Debug, Args)] +pub struct SledAgentArgs { + /// URL of the Sled internal API + #[clap(long, env("OMDB_SLED_AGENT_URL"))] + sled_agent_url: Option, + + #[command(subcommand)] + command: SledAgentCommands, +} + +/// Subcommands for the "omdb sled" subcommand +#[derive(Debug, Subcommand)] +enum SledAgentCommands { + /// print information about zones + #[clap(subcommand)] + Zones(ZoneCommands), + + /// print information about zpools + #[clap(subcommand)] + Zpools(ZpoolCommands), +} + +#[derive(Debug, Subcommand)] +enum ZoneCommands { + /// Print list of all running control plane zones + List, +} + +#[derive(Debug, Subcommand)] +enum ZpoolCommands { + /// Print list of all zpools managed by the sled agent + List, +} + +impl SledAgentArgs { + /// Run a `omdb sled` subcommand. + pub async fn run_cmd( + &self, + log: &slog::Logger, + ) -> Result<(), anyhow::Error> { + // This is a little goofy. The sled URL is required, but can come + // from the environment, in which case it won't be on the command line. + let Some(sled_agent_url) = &self.sled_agent_url else { + bail!( + "sled URL must be specified with --sled-agent-url or \ + OMDB_SLED_AGENT_URL" + ); + }; + let client = + sled_agent_client::Client::new(sled_agent_url, log.clone()); + + match &self.command { + SledAgentCommands::Zones(ZoneCommands::List) => { + cmd_zones_list(&client).await + } + SledAgentCommands::Zpools(ZpoolCommands::List) => { + cmd_zpools_list(&client).await + } + } + } +} + +/// Runs `omdb sled zones list` +async fn cmd_zones_list( + client: &sled_agent_client::Client, +) -> Result<(), anyhow::Error> { + let response = client.zones_list().await.context("listing zones")?; + let zones = response.into_inner(); + let zones: Vec<_> = zones.into_iter().collect(); + + println!("zones:"); + if zones.is_empty() { + println!(" "); + } + for zone in &zones { + println!(" {:?}", zone); + } + + Ok(()) +} + +/// Runs `omdb sled zpools list` +async fn cmd_zpools_list( + client: &sled_agent_client::Client, +) -> Result<(), anyhow::Error> { + let response = client.zpools_get().await.context("listing zpools")?; + let zpools = response.into_inner(); + + println!("zpools:"); + if zpools.is_empty() { + println!(" "); + } + for zpool in &zpools { + println!(" {:?}", zpool); + } + + Ok(()) +} diff --git a/omdb/tests/usage_errors.out b/omdb/tests/usage_errors.out index 860c0efe21..b381ab46c1 100644 --- a/omdb/tests/usage_errors.out +++ b/omdb/tests/usage_errors.out @@ -9,9 +9,10 @@ Omicron debugger (unstable) Usage: omdb [OPTIONS] Commands: - db Query the control plane database (CockroachDB) - nexus Debug a specific Nexus instance - help Print this message or the help of the given subcommand(s) + db Query the control plane database (CockroachDB) + nexus Debug a specific Nexus instance + sled-agent Debug a specific Sled + help Print this message or the help of the given subcommand(s) Options: --log-level log level filter [env: LOG_LEVEL=] [default: warn] @@ -29,9 +30,10 @@ using internal APIs. This is a prototype. The commands and output are unstable Usage: omdb [OPTIONS] Commands: - db Query the control plane database (CockroachDB) - nexus Debug a specific Nexus instance - help Print this message or the help of the given subcommand(s) + db Query the control plane database (CockroachDB) + nexus Debug a specific Nexus instance + sled-agent Debug a specific Sled + help Print this message or the help of the given subcommand(s) Options: --log-level