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

Merged
merged 32 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
07dd473
[nexus] Put support bundles in internal API too
smklein Apr 11, 2025
c079c3f
[omdb] Basic commands to access support bundles
smklein Apr 14, 2025
df47341
Updated output
smklein Apr 14, 2025
219d284
Merge branch 'main' into sb-internal-api
smklein Apr 14, 2025
e39785a
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 14, 2025
12f461b
[nexus] Make it 'more default' for Debug datasets to exist in test en…
smklein Apr 15, 2025
1af91c6
test patching
smklein Apr 15, 2025
8969fbe
Merge branch 'main' into sb-internal-api
smklein Apr 15, 2025
3dfd8ab
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 15, 2025
07de40c
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 15, 2025
4bf9d9a
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 15, 2025
73b4975
Try compiling
smklein Apr 15, 2025
c3876e2
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 15, 2025
600a537
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 15, 2025
8022e12
Use internal opctx
smklein Apr 15, 2025
47819c0
Patching tests more
smklein Apr 15, 2025
78af872
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 15, 2025
192e255
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 15, 2025
6374a7b
Don't inject newlines
smklein Apr 16, 2025
ab14729
Merge branch 'main' into omicron-dev-disk-test
smklein Apr 23, 2025
ceedbc3
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 23, 2025
8278c09
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 23, 2025
7fb1168
Merge branch 'main' into omicron-dev-disk-test
smklein Apr 25, 2025
36d2d04
Make datasets private, add helpers to access them
smklein Apr 25, 2025
bcfbb51
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 25, 2025
b21b525
feedback
smklein Apr 25, 2025
b9b94d5
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 25, 2025
53c0a76
feedback, less utf8
smklein Apr 25, 2025
9597433
Merge branch 'main' into omicron-dev-disk-test
smklein Apr 28, 2025
d2d0c76
Merge branch 'omicron-dev-disk-test' into sb-internal-api
smklein Apr 28, 2025
c8d9546
Merge branch 'sb-internal-api' into omdb-sb
smklein Apr 28, 2025
2790d9a
expectorate
smklein Apr 28, 2025
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
3 changes: 3 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 [aliases: sb]
help Print this message or the help of the given subcommand(s)

Options:
Expand Down
1 change: 1 addition & 0 deletions nexus/internal-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ workspace = true

[dependencies]
dropshot.workspace = true
http.workspace = true
nexus-types.workspace = true
omicron-common.workspace = true
omicron-uuid-kinds.workspace = true
Expand Down
Loading
Loading