From 4bfe75a1d7c2c0256f679a4a2d00cd992c6970f7 Mon Sep 17 00:00:00 2001 From: centra Date: Fri, 19 Dec 2025 16:54:06 +0900 Subject: [PATCH] feat(flaredb): Add --http-addr CLI flag and region peer management API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --http-addr CLI flag for HTTP REST bind address - Fix config env var parsing (FLAREDB_HTTP_ADDR wasn't working due to separator conflict) - Add GET /api/v1/regions/{id} endpoint to view region info - Add POST /api/v1/regions/{id}/add_peer endpoint for multi-peer region management - Update NixOS module to use --http-addr 0.0.0.0 CLI flag instead of env var This enables FlareDB region cluster formation with multiple peers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- flaredb/crates/flaredb-server/src/main.rs | 11 +++- flaredb/crates/flaredb-server/src/rest.rs | 75 +++++++++++++++++++++++ nix/modules/flaredb.nix | 8 +-- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/flaredb/crates/flaredb-server/src/main.rs b/flaredb/crates/flaredb-server/src/main.rs index 66bca87..2ee2df8 100644 --- a/flaredb/crates/flaredb-server/src/main.rs +++ b/flaredb/crates/flaredb-server/src/main.rs @@ -44,6 +44,10 @@ struct Args { #[arg(long)] addr: Option, + /// Listen address for HTTP REST API (overrides config) + #[arg(long)] + http_addr: Option, + /// Data directory for RocksDB (overrides config) #[arg(long)] data_dir: Option, @@ -99,7 +103,10 @@ async fn main() -> Result<(), Box> { .addr .map(|s| s.parse().unwrap_or(loaded_config.addr)) .unwrap_or(loaded_config.addr), - http_addr: loaded_config.http_addr, + http_addr: args + .http_addr + .map(|s| s.parse().unwrap_or(loaded_config.http_addr)) + .unwrap_or(loaded_config.http_addr), data_dir: args.data_dir.unwrap_or(loaded_config.data_dir), pd_addr: args.pd_addr.unwrap_or(loaded_config.pd_addr), peers: if args.peers.is_empty() { @@ -441,6 +448,8 @@ async fn main() -> Result<(), Box> { let http_addr = server_config.http_addr; let rest_state = rest::RestApiState { server_addr: server_config.addr.to_string(), + pd_addr: server_config.pd_addr.clone(), + store_id: server_config.store_id, }; let rest_app = rest::build_router(rest_state); let http_listener = tokio::net::TcpListener::bind(&http_addr).await?; diff --git a/flaredb/crates/flaredb-server/src/rest.rs b/flaredb/crates/flaredb-server/src/rest.rs index d79bfac..eb29063 100644 --- a/flaredb/crates/flaredb-server/src/rest.rs +++ b/flaredb/crates/flaredb-server/src/rest.rs @@ -14,6 +14,7 @@ use axum::{ routing::{get, post, put}, Json, Router, }; +use crate::pd_client::PdClient; use flaredb_client::RdbClient; use flaredb_sql::executor::{ExecutionResult, SqlExecutor}; use serde::{Deserialize, Serialize}; @@ -23,6 +24,8 @@ use std::sync::Arc; #[derive(Clone)] pub struct RestApiState { pub server_addr: String, + pub pd_addr: String, + pub store_id: u64, } /// Standard REST error response @@ -127,6 +130,20 @@ pub struct ScanResponse { pub items: Vec, } +/// Add peer request +#[derive(Debug, Deserialize)] +pub struct AddPeerRequest { + pub peer_id: u64, +} + +/// Region info response +#[derive(Debug, Serialize)] +pub struct RegionResponse { + pub id: u64, + pub peers: Vec, + pub leader_id: u64, +} + /// Build the REST API router pub fn build_router(state: RestApiState) -> Router { Router::new() @@ -134,6 +151,8 @@ pub fn build_router(state: RestApiState) -> Router { .route("/api/v1/tables", get(list_tables)) .route("/api/v1/kv/{key}", get(get_kv).put(put_kv)) .route("/api/v1/scan", get(scan_kv)) + .route("/api/v1/regions/{id}", get(get_region)) + .route("/api/v1/regions/{id}/add_peer", post(add_peer_to_region)) .route("/health", get(health_check)) .with_state(state) } @@ -245,6 +264,62 @@ async fn scan_kv( Ok(Json(SuccessResponse::new(ScanResponse { items }))) } +/// GET /api/v1/regions/{id} - Get region info +async fn get_region( + State(state): State, + Path(id): Path, +) -> Result>, (StatusCode, Json)> { + let mut pd_client = PdClient::connect(state.pd_addr.clone()) + .await + .map_err(|e| error_response(StatusCode::SERVICE_UNAVAILABLE, "PD_UNAVAILABLE", &format!("Failed to connect to PD: {}", e)))?; + + let region = pd_client + .get_region(id) + .await + .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))? + .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", &format!("Region {} not found", id)))?; + + Ok(Json(SuccessResponse::new(RegionResponse { + id: region.id, + peers: region.peers, + leader_id: region.leader_id, + }))) +} + +/// POST /api/v1/regions/{id}/add_peer - Add peer to region +async fn add_peer_to_region( + State(state): State, + Path(id): Path, + Json(req): Json, +) -> Result>, (StatusCode, Json)> { + let mut pd_client = PdClient::connect(state.pd_addr.clone()) + .await + .map_err(|e| error_response(StatusCode::SERVICE_UNAVAILABLE, "PD_UNAVAILABLE", &format!("Failed to connect to PD: {}", e)))?; + + let mut region = pd_client + .get_region(id) + .await + .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))? + .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", &format!("Region {} not found", id)))?; + + // Add peer if not already present + if !region.peers.contains(&req.peer_id) { + region.peers.push(req.peer_id); + region.peers.sort(); + + pd_client + .put_region(region.clone()) + .await + .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))?; + } + + Ok(Json(SuccessResponse::new(RegionResponse { + id: region.id, + peers: region.peers, + leader_id: region.leader_id, + }))) +} + /// Helper to create error response fn error_response( status: StatusCode, diff --git a/nix/modules/flaredb.nix b/nix/modules/flaredb.nix index 471065e..0ede034 100644 --- a/nix/modules/flaredb.nix +++ b/nix/modules/flaredb.nix @@ -62,10 +62,6 @@ in after = [ "network.target" "chainfire.service" ]; requires = [ "chainfire.service" ]; - environment = { - FLAREDB_HTTP_ADDR = "0.0.0.0:${toString cfg.httpPort}"; - }; - serviceConfig = { Type = "simple"; User = "flaredb"; @@ -84,8 +80,8 @@ in ProtectHome = true; ReadWritePaths = [ cfg.dataDir ]; - # Start command - ExecStart = "${cfg.package}/bin/flaredb-server --addr 0.0.0.0:${toString cfg.port} --data-dir ${cfg.dataDir}"; + # Start command - use CLI flags for bind addresses + ExecStart = "${cfg.package}/bin/flaredb-server --addr 0.0.0.0:${toString cfg.port} --http-addr 0.0.0.0:${toString cfg.httpPort} --data-dir ${cfg.dataDir}"; }; }; };