Skip to content

[omdb] Basic commands to access support bundles #7972

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

Open
wants to merge 11 commits into
base: sb-internal-api
Choose a base branch
from
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dev-tools/omdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ omicron-rpaths.workspace = true
[dependencies]
anyhow.workspace = true
async-bb8-diesel.workspace = true
bytes.workspace = true
camino.workspace = true
chrono.workspace = true
clap.workspace = true
Expand Down Expand Up @@ -50,6 +51,7 @@ oximeter-db = { workspace = true, default-features = false, features = [ "oxql"
pq-sys = "*"
ratatui.workspace = true
reedline.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
sled-agent-client.workspace = true
Expand Down
198 changes: 198 additions & 0 deletions dev-tools/omdb/src/bin/omdb/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use clap::Args;
use clap::ColorChoice;
use clap::Subcommand;
use clap::ValueEnum;
use futures::StreamExt;
use futures::TryStreamExt;
use futures::future::try_join;
use http::StatusCode;
Expand Down Expand Up @@ -69,6 +70,7 @@ use omicron_uuid_kinds::GenericUuid;
use omicron_uuid_kinds::ParseError;
use omicron_uuid_kinds::PhysicalDiskUuid;
use omicron_uuid_kinds::SledUuid;
use omicron_uuid_kinds::SupportBundleUuid;
use serde::Deserialize;
use slog_error_chain::InlineErrorChain;
use std::collections::BTreeMap;
Expand Down Expand Up @@ -126,6 +128,9 @@ enum NexusCommands {
Sagas(SagasArgs),
/// interact with sleds
Sleds(SledsArgs),
/// interact with support bundles
#[command(visible_alias = "sb")]
SupportBundles(SupportBundleArgs),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support-bundles is a bit of a mouthful, WDYT about adding an alias?

Suggested change
SupportBundles(SupportBundleArgs),
#[command(visible_alias = "sb")]
SupportBundles(SupportBundleArgs),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -474,6 +479,49 @@ struct DiskExpungeArgs {
physical_disk_id: PhysicalDiskUuid,
}

#[derive(Debug, Args)]
struct SupportBundleArgs {
#[command(subcommand)]
command: SupportBundleCommands,
}

#[derive(Debug, Subcommand)]
#[allow(clippy::large_enum_variant)]
enum SupportBundleCommands {
/// List all support bundles
List,
/// Create a new support bundle
Create,
/// Delete a support bundle
Delete(SupportBundleDeleteArgs),
/// Download the index of a support bundle
///
/// This is a "list of files", from which individual files can be accessed
GetIndex(SupportBundleIndexArgs),
/// View a file within a support bundle
GetFile(SupportBundleFileArgs),
}

#[derive(Debug, Args)]
struct SupportBundleDeleteArgs {
id: SupportBundleUuid,
}

#[derive(Debug, Args)]
struct SupportBundleIndexArgs {
id: SupportBundleUuid,
}

#[derive(Debug, Args)]
struct SupportBundleFileArgs {
id: SupportBundleUuid,
path: Utf8PathBuf,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll eventually have non-text files in bundles, e.g. core dumps, SP dumps. An option to directly save the file would be useful for these.

Suggested change
path: Utf8PathBuf,
path: Utf8PathBuf,
output_path: Utf8PathBuf,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, also, removed utf8 parsing so the core dump cases can be passed through as regular binary files.

/// Optional output path where the file should be written,
/// instead of stdout.
#[arg(short, long)]
output: Option<Utf8PathBuf>,
}

impl NexusArgs {
/// Run a `omdb nexus` subcommand.
pub(crate) async fn run_cmd(
Expand Down Expand Up @@ -667,6 +715,27 @@ impl NexusArgs {
cmd_nexus_sled_expunge_disk(&client, args, omdb, log, token)
.await
}
NexusCommands::SupportBundles(SupportBundleArgs {
command: SupportBundleCommands::List,
}) => cmd_nexus_support_bundles_list(&client).await,
NexusCommands::SupportBundles(SupportBundleArgs {
command: SupportBundleCommands::Create,
}) => {
let token = omdb.check_allow_destructive()?;
cmd_nexus_support_bundles_create(&client, token).await
}
NexusCommands::SupportBundles(SupportBundleArgs {
command: SupportBundleCommands::Delete(args),
}) => {
let token = omdb.check_allow_destructive()?;
cmd_nexus_support_bundles_delete(&client, args, token).await
}
NexusCommands::SupportBundles(SupportBundleArgs {
command: SupportBundleCommands::GetIndex(args),
}) => cmd_nexus_support_bundles_get_index(&client, args).await,
NexusCommands::SupportBundles(SupportBundleArgs {
command: SupportBundleCommands::GetFile(args),
}) => cmd_nexus_support_bundles_get_file(&client, args).await,
}
}
}
Expand Down Expand Up @@ -3385,3 +3454,132 @@ async fn cmd_nexus_sled_expunge_disk_with_datastore(
eprintln!("expunged disk {}", args.physical_disk_id);
Ok(())
}

/// Runs `omdb nexus support-bundles list`
async fn cmd_nexus_support_bundles_list(
client: &nexus_client::Client,
) -> Result<(), anyhow::Error> {
let support_bundle_stream = client.support_bundle_list_stream(None, None);

let support_bundles = support_bundle_stream
.try_collect::<Vec<_>>()
.await
.context("listing support bundles")?;

#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct SupportBundleInfo {
id: Uuid,
time_created: DateTime<Utc>,
reason_for_creation: String,
reason_for_failure: String,
state: String,
}
let rows = support_bundles.into_iter().map(|sb| SupportBundleInfo {
id: *sb.id,
time_created: sb.time_created,
reason_for_creation: sb.reason_for_creation,
reason_for_failure: sb
.reason_for_failure
.unwrap_or_else(|| "-".to_string()),
state: format!("{:?}", sb.state),
});
let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();
println!("{}", table);
Ok(())
}

/// Runs `omdb nexus support-bundles create`
async fn cmd_nexus_support_bundles_create(
client: &nexus_client::Client,
_destruction_token: DestructiveOperationToken,
) -> Result<(), anyhow::Error> {
let support_bundle_id = client
.support_bundle_create()
.await
.context("creating support bundle")?
.into_inner()
.id;
println!("created support bundle: {support_bundle_id}");
Ok(())
}

/// Runs `omdb nexus support-bundles delete`
async fn cmd_nexus_support_bundles_delete(
client: &nexus_client::Client,
args: &SupportBundleDeleteArgs,
_destruction_token: DestructiveOperationToken,
) -> Result<(), anyhow::Error> {
let _ = client
.support_bundle_delete(args.id.as_untyped_uuid())
.await
.with_context(|| format!("deleting support bundle {}", args.id))?;
println!("support bundle {} deleted", args.id);
Ok(())
}

async fn write_stream_to_sink(
mut stream: impl futures::Stream<Item = reqwest::Result<bytes::Bytes>>
+ std::marker::Unpin,
mut sink: impl std::io::Write,
) -> Result<(), anyhow::Error> {
while let Some(data) = stream.next().await {
match data {
Err(err) => return Err(anyhow::anyhow!(err)),
Ok(data) => sink.write_all(&data)?,
}
}
Ok(())
}

/// Runs `omdb nexus support-bundles get-index`
async fn cmd_nexus_support_bundles_get_index(
client: &nexus_client::Client,
args: &SupportBundleIndexArgs,
) -> Result<(), anyhow::Error> {
let stream = client
.support_bundle_index(args.id.as_untyped_uuid())
.await
.with_context(|| {
format!("downloading support bundle index {}", args.id)
})?
.into_inner_stream();

write_stream_to_sink(stream, std::io::stdout()).await.with_context(
|| format!("streaming support bundle index {}", args.id),
)?;
Ok(())
}

/// Runs `omdb nexus support-bundles get-file`
async fn cmd_nexus_support_bundles_get_file(
client: &nexus_client::Client,
args: &SupportBundleFileArgs,
) -> Result<(), anyhow::Error> {
let stream = client
.support_bundle_download_file(
args.id.as_untyped_uuid(),
args.path.as_str(),
)
.await
.with_context(|| {
format!(
"downloading support bundle file {}: {}",
args.id, args.path
)
})?
.into_inner_stream();

let sink: Box<dyn std::io::Write> = match &args.output {
Some(path) => Box::new(std::fs::File::create(path)?),
None => Box::new(std::io::stdout()),
};

write_stream_to_sink(stream, sink).await.with_context(|| {
format!("streaming support bundle file {}: {}", args.id, args.path)
})?;
Ok(())
}
1 change: 1 addition & 0 deletions dev-tools/omdb/tests/usage_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ Commands:
oximeter-read-policy interact with oximeter read policy
sagas view sagas, create and complete demo sagas
sleds interact with sleds
support-bundles interact with support bundles
help Print this message or the help of the given subcommand(s)

Options:
Expand Down
Loading