Skip to content

Commit

Permalink
[nexus] Support Bundle API Skeleton (#7008)
Browse files Browse the repository at this point in the history
PR 1 / ???

Adds support bundles to the experimental Nexus API.
All methods exist, but return "not implemented" errors.

These methods will be implemented in a later PR:
#7187
  • Loading branch information
smklein authored Jan 6, 2025
1 parent 0afbd6e commit 1200dcb
Show file tree
Hide file tree
Showing 8 changed files with 799 additions and 12 deletions.
1 change: 1 addition & 0 deletions clients/sled-agent-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ progenitor::generate_api!(
TypedUuidForOmicronZoneKind = omicron_uuid_kinds::OmicronZoneUuid,
TypedUuidForPropolisKind = omicron_uuid_kinds::PropolisUuid,
TypedUuidForSledKind = omicron_uuid_kinds::SledUuid,
TypedUuidForSupportBundleKind = omicron_uuid_kinds::SupportBundleUuid,
TypedUuidForZpoolKind = omicron_uuid_kinds::ZpoolUuid,
Vni = omicron_common::api::external::Vni,
ZpoolKind = omicron_common::zpool_name::ZpoolKind,
Expand Down
9 changes: 9 additions & 0 deletions nexus/external-api/output/nexus_tags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ probe_create POST /experimental/v1/probes
probe_delete DELETE /experimental/v1/probes/{probe}
probe_list GET /experimental/v1/probes
probe_view GET /experimental/v1/probes/{probe}
support_bundle_create POST /experimental/v1/system/support-bundles
support_bundle_delete DELETE /experimental/v1/system/support-bundles/{support_bundle}
support_bundle_download GET /experimental/v1/system/support-bundles/{support_bundle}/download
support_bundle_download_file GET /experimental/v1/system/support-bundles/{support_bundle}/download/{file}
support_bundle_head HEAD /experimental/v1/system/support-bundles/{support_bundle}/download
support_bundle_head_file HEAD /experimental/v1/system/support-bundles/{support_bundle}/download/{file}
support_bundle_index GET /experimental/v1/system/support-bundles/{support_bundle}/index
support_bundle_list GET /experimental/v1/system/support-bundles
support_bundle_view GET /experimental/v1/system/support-bundles/{support_bundle}
timeseries_query POST /v1/timeseries/query

API operations found with tag "images"
Expand Down
103 changes: 103 additions & 0 deletions nexus/external-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2780,6 +2780,109 @@ pub trait NexusExternalApi {
path_params: Path<params::SshKeyPath>,
) -> Result<HttpResponseDeleted, HttpError>;

// Support bundles (experimental)

/// List all support bundles
#[endpoint {
method = GET,
path = "/experimental/v1/system/support-bundles",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_list(
rqctx: RequestContext<Self::Context>,
query_params: Query<PaginatedById>,
) -> Result<HttpResponseOk<ResultsPage<shared::SupportBundleInfo>>, HttpError>;

/// View a support bundle
#[endpoint {
method = GET,
path = "/experimental/v1/system/support-bundles/{support_bundle}",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_view(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundlePath>,
) -> Result<HttpResponseOk<shared::SupportBundleInfo>, HttpError>;

/// Download the index of a support bundle
#[endpoint {
method = GET,
path = "/experimental/v1/system/support-bundles/{support_bundle}/index",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_index(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError>;

/// Download the contents of a support bundle
#[endpoint {
method = GET,
path = "/experimental/v1/system/support-bundles/{support_bundle}/download",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_download(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError>;

/// Download a file within a support bundle
#[endpoint {
method = GET,
path = "/experimental/v1/system/support-bundles/{support_bundle}/download/{file}",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_download_file(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundleFilePath>,
) -> Result<Response<Body>, HttpError>;

/// Download the metadata of a support bundle
#[endpoint {
method = HEAD,
path = "/experimental/v1/system/support-bundles/{support_bundle}/download",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_head(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError>;

/// Download the metadata of a file within the support bundle
#[endpoint {
method = HEAD,
path = "/experimental/v1/system/support-bundles/{support_bundle}/download/{file}",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_head_file(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundleFilePath>,
) -> Result<Response<Body>, HttpError>;

/// Create a new support bundle
#[endpoint {
method = POST,
path = "/experimental/v1/system/support-bundles",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_create(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseCreated<shared::SupportBundleInfo>, HttpError>;

/// Delete an existing support bundle
///
/// May also be used to cancel a support bundle which is currently being
/// collected, or to remove metadata for a support bundle that has failed.
#[endpoint {
method = DELETE,
path = "/experimental/v1/system/support-bundles/{support_bundle}",
tags = ["hidden"], // system/support-bundles: only one tag is allowed
}]
async fn support_bundle_delete(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::SupportBundlePath>,
) -> Result<HttpResponseDeleted, HttpError>;

// Probes (experimental)

/// List instrumentation probes
Expand Down
207 changes: 207 additions & 0 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6026,6 +6026,213 @@ impl NexusExternalApi for NexusExternalApiImpl {
.await
}

async fn support_bundle_list(
rqctx: RequestContext<ApiContext>,
_query_params: Query<PaginatedById>,
) -> Result<HttpResponseOk<ResultsPage<shared::SupportBundleInfo>>, HttpError>
{
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_view(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundlePath>,
) -> Result<HttpResponseOk<shared::SupportBundleInfo>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_index(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_download(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_download_file(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundleFilePath>,
) -> Result<Response<Body>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_head(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundlePath>,
) -> Result<Response<Body>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_head_file(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundleFilePath>,
) -> Result<Response<Body>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_create(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseCreated<shared::SupportBundleInfo>, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn support_bundle_delete(
rqctx: RequestContext<Self::Context>,
_path_params: Path<params::SupportBundlePath>,
) -> Result<HttpResponseDeleted, HttpError> {
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;

let opctx =
crate::context::op_context_for_external_api(&rqctx).await?;

Err(nexus
.unimplemented_todo(&opctx, crate::app::Unimpl::Public)
.await
.into())
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}

async fn probe_list(
rqctx: RequestContext<ApiContext>,
query_params: Query<PaginatedByNameOrId<params::ProjectSelector>>,
Expand Down
9 changes: 9 additions & 0 deletions nexus/tests/output/uncovered-authz-endpoints.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
API endpoints with no coverage in authz tests:
probe_delete (delete "/experimental/v1/probes/{probe}")
support_bundle_delete (delete "/experimental/v1/system/support-bundles/{support_bundle}")
probe_list (get "/experimental/v1/probes")
probe_view (get "/experimental/v1/probes/{probe}")
support_bundle_list (get "/experimental/v1/system/support-bundles")
support_bundle_view (get "/experimental/v1/system/support-bundles/{support_bundle}")
support_bundle_download (get "/experimental/v1/system/support-bundles/{support_bundle}/download")
support_bundle_download_file (get "/experimental/v1/system/support-bundles/{support_bundle}/download/{file}")
support_bundle_index (get "/experimental/v1/system/support-bundles/{support_bundle}/index")
ping (get "/v1/ping")
networking_switch_port_status (get "/v1/system/hardware/switch-port/{port}/status")
support_bundle_head (head "/experimental/v1/system/support-bundles/{support_bundle}/download")
support_bundle_head_file (head "/experimental/v1/system/support-bundles/{support_bundle}/download/{file}")
device_auth_request (post "/device/auth")
device_auth_confirm (post "/device/confirm")
device_access_token (post "/device/token")
probe_create (post "/experimental/v1/probes")
support_bundle_create (post "/experimental/v1/system/support-bundles")
login_saml (post "/login/{silo_name}/saml/{provider_name}")
login_local (post "/v1/login/{silo_name}/local")
logout (post "/v1/logout")
10 changes: 10 additions & 0 deletions nexus/types/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ path_param!(AddressLotPath, address_lot, "address lot");
path_param!(ProbePath, probe, "probe");
path_param!(CertificatePath, certificate, "certificate");

id_path_param!(SupportBundlePath, support_bundle, "support bundle");
id_path_param!(GroupPath, group_id, "group");

// TODO: The hardware resources should be represented by its UUID or a hardware
Expand Down Expand Up @@ -142,6 +143,15 @@ impl From<Name> for SiloSelector {
}
}

#[derive(Serialize, Deserialize, JsonSchema)]
pub struct SupportBundleFilePath {
#[serde(flatten)]
pub bundle: SupportBundlePath,

/// The file within the bundle to download
pub file: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct OptionalSiloSelector {
/// Name or ID of the silo
Expand Down
Loading

0 comments on commit 1200dcb

Please sign in to comment.