feat(chainfire): Add /admin/member/add legacy endpoint for cluster join
- AddMemberRequestLegacy accepts string node ID (e.g., 'node01')
- string_to_node_id converts to numeric node_id for Raft
- Required by first-boot-automation.nix cluster join logic
- Also fixes axum 0.8 route syntax (:param -> {param})
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9a72c8d3ec
commit
aa5973bb96
1 changed files with 57 additions and 3 deletions
|
|
@ -116,6 +116,15 @@ pub struct AddMemberRequest {
|
||||||
pub raft_addr: String,
|
pub raft_addr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add member request (legacy format from first-boot-automation)
|
||||||
|
/// Accepts string id and converts to numeric node_id
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AddMemberRequestLegacy {
|
||||||
|
/// Node ID as string (e.g., "node01", "node02")
|
||||||
|
pub id: String,
|
||||||
|
pub raft_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Query parameters for prefix scan
|
/// Query parameters for prefix scan
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct PrefixQuery {
|
pub struct PrefixQuery {
|
||||||
|
|
@ -125,12 +134,14 @@ pub struct PrefixQuery {
|
||||||
/// Build the REST API router
|
/// Build the REST API router
|
||||||
pub fn build_router(state: RestApiState) -> Router {
|
pub fn build_router(state: RestApiState) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/api/v1/kv/:key", get(get_kv))
|
.route("/api/v1/kv/{key}", get(get_kv))
|
||||||
.route("/api/v1/kv/:key", put(put_kv))
|
.route("/api/v1/kv/{key}", put(put_kv))
|
||||||
.route("/api/v1/kv/:key", delete(delete_kv))
|
.route("/api/v1/kv/{key}", delete(delete_kv))
|
||||||
.route("/api/v1/kv", get(list_kv))
|
.route("/api/v1/kv", get(list_kv))
|
||||||
.route("/api/v1/cluster/status", get(cluster_status))
|
.route("/api/v1/cluster/status", get(cluster_status))
|
||||||
.route("/api/v1/cluster/members", post(add_member))
|
.route("/api/v1/cluster/members", post(add_member))
|
||||||
|
// Legacy endpoint for first-boot-automation compatibility
|
||||||
|
.route("/admin/member/add", post(add_member_legacy))
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
@ -258,6 +269,22 @@ async fn cluster_status(
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert string node ID to numeric (e.g., "node01" -> 1, "node02" -> 2)
|
||||||
|
fn string_to_node_id(s: &str) -> u64 {
|
||||||
|
// Try to extract number from string like "node01", "node02"
|
||||||
|
if let Some(num_str) = s.strip_prefix("node") {
|
||||||
|
if let Ok(num) = num_str.parse::<u64>() {
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback: use hash of the string
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
s.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// POST /api/v1/cluster/members - Add member
|
/// POST /api/v1/cluster/members - Add member
|
||||||
async fn add_member(
|
async fn add_member(
|
||||||
State(state): State<RestApiState>,
|
State(state): State<RestApiState>,
|
||||||
|
|
@ -286,6 +313,33 @@ async fn add_member(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST /admin/member/add - Add member (legacy format for first-boot-automation)
|
||||||
|
async fn add_member_legacy(
|
||||||
|
State(state): State<RestApiState>,
|
||||||
|
Json(req): Json<AddMemberRequestLegacy>,
|
||||||
|
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
let node_id = string_to_node_id(&req.id);
|
||||||
|
|
||||||
|
let rpc_client = state
|
||||||
|
.rpc_client
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| error_response(StatusCode::SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE", "RPC client not available"))?;
|
||||||
|
|
||||||
|
// Add node to RPC client's routing table
|
||||||
|
rpc_client.add_node(node_id, req.raft_addr.clone()).await;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::CREATED,
|
||||||
|
Json(SuccessResponse::new(serde_json::json!({
|
||||||
|
"id": req.id,
|
||||||
|
"node_id": node_id,
|
||||||
|
"raft_addr": req.raft_addr,
|
||||||
|
"success": true,
|
||||||
|
"note": "Node registered in RPC client routing table (legacy API)"
|
||||||
|
}))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to create error response
|
/// Helper to create error response
|
||||||
fn error_response(
|
fn error_response(
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue