summaryrefslogtreecommitdiffstats
path: root/src/remake/api.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/api.rs')
-rw-r--r--src/remake/api.rs57
1 files changed, 55 insertions, 2 deletions
diff --git a/src/remake/api.rs b/src/remake/api.rs
index 5c10b66..5e99d77 100644
--- a/src/remake/api.rs
+++ b/src/remake/api.rs
@@ -16,7 +16,8 @@
//! is in progress, without having to know its ID.
//!
//! Request body for POST (all optional):
-//! `{ "replayWindow": {"epochs": 1, "seconds": 604800},
+//! `{ "kind": "compact", "spec": null,
+//! "replayWindow": {"epochs": 1, "seconds": 604800},
//! "archiveRetention": {"epochs": 2, "seconds": 2592000},
//! "settleSecs": 480 }`
@@ -35,12 +36,12 @@ use tokio::sync::broadcast;
use tracing::{error, info, warn};
use uuid::Uuid;
-use super::Host;
use super::coordinator;
use super::local::LocalStatus;
use super::records::{
self, AbortRequest, EpochRecord, Phase, Policy, Proposal, Record, Retention, unix_millis,
};
+use super::{Host, kind};
use crate::document::store::StoreError;
#[derive(Debug, Default, Deserialize)]
@@ -61,6 +62,10 @@ pub(crate) struct StartQuery {
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct StartRequest {
+ /// The remake kind; `compact` if absent.
+ kind: Option<String>,
+ /// Kind-specific part of the request.
+ spec: Option<JsonValue>,
replay_window: Option<Retention>,
archive_retention: Option<Retention>,
settle_secs: Option<u64>,
@@ -154,11 +159,30 @@ async fn wait_until_final<H: Host>(host: &H, id: &str) -> Option<Report> {
}
}
+/// `POST /admin/remakes`.
pub(crate) async fn start<H: Host>(
State(host): State<H>,
Query(query): Query<StartQuery>,
body: Bytes,
) -> Response {
+ start_kind(host, query, body, None).await
+}
+
+/// `POST /admin/compaction`: the original endpoint, for compacting remakes only.
+pub(crate) async fn start_compaction<H: Host>(
+ State(host): State<H>,
+ Query(query): Query<StartQuery>,
+ body: Bytes,
+) -> Response {
+ start_kind(host, query, body, Some(kind::DEFAULT_KIND)).await
+}
+
+async fn start_kind<H: Host>(
+ host: H,
+ query: StartQuery,
+ body: Bytes,
+ only: Option<&'static str>,
+) -> Response {
if !host.is_node_ready().await {
return error_response(StatusCode::SERVICE_UNAVAILABLE, "node is not ready", None);
}
@@ -178,6 +202,32 @@ pub(crate) async fn start<H: Host>(
}
};
+ let kind_name = request.kind.as_deref().unwrap_or(kind::DEFAULT_KIND);
+ if only.is_some_and(|only| only != kind_name) {
+ return error_response(
+ StatusCode::BAD_REQUEST,
+ format!("this endpoint only starts {} remakes", kind::DEFAULT_KIND),
+ None,
+ );
+ }
+ let Some(kind) = kind::by_name(kind_name) else {
+ return error_response(
+ StatusCode::BAD_REQUEST,
+ format!("unknown remake kind {kind_name:?}"),
+ None,
+ );
+ };
+ let prepared = match kind.propose(request.spec.as_ref().unwrap_or(&JsonValue::Null)) {
+ Ok(prepared) => prepared,
+ Err(reason) => {
+ return error_response(
+ StatusCode::BAD_REQUEST,
+ format!("invalid {kind_name} spec: {reason}"),
+ None,
+ );
+ }
+ };
+
let dormant_after = host.dormant_after();
let settle = request
.settle_secs
@@ -239,6 +289,9 @@ pub(crate) async fn start<H: Host>(
proposed_at: now,
settle_until: now + settle_ms,
peers_considered,
+ kind: kind.name().to_owned(),
+ content: prepared.content,
+ translation: prepared.translation,
};
// From here on, nothing depends on the client: writing the proposal and starting the