From c9559af92ba2e3d27188d0a8efd1861180613840 Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 21 Oct 2026 04:08:26 +0200 Subject: turn compaction into a kind of remake --- src/remake/api.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) (limited to 'src/remake/api.rs') diff --git a/src/remake/api.rs b/src/remake/api.rs index b9c0625..a96426e 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 }` @@ -40,7 +41,7 @@ use super::local::LocalStatus; use super::records::{ self, unix_millis, AbortRequest, EpochRecord, Phase, Policy, Proposal, Record, Retention, }; -use super::Host; +use super::{kind, Host}; 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, + /// Kind-specific part of the request. + spec: Option, replay_window: Option, archive_retention: Option, settle_secs: Option, @@ -154,10 +159,29 @@ async fn wait_until_final(host: &H, id: &str) -> Option { } } +/// `POST /admin/remakes`. pub(crate) async fn start( State(host): State, Query(query): Query, 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( + State(host): State, + Query(query): Query, + body: Bytes, +) -> Response { + start_kind(host, query, body, Some(kind::DEFAULT_KIND)).await +} + +async fn start_kind( + 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( } }; + 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( 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 -- cgit v1.3.1