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,
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PrefixQuery {
|
||||
|
|
@ -125,12 +134,14 @@ pub struct PrefixQuery {
|
|||
/// Build the REST API router
|
||||
pub fn build_router(state: RestApiState) -> Router {
|
||||
Router::new()
|
||||
.route("/api/v1/kv/:key", get(get_kv))
|
||||
.route("/api/v1/kv/:key", put(put_kv))
|
||||
.route("/api/v1/kv/:key", delete(delete_kv))
|
||||
.route("/api/v1/kv/{key}", get(get_kv))
|
||||
.route("/api/v1/kv/{key}", put(put_kv))
|
||||
.route("/api/v1/kv/{key}", delete(delete_kv))
|
||||
.route("/api/v1/kv", get(list_kv))
|
||||
.route("/api/v1/cluster/status", get(cluster_status))
|
||||
.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))
|
||||
.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
|
||||
async fn add_member(
|
||||
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
|
||||
fn error_response(
|
||||
status: StatusCode,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue