use std::collections::HashSet;
use std::sync::Arc;
use actix_web::web::Data;
use actix_web::web::Json;
use actix_web::{HttpResponse, Responder};
use async_raft_ext::raft::ClientWriteRequest;
use serde_json::json;
use crate::common::appdata::AppShareData;
use crate::raft::join_node;
use crate::raft::store::ClientRequest;
use crate::raft::store::NodeId;
pub async fn join_learner(
app: Data<Arc<AppShareData>>,
req: Json<(NodeId, String)>,
) -> actix_web::Result<impl Responder> {
let node_id = req.0 .0;
let addr = Arc::new(req.0 .1);
app.raft
.client_write(ClientWriteRequest::new(ClientRequest::NodeAddr {
id: node_id,
addr,
}))
.await
.unwrap();
app.raft.add_non_voter(node_id).await.unwrap();
join_node(app.raft.as_ref(), app.raft_store.as_ref(), node_id)
.await
.ok();
Ok("{\"ok\":1}")
}
pub async fn add_learner(
app: Data<Arc<AppShareData>>,
req: Json<(NodeId, String)>,
) -> actix_web::Result<impl Responder> {
let node_id = req.0 .0;
let addr = Arc::new(req.0 .1);
app.raft
.client_write(ClientWriteRequest::new(ClientRequest::NodeAddr {
id: node_id,
addr,
}))
.await
.unwrap();
app.raft.add_non_voter(node_id).await.unwrap();
Ok("{\"ok\":1}")
}
pub async fn change_membership(
app: Data<Arc<AppShareData>>,
req: Json<HashSet<NodeId>>,
) -> actix_web::Result<impl Responder> {
app.raft.change_membership(req.0).await.unwrap();
Ok("{\"ok\":1}")
}
pub async fn init(app: Data<Arc<AppShareData>>) -> actix_web::Result<impl Responder> {
let mut members = HashSet::new();
let node_id = app.sys_config.raft_node_id.to_owned();
members.insert(node_id);
app.raft.initialize(members).await.ok();
app.raft
.client_write(ClientWriteRequest::new(ClientRequest::NodeAddr {
id: node_id,
addr: Arc::new(app.sys_config.raft_node_addr.to_owned()),
}))
.await
.unwrap();
Ok("{\"ok\":1}")
}
pub async fn metrics(app: Data<Arc<AppShareData>>) -> actix_web::Result<impl Responder> {
let metrics = app.raft.metrics().borrow().clone();
Ok(Json(metrics))
}
pub async fn close_write(app: Data<Arc<AppShareData>>) -> actix_web::Result<impl Responder> {
let local_db_dir = &app.sys_config.local_db_dir;
if !close_raft_mark_exists(local_db_dir.as_str()) {
log::warn!(
"close_write rejected, mark file not found: {}/close_raft_mark",
local_db_dir
);
return Ok(HttpResponse::Ok().json(json!({
"ok": 0,
"msg": format!("执行前需要在指定目录({})创建对应标记文件 close_raft_mark", local_db_dir)
})));
}
app.raft_store.set_close_write();
log::info!(
"close_write enabled, raft write disabled, local_db_dir:{}",
local_db_dir
);
Ok(HttpResponse::Ok().json(json!({ "ok": 1 })))
}
#[cfg(feature = "debug")]
#[derive(serde::Deserialize)]
pub struct InjectErrorReq {
pub scene: String,
pub times: u64,
}
#[cfg(feature = "debug")]
pub async fn inject_error(
app: Data<Arc<AppShareData>>,
req: Json<InjectErrorReq>,
) -> actix_web::Result<impl Responder> {
match req.scene.as_str() {
"discard_log" => match app.raft_store.inject_discard_log(req.times).await {
Ok(_) => {
log::warn!(
"inject_error discard_log ok, node:{} times:{}",
app.sys_config.raft_node_id,
req.times
);
Ok(HttpResponse::Ok().json(json!({ "ok": 1 })))
}
Err(e) => {
log::error!(
"inject_error discard_log failed, node:{} times:{} err:{:?}",
app.sys_config.raft_node_id,
req.times,
e
);
Ok(HttpResponse::Ok().json(json!({ "ok": 0, "msg": format!("{:?}", e) })))
}
},
other => {
log::warn!("inject_error unknown scene:{}", other);
Ok(HttpResponse::Ok()
.json(json!({ "ok": 0, "msg": format!("unknown scene: {other}") })))
}
}
}
fn close_raft_mark_exists(local_db_dir: &str) -> bool {
std::path::Path::new(local_db_dir)
.join("close_raft_mark")
.exists()
}
#[cfg(test)]
mod tests {
use super::close_raft_mark_exists;
use tempfile::tempdir;
#[test]
fn close_raft_mark_not_exists_when_absent() {
let dir = tempdir().unwrap();
assert!(!close_raft_mark_exists(dir.path().to_str().unwrap()));
}
#[test]
fn close_raft_mark_exists_when_present() {
let dir = tempdir().unwrap();
let mark = dir.path().join("close_raft_mark");
std::fs::write(&mark, b"").unwrap();
assert!(close_raft_mark_exists(dir.path().to_str().unwrap()));
}
}