use std::io::Cursor;
use std::time::Duration;
use actix_web::App;
use actix_web::HttpServer;
use actix_web::web;
use actix_web::web::Data;
use openraft::Snapshot;
use openraft::errors::ClientWriteError;
use openraft::errors::Infallible;
use openraft::errors::RaftError;
use openraft::errors::decompose::DecomposeResult;
use openraft::raft;
use openraft::raft::SnapshotResponse;
use crate::admin::MembershipChange;
use crate::admin::Redirect;
use crate::app::EzApp;
use crate::network::SnapshotTransfer;
use crate::node_role::NodeRole;
use crate::raft::EzRaft;
use crate::type_config::OpenRaftTypes;
type C<T> = OpenRaftTypes<T>;
pub struct EzServer<T>
where T: EzApp
{
raft: EzRaft<T>,
}
impl<T> EzServer<T>
where T: EzApp
{
pub fn new(raft: EzRaft<T>) -> Self {
Self { raft }
}
pub async fn run(self) -> std::io::Result<()> {
let addr = self.raft.addr().to_string();
let server_data = Data::new(self);
let server = HttpServer::new(move || {
App::new()
.app_data(server_data.clone())
.route("/raft/append", web::post().to(Self::handle_append))
.route("/raft/vote", web::post().to(Self::handle_vote))
.route("/raft/snapshot", web::post().to(Self::handle_snapshot))
.route("/raft/transfer_leader", web::post().to(Self::handle_transfer_leader))
.route("/api/write", web::post().to(Self::handle_write))
.route("/api/read", web::post().to(Self::handle_read))
.route("/api/node_id", web::post().to(Self::handle_node_id))
.route("/api/membership", web::post().to(Self::handle_membership))
.route("/api/metrics", web::get().to(Self::handle_metrics))
})
.bind(&addr)?;
server.run().await
}
async fn handle_append(
req: web::Json<raft::AppendEntriesRequest<C<T>>>,
ez: Data<Self>,
) -> Result<web::Json<Result<raft::AppendEntriesResponse<C<T>>, Infallible>>, actix_web::Error> {
let resp = ez
.raft
.inner()
.append_entries(req.into_inner())
.await
.decompose()
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("append_entries failed: {}", e)))?;
Ok(web::Json(resp))
}
async fn handle_vote(
req: web::Json<raft::VoteRequest<C<T>>>,
ez: Data<Self>,
) -> Result<web::Json<Result<raft::VoteResponse<C<T>>, Infallible>>, actix_web::Error> {
let resp = ez
.raft
.inner()
.vote(req.into_inner())
.await
.decompose()
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("vote failed: {}", e)))?;
Ok(web::Json(resp))
}
async fn handle_transfer_leader(
req: web::Json<raft::TransferLeaderRequest<C<T>>>,
ez: Data<Self>,
) -> Result<web::Json<Result<raft::TransferLeaderResponse<C<T>>, Infallible>>, actix_web::Error> {
let resp = ez
.raft
.inner()
.handle_transfer_leader(req.into_inner())
.await
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("transfer_leader failed: {}", e)))?;
Ok(web::Json(Ok(resp)))
}
async fn handle_snapshot(
req: web::Json<SnapshotTransfer>,
ez: Data<Self>,
) -> Result<web::Json<Result<SnapshotResponse<C<T>>, Infallible>>, actix_web::Error> {
let SnapshotTransfer { vote, meta, data } = req.into_inner();
let snapshot = Snapshot {
meta,
snapshot: Cursor::new(data),
};
let resp = ez
.raft
.inner()
.install_full_snapshot(vote, snapshot)
.await
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("install_snapshot failed: {}", e)))?;
Ok(web::Json(Ok(resp)))
}
async fn handle_write(
req: web::Json<T::Request>,
ez: Data<Self>,
) -> Result<web::Json<T::Response>, actix_web::Error> {
let req = req.into_inner();
let mut refused = String::new();
for attempt in 0..2 {
let err = match ez.raft.inner().client_write(req.clone()).await {
Ok(resp) => {
let applied = resp
.data
.ok_or_else(|| actix_web::error::ErrorInternalServerError("write produced no response"))?;
return Ok(web::Json(applied));
}
Err(e) => e,
};
let RaftError::APIError(ClientWriteError::ForwardToLeader(to_leader)) = &err else {
return Err(actix_web::error::ErrorInternalServerError(format!(
"write failed: {}",
err
)));
};
match to_leader.leader_node.as_ref().map(|n| n.addr.as_str()) {
Some(leader) if leader != ez.raft.addr() => {
let applied = forward_write::<T>(leader, &req)
.await
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("write failed: {}", e)))?;
return Ok(web::Json(applied));
}
_ => {
refused = err.to_string();
if attempt == 0 {
let _ = ez
.raft
.inner()
.wait(Some(WAIT_FOR_LEADER))
.metrics(|m| m.current_leader.is_some(), "a leader to write to")
.await;
}
}
}
}
Err(actix_web::error::ErrorInternalServerError(format!(
"write failed: {}",
refused
)))
}
async fn handle_read(
req: web::Json<T::ReadRequest>,
ez: Data<Self>,
) -> Result<web::Json<T::ReadResponse>, actix_web::Error> {
let req = req.into_inner();
let resp = ez.raft.read(move |app| app.read(req)).await.map_err(actix_web::error::ErrorInternalServerError)?;
Ok(web::Json(resp))
}
async fn handle_metrics(ez: Data<Self>) -> Result<web::Json<openraft::RaftMetrics<C<T>>>, actix_web::Error> {
let metrics = ez.raft.metrics().await;
Ok(web::Json(metrics))
}
async fn handle_node_id(ez: Data<Self>) -> Result<web::Json<Redirect<u64>>, actix_web::Error> {
let leader = match Self::leader_or_redirect(&ez).await {
Ok(leader) => leader,
Err(redirect) => return Ok(web::Json(Err(redirect))),
};
let write_result = leader
.inner()
.write_blank()
.await
.map_err(|e| actix_web::error::ErrorInternalServerError(format!("node id write failed: {}", e)))?;
Ok(web::Json(Ok(write_result.log_id.index)))
}
async fn handle_membership(
req: web::Json<MembershipChange>,
ez: Data<Self>,
) -> Result<web::Json<Redirect<()>>, actix_web::Error> {
let leader = match Self::leader_or_redirect(&ez).await {
Ok(leader) => leader,
Err(redirect) => return Ok(web::Json(Err(redirect))),
};
let change = req.into_inner();
let changed = match &change {
MembershipChange::Add { node_id, addr } => leader.add_learner(*node_id, addr.clone()).await,
MembershipChange::SetRole { node_id, role } => match role {
NodeRole::Voter => leader.promote(*node_id).await,
NodeRole::Learner => leader.demote(*node_id).await,
},
MembershipChange::Remove { node_id } => leader.remove_node(*node_id).await,
};
changed.map_err(|e| actix_web::error::ErrorInternalServerError(format!("{:?} failed: {}", change, e)))?;
Ok(web::Json(Ok(())))
}
async fn leader_or_redirect(ez: &Data<Self>) -> Result<&EzRaft<T>, Option<String>> {
let metrics = ez.raft.metrics().await;
if metrics.current_leader == Some(metrics.id) {
return Ok(&ez.raft);
}
Err(metrics
.current_leader
.and_then(|leader_id| metrics.membership_config.membership().get_node(&leader_id).map(|n| n.addr.clone())))
}
}
pub(crate) async fn run<T>(raft: EzRaft<T>) -> std::io::Result<()>
where T: EzApp {
EzServer::new(raft).run().await
}
const WAIT_FOR_LEADER: Duration = Duration::from_secs(10);
const FORWARD_WRITE_TIMEOUT: Duration = Duration::from_secs(10);
async fn forward_write<T>(leader_addr: &str, req: &T::Request) -> Result<T::Response, std::io::Error>
where T: EzApp {
let client = reqwest::Client::builder()
.no_proxy()
.timeout(FORWARD_WRITE_TIMEOUT)
.build()
.map_err(|e| std::io::Error::other(e.to_string()))?;
let url = format!("http://{}/api/write", leader_addr);
let resp = client
.post(&url)
.json(req)
.send()
.await
.map_err(|e| std::io::Error::other(format!("forwarding write to {} failed: {}", url, e)))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(std::io::Error::other(format!("{} responded {}: {}", url, status, body)));
}
resp.json()
.await
.map_err(|e| std::io::Error::other(format!("failed to parse write response: {}", e)))
}