Skip to content

Commit

Permalink
[π˜€π—½π—Ώ] changes to main this commit is based on
Browse files Browse the repository at this point in the history
Created using spr 1.3.6-beta.1

[skip ci]
  • Loading branch information
sunshowers committed Jul 13, 2024
1 parent fe60eb9 commit 85ebaf5
Show file tree
Hide file tree
Showing 31 changed files with 340 additions and 531 deletions.
35 changes: 14 additions & 21 deletions Cargo.lock

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

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = [
"clients/dns-service-client",
"clients/dpd-client",
"clients/gateway-client",
"clients/installinator-artifact-client",
"clients/installinator-client",
"clients/nexus-client",
"clients/oxide-client",
"clients/oximeter-client",
Expand All @@ -31,7 +31,7 @@ members = [
"gateway-test-utils",
"gateway",
"illumos-utils",
"installinator-artifactd",
"installinator-api",
"installinator-common",
"installinator",
"internal-dns-cli",
Expand Down Expand Up @@ -100,7 +100,7 @@ default-members = [
"clients/dns-service-client",
"clients/dpd-client",
"clients/gateway-client",
"clients/installinator-artifact-client",
"clients/installinator-client",
"clients/nexus-client",
"clients/oxide-client",
"clients/oximeter-client",
Expand All @@ -125,7 +125,7 @@ default-members = [
"gateway-test-utils",
"gateway",
"illumos-utils",
"installinator-artifactd",
"installinator-api",
"installinator-common",
"installinator",
"internal-dns-cli",
Expand Down Expand Up @@ -318,8 +318,8 @@ indent_write = "2.2.0"
indexmap = "2.2.6"
indicatif = { version = "0.17.8", features = ["rayon"] }
installinator = { path = "installinator" }
installinator-artifactd = { path = "installinator-artifactd" }
installinator-artifact-client = { path = "clients/installinator-artifact-client" }
installinator-api = { path = "installinator-api" }
installinator-client = { path = "clients/installinator-client" }
installinator-common = { path = "installinator-common" }
internal-dns = { path = "internal-dns" }
ipcc = { path = "ipcc" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "installinator-artifact-client"
name = "installinator-client"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// 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/.

//! Interface for making API requests to installinator-artifactd.
//! Interface for installinator to make API requests.
progenitor::generate_api!(
spec = "../../openapi/installinator-artifactd.json",
spec = "../../openapi/installinator.json",
inner_type = slog::Logger,
pre_hook = (|log: &slog::Logger, request: &reqwest::Request| {
slog::debug!(log, "client request";
Expand Down
1 change: 1 addition & 0 deletions dev-tools/openapi-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ clap.workspace = true
dropshot.workspace = true
fs-err.workspace = true
indent_write.workspace = true
installinator-api.workspace = true
nexus-internal-api.workspace = true
omicron-workspace-hack.workspace = true
openapiv3.workspace = true
Expand Down
12 changes: 12 additions & 0 deletions dev-tools/openapi-manager/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ use openapiv3::OpenAPI;
/// All APIs managed by openapi-manager.
pub fn all_apis() -> Vec<ApiSpec> {
vec![
ApiSpec {
title: "Installinator API".to_string(),
version: "0.0.1".to_string(),
description: "API for installinator to fetch artifacts \
and report progress"
.to_string(),
boundary: ApiBoundary::Internal,
api_description:
installinator_api::installinator_api::stub_api_description,
filename: "installinator.json".to_string(),
extra_validation: None,
},
ApiSpec {
title: "Nexus internal API".to_string(),
version: "0.0.1".to_string(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "installinator-artifactd"
name = "installinator-api"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
Expand All @@ -9,24 +9,12 @@ workspace = true

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
clap.workspace = true
dropshot.workspace = true
hyper.workspace = true
installinator-common.workspace = true
omicron-common.workspace = true
omicron-workspace-hack.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
slog.workspace = true
uuid.workspace = true

installinator-common.workspace = true
omicron-common.workspace = true
omicron-workspace-hack.workspace = true

[dev-dependencies]
expectorate.workspace = true
omicron-test-utils.workspace = true
openapiv3.workspace = true
openapi-lint.workspace = true
serde_json.workspace = true
subprocess.workspace = true
167 changes: 167 additions & 0 deletions installinator-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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/.

//! The REST API that installinator is a client of.
//!
//! Note that most of our APIs are named by their server. This one is instead
//! named by the client, since it is expected that multiple services will
//! implement it.
use anyhow::{anyhow, Result};
use dropshot::{
ConfigDropshot, FreeformBody, HandlerTaskMode, HttpError,
HttpResponseHeaders, HttpResponseOk, HttpResponseUpdatedNoContent,
HttpServerStarter, Path, RequestContext, TypedBody,
};
use hyper::{header, Body, StatusCode};
use installinator_common::EventReport;
use omicron_common::update::ArtifactHashId;
use schemars::JsonSchema;
use serde::Deserialize;
use uuid::Uuid;

#[derive(Debug, Deserialize, JsonSchema)]
pub struct ReportQuery {
/// A unique identifier for the update.
pub update_id: Uuid,
}

#[dropshot::api_description]
pub trait InstallinatorApi {
type Context;

/// Fetch an artifact by hash.
#[endpoint {
method = GET,
path = "/artifacts/by-hash/{kind}/{hash}",
}]
async fn get_artifact_by_hash(
rqctx: RequestContext<Self::Context>,
path: Path<ArtifactHashId>,
) -> Result<HttpResponseHeaders<HttpResponseOk<FreeformBody>>, HttpError>;

/// Report progress and completion to the server.
///
/// This method requires an `update_id` path parameter. This update ID is
/// matched against the server currently performing an update. If the
/// server is unaware of the update ID, it will return an HTTP 422
/// Unprocessable Entity code.
#[endpoint {
method = POST,
path = "/report-progress/{update_id}",
}]
async fn report_progress(
rqctx: RequestContext<Self::Context>,
path: Path<ReportQuery>,
report: TypedBody<EventReport>,
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
}

/// Add a content length header to a response.
///
/// Intended to be called by `get_artifact_by_hash` implementations.
pub fn body_to_artifact_response(
size: u64,
body: Body,
) -> HttpResponseHeaders<HttpResponseOk<FreeformBody>> {
let mut response =
HttpResponseHeaders::new_unnamed(HttpResponseOk(body.into()));
let headers = response.headers_mut();
headers.append(header::CONTENT_LENGTH, size.into());
response
}

/// The result of processing an installinator event report.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[must_use]
pub enum EventReportStatus {
/// This report was processed by the server.
Processed,

/// The update ID was not recognized by the server.
UnrecognizedUpdateId,

/// The progress receiver is closed.
ReceiverClosed,
}

impl EventReportStatus {
/// Convert this status to an HTTP result.
///
/// Intended to be called by `report_progress` implementations.
pub fn to_http_result(
self,
update_id: Uuid,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
match self {
EventReportStatus::Processed => Ok(HttpResponseUpdatedNoContent()),
EventReportStatus::UnrecognizedUpdateId => {
Err(HttpError::for_client_error(
None,
StatusCode::UNPROCESSABLE_ENTITY,
format!(
"update ID {update_id} unrecognized by this server"
),
))
}
EventReportStatus::ReceiverClosed => {
Err(HttpError::for_client_error(
None,
StatusCode::GONE,
format!("update ID {update_id}: receiver closed"),
))
}
}
}
}

/// Creates a default `ConfigDropshot` for the installinator API.
pub fn default_config(bind_address: std::net::SocketAddr) -> ConfigDropshot {
ConfigDropshot {
bind_address,
// Even though the installinator sets an upper bound on the number of
// items in a progress report, they can get pretty large if they
// haven't gone through for a bit. Ensure that hitting the max request
// size won't cause a failure by setting a generous upper bound for the
// request size.
//
// TODO: replace with an endpoint-specific option once
// https://github.com/oxidecomputer/dropshot/pull/618 lands and is
// available in omicron.
request_body_max_bytes: 4 * 1024 * 1024,
default_handler_task_mode: HandlerTaskMode::Detached,
}
}

/// Make an `HttpServerStarter` for the installinator API with default settings.
pub fn make_server_starter<T: InstallinatorApi>(
context: T::Context,
bind_address: std::net::SocketAddr,
log: &slog::Logger,
) -> Result<HttpServerStarter<T::Context>> {
let dropshot_config = dropshot::ConfigDropshot {
bind_address,
// Even though the installinator sets an upper bound on the number
// of items in a progress report, they can get pretty large if they
// haven't gone through for a bit. Ensure that hitting the max
// request size won't cause a failure by setting a generous upper
// bound for the request size.
//
// TODO: replace with an endpoint-specific option once
// https://github.com/oxidecomputer/dropshot/pull/618 lands and is
// available in omicron.
request_body_max_bytes: 4 * 1024 * 1024,
default_handler_task_mode: HandlerTaskMode::Detached,
};

let api = crate::installinator_api::api_description::<T>()?;
let server =
dropshot::HttpServerStarter::new(&dropshot_config, api, context, &log)
.map_err(|error| {
anyhow!(error)
.context("failed to create installinator artifact server")
})?;

Ok(server)
}
Loading

0 comments on commit 85ebaf5

Please sign in to comment.