diff --git a/Cargo.lock b/Cargo.lock index 81190efe2fa413be6ddafd4f5fae8635eea4dc2c..6434513679d3724921a8471f734d0976c85cf272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2374,6 +2374,7 @@ dependencies = [ "clap_mangen", "diesel", "diesel_migrations", + "dirs", "env_logger", "futures", "futures-util", diff --git a/bin/nanocl/src/commands/generic.rs b/bin/nanocl/src/commands/generic.rs index 11d1b48e171084296a713948a59ff8ff86c0d382..ea8abbfde281b6da2764c0361b21dffad0094ada 100644 --- a/bin/nanocl/src/commands/generic.rs +++ b/bin/nanocl/src/commands/generic.rs @@ -5,6 +5,7 @@ use std::io::Write; use dirs::home_dir; use serde_json::json; use nanocld_client::stubs::node::Node; +use std::fs; use nanocl_error::{ http_client::HttpClientError, @@ -19,7 +20,8 @@ use nanocld_client::{ }; use serde::{de::DeserializeOwned, Serialize}; use gethostname::gethostname; - +use ntex::http::client::ClientResponse; +use url::quirks::hostname; use crate::{ config::CliConfig, models::{ @@ -317,17 +319,36 @@ pub trait GenericCommandJoin: GenericCommand { opts: &GenericJoinOpts, ) -> IoResult<()> { + + let mut local_client = client.clone(); + if local_client.unix_socket != None { + local_client.url = "http://localhost".to_owned(); + local_client.unix_socket = Some(String::from("/run/nanocl/nanocl.sock")); + } + let hostname = gethostname().into_string().unwrap_or_else(|_| "Unknown".to_string()); - let mut res = client + let mut res = match local_client .send_get( &format!("/{}/get_by_name?node_name={}", Self::object_name(), hostname), None::<()> ) - .await?; + .await{ + Ok(res) => res, + Err(_) => { + println!("Node join Error! Be sure your node name is your hostname!"); + return Ok(()) + } + }; let mut node_info: Node = res .json() .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + if node_info.role == "site" { + println!("Already joined a cluster! master ip is: {}",node_info.master_endpoint); + return Ok(()) + } + node_info.role = "site".parse().unwrap(); node_info.master_endpoint = opts.master_ip.clone(); @@ -389,9 +410,80 @@ pub trait GenericCommandDelete: GenericCommand { opts: &GenericDeleteOpts, ) -> IoResult<()> { - println!("client url: {}", client.url); - println!("master ip: {}", opts.node_name); - println!("exec_delete!"); + let mut res = match client + .send_get( + &format!("/{}/get_by_name?node_name={}", Self::object_name(), opts.node_name), + None::<()> + ) + .await { + Ok(res) => res, + Err(e) => { + println!("Node Delete Error: {}",e); + return Ok(()) + } + }; + let mut node_info: Node = res + .json() + .await + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + if node_info.role == "master" { + println!("cannot delete the master node!"); + return Ok(()) + } + + let res = match client + .send_delete( + &format!("/{}/delete_by_name?node_name={}", Self::object_name(), opts.node_name), + None::<()> + ) + .await { + Ok(res) => { + println!("Node {} Delete Success", opts.node_name); + res + }, + Err(e) => { + println!("Node Delete Error: {}", e); + return Ok(()) + } + }; + + if res.status().is_success() { + + let hostname = gethostname().into_string().unwrap_or_else(|_| "Unknown".to_string()); + + let local_client: NanocldClient; + + if node_info.name == hostname{ + local_client = NanocldClient{ + url: "http://localhost".to_owned(), + version: node_info.version.clone(), + unix_socket: Some(String::from("/run/nanocl/nanocl.sock")), + ssl: None, + }; + } else { + local_client = NanocldClient{ + url: format!("http://{}:8585",node_info.endpoint), + version: node_info.version.clone(), + unix_socket: None, + ssl: None, + }; + } + + node_info.role = "master".parse().unwrap(); + node_info.master_endpoint = node_info.endpoint.clone(); + + local_client.send_post( + &format!("/{}/update_by_name", Self::object_name()), + Some(node_info), + None::<()> + ).await?; + local_client.send_delete( + &format!("/{}/delete_conf", Self::object_name()), + None::<()>, + ).await?; + } + Ok(()) } } \ No newline at end of file diff --git a/bin/nanocld/Cargo.toml b/bin/nanocld/Cargo.toml index 54447bd28d3ea0b8daa9418235b7b29ff9ac4115..68404498e1bc2c98558dee26f6375f85044eb33f 100644 --- a/bin/nanocld/Cargo.toml +++ b/bin/nanocld/Cargo.toml @@ -98,4 +98,5 @@ tonic = "0.11.0" tower = "0.4.10" thiserror = "1.0.63" serde_urlencoded = "0.7.1" -http = "1.1.0" \ No newline at end of file +http = "1.1.0" +dirs = "5.0.1" \ No newline at end of file diff --git a/bin/nanocld/src/main.rs b/bin/nanocld/src/main.rs index bff2b2a2777331958afa452486a46bde2f140bcf..d3e3c8eae35d002c21aa18dd89dfe128574868f3 100644 --- a/bin/nanocld/src/main.rs +++ b/bin/nanocld/src/main.rs @@ -182,16 +182,16 @@ async fn start_monitoring_task(shutdown_rx: watch::Receiver, daemon_state: // 读取节点信息并发送心跳到 master match NodeDb::read_by_pk(&hostname, &db_pool).await { Ok(new_node) => { - let master_ip = new_node.master_endpoint.clone(); + let master_endpoint = new_node.master_endpoint.clone(); let node_endpoint = new_node.endpoint.clone(); // 如果 master_endpoint 和 endpoint 不同,则发送 HTTP 请求 - if master_ip != node_endpoint { - println!("master_ip:{}", master_ip); + if master_endpoint != node_endpoint { + println!("master_ip:{}", master_endpoint); // 使用 ntex::http::client 发送 HTTP 请求 let client = NanocldClient{ - url: format!("http://{}:8585",master_ip), + url: format!("http://{}:8585",master_endpoint), version: new_node.version.clone(), unix_socket: None, ssl: None, diff --git a/bin/nanocld/src/repositories/generic/read.rs b/bin/nanocld/src/repositories/generic/read.rs index 035c11fabbaa2fc36bb48545d659d85ba1d440fa..ce5b853df70fd0745d4497a0cc43d12db89d1588 100644 --- a/bin/nanocld/src/repositories/generic/read.rs +++ b/bin/nanocld/src/repositories/generic/read.rs @@ -37,7 +37,6 @@ pub trait RepositoryReadBy: super::RepositoryBase { let item = query .get_result::(&mut conn) .map_err(|err| { - log::warn!("Failed to read item from database with function read_one_by: {:?}", err); Self::map_err(err) })?; Ok(item) @@ -74,7 +73,6 @@ pub trait RepositoryReadBy: super::RepositoryBase { let items = query .get_results::(&mut conn) .map_err(|err| { - log::warn!("Failed to read item from database with function read_by: {:?}", err); Self::map_err(err) })?; Ok(items) diff --git a/bin/nanocld/src/repositories/node.rs b/bin/nanocld/src/repositories/node.rs index 9c5107bbae3f5d89e7ef4e040b29173c0f48891c..0279c9b9e13237e4b88cee50bd0274a4bc37e795 100644 --- a/bin/nanocld/src/repositories/node.rs +++ b/bin/nanocld/src/repositories/node.rs @@ -130,12 +130,7 @@ impl NodeDb { }; match NodeDb::create_if_not_exists(&node, &state.inner.pool).await { Ok(_) => Ok(()), - Err(e) => { - if e.inner.kind() == std::io::ErrorKind::AlreadyExists { - NodeDb::update_if_exist(&node, &state.inner.pool).await?; - } - Ok(()) - } + Err(_) => Ok(()), } } } diff --git a/bin/nanocld/src/services/node.rs b/bin/nanocld/src/services/node.rs index 5aba6876b27604efa594ff195dd997c0406e4ee2..64fef185222c563c25801045136c3e6649624d6a 100644 --- a/bin/nanocld/src/services/node.rs +++ b/bin/nanocld/src/services/node.rs @@ -1,8 +1,9 @@ -use std::{cell::RefCell, rc::Rc, time::Instant}; +use std::{cell::RefCell, fs, rc::Rc, time::Instant}; use std::io::ErrorKind; use futures::future::ready; use crate::schema::nodes::dsl::*; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; +use dirs::home_dir; use ntex::{ chain, channel::oneshot, @@ -70,6 +71,56 @@ pub async fn get_node_by_name( } } +/// Delete node conf +#[cfg_attr(feature = "dev", utoipa::path( + delete, + tag = "Nodes", + path = "/nodes/delete_conf", + responses( + (status = 200, description = "Success delete a node", body = [None]), + ), +))] +#[web::delete("/nodes/delete_conf")] +pub async fn delete_conf() -> HttpResult { + // 获取用户的 HOME 目录 + let home_dir = home_dir().expect("Failed to get home directory"); + + // 文件路径 ~/.nanocl/conf.yml + let conf_file_path = home_dir.join(".nanocl").join("conf.yml"); + + // 尝试删除文件 + match fs::remove_file(conf_file_path) { + Ok(_) => Ok(web::HttpResponse::Ok().finish()), + Err(_) => Ok(web::HttpResponse::BadRequest().body("Delete node conf error!")), + } +} + + +/// Delete a node by name +#[cfg_attr(feature = "dev", utoipa::path( + delete, + tag = "Nodes", + path = "/nodes/delete_by_name", + params( + ("name" = String,), + ), + responses( + (status = 200, description = "Success delete a node", body = [None]), + ), +))] +#[web::delete("/nodes/delete_by_name")] +pub async fn delete_node_by_name( + state: web::types::State, + query: web::types::Query> +) -> HttpResult { + if let Some(node_name) = query.get("node_name") { + NodeDb::del_by_pk(node_name, &state.inner.pool).await?; + Ok(web::HttpResponse::Ok().finish()) + } else { + Ok(web::HttpResponse::BadRequest().body("Missing node_name in request body")) + } +} + /// Update a node by name #[cfg_attr(feature = "dev", utoipa::path( post, @@ -236,10 +287,12 @@ pub async fn node_ws( } pub fn ntex_config(config: &mut web::ServiceConfig) { - config.service(list_node); config.service(get_node_by_name); + config.service(delete_node_by_name); + config.service(delete_conf); config.service(update_node_by_name); config.service(join_node); + config.service(list_node); config.service(count_node); config.service(web::resource("/nodes/ws").route(web::get().to(node_ws))); }