From f719306dd74a9f2a6af847356eed93f70154b790 Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Mon, 9 Dec 2024 02:25:01 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=97=A0=E6=B3=95=E5=9C=A8=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=ACapiserver=E4=B8=8A=E8=BF=90=E8=A1=8C=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 114 +++++++++++++++++++++++++++++++----------- Cargo.toml | 3 +- src/cores/handlers.rs | 109 +++++++++++++++++++++++++++++++--------- tests/route_tests.rs | 0 4 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 tests/route_tests.rs diff --git a/Cargo.lock b/Cargo.lock index f509f09..3cb9db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -182,7 +182,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -356,7 +356,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -367,7 +367,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -668,7 +668,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.89", ] [[package]] @@ -679,7 +679,20 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.89", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -717,7 +730,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.89", ] [[package]] @@ -746,7 +759,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -766,7 +779,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn", + "syn 2.0.89", ] [[package]] @@ -796,7 +809,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -816,7 +829,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -961,6 +974,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "serial_test", "tokio", ] @@ -1056,7 +1070,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1135,6 +1149,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" @@ -1392,7 +1412,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1436,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -2020,7 +2040,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2209,7 +2229,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.89", ] [[package]] @@ -2261,7 +2281,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2272,7 +2292,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2304,7 +2324,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2328,6 +2348,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2464,6 +2509,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -2492,7 +2548,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2512,7 +2568,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2597,7 +2653,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2839,7 +2895,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -2873,7 +2929,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3088,7 +3144,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3110,7 +3166,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3130,7 +3186,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3151,7 +3207,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3173,7 +3229,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1e3061b..2c73961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,5 @@ schemars = "0.8.21" chrono = "0.4.38" once_cell = "1.20.2" lazy_static = "1.5.0" -async-stream = "0.3.6" \ No newline at end of file +async-stream = "0.3.6" +serial_test = "0.10.0" \ No newline at end of file diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 4aca858..6cdb93f 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, LazyLock, Mutex}; -use std::{time}; +use std::{time, vec::Vec}; use async_trait::async_trait; use actix_web::{HttpResponse, Result, web, Error}; use chrono::Utc; @@ -1123,18 +1123,39 @@ async fn get_all_data_from_kine( } // 检查所有表的数据是否一致 - let mut unique_results: HashMap = HashMap::new(); - for results in table_results.values() { - for result in results { - *unique_results.entry(result.clone()).or_insert(0) += 1; + let vec1 = table_results[tables[0]].clone(); + let vec2 = table_results[tables[1]].clone(); + let vec3 = table_results[tables[2]].clone(); + + let max_len = vec1.len().max(vec2.len()).max(vec3.len()); + let mut filtered_results = Vec::new(); + + // 筛选出出现次数大于等于 2 的数据 + for i in 0..max_len { + // 获取当前索引的值,越界的填入 None + let value1 = vec1.get(i); + let value2 = vec2.get(i); + let value3 = vec3.get(i); + + // 统计每个值的出现次数 + let mut counts = HashMap::new(); + if let Some(v) = value1 { + *counts.entry(v).or_insert(0) += 1; + } + if let Some(v) = value2 { + *counts.entry(v).or_insert(0) += 1; } + if let Some(v) = value3 { + *counts.entry(v).or_insert(0) += 1; + } + + // 找出出现次数大于等于 2 的值 + if let Some((value, &_)) = counts.iter().find(|(_, &count)| count >= 2) { + filtered_results.push((*value).clone()); // 将该值存入结果 + } + } - // 筛选出出现次数大于等于 2 的数据 - let filtered_results: Vec = unique_results - .into_iter() - .filter(|(_, count)| *count >= 2) // 过滤条件 - .map(|(data, _)| data) // 提取数据部分 - .collect(); + Ok(filtered_results) } @@ -2642,6 +2663,8 @@ mod tests { use std::sync::Arc; use feventbus::traits::controller::EventBus; use once_cell::sync::Lazy; + use crate::cores::handlers::EventManager; + use serial_test::serial; // 使用 Lazy 来初始化静态的共享资源 static DB_POOL: Lazy> = Lazy::new(|| { @@ -2652,22 +2675,29 @@ mod tests { static HANDLER: Lazy> = Lazy::new(|| Arc::new(DefaultHandler::new())); + #[actix_web::test] + #[serial] async fn test_api_with_namespace() { let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + // 初始化事件管理器 + let event_manager = EventManager::new(); let app = test::init_service( App::new() .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -2675,14 +2705,17 @@ mod tests { })) .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -2690,14 +2723,17 @@ mod tests { })) .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + move |path| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli).await, + Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -2705,6 +2741,7 @@ mod tests { })) .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ let nats_cli = Arc::clone(&nats_cli); + move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); @@ -2997,22 +3034,26 @@ mod tests { #[actix_web::test] + #[serial] async fn test_api_without_namespace() { let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); let app = test::init_service( App::new() .route("/api/{version}/{plural}", web::post().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3020,15 +3061,17 @@ mod tests { })) .route("/api/{version}/{plural}/{name}", web::put().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3036,15 +3079,17 @@ mod tests { })) .route("/api/{version}/{plural}/{name}", web::delete().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli).await, + Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3316,22 +3361,26 @@ mod tests { #[actix_web::test] + #[serial] async fn test_apis_with_namespace() { let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); let app = test::init_service( App::new() .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3339,15 +3388,17 @@ mod tests { })) .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3355,15 +3406,17 @@ mod tests { })) .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli).await, + Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3634,22 +3687,26 @@ mod tests { #[actix_web::test] + #[serial] async fn test_crd_and_apis_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwarp()); + let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); let app = test::init_service( App::new() .route("/apis/{group}/{version}/{plural}", web::post().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3657,15 +3714,17 @@ mod tests { })) .route("/apis/{group}/{version}/{plural}/{name}", web::put().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path, data| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli).await, + Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } @@ -3673,15 +3732,17 @@ mod tests { })) .route("/apis/{group}/{version}/{plural}/{name}", web::delete().to({ let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); move |path| { let handler = HANDLER.clone(); let db_pool = DB_POOL.clone(); let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); async move { match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli).await, + Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager).await, Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), } } diff --git a/tests/route_tests.rs b/tests/route_tests.rs new file mode 100644 index 0000000..e69de29 -- Gitee From 93b114a4abd874ee8d6a7a640b722e4f42c47ef2 Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Mon, 9 Dec 2024 02:31:43 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E5=88=B0=E5=8D=95=E7=8B=AC=E7=9A=84=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cores/handlers.rs | 1387 ---------------------------------------- tests/route_tests.rs | 1393 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1393 insertions(+), 1387 deletions(-) diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 6cdb93f..6c0ab5d 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -2653,1390 +2653,3 @@ impl Handler for DefaultHandler { DefaultHandler {} } } - -#[cfg(test)] -mod tests { - use super::*; - use actix_web::{test, web, App}; - use serde_json::json; - use crate::cores::db::DbPool; - use std::sync::Arc; - use feventbus::traits::controller::EventBus; - use once_cell::sync::Lazy; - use crate::cores::handlers::EventManager; - use serial_test::serial; - - // 使用 Lazy 来初始化静态的共享资源 - static DB_POOL: Lazy> = Lazy::new(|| { - let pool = DbPool::new_in_memory().expect("Failed to create in-memory database pool"); - Arc::new(pool) - }); - - static HANDLER: Lazy> = Lazy::new(|| Arc::new(DefaultHandler::new())); - - - - #[actix_web::test] - #[serial] - async fn test_api_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - // 初始化事件管理器 - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_api_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_api_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - - - let create_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/api/v1/namespaces/global/cargos") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/api/v1/namespaces/global/cargos") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }, - { - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req2 = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - - #[actix_web::test] - #[serial] - async fn test_api_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/api/{version}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - let create_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/api/v1/nodes") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/api/v1/nodes/node-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/api/v1/nodes/node-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/api/v1/nodes") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/api/v1/nodes") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/api/v1/nodes/node-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/api/v1/nodes") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - - - #[actix_web::test] - #[serial] - async fn test_apis_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_apis_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_apis_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - - let create_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/apis/batch/v1/namespaces/global/jobs") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/apis/batch/v1/namespaces/global/jobs") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - #[actix_web::test] - #[serial] - async fn test_crd_and_apis_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_apis_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_apis_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - let crd_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "CustomResourceDefinition", - "metadata": { - "name": "myjob.iscas.cn" - }, - "spec": { - "names": - { - "kind": "Myjob", - "listKind": "MyjobList", - "plural": "myjobs", - "singular": "myjob" - } - } - }); - - let crd_req = test::TestRequest::post() - .uri("/apis/batch/v1/crds") - .set_json(&crd_req_data) - .to_request(); - - let crd_resp = test::call_service(&app, crd_req).await; - - assert!(crd_resp.status().is_success()); - - let crd_resp_body = test::read_body_json::(crd_resp).await; - assert_eq!(crd_resp_body, crd_req_data); - - - let create_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/apis/batch/v1/myjobs") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/apis/batch/v1/myjobs") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } -} diff --git a/tests/route_tests.rs b/tests/route_tests.rs index e69de29..824b463 100644 --- a/tests/route_tests.rs +++ b/tests/route_tests.rs @@ -0,0 +1,1393 @@ +use actix_web::error::ErrorInternalServerError; +use feventbus::impls::nats::nats::NatsCli; +use serde_json::Value; +use fleet_apiserver::cores::db::DbPool; +use fleet_apiserver::cores::handlers::{DefaultHandler, EventManager}; + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; + use serde_json::json; + use crate::DbPool; + use std::sync::Arc; + use feventbus::traits::controller::EventBus; + use once_cell::sync::Lazy; + use crate::EventManager; + use serial_test::serial; + use fleet_apiserver::cores::handlers::Handler; + + // 使用 Lazy 来初始化静态的共享资源 + static DB_POOL: Lazy> = Lazy::new(|| { + let pool = DbPool::new_in_memory().expect("Failed to create in-memory database pool"); + Arc::new(pool) + }); + + static HANDLER: Lazy> = Lazy::new(|| Arc::new(DefaultHandler::new())); + + + + #[actix_web::test] + #[serial] + async fn test_api_with_namespace() { + let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + // 初始化事件管理器 + let event_manager = EventManager::new(); + + let app = test::init_service( + App::new() + .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.getone_api_with_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/namespaces/{namespace}/{plural}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.listall_api_with_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + ).await; + + + + + let create_req_data = json!({ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-01", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-01", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req = test::TestRequest::post() + .uri("/api/v1/namespaces/global/cargos") + .set_json(&create_req_data) + .to_request(); + + let create_resp = test::call_service(&app, create_req).await; + + assert!(create_resp.status().is_success()); + + let create_resp_body = test::read_body_json::(create_resp).await; + assert_eq!(create_resp_body, create_req_data); + + + + + + + + let update_req_data = json!({ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-01", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-01", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let update_req = test::TestRequest::put() + .uri("/api/v1/namespaces/global/cargos/cargo-case-01") + .set_json(&update_req_data) + .to_request(); + + let update_resp = test::call_service(&app, update_req).await; + + assert!(update_resp.status().is_success()); + + let update_resp_body = test::read_body_json::(update_resp).await; + assert_eq!(update_resp_body, update_req_data); + + + + + let get_req_data = json!({ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-01", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-01", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let get_req = test::TestRequest::get() + .uri("/api/v1/namespaces/global/cargos/cargo-case-01") + .to_request(); + + let get_resp = test::call_service(&app, get_req).await; + + assert!(get_resp.status().is_success()); + + let get_resp_body = test::read_body_json::(get_resp).await; + assert_eq!(get_resp_body, get_req_data); + + + + + let create_req_data2 = json!({ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-02", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-02", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req2 = test::TestRequest::post() + .uri("/api/v1/namespaces/global/cargos") + .set_json(&create_req_data2) + .to_request(); + + let create_resp2 = test::call_service(&app, create_req2).await; + + assert!(create_resp2.status().is_success()); + + let create_resp_body2 = test::read_body_json::(create_resp2).await; + assert_eq!(create_resp_body2, create_req_data2); + + + + let list_req_data = json!([{ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-01", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-01", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-02", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-02", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + + let list_req = test::TestRequest::get() + .uri("/api/v1/namespaces/global/cargos") + .to_request(); + + let list_resp = test::call_service(&app, list_req).await; + + assert!(list_resp.status().is_success()); + + let list_resp_body = test::read_body_json::(list_resp).await; + assert_eq!(list_resp_body, list_req_data); + + + + let delete_req = test::TestRequest::delete() + .uri("/api/v1/namespaces/global/cargos/cargo-case-01") + .to_request(); + + let delete_resp = test::call_service(&app, delete_req).await; + + assert!(delete_resp.status().is_success()); + + let list_req_data2 = json!([ + { + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "cargo-case-02", + "annotations": { + "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" + } + }, + "spec": { + "containers": [ + { + "name": "cargo-case-02", + "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + + let list_req2 = test::TestRequest::get() + .uri("/api/v1/namespaces/global/cargos") + .to_request(); + + let list_resp2 = test::call_service(&app, list_req2).await; + + assert!(list_resp2.status().is_success()); + + let list_resp_body2 = test::read_body_json::(list_resp2).await; + assert_eq!(list_resp_body2, list_req_data2); + + } + + + + #[actix_web::test] + #[serial] + async fn test_api_without_namespace() { + let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); + + let app = test::init_service( + App::new() + .route("/api/{version}/{plural}", web::post().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/{plural}/{name}", web::put().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/{plural}/{name}", web::delete().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/{plural}/{name}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.getone_api_without_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/api/{version}/{plural}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.listall_api_without_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + ).await; + + + let create_req_data = json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-01", + }, + "spec": { + "containers": [ + { + "name": "node-case-01", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req = test::TestRequest::post() + .uri("/api/v1/nodes") + .set_json(&create_req_data) + .to_request(); + + let create_resp = test::call_service(&app, create_req).await; + + assert!(create_resp.status().is_success()); + + let create_resp_body = test::read_body_json::(create_resp).await; + assert_eq!(create_resp_body, create_req_data); + + + + + + + + let update_req_data = json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-01", + }, + "spec": { + "containers": [ + { + "name": "node-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let update_req = test::TestRequest::put() + .uri("/api/v1/nodes/node-case-01") + .set_json(&update_req_data) + .to_request(); + + let update_resp = test::call_service(&app, update_req).await; + + assert!(update_resp.status().is_success()); + + let update_resp_body = test::read_body_json::(update_resp).await; + assert_eq!(update_resp_body, update_req_data); + + + + + let get_req_data = json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-01", + }, + "spec": { + "containers": [ + { + "name": "node-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let get_req = test::TestRequest::get() + .uri("/api/v1/nodes/node-case-01") + .to_request(); + + let get_resp = test::call_service(&app, get_req).await; + + assert!(get_resp.status().is_success()); + + let get_resp_body = test::read_body_json::(get_resp).await; + assert_eq!(get_resp_body, get_req_data); + + + + + let create_req_data2 = json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-02", + }, + "spec": { + "containers": [ + { + "name": "node-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req2 = test::TestRequest::post() + .uri("/api/v1/nodes") + .set_json(&create_req_data2) + .to_request(); + + let create_resp2 = test::call_service(&app, create_req2).await; + + assert!(create_resp2.status().is_success()); + + let create_resp_body2 = test::read_body_json::(create_resp2).await; + assert_eq!(create_resp_body2, create_req_data2); + + + + let list_req_data = json!([{ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-01", + }, + "spec": { + "containers": [ + { + "name": "node-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-02", + }, + "spec": { + "containers": [ + { + "name": "node-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + + let list_req = test::TestRequest::get() + .uri("/api/v1/nodes") + .to_request(); + + let list_resp = test::call_service(&app, list_req).await; + + assert!(list_resp.status().is_success()); + + let list_resp_body = test::read_body_json::(list_resp).await; + assert_eq!(list_resp_body, list_req_data); + + + + let delete_req = test::TestRequest::delete() + .uri("/api/v1/nodes/node-case-01") + .to_request(); + + let delete_resp = test::call_service(&app, delete_req).await; + + assert!(delete_resp.status().is_success()); + + let list_req_data2 = json!([ + { + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "node-case-02", + }, + "spec": { + "containers": [ + { + "name": "node-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + let list_req2 = test::TestRequest::get() + .uri("/api/v1/nodes") + .to_request(); + + let list_resp2 = test::call_service(&app, list_req2).await; + + assert!(list_resp2.status().is_success()); + + let list_resp_body2 = test::read_body_json::(list_resp2).await; + assert_eq!(list_resp_body2, list_req_data2); + + } + + + + + #[actix_web::test] + #[serial] + async fn test_apis_with_namespace() { + let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); + + let app = test::init_service( + App::new() + .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.getone_apis_with_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.listall_apis_with_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + ).await; + + + + let create_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-01", + }, + "spec": { + "containers": [ + { + "name": "job-case-01", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req = test::TestRequest::post() + .uri("/apis/batch/v1/namespaces/global/jobs") + .set_json(&create_req_data) + .to_request(); + + let create_resp = test::call_service(&app, create_req).await; + + assert!(create_resp.status().is_success()); + + let create_resp_body = test::read_body_json::(create_resp).await; + assert_eq!(create_resp_body, create_req_data); + + + + + + + + let update_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-01", + }, + "spec": { + "containers": [ + { + "name": "job-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let update_req = test::TestRequest::put() + .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") + .set_json(&update_req_data) + .to_request(); + + let update_resp = test::call_service(&app, update_req).await; + + assert!(update_resp.status().is_success()); + + let update_resp_body = test::read_body_json::(update_resp).await; + assert_eq!(update_resp_body, update_req_data); + + + + + let get_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-01", + }, + "spec": { + "containers": [ + { + "name": "job-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let get_req = test::TestRequest::get() + .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") + .to_request(); + + let get_resp = test::call_service(&app, get_req).await; + + assert!(get_resp.status().is_success()); + + let get_resp_body = test::read_body_json::(get_resp).await; + assert_eq!(get_resp_body, get_req_data); + + + + + let create_req_data2 = json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-02", + }, + "spec": { + "containers": [ + { + "name": "job-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req2 = test::TestRequest::post() + .uri("/apis/batch/v1/namespaces/global/jobs") + .set_json(&create_req_data2) + .to_request(); + + let create_resp2 = test::call_service(&app, create_req2).await; + + assert!(create_resp2.status().is_success()); + + let create_resp_body2 = test::read_body_json::(create_resp2).await; + assert_eq!(create_resp_body2, create_req_data2); + + + + let list_req_data = json!([{ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-01", + }, + "spec": { + "containers": [ + { + "name": "job-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }, + { + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-02", + }, + "spec": { + "containers": [ + { + "name": "job-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + + let list_req = test::TestRequest::get() + .uri("/apis/batch/v1/namespaces/global/jobs") + .to_request(); + + let list_resp = test::call_service(&app, list_req).await; + + assert!(list_resp.status().is_success()); + + let list_resp_body = test::read_body_json::(list_resp).await; + assert_eq!(list_resp_body, list_req_data); + + + + let delete_req = test::TestRequest::delete() + .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") + .to_request(); + + let delete_resp = test::call_service(&app, delete_req).await; + + assert!(delete_resp.status().is_success()); + + let list_req_data2 = json!([ + { + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "job-case-02", + }, + "spec": { + "containers": [ + { + "name": "job-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + let list_req2 = test::TestRequest::get() + .uri("/apis/batch/v1/namespaces/global/jobs") + .to_request(); + + let list_resp2 = test::call_service(&app, list_req2).await; + + assert!(list_resp2.status().is_success()); + + let list_resp_body2 = test::read_body_json::(list_resp2).await; + assert_eq!(list_resp_body2, list_req_data2); + + } + + + #[actix_web::test] + #[serial] + async fn test_crd_and_apis_without_namespace() { + let nats_cli = Arc::new(NatsCli::new().await.unwrap()); + let event_manager = EventManager::new(); + + let app = test::init_service( + App::new() + .route("/apis/{group}/{version}/{plural}", web::post().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/{plural}/{name}", web::put().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/{plural}/{name}", web::delete().to({ + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + move |path| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/{plural}/{name}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.getone_apis_without_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + .route("/apis/{group}/{version}/{plural}", web::get().to({ + let nats_cli = Arc::clone(&nats_cli); + + move |path, data| { + let handler = HANDLER.clone(); + let db_pool = DB_POOL.clone(); + let nats_cli = Arc::clone(&nats_cli); + + async move { + match db_pool.get_connection() { + Ok(mut conn) => handler.listall_apis_without_namespace(path, data, &mut conn, nats_cli).await, + Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), + } + } + } + })) + ).await; + + + let crd_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "name": "myjob.iscas.cn" + }, + "spec": { + "names": + { + "kind": "Myjob", + "listKind": "MyjobList", + "plural": "myjobs", + "singular": "myjob" + } + } + }); + + let crd_req = test::TestRequest::post() + .uri("/apis/batch/v1/crds") + .set_json(&crd_req_data) + .to_request(); + + let crd_resp = test::call_service(&app, crd_req).await; + + assert!(crd_resp.status().is_success()); + + let crd_resp_body = test::read_body_json::(crd_resp).await; + assert_eq!(crd_resp_body, crd_req_data); + + + let create_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-01", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-01", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req = test::TestRequest::post() + .uri("/apis/batch/v1/myjobs") + .set_json(&create_req_data) + .to_request(); + + let create_resp = test::call_service(&app, create_req).await; + + assert!(create_resp.status().is_success()); + + let create_resp_body = test::read_body_json::(create_resp).await; + assert_eq!(create_resp_body, create_req_data); + + + + + + + + let update_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-01", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let update_req = test::TestRequest::put() + .uri("/apis/batch/v1/myjobs/myjob-case-01") + .set_json(&update_req_data) + .to_request(); + + let update_resp = test::call_service(&app, update_req).await; + + assert!(update_resp.status().is_success()); + + let update_resp_body = test::read_body_json::(update_resp).await; + assert_eq!(update_resp_body, update_req_data); + + + + + let get_req_data = json!({ + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-01", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }); + + let get_req = test::TestRequest::get() + .uri("/apis/batch/v1/myjobs/myjob-case-01") + .to_request(); + + let get_resp = test::call_service(&app, get_req).await; + + assert!(get_resp.status().is_success()); + + let get_resp_body = test::read_body_json::(get_resp).await; + assert_eq!(get_resp_body, get_req_data); + + + + + let create_req_data2 = json!({ + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-02", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }); + + let create_req2 = test::TestRequest::post() + .uri("/apis/batch/v1/myjobs") + .set_json(&create_req_data2) + .to_request(); + + let create_resp2 = test::call_service(&app, create_req2).await; + + assert!(create_resp2.status().is_success()); + + let create_resp_body2 = test::read_body_json::(create_resp2).await; + assert_eq!(create_resp_body2, create_req_data2); + + + + let list_req_data = json!([{ + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-01", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-01", + "command": [ + "sleep", + "1" + ] + } + ] + } + }, + { + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-02", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + + let list_req = test::TestRequest::get() + .uri("/apis/batch/v1/myjobs") + .to_request(); + + let list_resp = test::call_service(&app, list_req).await; + + assert!(list_resp.status().is_success()); + + let list_resp_body = test::read_body_json::(list_resp).await; + assert_eq!(list_resp_body, list_req_data); + + + + let delete_req = test::TestRequest::delete() + .uri("/apis/batch/v1/myjobs/myjob-case-01") + .to_request(); + + let delete_resp = test::call_service(&app, delete_req).await; + + assert!(delete_resp.status().is_success()); + + let list_req_data2 = json!([ + { + "apiVersion": "batch/v1", + "kind": "Myjob", + "metadata": { + "name": "myjob-case-02", + }, + "spec": { + "containers": [ + { + "name": "myjob-case-02", + "command": [ + "sleep", + "3600" + ] + } + ] + } + }]); + let list_req2 = test::TestRequest::get() + .uri("/apis/batch/v1/myjobs") + .to_request(); + + let list_resp2 = test::call_service(&app, list_req2).await; + + assert!(list_resp2.status().is_success()); + + let list_resp_body2 = test::read_body_json::(list_resp2).await; + assert_eq!(list_resp_body2, list_req_data2); + + } +} -- Gitee From 99cdf6e6ce9840381f51c7b738d8eb081695ecdf Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Wed, 11 Dec 2024 08:43:29 +0000 Subject: [PATCH 3/9] =?UTF-8?q?apiserver=20=E8=AE=A2=E9=98=85=20topic?= =?UTF-8?q?=E4=B8=BA=20GET=20=E7=9A=84=E6=B6=88=E6=81=AF=EF=BC=8C=E4=B8=BA?= =?UTF-8?q?scheduler=E7=BB=84=E4=BB=B6=E6=8F=90=E4=BE=9B=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 343 +++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +- src/cores/apiserver.rs | 7 +- src/lib.rs | 112 +++++++++++++- 4 files changed, 448 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cb9db6..8ee8846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ "encoding_rs", "flate2", "futures-core", - "h2", + "h2 0.3.26", "http 0.2.12", "httparse", "httpdate", @@ -289,7 +289,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -299,7 +299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -323,7 +323,7 @@ dependencies = [ "nuid", "once_cell", "regex", - "rustls-pemfile", + "rustls-pemfile 0.3.0", "serde", "serde_json", "serde_nanos", @@ -331,7 +331,7 @@ dependencies = [ "subslice", "time", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-util", "url", "webpki-roots", @@ -370,6 +370,12 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" @@ -600,6 +606,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -912,6 +928,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fancy-regex" version = "0.13.0" @@ -923,6 +949,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + [[package]] name = "feventbus" version = "0.3.0" @@ -971,6 +1003,7 @@ dependencies = [ "lazy_static", "once_cell", "r2d2", + "reqwest", "schemars", "serde", "serde_json", @@ -995,6 +1028,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1149,6 +1197,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1245,6 +1312,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.7", "http 1.1.0", "http-body", "httparse", @@ -1255,6 +1323,39 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper", + "hyper-util", + "rustls 0.23.19", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.9" @@ -1563,6 +1664,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -1654,7 +1761,24 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1788,6 +1912,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2099,26 +2267,34 @@ checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.7", "http 1.1.0", "http-body", "http-body-util", "hyper", + "hyper-rustls", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", + "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -2154,7 +2330,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2172,6 +2348,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.20.9" @@ -2184,6 +2373,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.23.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "0.3.0" @@ -2193,12 +2395,47 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.7" @@ -2248,6 +2485,29 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.23" @@ -2458,7 +2718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2551,6 +2811,40 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2642,7 +2936,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2656,17 +2950,37 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls 0.23.19", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3039,6 +3353,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 2c73961..1f7f076 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,5 @@ chrono = "0.4.38" once_cell = "1.20.2" lazy_static = "1.5.0" async-stream = "0.3.6" -serial_test = "0.10.0" \ No newline at end of file +serial_test = "0.10.0" +reqwest = "0.12.9" \ No newline at end of file diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index 1a0597a..ce1a21b 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -2,12 +2,11 @@ use actix_web::{HttpServer, App, web, HttpResponse}; use crate::cores::config::{Config, APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE}; use crate::cores::handlers::{EventManager, Handler}; - use std::sync::{Arc}; use feventbus::impls::nats::nats::NatsCli; -use feventbus::traits::controller::EventBus; use crate::cores::db::{DbPool}; + pub struct ApiServer { // Config是用户请求URL与路由处理器的映射关系封装 @@ -25,11 +24,9 @@ impl ApiServer // TODO 未来加上else // TODO 优化注册流程 pub async fn start - (self: Arc, addr: &str, handler: T, db_pool: Arc) -> Result<(), std::io::Error> { + (self: Arc, addr: &str, handler: T, db_pool: Arc, nats_cli: Arc) -> Result<(), std::io::Error> { let handler = Arc::new(handler); - // 初始化 NatsCli 实例 - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); // 初始化事件管理器 let event_manager = EventManager::new(); diff --git a/src/lib.rs b/src/lib.rs index 2f3e95c..9811361 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,34 @@ pub mod cores; pub mod schema; +use std::fmt::Debug; use cores::apiserver::ApiServer; use cores::config::DefaultConfig; use cores::handlers::DefaultHandler; use cores::db::DbPool; use std::sync::Arc; +use feventbus::err::Error; +use feventbus::impls::nats::nats::NatsCli; +use feventbus::message::Message; +use feventbus::traits::consumer::{Consumer, MessageHandler}; +use feventbus::traits::controller::EventBus; +use serde::{Deserialize, Serialize}; +use reqwest::Client; +#[derive(Serialize, Deserialize, Debug, Clone)] +struct ResourcesMessage { + group: Option, + version: String, + namespace: Option, + plural: String +} + +async fn subscribe_to_topic(topic: &str, nats_cli: Arc, handler: MessageHandler) +where + T: Serialize + for<'de> Deserialize<'de> + Debug + Clone + Send + Sync + 'static, +{ + nats_cli.subscribe(topic, handler).await; + println!("Subscribed to topic: {}", topic); +} /// 启动 Web 服务器 /// @@ -25,7 +48,94 @@ pub async fn start_server(database_url: &str, address: &str) -> Result<(), Box = Arc::new(move |msg: Message| { + let http_client = Arc::clone(&http_client); + Box::pin(async move { + match msg.body { + Some(body) => { + if let Ok(resource_message) = serde_json::from_value::(body.clone()) { + let route_path = match resource_message.group { + Some(group) => { + match resource_message.namespace { + Some(namespace) => { + format!( + "/apis/{}/{}/namespaces/{}/{}", + group, resource_message.version, namespace, resource_message.plural + ) + } + None => { + format!( + "/apis/{}/{}/{}", + group, resource_message.version, resource_message.plural + ) + } + } + } + None => { + match resource_message.namespace { + Some(namespace) => { + format!( + "/api/{}/namespaces/{}/{}", + resource_message.version, namespace, resource_message.plural + ) + } + None => { + format!("/api/{}/{}", resource_message.version, resource_message.plural) + } + } + } + }; + + let base_url = "http://localhost:8080"; + let full_url = format!("{}{}", base_url, route_path); + match http_client.get(&full_url).send().await { + Ok(response) => { + if response.status().is_success() { + let text = response.text().await.unwrap_or_default(); + println!("{}",text); + Ok(format!("---Sub get resources topic success: {}---", text)) + } else { + Err(Error::MessageHandling(format!( + "---Sub get resources topic failed: HTTP error {}---", + response.status() + ))) + } + } + Err(err) => Err(Error::MessageHandling(format!( + "---Sub get resources topic failed: Request error: {}---", + err + ))), + }.expect("TODO: panic message"); + Ok("---Sub get resources topic success---".to_string()) + } else { + Err(Error::MessageHandling( + "---Sub get resources topic failed: message body is null---".to_string(), + )) + } + } + None => { + Err(Error::MessageHandling( + "---Sub get resources topic failed: message body is null---".to_string(), + )) + } + } + }) + }); + + tokio::spawn(subscribe_to_topic( + "GET", + Arc::clone(&nats_cli), + sub_get_handler, + )); + + let _ = Arc::new(server).start(address, handler, db_pool, nats_cli).await?; + + Ok(()) } -- Gitee From 9f2ad307a9ff7dcd70a1e514b7a01c458600f76f Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Thu, 12 Dec 2024 16:58:46 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0pods=E8=B5=84=E6=BA=90=20?= =?UTF-8?q?eventbus=E8=AE=A2=E9=98=85topic=E4=B8=BAGET=E7=9A=84=E8=AF=9D?= =?UTF-8?q?=E9=A2=98=20=E4=BE=9B=E5=85=B6=E4=BB=96=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.sqlite | Bin 0 -> 61440 bytes src/cores/db.rs | 4 ++-- src/cores/handlers.rs | 2 +- src/lib.rs | 28 +++++++++++++++------------- 4 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 database.sqlite diff --git a/database.sqlite b/database.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..501647cee188e8571a6facf03844690e57bfa4ff GIT binary patch literal 61440 zcmeI5O>^7E8OKS0?mU>@nflx9?_$57`qN}K#vy?f!W-nG?To&8Gdu$sxbx>i@^p}^ghN~u^a3Dx398zo^VNw_~H&_;0$ z;YqQw^7CTlzMNI&7o6yh+GYXKR#&Z`m8N#JrfKxLXB2CwwklLhpH|(<>n2le)~MOq znFr~0DSvQsxw5`ltb8UsDt#v0=gnUbcx&C?PU98?uPyVAG4qAHk;LY`aELOq=hhcG zT?W+Do^3ec+7OnSG!%>}>d}s6VQC zUrddFeVcgRn~EeJ%$_@RopE^kI1!B|QmOFEXAZqKvsHfD`SZd^#N-gyk>oz)3nzk) zwzoG*#Vz;D_RhOEItN^I_7^83iJeps!V$x3(nwV7I5BY^+>sJec{GKA)CFIU`A8 zTFlFGI>$qcl1v{#%r8s%<#cw6>(ecE6^gFqn+?SFIN&dUiWo1Bf!z09tyeQxYpyQ*DprAAEhqE8I_*x3Ew?Xp?BtJB+S+{W|J44|kR9b2XM zoJ=D3lz^K}j!-pC^?=%;wbkYT!V#p;j%W+wAg9wgDL64J=U!-Gf;4lyy|zYOa(<7O zjm9Z3YNun^?z;xm6w-emAq1r3-JHn;JFK?fdB}A00@8p2!H?xfB*=900`V<0jcTMcE?hSx8OY*j4orJ<~yn)=|OE zy2%{5&Z^dCmQ9tBY^Q(EOsvv3Wr`wwe(LOy*r%uH%5`9Q-uA&F-y!wVzOw6Jizq*j zwYqAZE0#9cp4!vxLDkn&`k$`U>C@z5lZrM});2nVvA6di`D`KSYlA*bcA8q}IgewV zeMcg-f9%rMliKF?YKaQ(t9p+glQd)-y}GR#9Xjlx*GQi6NvZO9eS6Ex`_MGnT%&bQ zV|t^)_Wb>_YPYzd%vKw!tu8soB8gQxyeGx#+3bXux!SA6wQikYxu@%8L)YpDRLT0@ zmSLAoX0gtI7_L{`he2h#hH1O1@qRSf7dPNvWp(skp@vZ?KBI%~3$RaXff@2h3o>@jXyKZ*2( z-546KjqQ$pP%#YqA(gTYEK=C@Q+u7FwPqSU8k&+Q5-O9&>{!fnBd8hshTdzlRkp7= zmXg43!`bimbBaPKtHBk2`=iQDUUbVPdB3~6n?|qG@1r)~^(tU?J>`th6m`Si5|_yB z*TlnG)2oTobB%GE&&TIbMLo+*SuNFM{pwnFTR*9c4@R%53yZ2tHWBw5;*@O}mhD$S zR()xhq~zRO)reIxb>_jj@w`}3*Hnv-6Qak%+&&)S(}8YKFD~*}I-W{$z%6FFK`N~A zIYW|jOCtS~9GG5tN}$qyh}uee(p#=vG$rs5Rp_F3kL=>yRH;t?%yz&px+#2s_Q`vv znAeGfnsk}%1IO6IlYFP4^5(exN($BPL@+Qw?1L_!W2h1mx8`TYKt;}&^6}E~URPDm z=pZyx+db!`Bkq=~2ffQ^Ruoz!pR99G-4^u_HLlD$4KB6Kr@*GMHrm% zi&YA%6jrHay3oQZ#ivQEQa@n9`+u%dB3)i&WJSu$be%f%KnSf;{X|)!2LQPLe=Ppz zQ2ZbC3mXW400@8p2!H?xfB*=900@8p2!Oy9CGcK^dqcxq&fmMmKj;b_n~U(f(2m^y zK)QDen(P0=|9?fh7R@??0Q~>l%LxC!^Ci0Q|HJ?9Jj~!c^KtH@1MvU5&q=}m{~G-N z1&NfRPrXIz`~M3PeGzvK{(r1e{YP)GN@114Dut)i-rQ4aj*CAnWpbjDIotnFiTQjc z1ONXSe_|pE1V8`;KmY_l00ck)1V8`;KmY`;90Bms7wh_2yQo zzW=|F6UBh{|I<=|L~IoJ|1a@RRa6ZEAOHd&00JNY0w4eaAOHd&00OU#zz_5Pe-EUM B6PN%1 literal 0 HcmV?d00001 diff --git a/src/cores/db.rs b/src/cores/db.rs index 6e9fd6a..56016fb 100644 --- a/src/cores/db.rs +++ b/src/cores/db.rs @@ -24,12 +24,12 @@ pub enum DbConnection { fn resource_templates() -> HashMap<&'static str, (bool, Value)> { let mut templates = HashMap::new(); templates.insert( - "cargos", + "pods", ( true, json!({ "apiVersion": "v1", - "kind": "Cargo", + "kind": "Pod", "metadata": { "name": "string", "annotations": "object" diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 6c0ab5d..8798c30 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -23,7 +23,7 @@ use actix_web::web::Bytes; // 定义全局哈希表来获取model名 static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { let mut map = HashMap::new(); - map.insert("cargos".to_string(), "CARGO".to_string()); + map.insert("pods".to_string(), "POD".to_string()); map.insert("nodes".to_string(), "NODE".to_string()); map.insert("jobs".to_string(), "JOB".to_string()); Mutex::new(map) diff --git a/src/lib.rs b/src/lib.rs index 9811361..559854f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,14 +22,18 @@ struct ResourcesMessage { plural: String } -async fn subscribe_to_topic(topic: &str, nats_cli: Arc, handler: MessageHandler) -where - T: Serialize + for<'de> Deserialize<'de> + Debug + Clone + Send + Sync + 'static, +async fn reply_to_topic( + topic: &str, + nats_cli: Arc, + handler: MessageHandler, +) -> Result<(), Box> { - nats_cli.subscribe(topic, handler).await; - println!("Subscribed to topic: {}", topic); + nats_cli + .reply(topic, handler) + .await + .map_err(|e| Box::new(e) as Box)?; + Ok(()) } - /// 启动 Web 服务器 /// /// # 参数 @@ -53,7 +57,7 @@ pub async fn start_server(database_url: &str, address: &str) -> Result<(), Box = Arc::new(move |msg: Message| { + let reply_get_handler: MessageHandler = Arc::new(move |msg: Message| { let http_client = Arc::clone(&http_client); Box::pin(async move { match msg.body { @@ -97,8 +101,7 @@ pub async fn start_server(database_url: &str, address: &str) -> Result<(), Box { if response.status().is_success() { let text = response.text().await.unwrap_or_default(); - println!("{}",text); - Ok(format!("---Sub get resources topic success: {}---", text)) + Ok(text) } else { Err(Error::MessageHandling(format!( "---Sub get resources topic failed: HTTP error {}---", @@ -110,8 +113,7 @@ pub async fn start_server(database_url: &str, address: &str) -> Result<(), Box Result<(), Box Date: Fri, 13 Dec 2024 09:06:14 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=80=BB=E8=BE=91=EF=BC=8C=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.sqlite | Bin 61440 -> 0 bytes src/cores/apiserver.rs | 747 ++++------------- src/cores/config.rs | 134 ++- src/cores/handlers.rs | 1805 +++++++++------------------------------- 4 files changed, 613 insertions(+), 2073 deletions(-) delete mode 100644 database.sqlite diff --git a/database.sqlite b/database.sqlite deleted file mode 100644 index 501647cee188e8571a6facf03844690e57bfa4ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61440 zcmeI5O>^7E8OKS0?mU>@nflx9?_$57`qN}K#vy?f!W-nG?To&8Gdu$sxbx>i@^p}^ghN~u^a3Dx398zo^VNw_~H&_;0$ z;YqQw^7CTlzMNI&7o6yh+GYXKR#&Z`m8N#JrfKxLXB2CwwklLhpH|(<>n2le)~MOq znFr~0DSvQsxw5`ltb8UsDt#v0=gnUbcx&C?PU98?uPyVAG4qAHk;LY`aELOq=hhcG zT?W+Do^3ec+7OnSG!%>}>d}s6VQC zUrddFeVcgRn~EeJ%$_@RopE^kI1!B|QmOFEXAZqKvsHfD`SZd^#N-gyk>oz)3nzk) zwzoG*#Vz;D_RhOEItN^I_7^83iJeps!V$x3(nwV7I5BY^+>sJec{GKA)CFIU`A8 zTFlFGI>$qcl1v{#%r8s%<#cw6>(ecE6^gFqn+?SFIN&dUiWo1Bf!z09tyeQxYpyQ*DprAAEhqE8I_*x3Ew?Xp?BtJB+S+{W|J44|kR9b2XM zoJ=D3lz^K}j!-pC^?=%;wbkYT!V#p;j%W+wAg9wgDL64J=U!-Gf;4lyy|zYOa(<7O zjm9Z3YNun^?z;xm6w-emAq1r3-JHn;JFK?fdB}A00@8p2!H?xfB*=900`V<0jcTMcE?hSx8OY*j4orJ<~yn)=|OE zy2%{5&Z^dCmQ9tBY^Q(EOsvv3Wr`wwe(LOy*r%uH%5`9Q-uA&F-y!wVzOw6Jizq*j zwYqAZE0#9cp4!vxLDkn&`k$`U>C@z5lZrM});2nVvA6di`D`KSYlA*bcA8q}IgewV zeMcg-f9%rMliKF?YKaQ(t9p+glQd)-y}GR#9Xjlx*GQi6NvZO9eS6Ex`_MGnT%&bQ zV|t^)_Wb>_YPYzd%vKw!tu8soB8gQxyeGx#+3bXux!SA6wQikYxu@%8L)YpDRLT0@ zmSLAoX0gtI7_L{`he2h#hH1O1@qRSf7dPNvWp(skp@vZ?KBI%~3$RaXff@2h3o>@jXyKZ*2( z-546KjqQ$pP%#YqA(gTYEK=C@Q+u7FwPqSU8k&+Q5-O9&>{!fnBd8hshTdzlRkp7= zmXg43!`bimbBaPKtHBk2`=iQDUUbVPdB3~6n?|qG@1r)~^(tU?J>`th6m`Si5|_yB z*TlnG)2oTobB%GE&&TIbMLo+*SuNFM{pwnFTR*9c4@R%53yZ2tHWBw5;*@O}mhD$S zR()xhq~zRO)reIxb>_jj@w`}3*Hnv-6Qak%+&&)S(}8YKFD~*}I-W{$z%6FFK`N~A zIYW|jOCtS~9GG5tN}$qyh}uee(p#=vG$rs5Rp_F3kL=>yRH;t?%yz&px+#2s_Q`vv znAeGfnsk}%1IO6IlYFP4^5(exN($BPL@+Qw?1L_!W2h1mx8`TYKt;}&^6}E~URPDm z=pZyx+db!`Bkq=~2ffQ^Ruoz!pR99G-4^u_HLlD$4KB6Kr@*GMHrm% zi&YA%6jrHay3oQZ#ivQEQa@n9`+u%dB3)i&WJSu$be%f%KnSf;{X|)!2LQPLe=Ppz zQ2ZbC3mXW400@8p2!H?xfB*=900@8p2!Oy9CGcK^dqcxq&fmMmKj;b_n~U(f(2m^y zK)QDen(P0=|9?fh7R@??0Q~>l%LxC!^Ci0Q|HJ?9Jj~!c^KtH@1MvU5&q=}m{~G-N z1&NfRPrXIz`~M3PeGzvK{(r1e{YP)GN@114Dut)i-rQ4aj*CAnWpbjDIotnFiTQjc z1ONXSe_|pE1V8`;KmY_l00ck)1V8`;KmY`;90Bms7wh_2yQo zzW=|F6UBh{|I<=|L~IoJ|1a@RRa6ZEAOHd&00JNY0w4eaAOHd&00OU#zz_5Pe-EUM B6PN%1 diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index ce1a21b..c4a9c7b 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -1,15 +1,43 @@ -use actix_web::{HttpServer, App, web, HttpResponse}; - -use crate::cores::config::{Config, APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE}; +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + +use actix_web::{HttpServer, App, web, HttpResponse, HttpRequest}; +use crate::cores::config::{Config}; use crate::cores::handlers::{EventManager, Handler}; use std::sync::{Arc}; use feventbus::impls::nats::nats::NatsCli; use crate::cores::db::{DbPool}; +use std::collections::HashMap; + +fn parse_path(template: &str, path: &str) -> HashMap { + let template_parts: Vec<&str> = template.split('/').collect(); + let path_parts: Vec<&str> = path.split('/').collect(); + + if template_parts.len() != path_parts.len() { + return HashMap::new(); + } + + let mut params = HashMap::new(); + for (template_part, path_part) in template_parts.iter().zip(path_parts.iter()) { + if template_part.starts_with('{') && template_part.ends_with('}') { + let key = &template_part[1..template_part.len() - 1]; + params.insert(key.to_string(), path_part.to_string()); + } else if template_part != path_part { + return HashMap::new(); + } + } + + params +} + pub struct ApiServer { - // Config是用户请求URL与路由处理器的映射关系封装 config: Arc, } @@ -21,8 +49,6 @@ impl ApiServer } } - // TODO 未来加上else - // TODO 优化注册流程 pub async fn start (self: Arc, addr: &str, handler: T, db_pool: Arc, nats_cli: Arc) -> Result<(), std::io::Error> { @@ -40,696 +66,239 @@ impl ApiServer let nats_cli = Arc::clone(&nats_cli); let event_manager = event_manager.clone(); - // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, - // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 - for (key, route) in config.create_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::post().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::post().to( - move |path, data| { + for key in config.create_routes() { + let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + let route_key = key.clone(); + app = app.route( + key, + web::post().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let db_pool = Arc::clone(&db_pool); let nats_cli = Arc::clone(&nats_cli); let event_manager = event_manager.clone(); + let template = route_key.clone(); // 路由模板 async move { match db_pool.get_connection() { - Ok(mut conn) => { + Ok(mut conn) => { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager) + .create_resource(params, data, &mut conn, nats_cli, event_manager) .await - }, + } Err(_) => Err(actix_web::error::ErrorInternalServerError( "Failed to get database connection", )), } } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::post().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::post().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } + }, + ), + ); } - // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, - // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 - for (key, route) in config.delete_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::delete().to( - move |path| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .delete_api_without_namespace(path, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::delete().to( - move |path| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .delete_api_with_namespace(path, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::delete().to( - move |path| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::delete().to( - move |path| { + for key in config.delete_routes() { + let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + let route_key = key.clone(); + + app = app.route( + key, + web::delete().to( + move |req: HttpRequest| { let handler = Arc::clone(&handler); let db_pool = Arc::clone(&db_pool); let nats_cli = Arc::clone(&nats_cli); let event_manager = event_manager.clone(); + let template = route_key.clone(); // 路由模板 async move { match db_pool.get_connection() { - Ok(mut conn) => { + Ok(mut conn) => { + // 自定义路径解析 + let params = parse_path(&template, req.path()); handler .default() - .delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager) + .delete_resource(params, &mut conn, nats_cli, event_manager) .await - }, + } Err(_) => Err(actix_web::error::ErrorInternalServerError( "Failed to get database connection", )), } } - })); - } + }, + ), + ); } - // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, - // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 - for (key, route) in config.update_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::put().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::put().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .update_api_with_namespace(path, data, &mut conn, nats_cli,event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::put().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - app = app.route(key, web::put().to( - move |path, data| { + for key in config.update_routes() { + let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); + let nats_cli = Arc::clone(&nats_cli); + let event_manager = event_manager.clone(); + let route_key = key.clone(); + + app = app.route( + key, + web::put().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let db_pool = Arc::clone(&db_pool); let nats_cli = Arc::clone(&nats_cli); let event_manager = event_manager.clone(); + let template = route_key.clone(); // 路由模板 async move { match db_pool.get_connection() { - Ok(mut conn) => { + Ok(mut conn) => { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager) + .update_resource(params, data, &mut conn, nats_cli, event_manager) .await - }, + } Err(_) => Err(actix_web::error::ErrorInternalServerError( "Failed to get database connection", )), } } - })); - } + }, + ), + ); } - // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, - // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 - for (key, route) in config.getone_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .getone_api_without_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); + for key in config.getone_routes() { + let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); + let nats_cli = Arc::clone(&nats_cli); + let route_key = key.clone(); - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .getone_api_with_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { + app = app.route( + key, + web::get().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let db_pool = Arc::clone(&db_pool); let nats_cli = Arc::clone(&nats_cli); + let template = route_key.clone(); // 路由模板 async move { match db_pool.get_connection() { - Ok(mut conn) => { + Ok(mut conn) => { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .getone_apis_without_namespace(path, data, &mut conn, nats_cli) + .getone_resource(params, data, &mut conn, nats_cli) .await - }, + } Err(_) => Err(actix_web::error::ErrorInternalServerError( "Failed to get database connection", )), } } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .getone_apis_with_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } + }, + ), + ); } + for key in config.listall_routes() { + let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); + let nats_cli = Arc::clone(&nats_cli); + let route_key = key.clone(); - // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, - // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 - for (key, route) in config.listall_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .listall_api_without_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .listall_api_with_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - handler - .default() - .listall_apis_without_namespace(path, data, &mut conn, nats_cli) - .await - }, - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - - app = app.route(key, web::get().to( - move |path, data| { + app = app.route( + key, + web::get().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let db_pool = Arc::clone(&db_pool); let nats_cli = Arc::clone(&nats_cli); + let template = route_key.clone(); // 路由模板 async move { match db_pool.get_connection() { - Ok(mut conn) => { + Ok(mut conn) => { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .listall_apis_with_namespace(path, data, &mut conn, nats_cli) + .listall_resource(params, data, &mut conn, nats_cli) .await - }, + } Err(_) => Err(actix_web::error::ErrorInternalServerError( "Failed to get database connection", )), } } - })); - } + }, + ), + ); } - for (key, route) in config.watchall_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - async move { - handler - .default() - .watchall_api_without_namespace(path, data, event_manager) - .await - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); + for key in config.watchall_routes() { + let handler = Arc::clone(&handler); + let event_manager = event_manager.clone(); + let route_key = key.clone(); - async move { - handler - .default() - .watchall_api_with_namespace(path, data, event_manager) - .await - } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { + app = app.route( + key, + web::get().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let event_manager = event_manager.clone(); + let template = route_key.clone(); // 路由模板 async move { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .watchall_apis_without_namespace(path, data, event_manager) + .watchall_resource(params, data, event_manager) .await } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - async move { - handler - .default() - .watchall_apis_with_namespace(path, data, event_manager) - .await - } - })); - } + }, + ), + ); } - for (key, route) in config.watchone_routes() { - if route == API_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); + for key in config.watchone_routes() { + let handler = Arc::clone(&handler); + let event_manager = event_manager.clone(); + let route_key = key.clone(); - async move { - handler - .default() - .watchone_api_without_namespace(path, data, event_manager) - .await - } - })); - } else if route == API_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { + app = app.route( + key, + web::get().to( + move |req: HttpRequest, data| { let handler = Arc::clone(&handler); let event_manager = event_manager.clone(); + let template = route_key.clone(); // 路由模板 async move { + // 自定义解析路径 + let params = parse_path(&template, req.path()); handler .default() - .watchone_api_with_namespace(path, data, event_manager) + .watchone_resource(params, data, event_manager) .await } - })); - } else if route == APIS_WITHOUT_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); + }, + ), + ); + } - async move { - handler - .default() - .watchone_apis_without_namespace(path, data, event_manager) - .await - } - })); - } else if route == APIS_WITH_NAMESPACE { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - app = app.route(key, web::get().to( - move |path, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - async move { - handler - .default() - .watchone_apis_with_namespace(path, data, event_manager) - .await - } - })); - } - } // TODO 与Kubernetes的状态一致 app.default_service(web::to(move || async { diff --git a/src/cores/config.rs b/src/cores/config.rs index f741fca..50e3f36 100644 --- a/src/cores/config.rs +++ b/src/cores/config.rs @@ -1,11 +1,10 @@ /** * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: wuheng@iscas.ac.cn + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn * since: 0.1.0 * **/ -use std::collections::HashMap; // 执行kubectl api-resources,有以下输出,其中APIVERSION为v1的都在URL都是/api/开头,其它的都是/apis/开头,是否支持namespace // 由NAMESPACED取值确定。因此,定于API_WITH_NAMESPACE、API_WITHOUT_NAMESPACE、APIS_WITH_NAMESPACE、APIS_WITHOUT_NAMESPACE @@ -68,10 +67,6 @@ use std::collections::HashMap; // csistoragecapacities storage.k8s.io/v1 true CSIStorageCapacity // storageclasses sc storage.k8s.io/v1 false StorageClass // volumeattachments storage.k8s.io/v1 false VolumeAttachment -pub const API_WITH_NAMESPACE: &str = "api_with_namespace"; -pub const API_WITHOUT_NAMESPACE: &str = "api_without_namespace"; -pub const APIS_WITH_NAMESPACE: &str = "apis_with_namespace"; -pub const APIS_WITHOUT_NAMESPACE: &str = "apis_without_namespace"; // 定义URL配置的接口,用户可以自定义自己需要的URL规则 @@ -81,113 +76,113 @@ pub const APIS_WITHOUT_NAMESPACE: &str = "apis_without_namespace"; pub trait Config: Sync + Send { // 创建一个对象(如Pod、Job的具体实例)的URL和Web服务器路由的映射关系 - fn create_routes(&self) -> &HashMap; + fn create_routes(&self) -> &Vec; // 删除指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn delete_routes(&self) -> &HashMap; + fn delete_routes(&self) -> &Vec; // 更新指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn update_routes(&self) -> &HashMap; + fn update_routes(&self) -> &Vec; // 获取指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn getone_routes(&self) -> &HashMap; + fn getone_routes(&self) -> &Vec; // 获取指定类型(如Pod、Job的所有实例)的URL列表和Web服务器路由的映射关系 - fn listall_routes(&self) -> &HashMap; + fn listall_routes(&self) -> &Vec; - fn watchall_routes(&self) -> &HashMap; + fn watchall_routes(&self) -> &Vec; - fn watchone_routes(&self) -> &HashMap; + fn watchone_routes(&self) -> &Vec; } // Config接口的默认实现,用户可以自定义自己需要的URL规则 // DefaultConfig实现了Kubernetes风格的URL #[derive(Clone)] pub struct DefaultConfig { - create_routes: HashMap, - delete_routes: HashMap, - update_routes: HashMap, - getone_routes: HashMap, - listall_routes: HashMap, - watchall_routes: HashMap, - watchone_routes: HashMap, + create_routes: Vec, + delete_routes: Vec, + update_routes: Vec, + getone_routes: Vec, + listall_routes: Vec, + watchall_routes: Vec, + watchone_routes: Vec, } impl DefaultConfig { pub fn new() -> Self { DefaultConfig { create_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // create a specific resource: POST // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural} // - without namespace/api(s)/{group}/{version}/namespaces/{plural} - map.insert("/api/{version}/namespaces/{namespace}/{plural}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/{plural}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/{plural}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/namespaces/{namespace}/{plural}".to_string()); + vec.push("/api/{version}/{plural}".to_string()); + vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string()); + vec.push("/apis/{group}/{version}/{plural}".to_string()); + vec }, delete_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // delete a specific resource: DELETE // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - map.insert("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/{plural}/{name}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/{plural}/{name}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/api/{version}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); + vec }, update_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // update a specific resource:PUT // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - map.insert("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/{plural}/{name}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/{plural}/{name}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/api/{version}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); + vec }, getone_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // get a specific resource: GET // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - map.insert("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/{plural}/{name}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/{plural}/{name}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/api/{version}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); + vec }, listall_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // get some resources: GET // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural} // - without namespace/api(s)/{group}/{version}/namespaces/{plural} - map.insert("/api/{version}/namespaces/{namespace}/{plural}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/{plural}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/{plural}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/namespaces/{namespace}/{plural}".to_string()); + vec.push("/api/{version}/{plural}".to_string()); + vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string()); + vec.push("/apis/{group}/{version}/{plural}".to_string()); + vec }, watchall_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // watch some resources: GET // - with namespace: /api(s)/{group}/{version}/watch/namespaces/{namespace}/{plural} // - without namespace/api(s)/{group}/{version}/watch/namespaces/{plural} - map.insert("/api/{version}/watch/namespaces/{namespace}/{plural}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/watch/{plural}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/watch/{plural}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/watch/namespaces/{namespace}/{plural}".to_string()); + vec.push("/api/{version}/watch/{plural}".to_string()); + vec.push("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}".to_string()); + vec.push("/apis/{group}/{version}/watch/{plural}".to_string()); + vec }, watchone_routes: { - let mut map: HashMap = HashMap::new(); + let mut vec: Vec = Vec::new(); // watch one resource: GET // - with namespace: /api(s)/{group}/{version}/watch/namespaces/{namespace}/{plural}/{name} // - without namespace/api(s)/{group}/{version}/watch/namespaces/{plural}/{name} - map.insert("/api/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string(), API_WITH_NAMESPACE.to_string()); - map.insert("/api/{version}/watch/{plural}/{name}".to_string(), API_WITHOUT_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string(), APIS_WITH_NAMESPACE.to_string()); - map.insert("/apis/{group}/{version}/watch/{plural}/{name}".to_string(), APIS_WITHOUT_NAMESPACE.to_string()); - map + vec.push("/api/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/api/{version}/watch/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string()); + vec.push("/apis/{group}/{version}/watch/{plural}/{name}".to_string()); + vec } } } @@ -196,26 +191,25 @@ impl DefaultConfig { // 返回所有的注册URL和路由关系集合 impl Config for DefaultConfig { - - fn create_routes(&self) -> &HashMap { + fn create_routes(&self) -> &Vec { &self.create_routes } - fn delete_routes(&self) -> &HashMap { + fn delete_routes(&self) -> &Vec { &self.delete_routes } - fn update_routes(&self) -> &HashMap { + fn update_routes(&self) -> &Vec { &self.update_routes } - fn getone_routes(&self) -> &HashMap { + fn getone_routes(&self) -> &Vec { &self.getone_routes } - fn listall_routes(&self) -> &HashMap { &self.listall_routes } + fn listall_routes(&self) -> &Vec { &self.listall_routes } - fn watchall_routes(&self) -> &HashMap { &self.watchall_routes } + fn watchall_routes(&self) -> &Vec { &self.watchall_routes } - fn watchone_routes(&self) -> &HashMap { &self.watchone_routes } + fn watchone_routes(&self) -> &Vec { &self.watchone_routes } } diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 8798c30..01f467c 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::sync::{Arc, LazyLock, Mutex}; use std::{time, vec::Vec}; use async_trait::async_trait; -use actix_web::{HttpResponse, Result, web, Error}; +use actix_web::{HttpResponse, Result, web}; use chrono::Utc; use diesel::{QueryResult, RunQueryDsl, QueryDsl, ExpressionMethods, OptionalExtension, QueryableByName, PgConnection, sql_query, SqliteConnection, Connection}; use serde_json::{json}; @@ -11,7 +11,6 @@ use serde_json::Value; use crate::cores::db::DbConnection; use diesel::sql_types::{Text}; use actix_web::error::ErrorInternalServerError; -use actix_web::web::{Path, Query}; use feventbus::impls::nats::nats::NatsCli; use feventbus::message::Message; use feventbus::message; @@ -43,248 +42,60 @@ fn get_value(key: &str) -> Option { #[async_trait] pub trait Handler { - // /api/{version}/{plural} - async fn create_api_without_namespace( + async fn create_resource( &self, - info: web::Path<(String, String)>, + params: HashMap, data: web::Json, db_connection: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/namespaces/{namespace}/{plural} - async fn create_api_with_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /apis/{group}/{version}/{plural} - async fn create_apis_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn create_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/{plural}/{name} - async fn delete_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /apis/{group}/{version}/{plural}/{name} - async fn delete_apis_without_namespace( - &self, - info: web::Path<(String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/{plural}/{name} - async fn update_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager + event_manager: EventManager, ) -> Result; - // /apis/{group}/{version}/{plural}/{name} - async fn update_apis_without_namespace( + async fn delete_resource( &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, + params: HashMap, db_connection: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager + event_manager: EventManager, ) -> Result; - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_apis_with_namespace( + async fn update_resource( &self, - info: web::Path<(String, String, String, String, String)>, + params: HashMap, data: web::Json, db_connection: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager - ) -> Result; - - // /api/{version}/{plural}/{name} - async fn getone_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - // /apis/{group}/{version}/{plural}/{name} - async fn getone_apis_without_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - // /api/{version}/{plural} - async fn listall_api_without_namespace( - &self, - info: web::Path<(String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - // /api/{version}/namespaces/{namespace}/{plural} - async fn listall_api_with_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Query, - db_connection: &mut DbConnection, - nats_cli: Arc, + event_manager: EventManager, ) -> Result; - // /apis/{group}/{version}/{plural} - async fn listall_apis_without_namespace( + async fn getone_resource( &self, - info: web::Path<(String, String, String)>, - data: web::Query, + params: HashMap, + _data: web::Query, db_connection: &mut DbConnection, - nats_cli: Arc, + _nats_cli: Arc, ) -> Result; - // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn listall_apis_with_namespace( + async fn listall_resource( &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, + params: HashMap, + _data: web::Query, db_connection: &mut DbConnection, - nats_cli: Arc, - ) -> Result; - - async fn watchall_api_without_namespace( - &self, - info: web::Path<(String, String)>, - data: web::Query, - event_manager: EventManager - ) -> Result; - - async fn watchall_api_with_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Query, - event_manager: EventManager - ) -> Result; - - async fn watchall_apis_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Query, - event_manager: EventManager - ) -> Result; - - async fn watchall_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, - event_manager: EventManager - ) -> Result; - - async fn watchone_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Query, - event_manager: EventManager - ) -> Result; - - async fn watchone_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, - event_manager: EventManager + _nats_cli: Arc, ) -> Result; - async fn watchone_apis_without_namespace( + async fn watchall_resource( &self, - info: web::Path<(String, String, String, String)>, - data: web::Query, - event_manager: EventManager + params: HashMap, + _data: web::Query, + event_manager: EventManager, ) -> Result; - async fn watchone_apis_with_namespace( + async fn watchone_resource( &self, - info: web::Path<(String, String, String, String, String)>, - data: web::Query, - event_manager: EventManager + params: HashMap, + _data: web::Query, + event_manager: EventManager, ) -> Result; // 不满足以上请求路径的处理 @@ -1248,21 +1059,29 @@ impl EventManager { #[async_trait] impl Handler for DefaultHandler { - - // /api/{version}/{plural} - async fn create_api_without_namespace( + async fn create_resource( &self, - info: web::Path<(String, String)>, + params: HashMap, data: web::Json, db_connection: &mut DbConnection, nats_cli: Arc, event_manager: EventManager, ) -> Result { - let (version, plural) = info.into_inner(); let data = data.into_inner(); + // 获取 path 参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() + }; - //判断是正常的插入函数还是 crd 资源的注册函数 if plural == "crds" { + // 处理 CRD 资源注册 let item_kind = data .get("spec") .and_then(|spec| spec.get("names")) @@ -1277,47 +1096,59 @@ impl Handler for DefaultHandler { .and_then(|kind| kind.as_str()) .unwrap_or("error"); - - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, item_kind, &version,false).await.map_err(ErrorInternalServerError)?; + let metadata_exists = check_metadata(db_connection, item_kind, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 CRD 资源已存在,无需重复注册" }))); } - insert_metadata(db_connection, item_kind, &version, false, &data) + insert_metadata(db_connection, item_kind, &ver, namespace.is_some(), &data) .await .map_err(ErrorInternalServerError)?; insert_key_value(item_kind, kind_upper); } else { - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, &plural, &version,false).await.map_err(ErrorInternalServerError)?; + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" + }))); } - // 从 json_data 中提取 metadata.name + // 检查资源是否已存在 let item_name = data .get("metadata") .and_then(|metadata| metadata.get("name")) .and_then(|name| name.as_str()) .unwrap_or("error"); - let kine_exists = check_kine(db_connection, &plural, item_name, &version, None).await.map_err(ErrorInternalServerError)?; + let kine_exists = check_kine( + db_connection, + plural, + item_name, + &ver, + namespace, + ) + .await + .map_err(ErrorInternalServerError)?; if kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "该资源已存在,请勿重复创建" }))); + return Ok(HttpResponse::InternalServerError().json(json!({ + "error": "该资源已存在,请勿重复创建" + }))); } let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - + let kind = get_value(plural).unwrap(); model_map.insert("MODEL".to_string(), kind); - - // request消息 + // 创建请求消息 let request_message = Message::new( "CREATE".to_string(), message::NativeEventAction::Other, @@ -1328,818 +1159,221 @@ impl Handler for DefaultHandler { None, ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", version, plural); - let watchone_resource_type = format!("{}/{}/{}", version, plural, item_name); - - event_manager.send_event(&resource_type, EventType::Create, data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Create, data.clone()).await; + send_request(request_message, nats_cli) + .await + .map_err(ErrorInternalServerError)?; - insert_kine(db_connection, &plural, item_name, &data, &version, None) + // 发送事件 + let resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}", ver, namespace, plural) + } else { + format!("{}/{}", ver, plural) + }; + let watchone_resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}/{}", ver, namespace, plural, item_name) + } else { + format!("{}/{}/{}", ver, plural, item_name) + }; + + event_manager + .send_event(&resource_type, EventType::Create, data.clone()) + .await; + event_manager + .send_event(&watchone_resource_type, EventType::Create, data.clone()) + .await; + + insert_kine( + db_connection, + plural, + item_name, + &data, + &ver, + namespace, + ) .await .map_err(ErrorInternalServerError)?; } - Ok(HttpResponse::Ok().json(data)) } - // /api/{version}/namespaces/{namespace}/{plural} - async fn create_api_with_namespace( + async fn delete_resource( &self, - info: web::Path<(String, String, String)>, - data: web::Json, + params: HashMap, db_connection: &mut DbConnection, nats_cli: Arc, event_manager: EventManager, ) -> Result { - let (version, namespace, plural) = info.into_inner(); - let data = data.into_inner(); - - //判断是正常的插入函数还是 crd 资源的注册函数 - if plural == "crds" { - let item_kind = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("plural")) - .and_then(|plural| plural.as_str()) - .unwrap_or("error"); - - let kind_upper = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("kind")) - .and_then(|kind| kind.as_str()) - .unwrap_or("error"); - - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, item_kind, &version, true).await.map_err(ErrorInternalServerError)?; - - if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); - } - - insert_metadata(db_connection, item_kind, &version, true, &data) - .await - .map_err(ErrorInternalServerError)?; - - insert_key_value(item_kind, kind_upper); + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + let name = params.get("name").unwrap(); // 必须存在 + + let ver = if let Some(group) = group { + format!("{}/{}", group, version) } else { - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, &plural, &version, true).await.map_err(ErrorInternalServerError)?; + version.to_string() + }; - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); - } + // 检查 metadata + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; - // 从 json_data 中提取 metadata.name - let item_name = data - .get("metadata") - .and_then(|metadata| metadata.get("name")) - .and_then(|name| name.as_str()) - .unwrap_or("error"); + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" + }))); + } - let kine_exists = check_kine(db_connection, &plural, item_name, &version, Some(&namespace)).await.map_err(ErrorInternalServerError)?; + // 检查资源是否存在 + let kine_exists = check_kine( + db_connection, + plural, + name, + &ver, + namespace, + ) + .await + .map_err(ErrorInternalServerError)?; - if kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "该资源已存在,请勿重复创建" }))); - } + if !kine_exists { + return Ok(HttpResponse::InternalServerError().json(json!({ + "error": "指定数据不存在" + }))); + } - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); + let mut model_map = HashMap::new(); + let kind = get_value(plural).unwrap(); + model_map.insert("MODEL".to_string(), kind); - model_map.insert("MODEL".to_string(), kind); + let request_data = json!({ + "apiVersion": ver.clone(), + "Namespaces": namespace.unwrap_or(""), + "Plural": plural.clone(), + "Name": name.clone(), + }); - // request消息 - let request_message = Message::new( - "CREATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: data.clone(), - }), - None, - ); + // 创建请求消息 + let request_message = Message::new( + "DELETE".to_string(), + message::NativeEventAction::Other, + Some(model_map), + Some(ApiServerMessage { + content: request_data.clone(), + }), + None, + ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; + send_request(request_message, nats_cli) + .await + .map_err(ErrorInternalServerError)?; - let resource_type = format!("{}/{}/{}", version, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", version, namespace, plural, item_name); + // 发送事件 + let resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}", ver, namespace, plural) + } else { + format!("{}/{}", ver, plural) + }; + let watchone_resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}/{}", ver, namespace, plural, name) + } else { + format!("{}/{}/{}", ver, plural, name) + }; - event_manager.send_event(&resource_type, EventType::Create, data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Create, data.clone()).await; + event_manager + .send_event(&resource_type, EventType::Delete, request_data.clone()) + .await; + event_manager + .send_event(&watchone_resource_type, EventType::Delete, request_data) + .await; + + // 删除资源 + let deleted = delete_from_kine( + db_connection, + plural, + name, + &ver, + namespace, + ) + .await + .map_err(ErrorInternalServerError)?; - insert_kine(db_connection, &plural, item_name, &data, &version, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; + if !deleted { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "指定数据不存在" + }))); } - Ok(HttpResponse::Ok().json(data)) + Ok(HttpResponse::Ok().json(json!({ + "status": "资源删除成功" + }))) } - // /apis/{group}/{version}/{plural} - async fn create_apis_without_namespace( + async fn update_resource( &self, - info: web::Path<(String, String, String)>, + params: HashMap, data: web::Json, db_connection: &mut DbConnection, nats_cli: Arc, event_manager: EventManager, ) -> Result { - let (group, version, plural) = info.into_inner(); - let ver = group + "/" + &*version; + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + let name = params.get("name").unwrap(); // 必须存在 let data = data.into_inner(); - //判断是正常的插入函数还是 crd 资源的注册函数 - if plural == "crds" { - let item_kind = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("plural")) - .and_then(|plural| plural.as_str()) - .unwrap_or("error"); - - let kind_upper = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("kind")) - .and_then(|kind| kind.as_str()) - .unwrap_or("error"); - - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, item_kind, &ver, false).await.map_err(ErrorInternalServerError)?; - - if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); - } - - insert_metadata(db_connection, item_kind, &ver, false, &data) - .await - .map_err(ErrorInternalServerError)?; - - insert_key_value(item_kind, kind_upper); + let ver = if let Some(group) = group { + format!("{}/{}", group, version) } else { - // 调用check_metadata函数 - let metadata_exists = check_metadata(db_connection, &plural, &ver, false).await.map_err(ErrorInternalServerError)?; + version.to_string() + }; - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); - } + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; - // 从 json_data 中提取 metadata.name - let item_name = data - .get("metadata") - .and_then(|metadata| metadata.get("name")) - .and_then(|name| name.as_str()) - .unwrap_or("error"); + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" + }))); + } - let kine_exists = check_kine(db_connection, &plural, item_name, &ver, None).await.map_err(ErrorInternalServerError)?; + // 检查资源是否存在 + let kine_exists = check_kine( + db_connection, + plural, + name, + &ver, + namespace, + ) + .await + .map_err(ErrorInternalServerError)?; - if kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "该资源已存在,请勿重复创建" }))); - } + if !kine_exists { + return Ok(HttpResponse::InternalServerError().json(json!({ + "error": "指定数据不存在" + }))); + } - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - // request消息 - let request_message = Message::new( - "CREATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: data.clone(), - }), - None, - ); - - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", ver, plural); - let watchone_resource_type = format!("{}/{}/{}", ver, plural, item_name); - - event_manager.send_event(&resource_type, EventType::Create, data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Create, data.clone()).await; - - insert_kine(db_connection, &plural, item_name, &data, &ver, None) - .await - .map_err(ErrorInternalServerError)?; - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn create_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (group, version, namespace, plural) = info.into_inner(); - - let ver = group + "/" + &*version; - let data = data.into_inner(); - - //判断是正常的插入函数还是 crd 资源的注册函数 - if plural == "crds" { - let item_kind = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("plural")) - .and_then(|plural| plural.as_str()) - .unwrap_or("error"); - - let kind_upper = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("kind")) - .and_then(|kind| kind.as_str()) - .unwrap_or("error"); - - // 调用 check_metadata 函数 - let metadata_exists = check_metadata(db_connection, item_kind, &ver, true).await.map_err(ErrorInternalServerError)?; - - if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); - } - - insert_metadata(db_connection, item_kind, &ver, true, &data) - .await - .map_err(ErrorInternalServerError)?; - - insert_key_value(item_kind, kind_upper); - } else { - // 调用 check_metadata 函数 - let metadata_exists = check_metadata(db_connection, &plural, &ver, true).await.map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); - } - - // 从 json_data 中提取 metadata.name - let item_name = data - .get("metadata") - .and_then(|metadata| metadata.get("name")) - .and_then(|name| name.as_str()) - .unwrap_or("error"); - - let kine_exists = check_kine(db_connection, &plural, item_name, &ver, Some(&namespace)).await.map_err(ErrorInternalServerError)?; - - if kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "该资源已存在,请勿重复创建" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - // request消息 - let request_message = Message::new( - "CREATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: data.clone(), - }), - None, - ); - - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}/{}", ver, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", ver, namespace, plural, item_name); - - event_manager.send_event(&resource_type, EventType::Create, data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Create, data.clone()).await; - - insert_kine(db_connection, &plural, item_name, &data, &ver, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /api/{version}/{plural}/{name} - async fn delete_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (version, plural, name) = info.into_inner(); - - // 检查 metadata 中是否存在该 plural 且不需要 namespace - let metadata_exists = check_metadata(db_connection, &plural, &version, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &version, None).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": version.clone(), - "Namespaces": "", - "Plural": plural.clone(), - "Name": name.clone(), - }); - - // request消息 - let request_message = Message::new( - "DELETE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", version, plural); - let watchone_resource_type = format!("{}/{}/{}", version, plural, name); - - event_manager.send_event(&resource_type, EventType::Delete, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Delete, request_data).await; - - // 从 plural 表中删除指定的 name - let deleted = delete_from_kine(db_connection, &plural, &name, &version, None) - .await - .map_err(ErrorInternalServerError)?; - - if !deleted { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(json!({ "status": "delete_api_without_namespace success" }))) - } - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (version, namespace, plural, name) = info.into_inner(); - // 检查 metadata 中是否存在该 plural 且需要 namespace - let metadata_exists = check_metadata(db_connection, &plural, &version, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &version, Some(&namespace)).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": version.clone(), - "Namespaces": namespace.clone(), - "Plural": plural.clone(), - "Name": name.clone(), - }); - - // request消息 - let request_message = Message::new( - "DELETE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}/{}", version, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", version, namespace, plural, name); - - event_manager.send_event(&resource_type, EventType::Delete, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Delete, request_data).await; - - // 从 plural 表中删除指定的数据 - let deleted = delete_from_kine(db_connection, &plural, &name, &version, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - - if !deleted { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(json!({ "status": "delete_api_with_namespace success" }))) - } - - // /apis/{group}/{version}/{plural}/{name} - async fn delete_apis_without_namespace( - &self, - info: web::Path<(String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (group, version, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let metadata_exists = check_metadata(db_connection, &plural, &ver, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &ver, None).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": ver.clone(), - "Namespaces": "", - "Plural": plural.clone(), - "Name": name.clone(), - }); - - // request消息 - let request_message = Message::new( - "DELETE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", ver, plural); - let watchone_resource_type = format!("{}/{}/{}", ver, plural, name); - - event_manager.send_event(&resource_type, EventType::Delete, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Delete, request_data).await; - - let deleted = delete_from_kine(db_connection, &plural, &name, &ver, None) - .await - .map_err(ErrorInternalServerError)?; - - if !deleted { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(json!({ "status": "delete_apis_without_namespace success" }))) - } - - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String, String)>, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (group, version, namespace, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let metadata_exists = check_metadata(db_connection, &plural, &ver, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &ver, Some(&namespace)).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": ver.clone(), - "Namespaces": namespace.clone(), - "Plural": plural.clone(), - "Name": name.clone(), - }); - - // request消息 - let request_message = Message::new( - "DELETE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}/{}", ver, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", ver, namespace, plural, name); - - event_manager.send_event(&resource_type, EventType::Delete, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Delete, request_data).await; - - let deleted = delete_from_kine(db_connection, &plural, &name, &ver, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - - if !deleted { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(json!({ "status": "delete_apis_with_namespace success" }))) - } - - // /api/{version}/{plural}/{name} - async fn update_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (version, plural, name) = info.into_inner(); - let data = data.into_inner(); - - let metadata_exists = check_metadata(db_connection, &plural, &version, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &version, None).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": version.clone(), - "Namespaces": "", - "Plural": plural.clone(), - "Name": name.clone(), - "Data": data.clone() - }); - - // request消息 - let request_message = Message::new( - "UPDATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", version, plural); - let watchone_resource_type = format!("{}/{}/{}", version, plural, name); - - event_manager.send_event(&resource_type, EventType::Update, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Update, request_data).await; - - // 查询 plural 表中是否存在 name 匹配的数据 - let updated = update_data_in_kine(db_connection, &plural, &name, &version, None, &data) - .await - .map_err(ErrorInternalServerError)?; - - if !updated { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (version, namespace, plural, name) = info.into_inner(); - let data = data.into_inner(); - - // 检查 metadata 中是否存在 plural 且要求 namespace - let metadata_exists = check_metadata(db_connection, &plural, &version, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &version, Some(&namespace)).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": version.clone(), - "Namespaces": namespace.clone(), - "Plural": plural.clone(), - "Name": name.clone(), - "Data": data.clone() - }); - - // request消息 - let request_message = Message::new( - "UPDATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}/{}", version, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", version, namespace, plural, name); - - event_manager.send_event(&resource_type, EventType::Update, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Update, request_data).await; - - let updated = update_data_in_kine(db_connection, &plural, &name, &version, Some(&namespace), &data) - .await - .map_err(ErrorInternalServerError)?; - - if !updated { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /apis/{group}/{version}/{plural}/{name} - async fn update_apis_without_namespace( - &self, - info: web::Path<(String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (group, version, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - let data = data.into_inner(); - - let metadata_exists = check_metadata(db_connection, &plural, &ver, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &ver, None).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": ver, - "Namespaces": "", - "Plural": plural.clone(), - "Name": name.clone(), - "Data": data.clone() - }); - - // request消息 - let request_message = Message::new( - "UPDATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}", ver, plural); - let watchone_resource_type = format!("{}/{}/{}", ver, plural, name); - - event_manager.send_event(&resource_type, EventType::Update, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Update, request_data).await; - - let updated = update_data_in_kine(db_connection, &plural, &name, &ver, None, &data) - .await - .map_err(ErrorInternalServerError)?; - - if !updated { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String, String)>, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let (group, version, namespace, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - let data = data.into_inner(); - - let metadata_exists = check_metadata(db_connection, &plural, &ver, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let kine_exists = check_kine(db_connection, &plural, &name, &ver, Some(&namespace)).await.map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ "error": "指定数据不存在" }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(&*plural.clone()).unwrap(); - - model_map.insert("MODEL".to_string(), kind); + let mut model_map = HashMap::new(); + let kind = get_value(plural).unwrap(); + model_map.insert("MODEL".to_string(), kind); let request_data = json!({ - "apiVersion": ver, - "Namespaces": namespace.clone(), - "Plural": plural.clone(), - "Name": name.clone(), - "Data": data.clone() + "apiVersion": ver.clone(), + "Namespaces": namespace.unwrap_or(""), + "Plural": plural.clone(), + "Name": name.clone(), + "Data": data.clone() }); - // request消息 + // 创建请求消息 let request_message = Message::new( "UPDATE".to_string(), message::NativeEventAction::Other, @@ -2149,506 +1383,249 @@ impl Handler for DefaultHandler { }), None, ); - send_request(request_message, nats_cli).await.map_err(ErrorInternalServerError)?; - - let resource_type = format!("{}/{}/{}", ver, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", ver, namespace, plural, name); - - event_manager.send_event(&resource_type, EventType::Update, request_data.clone()).await; - event_manager.send_event(&watchone_resource_type, EventType::Update, request_data).await; - - let updated = update_data_in_kine(db_connection, &plural, &name, &ver, Some(&namespace), &data) - .await - .map_err(ErrorInternalServerError)?; - - if !updated { - return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /api/{version}/{plural}/{name} - async fn getone_api_without_namespace( - &self, - info: web::Path<(String, String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (version, plural, name) = info.into_inner(); - let metadata_exists = check_metadata(db_connection, &plural, &version, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - if let Some(data) = get_data_from_kine(db_connection, &plural, &name, &version, None) - .await - .map_err(ErrorInternalServerError)? { - // 将字符串转换为 JSON 格式 - match serde_json::from_str::(&data) { - Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 - Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), - } - } - Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) - } - - // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_api_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (version, namespace, plural, name) = info.into_inner(); - let metadata_exists = check_metadata(db_connection, &plural, &version, true) + send_request(request_message, nats_cli) .await .map_err(ErrorInternalServerError)?; - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - if let Some(data) = get_data_from_kine(db_connection, &plural, &name, &version, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)? { - // 将字符串转换为 JSON 格式 - match serde_json::from_str::(&data) { - Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 - Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), - } - } - Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) - } - - // /apis/{group}/{version}/{plural}/{name} - async fn getone_apis_without_namespace( - &self, - info: web::Path<(String, String, String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (group, version, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); + // 发送事件 + let resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}", ver, namespace, plural) + } else { + format!("{}/{}", ver, plural) + }; + let watchone_resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}/{}", ver, namespace, plural, name) + } else { + format!("{}/{}/{}", ver, plural, name) + }; - let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + event_manager + .send_event(&resource_type, EventType::Update, request_data.clone()) + .await; + event_manager + .send_event(&watchone_resource_type, EventType::Update, request_data) + .await; + + // 更新资源 + let updated = update_data_in_kine( + db_connection, + plural, + name, + &ver, + namespace, + &data, + ) .await .map_err(ErrorInternalServerError)?; - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "指定数据不存在" + }))); } - if let Some(data) = get_data_from_kine(db_connection, &plural, &name, &ver, None) - .await - .map_err(ErrorInternalServerError)? { - // 将字符串转换为 JSON 格式 - match serde_json::from_str::(&data) { - Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 - Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), - } - } - Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) + Ok(HttpResponse::Ok().json(data)) } - // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_apis_with_namespace( + async fn getone_resource( &self, - info: web::Path<(String, String, String, String, String)>, + params: HashMap, _data: web::Query, db_connection: &mut DbConnection, _nats_cli: Arc, ) -> Result { - let (group, version, namespace, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + let name = params.get("name").unwrap(); // 必须存在 + + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() + }; - let metadata_exists = check_metadata(db_connection, &plural, &ver, true) + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) .await .map_err(ErrorInternalServerError)?; if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" + }))); } - if let Some(data) = get_data_from_kine(db_connection, &plural, &name, &ver, Some(&namespace)) + // 获取资源数据 + if let Some(data) = get_data_from_kine( + db_connection, + plural, + name, + &ver, + namespace, + ) .await .map_err(ErrorInternalServerError)? { - // 将字符串转换为 JSON 格式 - match serde_json::from_str::(&data) { - Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 - Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), - } + // 将字符串解析为 JSON 格式 + match serde_json::from_str::(&data) { + Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), + Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ + "error": "数据格式错误,无法解析为 JSON" + }))), } - Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) - } - - // /api/{version}/{plural} - async fn listall_api_without_namespace( - &self, - info: web::Path<(String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (version, plural) = info.into_inner(); - let metadata_exists = check_metadata(db_connection, &plural, &version, false) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let data_list = get_all_data_from_kine(db_connection, &plural, &version, None) - .await - .map_err(ErrorInternalServerError)?; - - // 将所有字符串解析为 JSON 格式并收集到一个数组中 - let json_array: Vec = data_list - .into_iter() - .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 - .collect(); - - Ok(HttpResponse::Ok().json(json_array)) - } - - // /api/{version}/namespaces/{namespace}/{plural} - async fn listall_api_with_namespace( - &self, - info: web::Path<(String, String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (version, namespace, plural) = info.into_inner(); - let metadata_exists = check_metadata(db_connection, &plural, &version, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); } - let data_list = get_all_data_from_kine(db_connection, &plural, &version, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - - // 将所有字符串解析为 JSON 格式并收集到一个数组中 - let json_array: Vec = data_list - .into_iter() - .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 - .collect(); - - Ok(HttpResponse::Ok().json(json_array)) + // 如果资源不存在 + Ok(HttpResponse::NotFound().json(json!({ + "error": "指定数据不存在" + }))) } - // /apis/{group}/{version}/{plural} - async fn listall_apis_without_namespace( + async fn listall_resource( &self, - info: web::Path<(String, String, String)>, + params: HashMap, _data: web::Query, db_connection: &mut DbConnection, _nats_cli: Arc, ) -> Result { - let (group, version, plural) = info.into_inner(); - let ver = format!("{}/{}", group, version); + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + + // 组合版本号 + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() + }; - let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) .await .map_err(ErrorInternalServerError)?; if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" + }))); } - let data_list = get_all_data_from_kine(db_connection, &plural, &ver, None) + // 获取资源列表 + let data_list = get_all_data_from_kine(db_connection, plural, &ver, namespace) .await .map_err(ErrorInternalServerError)?; - // 将所有字符串解析为 JSON 格式并收集到一个数组中 + // 将所有字符串解析为 JSON 格式并收集到数组中 let json_array: Vec = data_list .into_iter() .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 .collect(); + // 返回结果 Ok(HttpResponse::Ok().json(json_array)) } - // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn listall_apis_with_namespace( + async fn watchall_resource( &self, - info: web::Path<(String, String, String, String)>, + params: HashMap, _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (group, version, namespace, plural) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let metadata_exists = check_metadata(db_connection, &plural, &ver, true) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); - } - - let data_list = get_all_data_from_kine(db_connection, &plural, &ver, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - - // 将所有字符串解析为 JSON 格式并收集到一个数组中 - let json_array: Vec = data_list - .into_iter() - .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 - .collect(); - - Ok(HttpResponse::Ok().json(json_array)) - } - - async fn watchall_api_without_namespace( - &self, - info: Path<(String, String)>, - _data: Query, - event_manager: EventManager + event_manager: EventManager, ) -> Result { - let (version, plural) = info.into_inner(); - let resource_type = format!("{}/{}", version, plural); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - println!("{}", json_event); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } - } + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + + // 生成 resource_type + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() }; - - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchall_api_with_namespace( - &self, - info: Path<(String, String, String)>, - _data: Query, - event_manager: EventManager - ) -> Result { - let (version, namespace, plural) = info.into_inner(); - let resource_type = format!("{}/{}/{}", version, namespace, plural); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } - } + let resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}", ver, namespace, plural) + } else { + format!("{}/{}", ver, plural) }; - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchall_apis_without_namespace( - &self, - info: Path<(String, String, String)>, - _data: Query, - event_manager: EventManager - ) -> Result { - let (group, version, plural) = info.into_inner(); - let ver = format!("{}/{}", group, version); - let resource_type = format!("{}/{}", ver, plural); - + // 监听事件 let channel = event_manager.get_channel(&resource_type); let mut rx = channel.subscribe(); let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } + loop { + match rx.recv().await { + Ok(event) => { + let json_event = serde_json::to_string(&event).unwrap(); + yield Ok::(Bytes::from(json_event)); } - } - }; - - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchall_apis_with_namespace( - &self, - info: Path<(String, String, String, String)>, - _data: Query, - event_manager: EventManager - ) -> Result { - let (group, version, namespace, plural) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let resource_type = format!("{}/{}/{}", ver, namespace, plural); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } + Err(_) => { + yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); } } - }; + } + }; + // 返回 SSE 流 Ok(HttpResponse::Ok() .content_type("text/event-stream") .streaming(stream)) } - async fn watchone_api_without_namespace(&self, info: Path<(String, String, String)>, _data: Query, event_manager: EventManager) -> Result { - let (version, plural, name) = info.into_inner(); - let resource_type = format!("{}/{}/{}", version, plural, name); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } - } + async fn watchone_resource( + &self, + params: HashMap, + _data: web::Query, + event_manager: EventManager, + ) -> Result { + // 解析路径参数 + let group = params.get("group").map(String::as_str); + let version = params.get("version").unwrap(); // 必须存在 + let namespace = params.get("namespace").map(String::as_str); + let plural = params.get("plural").unwrap(); // 必须存在 + let name = params.get("name").unwrap(); // 必须存在 + + // 生成 resource_type + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() }; - - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchone_api_with_namespace(&self, info: Path<(String, String, String, String)>, _data: Query, event_manager: EventManager) -> Result { - let (version, namespace, plural, name) = info.into_inner(); - let resource_type = format!("{}/{}/{}/{}", version, namespace, plural, name); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } - } + let resource_type = if let Some(namespace) = namespace { + format!("{}/{}/{}/{}", ver, namespace, plural, name) + } else { + format!("{}/{}/{}", ver, plural, name) }; - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchone_apis_without_namespace(&self, info: Path<(String, String, String, String)>, _data: Query, event_manager: EventManager) -> Result { - let (group, version, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let resource_type = format!("{}/{}/{}", ver, plural, name); - + // 订阅事件 let channel = event_manager.get_channel(&resource_type); let mut rx = channel.subscribe(); let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } + loop { + match rx.recv().await { + Ok(event) => { + let json_event = serde_json::to_string(&event).unwrap(); + yield Ok::(Bytes::from(json_event)); } - } - }; - - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - async fn watchone_apis_with_namespace(&self, info: Path<(String, String, String, String, String)>, _data: Query, event_manager: EventManager) -> Result { - let (group, version, namespace, plural, name) = info.into_inner(); - let ver = format!("{}/{}", group, version); - - let resource_type = format!("{}/{}/{}/{}", ver, namespace, plural, name); - - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } + Err(_) => { + yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); } } - }; + } + }; + // 返回 SSE 流 Ok(HttpResponse::Ok() .content_type("text/event-stream") .streaming(stream)) } - fn default(&self) -> DefaultHandler { DefaultHandler {} } -- Gitee From 1b2c119dc3932455bbff5294ccce1053acf605e1 Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Fri, 13 Dec 2024 09:11:13 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.sqlite | Bin 0 -> 61440 bytes src/cores/apiserver.rs | 2 -- src/cores/checker.rs | 4 ++-- src/cores/db.rs | 7 +++++++ src/cores/handlers.rs | 7 +++++++ src/cores/mod.rs | 5 +++-- src/lib.rs | 7 +++++++ src/main.rs | 7 +++++++ tests/route_tests.rs | 7 +++++++ 9 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 database.sqlite diff --git a/database.sqlite b/database.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a9f56ec84f00630aca3f332896a584e2c82972a7 GIT binary patch literal 61440 zcmeI)&2QRf9KdlqB)p}Brdot{h~%M3DN@p4FmK6aG^JVEytI^bt)>b&;H7kc8933V zu7}Y658H9l{)YVzQ?I@4u;Y$9?NIHs$57G`;$)qaNpZdw1p9d&+t2fP9vkov=fVB; znq??oneB#dDM{(7B+JrgiXuspM?AygIhcH6W_++9Ugab6PO~2A#us-&;kVM1_q8%uYG?zZGdzH5I_I{1Wp&|E`)rMxjFgO$CkcR zH@>bljdI&))oT?!F|;tXQY_?41*Md~xn59)vXtvVMNA^CsyxgWS3b`duP0LJ*nBU! zsW%LVr+^U1Gh)#Bx*K$i|#3iNeOWop0*mV?bHov&`Odd5EQ~ z4lRbe3zJUNLnYNKj@0UHPpQp@I_{3#0F zhi-2iPIeqk=a@K z<=vr?J197QBBgtV=i<0GvNC(-@H~W@ICv!cBKf@BUG906%hj6EG3w<`<*Cuo%MI~1 ztXnm+*>TPp8!~k+_fA=SPS`iv9kI>B13MLuDM#OlH@8a4=Jxt}|2!a8Xzw-4qI_v> zqwt`V-?*m?N+`DqxAWWUC1qv1SS)Oo%7;aQu?J(`$Zp=r!nvMh)j7}pZtu50Pld!& z4*w>J2NMDaAbfBAKz57SzOonpEPs zrFeEJnGT9}$>BdG@nAv#0R#|0009ILKmY**5I_I{R|rgdVu9gH0(Si06Z%97e>8Pl zyf7hv00IagfB*srAb(Xo-XdI&!ghDj)(oL8XoR=d{RjlP(Vc3MWo zE@~N#R$bihWzV0si6g62%%-J_JA>L{4_}JfwRi1Z>Fu`uo!C>Yq3<5q)yS@&yb#j~ z;o%#Zq^6})@s#SEspirLcgOq@|8#eHb+u_$jn0VAzWQEfQjBH_KJY8frA9@J#f<#(;IWotR8EN-fWsy z|6T)8owNJ;SjSb&Mnmt7pbzLfht5f8T2@OrW@a*K+>ZZy!f&MToA4juzb@}#PRkKM z009ILKmY**5I_I{1Q57j0@o(Q^(y)3#VSu|LR_7Z-(8v+n-m461N}=I#Q!f??^0a^ z5I_I{1Q0*~0R#{@KLO(Znz(N~>l8&6nTf1=k>dX;P0hv~V&Ym#OKKU{#Q&4&bTXNB zXsgJ~#B;>|&+pX1mJvVz0R#|0009ILKmY**5I9Ex;{WG}%cc=P009ILKmY**5J14i z0>uBdOeUo|MUh2jI+MFl@&BBfaQrnxDwoJ;?uq}4|N5WPoZ|o4gqBl@|GW6yMbi*K z009ILKmY**5I_I{1l%m}!SR3Ts+&1z8v+O*fB*srAb9 z0R#|0009ILKmY**5OBAE{r~^@{_pNSrgaD)fB*srAb( diff --git a/src/cores/checker.rs b/src/cores/checker.rs index ab13881..e47d071 100644 --- a/src/cores/checker.rs +++ b/src/cores/checker.rs @@ -1,7 +1,7 @@ /** * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: wuheng@iscas.ac.cn - * since: 0.1.3 + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 * **/ diff --git a/src/cores/db.rs b/src/cores/db.rs index 56016fb..881f079 100644 --- a/src/cores/db.rs +++ b/src/cores/db.rs @@ -1,3 +1,10 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + use std::collections::HashMap; use chrono::Utc; use diesel::pg::PgConnection; diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 01f467c..ff06fe5 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -1,3 +1,10 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, LazyLock, Mutex}; diff --git a/src/cores/mod.rs b/src/cores/mod.rs index a34795b..223f690 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -1,8 +1,9 @@ /** * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: wuheng@iscas.ac.cn + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn * since: 0.1.0 - **/ + * +**/ pub mod apiserver; pub mod config; diff --git a/src/lib.rs b/src/lib.rs index 559854f..ae9c790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,10 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + pub mod cores; pub mod schema; diff --git a/src/main.rs b/src/main.rs index d20e3b9..07fc105 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,10 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + use std::env; use dotenv::dotenv; use fleet_apiserver::start_server; diff --git a/tests/route_tests.rs b/tests/route_tests.rs index 824b463..befa906 100644 --- a/tests/route_tests.rs +++ b/tests/route_tests.rs @@ -1,3 +1,10 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ + use actix_web::error::ErrorInternalServerError; use feventbus::impls::nats::nats::NatsCli; use serde_json::Value; -- Gitee From a6fecb8a22112aa1cb358822f818092defce6858 Mon Sep 17 00:00:00 2001 From: Yuichi <913637919@qq.com> Date: Sat, 14 Dec 2024 12:48:30 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.sqlite | Bin 61440 -> 0 bytes src/cores/apiserver.rs | 2 +- src/cores/handlers.rs | 873 +--------------------------------------- src/cores/mod.rs | 1 - src/db/check_exist.rs | 193 +++++++++ src/{cores => db}/db.rs | 1 - src/db/delete.rs | 92 +++++ src/db/get.rs | 313 ++++++++++++++ src/db/insert.rs | 197 +++++++++ src/db/mod.rs | 12 + src/db/update.rs | 100 +++++ src/lib.rs | 3 +- 12 files changed, 916 insertions(+), 871 deletions(-) delete mode 100644 database.sqlite create mode 100644 src/db/check_exist.rs rename src/{cores => db}/db.rs (99%) create mode 100644 src/db/delete.rs create mode 100644 src/db/get.rs create mode 100644 src/db/insert.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/update.rs diff --git a/database.sqlite b/database.sqlite deleted file mode 100644 index a9f56ec84f00630aca3f332896a584e2c82972a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61440 zcmeI)&2QRf9KdlqB)p}Brdot{h~%M3DN@p4FmK6aG^JVEytI^bt)>b&;H7kc8933V zu7}Y658H9l{)YVzQ?I@4u;Y$9?NIHs$57G`;$)qaNpZdw1p9d&+t2fP9vkov=fVB; znq??oneB#dDM{(7B+JrgiXuspM?AygIhcH6W_++9Ugab6PO~2A#us-&;kVM1_q8%uYG?zZGdzH5I_I{1Wp&|E`)rMxjFgO$CkcR zH@>bljdI&))oT?!F|;tXQY_?41*Md~xn59)vXtvVMNA^CsyxgWS3b`duP0LJ*nBU! zsW%LVr+^U1Gh)#Bx*K$i|#3iNeOWop0*mV?bHov&`Odd5EQ~ z4lRbe3zJUNLnYNKj@0UHPpQp@I_{3#0F zhi-2iPIeqk=a@K z<=vr?J197QBBgtV=i<0GvNC(-@H~W@ICv!cBKf@BUG906%hj6EG3w<`<*Cuo%MI~1 ztXnm+*>TPp8!~k+_fA=SPS`iv9kI>B13MLuDM#OlH@8a4=Jxt}|2!a8Xzw-4qI_v> zqwt`V-?*m?N+`DqxAWWUC1qv1SS)Oo%7;aQu?J(`$Zp=r!nvMh)j7}pZtu50Pld!& z4*w>J2NMDaAbfBAKz57SzOonpEPs zrFeEJnGT9}$>BdG@nAv#0R#|0009ILKmY**5I_I{R|rgdVu9gH0(Si06Z%97e>8Pl zyf7hv00IagfB*srAb(Xo-XdI&!ghDj)(oL8XoR=d{RjlP(Vc3MWo zE@~N#R$bihWzV0si6g62%%-J_JA>L{4_}JfwRi1Z>Fu`uo!C>Yq3<5q)yS@&yb#j~ z;o%#Zq^6})@s#SEspirLcgOq@|8#eHb+u_$jn0VAzWQEfQjBH_KJY8frA9@J#f<#(;IWotR8EN-fWsy z|6T)8owNJ;SjSb&Mnmt7pbzLfht5f8T2@OrW@a*K+>ZZy!f&MToA4juzb@}#PRkKM z009ILKmY**5I_I{1Q57j0@o(Q^(y)3#VSu|LR_7Z-(8v+n-m461N}=I#Q!f??^0a^ z5I_I{1Q0*~0R#{@KLO(Znz(N~>l8&6nTf1=k>dX;P0hv~V&Ym#OKKU{#Q&4&bTXNB zXsgJ~#B;>|&+pX1mJvVz0R#|0009ILKmY**5I9Ex;{WG}%cc=P009ILKmY**5J14i z0>uBdOeUo|MUh2jI+MFl@&BBfaQrnxDwoJ;?uq}4|N5WPoZ|o4gqBl@|GW6yMbi*K z009ILKmY**5I_I{1l%m}!SR3Ts+&1z8v+O*fB*srAb9 z0R#|0009ILKmY**5OBAE{r~^@{_pNSrgaD)fB*srAb HashMap { diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index ff06fe5..e46f02e 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -11,12 +11,9 @@ use std::sync::{Arc, LazyLock, Mutex}; use std::{time, vec::Vec}; use async_trait::async_trait; use actix_web::{HttpResponse, Result, web}; -use chrono::Utc; -use diesel::{QueryResult, RunQueryDsl, QueryDsl, ExpressionMethods, OptionalExtension, QueryableByName, PgConnection, sql_query, SqliteConnection, Connection}; use serde_json::{json}; use serde_json::Value; -use crate::cores::db::DbConnection; -use diesel::sql_types::{Text}; +use crate::db::db::DbConnection; use actix_web::error::ErrorInternalServerError; use feventbus::impls::nats::nats::NatsCli; use feventbus::message::Message; @@ -25,7 +22,11 @@ use feventbus::traits::producer::Producer; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; use actix_web::web::Bytes; - +use crate::db::check_exist::{check_metadata, check_kine}; +use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; +use crate::db::insert::{insert_kine, insert_metadata}; +use crate::db::delete::delete_from_kine; +use crate::db::update::update_data_in_kine; // 定义全局哈希表来获取model名 static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { let mut map = HashMap::new(); @@ -120,867 +121,6 @@ impl DefaultHandler { } } - -// 查询 metadata 表中的 plural 是否存在,并检查 namespace 要求是否满足 -async fn check_metadata( - conn: &mut DbConnection, - plural: &str, - version: &str, - requires_namespace: bool, -) -> QueryResult { - use diesel::dsl::count_star; - use crate::schema::metadata::dsl as metadata_dsl; - use crate::schema::metadata_replica1::dsl as replica1_dsl; - use crate::schema::metadata_replica2::dsl as replica2_dsl; - - let count; - let count_replica1; - let count_replica2; - match conn { - DbConnection::Pg(pg_conn) => { - count = metadata_dsl::metadata - .filter(metadata_dsl::name.eq(plural)) - .filter(metadata_dsl::apigroup.eq(version)) - .filter(metadata_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::metadata_replica1 - .filter(replica1_dsl::name.eq(plural)) - .filter(replica1_dsl::apigroup.eq(version)) - .filter(replica1_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::metadata_replica2 - .filter(replica2_dsl::name.eq(plural)) - .filter(replica2_dsl::apigroup.eq(version)) - .filter(replica2_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(pg_conn)?; - }, - DbConnection::Sqlite(sqlite_conn) => { - count = metadata_dsl::metadata - .filter(metadata_dsl::name.eq(plural)) - .filter(metadata_dsl::apigroup.eq(version)) - .filter(metadata_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::metadata_replica1 - .filter(replica1_dsl::name.eq(plural)) - .filter(replica1_dsl::apigroup.eq(version)) - .filter(replica1_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::metadata_replica2 - .filter(replica2_dsl::name.eq(plural)) - .filter(replica2_dsl::apigroup.eq(version)) - .filter(replica2_dsl::namespace.eq(requires_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - } - } - let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); - Ok(positive_count >= 2) -} - - - -// 查询 kine 表中指定的数据是否存在 -async fn check_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult { - use diesel::dsl::count_star; - use crate::schema::kine::dsl as kine_dsl; - use crate::schema::kine_replica1::dsl as replica1_dsl; - use crate::schema::kine_replica2::dsl as replica2_dsl; - - let count; - let count_replica1; - let count_replica2; - match conn { - DbConnection::Pg(pg_conn) => { - if let Some(_) = item_namespace { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .filter(kine_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .filter(replica1_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .filter(replica2_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(pg_conn)?; - } else { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(pg_conn)?; - } - }, - DbConnection::Sqlite(sqlite_conn) => { - if let Some(_) = item_namespace { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .filter(kine_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .filter(replica1_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .filter(replica2_dsl::namespace.eq(item_namespace)) - .select(count_star()) - .first::(sqlite_conn)?; - } else { - count = kine_dsl::kine - .filter(kine_dsl::kind.eq(item_kind)) - .filter(kine_dsl::name.eq(item_name)) - .filter(kine_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica1 = replica1_dsl::kine_replica1 - .filter(replica1_dsl::kind.eq(item_kind)) - .filter(replica1_dsl::name.eq(item_name)) - .filter(replica1_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - - count_replica2 = replica2_dsl::kine_replica2 - .filter(replica2_dsl::kind.eq(item_kind)) - .filter(replica2_dsl::name.eq(item_name)) - .filter(replica2_dsl::apigroup.eq(item_version)) - .select(count_star()) - .first::(sqlite_conn)?; - } - } - } - let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); - Ok(positive_count >= 2) -} - -fn insert_metadata_in_transaction_pg( - transaction: &mut PgConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value, -) -> QueryResult<()> { - use diesel::sql_types::{Bool}; - - // 表名列表 - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT DO NOTHING;", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(plural) // 名称 - .bind::(namespace_required) // 是否需要命名空间 - .bind::(version) // 版本 - .bind::(json_data.to_string()) // JSON 数据 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -fn insert_metadata_in_transaction_sqlite( - transaction: &mut SqliteConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value, -) -> QueryResult<()> { - use diesel::sql_types::{Bool}; - - // 表名列表 - let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) - VALUES (?, ?, ?, ?, ?, ?);", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(plural) // 名称 - .bind::(namespace_required) // 是否需要命名空间 - .bind::(version) // 版本 - .bind::(json_data.to_string()) // JSON 数据 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -async fn insert_metadata( - conn: &mut DbConnection, - plural: &str, - version: &str, - namespace_required: bool, - json_data: &Value -) -> QueryResult<()> { - match conn { - DbConnection::Pg(pg_conn) => { - pg_conn.transaction(|transaction| { - insert_metadata_in_transaction_pg(transaction, plural, version, namespace_required, json_data) - }) - } - DbConnection::Sqlite(sqlite_conn) => { - sqlite_conn.transaction(|transaction| { - insert_metadata_in_transaction_sqlite(transaction, plural, version, namespace_required, json_data) - }) - } - }.expect("unknow conn in insert_metadata"); - Ok(()) -} - -fn insert_kine_in_transaction_pg( - transaction: &mut PgConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - - // 表列表 - let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; - - for table_name in table_array { - // 使用参数绑定构建插入查询 - let insert_metadata_query = format!( - "INSERT INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT DO NOTHING;", - table_name - ); - - // 执行插入操作 - sql_query(insert_metadata_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace.unwrap_or("")) // 空字符串作为默认 namespace - .bind::(version) - .bind::(json_data.to_string()) // 将 JSON 数据转换为字符串 - .bind::(Utc::now().naive_utc().to_string()) // 创建时间 - .bind::(Utc::now().naive_utc().to_string()) // 更新时间 - .execute(transaction)?; - } - - Ok(()) -} - - -fn insert_kine_in_transaction_sqlite( - transaction: &mut SqliteConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - - let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; - - for table_name in table_array { - let insert_metadata_query = format!( - "INSERT OR IGNORE INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) - VALUES (?, ?, ?, ?, ?, ?, ?);", - table_name - ); - - sql_query(insert_metadata_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace.unwrap_or("")) // 使用 Nullable 处理空值 - .bind::(version) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .execute(transaction)?; - } - Ok(()) -} - - -// 在 kine 表中插入新记录 -async fn insert_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - json_data: &Value, - version: &str, - namespace: Option<&str>, -) -> QueryResult<()> { - match conn { - DbConnection::Pg(pg_conn) => { - pg_conn.transaction(|transaction| { - insert_kine_in_transaction_pg(transaction, item_kind, item_name, json_data, version, namespace) - }) - } - DbConnection::Sqlite(sqlite_conn) => { - sqlite_conn.transaction(|transaction| { - insert_kine_in_transaction_sqlite(transaction, item_kind, item_name, json_data, version, namespace) - }) - } - }.expect("unknow conn in insert_kine"); - Ok(()) -} - -// 从 kine 表中删除特定 name 的记录 -async fn delete_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult { - use diesel::sql_types::Text; - - // 表名列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 遍历每个表,执行删除操作 - let mut total_rows_affected = 0; - - for &table in &tables { - let delete_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "DELETE FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", - table - ), - DbConnection::Sqlite(_) => format!( - "DELETE FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "DELETE FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "DELETE FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - // 执行删除 - let rows_affected = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(pg_conn)? - } else { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(pg_conn)? - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(sqlite_conn)? - } else { - diesel::sql_query(delete_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(sqlite_conn)? - } - } - }; - - total_rows_affected += rows_affected; - } - - // 如果至少有两个表进行了删除,则返回 true - Ok(total_rows_affected > 1) -} - - - -async fn update_data_in_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, - json_data: &Value, -) -> QueryResult { - use diesel::sql_types::Text; - use chrono::Utc; - - // 需要更新的表列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - let mut total_rows_affected = 0; - - for &table in &tables { - let update_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND namespace = $5 AND apigroup = $6", - table - ), - DbConnection::Sqlite(_) => format!( - "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND apigroup = $5", - table - ), - DbConnection::Sqlite(_) => format!( - "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - // 执行更新操作 - let rows_affected = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(pg_conn)? - } else { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(pg_conn)? - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .execute(sqlite_conn)? - } else { - diesel::sql_query(update_query) - .bind::(json_data.to_string()) - .bind::(Utc::now().naive_utc().to_string()) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .execute(sqlite_conn)? - } - } - }; - - total_rows_affected += rows_affected; - } - - // 如果至少有两个表更新成功,则返回 true - Ok(total_rows_affected > 1) -} - - -// 辅助查询函数,用于获取数据的 `data` 字段 -#[derive(QueryableByName)] -struct DataResult { - #[diesel(sql_type = Text)] - data: String, -} - - - -async fn get_data_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - use diesel::sql_types::Text; - use std::collections::HashMap; - - // 表名列表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 存储每个表的查询结果 - let mut results = HashMap::new(); - - for &table in &tables { - let select_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", - table - ), - } - }; - - let data_result = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(pg_conn) - .optional()? - .map(|res| res.data) - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(pg_conn) - .optional()? - .map(|res| res.data) - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional()? - .map(|res| res.data) - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional()? - .map(|res| res.data) - } - } - }; - - if let Some(data) = data_result { - *results.entry(data).or_insert(0) += 1; - } - } - - // 按少数服从多数规则返回数据 - if results.len() == 1 { - // 如果三个表的结果一致,直接返回任意结果 - Ok(results.into_iter().next().map(|(data, _)| data)) - } else if results.values().all(|&count| count == 1) { - // 如果所有表结果不同,直接回退到 kine 表的数据 - get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) - } else if let Some((data, _)) = results.into_iter().max_by_key(|&(_, count)| count) { - // 如果有多数一致的数据,返回该数据 - Ok(Some(data)) - } else { - // 默认回退到 kine 表的数据 - get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) - } -} - -/// 获取主表 `kine` 的数据 -fn get_data_from_kine_primary( - conn: &mut DbConnection, - item_kind: &str, - item_name: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - let fallback_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4".to_string(), - DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?".to_string(), - } - } else { - match conn { - DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND apigroup = $3".to_string(), - DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND apigroup = ?".to_string(), - } - }; - - match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(pg_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } else { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(pg_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(namespace) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } else { - diesel::sql_query(fallback_query) - .bind::(item_kind) - .bind::(item_name) - .bind::(item_version) - .get_result::(sqlite_conn) - .optional() - .map(|res| res.map(|data_result| data_result.data)) - } - } - } -} - - - - -// 辅助函数:从指定表中获取所有符合条件的数据的 `data` 字段 -async fn get_all_data_from_kine( - conn: &mut DbConnection, - item_kind: &str, - item_version: &str, - item_namespace: Option<&str>, -) -> QueryResult> { - use diesel::sql_types::Text; - use std::collections::HashMap; - - // 定义需要查询的表 - let tables = ["kine", "kine_replica1", "kine_replica2"]; - - // 存储每个表的查询结果 - let mut table_results: HashMap<&str, Vec> = HashMap::new(); - - // 遍历每个表进行查询 - for &table in &tables { - let select_query = if let Some(_) = item_namespace { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND namespace = $2 AND apigroup = $3", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND namespace = ? AND apigroup = ?", - table - ), - } - } else { - match conn { - DbConnection::Pg(_) => format!( - "SELECT data FROM {} WHERE kind = $1 AND apigroup = $2", - table - ), - DbConnection::Sqlite(_) => format!( - "SELECT data FROM {} WHERE kind = ? AND apigroup = ?", - table - ), - } - }; - - // 执行查询 - let results: Vec = match conn { - DbConnection::Pg(pg_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(namespace) - .bind::(item_version) - .load::(pg_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_version) - .load::(pg_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } - } - DbConnection::Sqlite(sqlite_conn) => { - if let Some(namespace) = item_namespace { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(namespace) - .bind::(item_version) - .load::(sqlite_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } else { - diesel::sql_query(select_query) - .bind::(item_kind) - .bind::(item_version) - .load::(sqlite_conn)? - .into_iter() - .map(|res| res.data) - .collect() - } - } - }; - - table_results.insert(table, results); - } - - // 检查所有表的数据是否一致 - let vec1 = table_results[tables[0]].clone(); - let vec2 = table_results[tables[1]].clone(); - let vec3 = table_results[tables[2]].clone(); - - let max_len = vec1.len().max(vec2.len()).max(vec3.len()); - let mut filtered_results = Vec::new(); - - // 筛选出出现次数大于等于 2 的数据 - for i in 0..max_len { - // 获取当前索引的值,越界的填入 None - let value1 = vec1.get(i); - let value2 = vec2.get(i); - let value3 = vec3.get(i); - - // 统计每个值的出现次数 - let mut counts = HashMap::new(); - if let Some(v) = value1 { - *counts.entry(v).or_insert(0) += 1; - } - if let Some(v) = value2 { - *counts.entry(v).or_insert(0) += 1; - } - if let Some(v) = value3 { - *counts.entry(v).or_insert(0) += 1; - } - - // 找出出现次数大于等于 2 的值 - if let Some((value, &_)) = counts.iter().find(|(_, &count)| count >= 2) { - filtered_results.push((*value).clone()); // 将该值存入结果 - } - - } - - - Ok(filtered_results) -} - - - - // 发送请求并等待响应 async fn send_request( message: Message, @@ -1011,7 +151,6 @@ pub struct ApiServerMessage { pub content: Value, } - // watch的事件类型 #[derive(Clone, Debug, Serialize)] pub enum EventType { diff --git a/src/cores/mod.rs b/src/cores/mod.rs index 223f690..2a299bc 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -9,4 +9,3 @@ pub mod apiserver; pub mod config; pub mod handlers; pub mod checker; -pub mod db; \ No newline at end of file diff --git a/src/db/check_exist.rs b/src/db/check_exist.rs new file mode 100644 index 0000000..c04cf76 --- /dev/null +++ b/src/db/check_exist.rs @@ -0,0 +1,193 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{QueryDsl, QueryResult, RunQueryDsl, ExpressionMethods}; +use crate::db::db::DbConnection; + +// 查询 metadata 表中的 plural 是否存在,并检查 namespace 要求是否满足 +pub async fn check_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, + requires_namespace: bool, +) -> QueryResult { + use diesel::dsl::count_star; + use crate::schema::metadata::dsl as metadata_dsl; + use crate::schema::metadata_replica1::dsl as replica1_dsl; + use crate::schema::metadata_replica2::dsl as replica2_dsl; + + let count; + let count_replica1; + let count_replica2; + match conn { + DbConnection::Pg(pg_conn) => { + count = metadata_dsl::metadata + .filter(metadata_dsl::name.eq(plural)) + .filter(metadata_dsl::apigroup.eq(version)) + .filter(metadata_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::metadata_replica1 + .filter(replica1_dsl::name.eq(plural)) + .filter(replica1_dsl::apigroup.eq(version)) + .filter(replica1_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::metadata_replica2 + .filter(replica2_dsl::name.eq(plural)) + .filter(replica2_dsl::apigroup.eq(version)) + .filter(replica2_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + count = metadata_dsl::metadata + .filter(metadata_dsl::name.eq(plural)) + .filter(metadata_dsl::apigroup.eq(version)) + .filter(metadata_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::metadata_replica1 + .filter(replica1_dsl::name.eq(plural)) + .filter(replica1_dsl::apigroup.eq(version)) + .filter(replica1_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::metadata_replica2 + .filter(replica2_dsl::name.eq(plural)) + .filter(replica2_dsl::apigroup.eq(version)) + .filter(replica2_dsl::namespace.eq(requires_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + } + } + let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); + Ok(positive_count >= 2) +} + + + +// 查询 kine 表中指定的数据是否存在 +pub async fn check_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult { + use diesel::dsl::count_star; + use crate::schema::kine::dsl as kine_dsl; + use crate::schema::kine_replica1::dsl as replica1_dsl; + use crate::schema::kine_replica2::dsl as replica2_dsl; + + let count; + let count_replica1; + let count_replica2; + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(_) = item_namespace { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .filter(kine_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .filter(replica1_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .filter(replica2_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(pg_conn)?; + } else { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(pg_conn)?; + } + }, + DbConnection::Sqlite(sqlite_conn) => { + if let Some(_) = item_namespace { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .filter(kine_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .filter(replica1_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .filter(replica2_dsl::namespace.eq(item_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + } else { + count = kine_dsl::kine + .filter(kine_dsl::kind.eq(item_kind)) + .filter(kine_dsl::name.eq(item_name)) + .filter(kine_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica1 = replica1_dsl::kine_replica1 + .filter(replica1_dsl::kind.eq(item_kind)) + .filter(replica1_dsl::name.eq(item_name)) + .filter(replica1_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + + count_replica2 = replica2_dsl::kine_replica2 + .filter(replica2_dsl::kind.eq(item_kind)) + .filter(replica2_dsl::name.eq(item_name)) + .filter(replica2_dsl::apigroup.eq(item_version)) + .select(count_star()) + .first::(sqlite_conn)?; + } + } + } + let positive_count = [count, count_replica1, count_replica2].iter().filter(|&&x| x > 0).count(); + Ok(positive_count >= 2) +} \ No newline at end of file diff --git a/src/cores/db.rs b/src/db/db.rs similarity index 99% rename from src/cores/db.rs rename to src/db/db.rs index 881f079..fb8aa9d 100644 --- a/src/cores/db.rs +++ b/src/db/db.rs @@ -14,7 +14,6 @@ use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use serde_json::{json, Value}; use diesel::connection::Connection; - pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); pub enum DbPool { diff --git a/src/db/delete.rs b/src/db/delete.rs new file mode 100644 index 0000000..bc646b7 --- /dev/null +++ b/src/db/delete.rs @@ -0,0 +1,92 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{QueryResult, RunQueryDsl}; +use crate::db::db::DbConnection; + +// 从 kine 表中删除特定 name 的记录 +pub async fn delete_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult { + use diesel::sql_types::Text; + + // 表名列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 遍历每个表,执行删除操作 + let mut total_rows_affected = 0; + + for &table in &tables { + let delete_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "DELETE FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", + table + ), + DbConnection::Sqlite(_) => format!( + "DELETE FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "DELETE FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "DELETE FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + // 执行删除 + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + + total_rows_affected += rows_affected; + } + + // 如果至少有两个表进行了删除,则返回 true + Ok(total_rows_affected > 1) +} \ No newline at end of file diff --git a/src/db/get.rs b/src/db/get.rs new file mode 100644 index 0000000..b07433a --- /dev/null +++ b/src/db/get.rs @@ -0,0 +1,313 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{OptionalExtension, QueryResult, QueryableByName, RunQueryDsl}; +use diesel::sql_types::Text; +use crate::db::db::DbConnection; + +// 辅助查询函数,用于获取数据的 `data` 字段 +#[derive(QueryableByName)] +struct DataResult { + #[diesel(sql_type = Text)] + data: String, +} +pub async fn get_data_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + use diesel::sql_types::Text; + use std::collections::HashMap; + + // 表名列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 存储每个表的查询结果 + let mut results = HashMap::new(); + + for &table in &tables { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND name = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + let data_result = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(pg_conn) + .optional()? + .map(|res| res.data) + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(pg_conn) + .optional()? + .map(|res| res.data) + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional()? + .map(|res| res.data) + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional()? + .map(|res| res.data) + } + } + }; + + if let Some(data) = data_result { + *results.entry(data).or_insert(0) += 1; + } + } + + // 按少数服从多数规则返回数据 + if results.len() == 1 { + // 如果三个表的结果一致,直接返回任意结果 + Ok(results.into_iter().next().map(|(data, _)| data)) + } else if results.values().all(|&count| count == 1) { + // 如果所有表结果不同,直接回退到 kine 表的数据 + get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) + } else if let Some((data, _)) = results.into_iter().max_by_key(|&(_, count)| count) { + // 如果有多数一致的数据,返回该数据 + Ok(Some(data)) + } else { + // 默认回退到 kine 表的数据 + get_data_from_kine_primary(conn, item_kind, item_name, item_version, item_namespace) + } +} + +/// 获取主表 `kine` 的数据 +fn get_data_from_kine_primary( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + let fallback_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND namespace = $3 AND apigroup = $4".to_string(), + DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?".to_string(), + } + } else { + match conn { + DbConnection::Pg(_) => "SELECT data FROM kine WHERE kind = $1 AND name = $2 AND apigroup = $3".to_string(), + DbConnection::Sqlite(_) => "SELECT data FROM kine WHERE kind = ? AND name = ? AND apigroup = ?".to_string(), + } + }; + + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(fallback_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + } + } +} + + + + +// 辅助函数:从指定表中获取所有符合条件的数据的 `data` 字段 +pub async fn get_all_data_from_kine( + conn: &mut DbConnection, + item_kind: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + use diesel::sql_types::Text; + use std::collections::HashMap; + + // 定义需要查询的表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + + // 存储每个表的查询结果 + let mut table_results: HashMap<&str, Vec> = HashMap::new(); + + // 遍历每个表进行查询 + for &table in &tables { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND namespace = $2 AND apigroup = $3", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "SELECT data FROM {} WHERE kind = $1 AND apigroup = $2", + table + ), + DbConnection::Sqlite(_) => format!( + "SELECT data FROM {} WHERE kind = ? AND apigroup = ?", + table + ), + } + }; + + // 执行查询 + let results: Vec = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(namespace) + .bind::(item_version) + .load::(pg_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_version) + .load::(pg_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(namespace) + .bind::(item_version) + .load::(sqlite_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } else { + diesel::sql_query(select_query) + .bind::(item_kind) + .bind::(item_version) + .load::(sqlite_conn)? + .into_iter() + .map(|res| res.data) + .collect() + } + } + }; + + table_results.insert(table, results); + } + + // 检查所有表的数据是否一致 + let vec1 = table_results[tables[0]].clone(); + let vec2 = table_results[tables[1]].clone(); + let vec3 = table_results[tables[2]].clone(); + + let max_len = vec1.len().max(vec2.len()).max(vec3.len()); + let mut filtered_results = Vec::new(); + + // 筛选出出现次数大于等于 2 的数据 + for i in 0..max_len { + // 获取当前索引的值,越界的填入 None + let value1 = vec1.get(i); + let value2 = vec2.get(i); + let value3 = vec3.get(i); + + // 统计每个值的出现次数 + let mut counts = HashMap::new(); + if let Some(v) = value1 { + *counts.entry(v).or_insert(0) += 1; + } + if let Some(v) = value2 { + *counts.entry(v).or_insert(0) += 1; + } + if let Some(v) = value3 { + *counts.entry(v).or_insert(0) += 1; + } + + // 找出出现次数大于等于 2 的值 + if let Some((value, &_)) = counts.iter().find(|(_, &count)| count >= 2) { + filtered_results.push((*value).clone()); // 将该值存入结果 + } + + } + + Ok(filtered_results) +} diff --git a/src/db/insert.rs b/src/db/insert.rs new file mode 100644 index 0000000..c0b9e3d --- /dev/null +++ b/src/db/insert.rs @@ -0,0 +1,197 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use chrono::Utc; +use diesel::{sql_query, Connection, PgConnection, QueryResult, RunQueryDsl, SqliteConnection}; +use diesel::sql_types::Text; +use serde_json::Value; +use crate::db::db::DbConnection; + +fn insert_metadata_in_transaction_pg( + transaction: &mut PgConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value, +) -> QueryResult<()> { + use diesel::sql_types::{Bool}; + + // 表名列表 + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT DO NOTHING;", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(plural) // 名称 + .bind::(namespace_required) // 是否需要命名空间 + .bind::(version) // 版本 + .bind::(json_data.to_string()) // JSON 数据 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + + +fn insert_metadata_in_transaction_sqlite( + transaction: &mut SqliteConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value, +) -> QueryResult<()> { + use diesel::sql_types::{Bool}; + + // 表名列表 + let table_array: [&str; 3] = ["metadata", "metadata_replica1", "metadata_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (name, namespace, apigroup, data, created_time, updated_time) + VALUES (?, ?, ?, ?, ?, ?);", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(plural) // 名称 + .bind::(namespace_required) // 是否需要命名空间 + .bind::(version) // 版本 + .bind::(json_data.to_string()) // JSON 数据 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + + +pub async fn insert_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value +) -> QueryResult<()> { + match conn { + DbConnection::Pg(pg_conn) => { + pg_conn.transaction(|transaction| { + insert_metadata_in_transaction_pg(transaction, plural, version, namespace_required, json_data) + }) + } + DbConnection::Sqlite(sqlite_conn) => { + sqlite_conn.transaction(|transaction| { + insert_metadata_in_transaction_sqlite(transaction, plural, version, namespace_required, json_data) + }) + } + }.expect("unknow conn in insert_metadata"); + Ok(()) +} + +fn insert_kine_in_transaction_pg( + transaction: &mut PgConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + + // 表列表 + let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; + + for table_name in table_array { + // 使用参数绑定构建插入查询 + let insert_metadata_query = format!( + "INSERT INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ON CONFLICT DO NOTHING;", + table_name + ); + + // 执行插入操作 + sql_query(insert_metadata_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace.unwrap_or("")) // 空字符串作为默认 namespace + .bind::(version) + .bind::(json_data.to_string()) // 将 JSON 数据转换为字符串 + .bind::(Utc::now().naive_utc().to_string()) // 创建时间 + .bind::(Utc::now().naive_utc().to_string()) // 更新时间 + .execute(transaction)?; + } + + Ok(()) +} + + +fn insert_kine_in_transaction_sqlite( + transaction: &mut SqliteConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + + let table_array: [&str; 3] = ["kine", "kine_replica1", "kine_replica2"]; + + for table_name in table_array { + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO {} (kind, name, namespace, apigroup, data, created_time, updated_time) + VALUES (?, ?, ?, ?, ?, ?, ?);", + table_name + ); + + sql_query(insert_metadata_query) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace.unwrap_or("")) // 使用 Nullable 处理空值 + .bind::(version) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .execute(transaction)?; + } + Ok(()) +} + + +// 在 kine 表中插入新记录 +pub async fn insert_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + match conn { + DbConnection::Pg(pg_conn) => { + pg_conn.transaction(|transaction| { + insert_kine_in_transaction_pg(transaction, item_kind, item_name, json_data, version, namespace) + }) + } + DbConnection::Sqlite(sqlite_conn) => { + sqlite_conn.transaction(|transaction| { + insert_kine_in_transaction_sqlite(transaction, item_kind, item_name, json_data, version, namespace) + }) + } + }.expect("unknow conn in insert_kine"); + Ok(()) +} \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..129c1fd --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,12 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +pub mod db; +pub mod check_exist; +pub mod insert; +pub mod update; +pub mod get; +pub mod delete; \ No newline at end of file diff --git a/src/db/update.rs b/src/db/update.rs new file mode 100644 index 0000000..d957223 --- /dev/null +++ b/src/db/update.rs @@ -0,0 +1,100 @@ +/** + * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences + * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn + * since: 0.1.0 + * +**/ +use diesel::{QueryResult, RunQueryDsl}; +use serde_json::Value; +use crate::db::db::DbConnection; + +pub async fn update_data_in_kine( + conn: &mut DbConnection, + item_kind: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, + json_data: &Value, +) -> QueryResult { + use diesel::sql_types::Text; + use chrono::Utc; + + // 需要更新的表列表 + let tables = ["kine", "kine_replica1", "kine_replica2"]; + let mut total_rows_affected = 0; + + for &table in &tables { + let update_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!( + "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND namespace = $5 AND apigroup = $6", + table + ), + DbConnection::Sqlite(_) => format!( + "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND namespace = ? AND apigroup = ?", + table + ), + } + } else { + match conn { + DbConnection::Pg(_) => format!( + "UPDATE {} SET data = $1, updated_time = $2 WHERE kind = $3 AND name = $4 AND apigroup = $5", + table + ), + DbConnection::Sqlite(_) => format!( + "UPDATE {} SET data = ?, updated_time = ? WHERE kind = ? AND name = ? AND apigroup = ?", + table + ), + } + }; + + // 执行更新操作 + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + } + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_kind) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + + total_rows_affected += rows_affected; + } + + // 如果至少有两个表更新成功,则返回 true + Ok(total_rows_affected > 1) +} diff --git a/src/lib.rs b/src/lib.rs index ae9c790..826987f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,13 @@ pub mod cores; pub mod schema; +pub mod db; use std::fmt::Debug; use cores::apiserver::ApiServer; use cores::config::DefaultConfig; use cores::handlers::DefaultHandler; -use cores::db::DbPool; +use db::db::DbPool; use std::sync::Arc; use feventbus::err::Error; use feventbus::impls::nats::nats::NatsCli; -- Gitee From fdf03a9fd3a985dd48737f6da2b6d172bbfe7b95 Mon Sep 17 00:00:00 2001 From: yzc1114 <1021777674@qq.com> Date: Thu, 19 Dec 2024 01:57:23 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E9=87=8D=E6=9E=84api=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0eventbus=E7=95=8C=E9=9D=A2(=E5=BC=80=E5=8F=91=E4=B8=AD?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Cargo.lock | 589 ++++++++- Cargo.toml | 10 +- src/cores/apiserver.rs | 398 ++---- src/cores/config.rs | 215 --- src/cores/events.rs | 203 +++ src/cores/handlers.rs | 763 +---------- src/cores/mod.rs | 47 +- src/cores/services.rs | 454 +++++++ src/db/db.rs | 1 + src/lib.rs | 141 +- src/main.rs | 24 - tests/route_tests.rs | 2800 ++++++++++++++++++++-------------------- tests/server_tests.rs | 60 + 14 files changed, 2875 insertions(+), 2831 deletions(-) delete mode 100644 src/cores/config.rs create mode 100644 src/cores/events.rs create mode 100644 src/cores/services.rs delete mode 100644 src/main.rs create mode 100644 tests/server_tests.rs diff --git a/.gitignore b/.gitignore index 49f8c02..e13928c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +database.sqlite \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8ee8846..7cb95fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags", + "bitflags 2.6.0", "bytes", "futures-core", "futures-sink", @@ -31,7 +31,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.22.1", - "bitflags", + "bitflags 2.6.0", "brotli", "bytes", "bytestring", @@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -182,7 +182,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.91" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "async-nats" @@ -356,7 +356,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -367,7 +367,16 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", ] [[package]] @@ -382,6 +391,51 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -403,6 +457,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -439,6 +499,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -502,6 +568,12 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +[[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + [[package]] name = "byteorder" version = "1.5.0" @@ -577,6 +649,12 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cidr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdf600c45bd958cf2945c445264471cca8b6c8e67bc87b71affd6d7e5682621" + [[package]] name = "colorchoice" version = "1.0.2" @@ -640,6 +718,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cri-api" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055697c8a1381d2196852aa8cdf9b7cde763bb1f811a8a8c47dd8d97b1872f3b" +dependencies = [ + "prost", + "serde", + "tonic", + "tonic-build", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -684,7 +774,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -695,7 +785,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -746,7 +836,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -755,7 +845,7 @@ version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" dependencies = [ - "bitflags", + "bitflags 2.6.0", "byteorder", "diesel_derives", "itoa", @@ -775,7 +865,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -795,7 +885,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -825,7 +915,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -845,7 +935,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -957,9 +1047,7 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "feventbus" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b7cca7940f29e24592a89727028106825660bd8f9aae1d85283008fea92220" +version = "0.3.1" dependencies = [ "async-nats", "async-trait", @@ -973,6 +1061,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.34" @@ -990,6 +1084,7 @@ dependencies = [ "actix-http", "actix-service", "actix-web", + "anyhow", "async-stream", "async-trait", "chrono", @@ -998,9 +1093,13 @@ dependencies = [ "dotenv", "env_logger", "feventbus", + "fleetmod", + "itertools 0.13.0", + "json-patch", "jsonschema", "k8s-openapi", "lazy_static", + "log", "once_cell", "r2d2", "reqwest", @@ -1011,6 +1110,31 @@ dependencies = [ "tokio", ] +[[package]] +name = "fleetmacros" +version = "0.1.0" +source = "git+https://gitee.com/iscas-system/fleetmacros#8ef2895a5d1bb3a1c2d619d989cd23aaeeb16fb3" +dependencies = [ + "darling", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "fleetmod" +version = "0.1.1" +dependencies = [ + "anyhow", + "chrono", + "cidr", + "cri-api", + "fleetmacros", + "regex", + "serde", + "serde_yaml", + "uuid", +] + [[package]] name = "fluent-uri" version = "0.3.2" @@ -1118,7 +1242,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1190,7 +1314,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1209,13 +1333,19 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1262,6 +1392,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1281,7 +1422,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.1", "pin-project-lite", ] @@ -1303,6 +1444,30 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.5.0" @@ -1314,7 +1479,7 @@ dependencies = [ "futures-util", "h2 0.4.7", "http 1.1.0", - "http-body", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -1331,7 +1496,7 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.5.0", "hyper-util", "rustls 0.23.19", "rustls-pki-types", @@ -1340,6 +1505,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.32", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1348,7 +1525,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.5.0", "hyper-util", "native-tls", "tokio", @@ -1366,8 +1543,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.1", + "hyper 1.5.0", "pin-project-lite", "socket2", "tokio", @@ -1513,7 +1690,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1550,6 +1727,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -1572,6 +1759,24 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1596,6 +1801,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "jsonschema" version = "0.23.0" @@ -1709,6 +1936,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" @@ -1764,6 +1997,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "native-tls" version = "0.2.12" @@ -1918,7 +2157,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1935,7 +2174,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2024,6 +2263,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.6.0", +] + [[package]] name = "phf" version = "0.11.2" @@ -2063,6 +2312,26 @@ dependencies = [ "uncased", ] +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2117,6 +2386,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.90", +] + [[package]] name = "proc-macro2" version = "1.0.91" @@ -2126,6 +2405,59 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.90", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.37" @@ -2188,7 +2520,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -2208,7 +2540,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2226,9 +2558,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2273,9 +2605,9 @@ dependencies = [ "futures-util", "h2 0.4.7", "http 1.1.0", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.5.0", "hyper-rustls", "hyper-tls", "hyper-util", @@ -2291,7 +2623,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -2354,7 +2686,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2421,6 +2753,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -2466,7 +2804,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2491,7 +2829,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2541,7 +2879,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2552,7 +2890,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2584,7 +2922,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2608,6 +2946,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.6.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "0.10.0" @@ -2644,6 +2995,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.9.9" @@ -2782,15 +3139,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -2808,7 +3171,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2817,7 +3180,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] @@ -2862,7 +3225,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2939,6 +3302,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.4.0" @@ -2947,7 +3320,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2981,6 +3354,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3021,13 +3405,79 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3042,9 +3492,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -3096,6 +3558,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -3142,6 +3610,11 @@ name = "uuid" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "atomic", + "getrandom", + "sha1_smol", +] [[package]] name = "uuid-simd" @@ -3209,7 +3682,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -3243,7 +3716,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3467,7 +3940,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -3489,7 +3962,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3509,7 +3982,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -3530,7 +4003,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3552,7 +4025,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1f7f076..d5c4462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ repository = "https://gitee.com/iscas-system/apiserver" readme = "README.md" [dependencies] -feventbus = "0.3.0" +#feventbus = "0.3.0" +feventbus = { path = "../eventbus" } +fleetmod = { path = "../fleetmod" } r2d2 = "0.8.10" dotenv = "0.15.0" diesel = { version = "2.2.0", features = ["sqlite", "postgres", "r2d2"] } @@ -32,4 +34,8 @@ once_cell = "1.20.2" lazy_static = "1.5.0" async-stream = "0.3.6" serial_test = "0.10.0" -reqwest = "0.12.9" \ No newline at end of file +reqwest = "0.12.9" +log = "0.4.22" +itertools = "0.13.0" +anyhow = "1.0.94" +json-patch = "3.0.1" diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index 7b709af..be6bf9b 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -1,314 +1,128 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use actix_web::{HttpServer, App, web, HttpResponse, HttpRequest}; -use crate::cores::config::{Config}; -use crate::cores::handlers::{EventManager, Handler}; -use std::sync::{Arc}; +use crate::cores::handlers::{Handler, HttpResponseWrapper}; +use crate::cores::services::{APIServerError, APIServerResult}; +use crate::db::db::DbPool; +use actix_web::dev::Server; +use actix_web::web::route; +use actix_web::Result; +use actix_web::{http, web, App, HttpResponse, HttpServer}; use feventbus::impls::nats::nats::NatsCli; -use crate::db::db::{DbPool}; -use std::collections::HashMap; - -fn parse_path(template: &str, path: &str) -> HashMap { - let template_parts: Vec<&str> = template.split('/').collect(); - let path_parts: Vec<&str> = path.split('/').collect(); +use http::Method; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::sync::Arc; + +pub struct APIServer {} + +#[derive(Clone)] +pub struct AppState { + pub db_pool: Arc, + pub nats_cli: Arc, + pub handler: Arc, +} - if template_parts.len() != path_parts.len() { - return HashMap::new(); +impl APIServer { + pub fn new() -> Self { + APIServer {} } - let mut params = HashMap::new(); - for (template_part, path_part) in template_parts.iter().zip(path_parts.iter()) { - if template_part.starts_with('{') && template_part.ends_with('}') { - let key = &template_part[1..template_part.len() - 1]; - params.insert(key.to_string(), path_part.to_string()); - } else if template_part != path_part { - return HashMap::new(); - } + pub fn start(&self, addr: &str, app_state: Arc) -> Server { + let server = HttpServer::new(move || { + let app = App::new() + .app_data(web::Data::new(app_state.clone())); + let routes = K8sStyleRoute::get_routes(); + let app = routes.iter().fold(app, |app, route| { + app.route(route.get_path().as_str(), route.get_web_route()) + }); + let app = app.default_service(web::to(move || async { + Ok::( + HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) + })); + app + }); + // 考虑到资源约束,目前只启动一个worker + // TODO 未来用户可以自定义该配置worker数 + server.workers(1).bind(addr).expect("reason").run() } - - params } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct APIServerPathParams { + pub group: Option, + pub version: String, + pub namespace: Option, + pub plural: String, + pub name: Option, +} +pub trait APIServerRoute { + fn get_method(&self) -> Method; + fn get_path(&self) -> String; + fn get_web_route(&self) -> actix_web::Route; +} -pub struct ApiServer -{ - config: Arc, +pub struct K8sStyleRoute { + pub method: Method, + pub with_group: bool, + pub with_namespace: bool, + pub with_name: bool, } -impl ApiServer -{ - pub fn new (config: Box) -> Self { - ApiServer { - config: Arc::from(config), - } +impl APIServerRoute for K8sStyleRoute { + fn get_method(&self) -> Method { + self.method.clone() } - pub async fn start - (self: Arc, addr: &str, handler: T, db_pool: Arc, nats_cli: Arc) -> Result<(), std::io::Error> { - - let handler = Arc::new(handler); - - // 初始化事件管理器 - let event_manager = EventManager::new(); - - let server = HttpServer::new(move || { - - let mut app = App::new(); - let handler = Arc::clone(&handler); - let config = Arc::clone(&self.config); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - for key in config.create_routes() { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let route_key = key.clone(); - app = app.route( - key, - web::post().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let template = route_key.clone(); // 路由模板 - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .create_resource(params, data, &mut conn, nats_cli, event_manager) - .await - } - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - }, - ), - ); - } - - for key in config.delete_routes() { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let route_key = key.clone(); - - app = app.route( - key, - web::delete().to( - move |req: HttpRequest| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let template = route_key.clone(); // 路由模板 - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - // 自定义路径解析 - let params = parse_path(&template, req.path()); - handler - .default() - .delete_resource(params, &mut conn, nats_cli, event_manager) - .await - } - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - }, - ), - ); - } + // apis/{group}/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] + // api/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] + fn get_path(&self) -> String { + let prefix = if self.with_group { "apis/{group}" } else { "api" }; + let version = "{version}"; + let namespace = if self.with_namespace { "namespaces/{namespace}" } else { "" }; + let plural = "{plural}"; + let name = if self.with_name { "{name}" } else { "" }; + format!("{}/{}/{}/{}/{}", prefix, version, namespace, plural, name) + } - for key in config.update_routes() { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let route_key = key.clone(); + fn get_web_route(&self) -> actix_web::Route { + route().method(self.method.clone()).to(Self::handler) + } +} - app = app.route( - key, - web::put().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - let template = route_key.clone(); // 路由模板 +impl K8sStyleRoute { + pub fn get_routes() -> Vec> { + itertools::iproduct!(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::PATCH], vec![true, false], vec![true, false], vec![true, false]) + .map(|(method, with_group, with_namespace, with_name)| { + Box::new(Self { method, with_group, with_namespace, with_name }) as Box + }).collect() + } - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .update_resource(params, data, &mut conn, nats_cli, event_manager) - .await - } - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - }, - ), - ); + async fn handler(method: Method, query: web::Query, path_params: web::Path, state: web::Data, body: web::Json) + -> HttpResponse { + log::info!("ApiServerHandler: method: {:?}, path_params: {:?}, body: {:?}", method, path_params, body); + let mut res = state.db_pool.get_connection(); + if let Err(e) = res { + log::error!("error getting db conn: {}", e); + return HttpResponse::from(HttpResponseWrapper::(Err(APIServerError::internal_error("DB pool error")))); + } + let mut conn = res.unwrap(); + let handler = state.handler.clone(); + match method { + Method::GET => { + handler.get_resource(path_params.into_inner(), query.into_inner(), &mut conn, state.nats_cli.clone()).await } - - for key in config.getone_routes() { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let route_key = key.clone(); - - app = app.route( - key, - web::get().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let template = route_key.clone(); // 路由模板 - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .getone_resource(params, data, &mut conn, nats_cli) - .await - } - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - }, - ), - ); + Method::POST => { + handler.create_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await } - - for key in config.listall_routes() { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let route_key = key.clone(); - - app = app.route( - key, - web::get().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let db_pool = Arc::clone(&db_pool); - let nats_cli = Arc::clone(&nats_cli); - let template = route_key.clone(); // 路由模板 - - async move { - match db_pool.get_connection() { - Ok(mut conn) => { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .listall_resource(params, data, &mut conn, nats_cli) - .await - } - Err(_) => Err(actix_web::error::ErrorInternalServerError( - "Failed to get database connection", - )), - } - } - }, - ), - ); + Method::PUT => { + handler.update_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await } - - for key in config.watchall_routes() { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - let route_key = key.clone(); - - app = app.route( - key, - web::get().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - let template = route_key.clone(); // 路由模板 - - async move { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .watchall_resource(params, data, event_manager) - .await - } - }, - ), - ); + Method::DELETE => { + handler.delete_resource(path_params.into_inner(), &mut conn, state.nats_cli.clone()).await } - - for key in config.watchone_routes() { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - let route_key = key.clone(); - - app = app.route( - key, - web::get().to( - move |req: HttpRequest, data| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - let template = route_key.clone(); // 路由模板 - - async move { - // 自定义解析路径 - let params = parse_path(&template, req.path()); - handler - .default() - .watchone_resource(params, data, event_manager) - .await - } - }, - ), - ); + Method::PATCH => { + handler.patch_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await } - - // TODO 与Kubernetes的状态一致 - app.default_service(web::to(move || async { - Ok::( - HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) - })) - - }); - - // 考虑到资源约束,目前只启动一个worker - // TODO 未来用户可以自定义该配置worker数 - server.workers(1).bind(addr).expect("reason").run().await + _ => todo!() + } } - -} \ No newline at end of file +} diff --git a/src/cores/config.rs b/src/cores/config.rs deleted file mode 100644 index 50e3f36..0000000 --- a/src/cores/config.rs +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - - -// 执行kubectl api-resources,有以下输出,其中APIVERSION为v1的都在URL都是/api/开头,其它的都是/apis/开头,是否支持namespace -// 由NAMESPACED取值确定。因此,定于API_WITH_NAMESPACE、API_WITHOUT_NAMESPACE、APIS_WITH_NAMESPACE、APIS_WITHOUT_NAMESPACE -// 四种情况 -// -// NAME SHORTNAMES APIVERSION NAMESPACED KIND -// bindings v1 true Binding -// componentstatuses cs v1 false ComponentStatus -// configmaps cm v1 true ConfigMap -// endpoints ep v1 true Endpoints -// events ev v1 true Event -// limitranges limits v1 true LimitRange -// namespaces ns v1 false Namespace -// nodes no v1 false Node -// persistentvolumeclaims pvc v1 true PersistentVolumeClaim -// persistentvolumes pv v1 false PersistentVolume -// pods po v1 true Pod -// podtemplates v1 true PodTemplate -// replicationcontrollers rc v1 true ReplicationController -// resourcequotas quota v1 true ResourceQuota -// secrets v1 true Secret -// serviceaccounts sa v1 true ServiceAccount -// services svc v1 true Service -// mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration -// validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration -// customresourcedefinitions crd,crds apiextensions.k8s.io/v1 false CustomResourceDefinition -// apiservices apiregistration.k8s.io/v1 false APIService -// controllerrevisions apps/v1 true ControllerRevision -// daemonsets ds apps/v1 true DaemonSet -// deployments deploy apps/v1 true Deployment -// replicasets rs apps/v1 true ReplicaSet -// statefulsets sts apps/v1 true StatefulSet -// selfsubjectreviews authentication.k8s.io/v1 false SelfSubjectReview -// tokenreviews authentication.k8s.io/v1 false TokenReview -// localsubjectaccessreviews authorization.k8s.io/v1 true LocalSubjectAccessReview -// selfsubjectaccessreviews authorization.k8s.io/v1 false SelfSubjectAccessReview -// selfsubjectrulesreviews authorization.k8s.io/v1 false SelfSubjectRulesReview -// subjectaccessreviews authorization.k8s.io/v1 false SubjectAccessReview -// horizontalpodautoscalers hpa autoscaling/v2 true HorizontalPodAutoscaler -// cronjobs cj batch/v1 true CronJob -// jobs batch/v1 true Job -// certificatesigningrequests csr certificates.k8s.io/v1 false CertificateSigningRequest -// leases coordination.k8s.io/v1 true Lease -// endpointslices discovery.k8s.io/v1 true EndpointSlice -// events ev events.k8s.io/v1 true Event -// flowschemas flowcontrol.apiserver.k8s.io/v1beta3 false FlowSchema -// prioritylevelconfigurations flowcontrol.apiserver.k8s.io/v1beta3 false PriorityLevelConfiguration -// ingressclasses networking.k8s.io/v1 false IngressClass -// ingresses ing networking.k8s.io/v1 true Ingress -// networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy -// runtimeclasses node.k8s.io/v1 false RuntimeClass -// poddisruptionbudgets pdb policy/v1 true PodDisruptionBudget -// clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding -// clusterroles rbac.authorization.k8s.io/v1 false ClusterRole -// rolebindings rbac.authorization.k8s.io/v1 true RoleBinding -// roles rbac.authorization.k8s.io/v1 true Role -// priorityclasses pc scheduling.k8s.io/v1 false PriorityClass -// csidrivers storage.k8s.io/v1 false CSIDriver -// csinodes storage.k8s.io/v1 false CSINode -// csistoragecapacities storage.k8s.io/v1 true CSIStorageCapacity -// storageclasses sc storage.k8s.io/v1 false StorageClass -// volumeattachments storage.k8s.io/v1 false VolumeAttachment - - -// 定义URL配置的接口,用户可以自定义自己需要的URL规则 -// 具体规则参见https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/ -// TODO 现在方法名定义容易导致误解,后续修改 -// TODO 新增WatchOne和WatchAll两个方法 -pub trait Config: Sync + Send { - - // 创建一个对象(如Pod、Job的具体实例)的URL和Web服务器路由的映射关系 - fn create_routes(&self) -> &Vec; - // 删除指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn delete_routes(&self) -> &Vec; - // 更新指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn update_routes(&self) -> &Vec; - // 获取指定对象(如Pod、Job的具体实例)的URL列表和Web服务器路由的映射关系 - fn getone_routes(&self) -> &Vec; - // 获取指定类型(如Pod、Job的所有实例)的URL列表和Web服务器路由的映射关系 - fn listall_routes(&self) -> &Vec; - - fn watchall_routes(&self) -> &Vec; - - fn watchone_routes(&self) -> &Vec; -} - -// Config接口的默认实现,用户可以自定义自己需要的URL规则 -// DefaultConfig实现了Kubernetes风格的URL -#[derive(Clone)] -pub struct DefaultConfig { - create_routes: Vec, - delete_routes: Vec, - update_routes: Vec, - getone_routes: Vec, - listall_routes: Vec, - watchall_routes: Vec, - watchone_routes: Vec, -} - -impl DefaultConfig { - pub fn new() -> Self { - DefaultConfig { - create_routes: { - let mut vec: Vec = Vec::new(); - // create a specific resource: POST - // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural} - // - without namespace/api(s)/{group}/{version}/namespaces/{plural} - vec.push("/api/{version}/namespaces/{namespace}/{plural}".to_string()); - vec.push("/api/{version}/{plural}".to_string()); - vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string()); - vec.push("/apis/{group}/{version}/{plural}".to_string()); - vec - }, - delete_routes: { - let mut vec: Vec = Vec::new(); - // delete a specific resource: DELETE - // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} - // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/api/{version}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); - vec - }, - update_routes: { - let mut vec: Vec = Vec::new(); - // update a specific resource:PUT - // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} - // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/api/{version}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); - vec - }, - getone_routes: { - let mut vec: Vec = Vec::new(); - // get a specific resource: GET - // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural}/{name} - // - without namespace/api(s)/{group}/{version}/namespaces/{plural}/{name} - vec.push("/api/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/api/{version}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/{plural}/{name}".to_string()); - vec - }, - listall_routes: { - let mut vec: Vec = Vec::new(); - // get some resources: GET - // - with namespace: /api(s)/{group}/{version}/namespaces/{namespace}/{plural} - // - without namespace/api(s)/{group}/{version}/namespaces/{plural} - vec.push("/api/{version}/namespaces/{namespace}/{plural}".to_string()); - vec.push("/api/{version}/{plural}".to_string()); - vec.push("/apis/{group}/{version}/namespaces/{namespace}/{plural}".to_string()); - vec.push("/apis/{group}/{version}/{plural}".to_string()); - vec - }, - watchall_routes: { - let mut vec: Vec = Vec::new(); - // watch some resources: GET - // - with namespace: /api(s)/{group}/{version}/watch/namespaces/{namespace}/{plural} - // - without namespace/api(s)/{group}/{version}/watch/namespaces/{plural} - vec.push("/api/{version}/watch/namespaces/{namespace}/{plural}".to_string()); - vec.push("/api/{version}/watch/{plural}".to_string()); - vec.push("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}".to_string()); - vec.push("/apis/{group}/{version}/watch/{plural}".to_string()); - vec - }, - watchone_routes: { - let mut vec: Vec = Vec::new(); - // watch one resource: GET - // - with namespace: /api(s)/{group}/{version}/watch/namespaces/{namespace}/{plural}/{name} - // - without namespace/api(s)/{group}/{version}/watch/namespaces/{plural}/{name} - vec.push("/api/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/api/{version}/watch/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/watch/namespaces/{namespace}/{plural}/{name}".to_string()); - vec.push("/apis/{group}/{version}/watch/{plural}/{name}".to_string()); - vec - } - } - } -} - - -// 返回所有的注册URL和路由关系集合 -impl Config for DefaultConfig { - fn create_routes(&self) -> &Vec { - &self.create_routes - } - - fn delete_routes(&self) -> &Vec { - &self.delete_routes - } - - fn update_routes(&self) -> &Vec { - &self.update_routes - } - - fn getone_routes(&self) -> &Vec { - &self.getone_routes - } - - fn listall_routes(&self) -> &Vec { &self.listall_routes } - - fn watchall_routes(&self) -> &Vec { &self.watchall_routes } - - fn watchone_routes(&self) -> &Vec { &self.watchone_routes } -} diff --git a/src/cores/events.rs b/src/cores/events.rs new file mode 100644 index 0000000..c709688 --- /dev/null +++ b/src/cores/events.rs @@ -0,0 +1,203 @@ +use crate::cores::apiserver::{APIServerPathParams, AppState}; +use crate::cores::services::APIServerResult; +use feventbus::err::Error as FEventBusError; +use feventbus::impls::nats::nats::NatsCli; +use feventbus::message::Message; +use feventbus::traits::consumer::{Consumer, MessageHandler}; +use fleetmod::utils::APIVersion; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::error::Error as SerdeError; +use serde_json::Value; +use std::fmt::{Debug, Display, Formatter}; +use std::future::Future; +use std::sync::Arc; + +async fn reply_to_topic( + topic: String, + nats_cli: Arc, + handler: MessageHandler, +) -> anyhow::Result<(), Box> +where + T: Serialize + for<'de> Deserialize<'de> + Send + 'static + Clone + Sync + Debug, +{ + nats_cli + .reply(topic.as_str(), handler) + .await + .map_err(|e| Box::new(e) as Box)?; + Ok(()) +} + +#[derive(Serialize, Deserialize)] +pub enum EventTopic { + // P2P events表示其他组件向api server发送的点对点事件 + P2P(P2PEventTopic), + // PubSub events表示api server向其他组件发送的广播事件 + PubSub(PubSubEventTopic), +} + +impl Display for EventTopic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EventTopic::P2P(topic) => write!(f, "P2P({:?})", topic), + EventTopic::PubSub(topic) => write!(f, "PubSub({:?})", topic), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum P2PEventTopic { + Create, + Update, + Patch, + Delete, + List, + StartsWatch(ResourceCollectionIdentifier), + EndsWatch(ResourceCollectionIdentifier), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum PubSubEventTopic { + Watch(ResourceCollectionIdentifier), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ResourceCollectionIdentifier { + pub api_version: APIVersion, + pub kind: String, + pub namespace: Option, + // todo! 未来支持更多字段的过滤,如label,annotation等 +} + +// Create, Update, Patch使用WriteEventRequest +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WriteEventRequest { + pub params: APIServerPathParams, + pub data: Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WriteEventResponse { + pub res: APIServerResult, +} + +// Delete 使用DeleteEventRequest +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DeleteEventRequest { + pub params: APIServerPathParams, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DeleteEventResponse { + pub res: APIServerResult<()>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ReadEventRequest { + pub params: APIServerPathParams, + pub query: Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ReadEventResponse { + pub res: APIServerResult>, +} + +pub struct EventServer { + app_state: Arc, +} + +impl EventServer { + pub fn new(app_state: Arc) -> Self { + Self { + app_state, + } + } + + pub fn start(&self) { + let nats_cli = self.app_state.nats_cli.clone(); + } + + pub async fn reply_list(req: ReadEventRequest) -> ReadEventResponse { + todo!() + } + + pub async fn reply_create(req: WriteEventRequest) -> ReadEventResponse { + todo!() + } + + pub async fn reply_update(req: WriteEventRequest) -> ReadEventResponse { + todo!() + } + + pub async fn reply_patch(req: WriteEventRequest) -> ReadEventResponse { + todo!() + } + + pub async fn reply_delete(req: DeleteEventRequest) -> ReadEventResponse { + todo!() + } + + pub async fn do_reply(body: Value, reply: F) -> Result + where + I: DeserializeOwned, + O: Serialize, + F: Fn(I) -> Fut, + Fut: Future, + { + let req: I = serde_json::from_value(body).map_err(|e: SerdeError| FEventBusError::MessageHandling(e.to_string()))?; + let res = reply(req).await; + Ok(serde_json::to_string(&res).unwrap()) + } + + pub fn get_reply_handler(event_topic: P2PEventTopic) -> MessageHandler + { + let reply_handler: MessageHandler = Arc::new(move |msg: Message| { + let event_topic = event_topic.clone(); + Box::pin(async move { + if msg.body.is_none() { + return Err(FEventBusError::MessageHandling( + "Message body is null".to_string(), + )); + } + let body = msg.body.unwrap(); + match event_topic { + P2PEventTopic::List => Self::do_reply(body, EventServer::reply_list).await, + P2PEventTopic::Create => Self::do_reply(body, EventServer::reply_create).await, + P2PEventTopic::Update => Self::do_reply(body, EventServer::reply_update).await, + P2PEventTopic::Patch => Self::do_reply(body, EventServer::reply_update).await, + P2PEventTopic::Delete => Self::do_reply(body, EventServer::reply_delete).await, + _ => Err(FEventBusError::MessageHandling( + "Unsupported event topic".to_string(), + )), + } + }) + }); + reply_handler + } + + pub fn start_reply(nats_cli: Arc) { + let p2p_topics = vec![ + EventTopic::P2P(P2PEventTopic::List), + EventTopic::P2P(P2PEventTopic::Create), + EventTopic::P2P(P2PEventTopic::Update), + EventTopic::P2P(P2PEventTopic::Patch), + EventTopic::P2P(P2PEventTopic::Delete), + ]; + for t in p2p_topics { + let topic_str = t.to_string(); + let p2p_topic = match t { + EventTopic::P2P(topic) => topic, + _ => unreachable!(), + }; + let reply_handler = Self::get_reply_handler(p2p_topic); + actix_web::rt::spawn(reply_to_topic( + topic_str, + Arc::clone(&nats_cli), + reply_handler, + )); + } + } +} + +pub struct EventClient {} \ No newline at end of file diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index e46f02e..c268d95 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -1,32 +1,14 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use std::collections::HashMap; -use std::fmt::Debug; -use std::sync::{Arc, LazyLock, Mutex}; -use std::{time, vec::Vec}; -use async_trait::async_trait; -use actix_web::{HttpResponse, Result, web}; -use serde_json::{json}; -use serde_json::Value; +use crate::cores::apiserver::APIServerPathParams; +use crate::cores::services::{APIServerResult, ApiServerService}; use crate::db::db::DbConnection; -use actix_web::error::ErrorInternalServerError; +use actix_web::HttpResponse; +use async_trait::async_trait; use feventbus::impls::nats::nats::NatsCli; -use feventbus::message::Message; -use feventbus::message; -use feventbus::traits::producer::Producer; -use serde::{Deserialize, Serialize}; -use tokio::sync::broadcast; -use actix_web::web::Bytes; -use crate::db::check_exist::{check_metadata, check_kine}; -use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; -use crate::db::insert::{insert_kine, insert_metadata}; -use crate::db::delete::delete_from_kine; -use crate::db::update::update_data_in_kine; +use serde::Serialize; +use serde_json::Value; +use std::collections::HashMap; +use std::sync::{Arc, LazyLock, Mutex}; + // 定义全局哈希表来获取model名 static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { let mut map = HashMap::new(); @@ -48,63 +30,46 @@ fn get_value(key: &str) -> Option { map.get(key).cloned() } + #[async_trait] pub trait Handler { async fn create_resource( &self, - params: HashMap, - data: web::Json, - db_connection: &mut DbConnection, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager, - ) -> Result; + ) -> HttpResponse; async fn delete_resource( &self, - params: HashMap, - db_connection: &mut DbConnection, + params: APIServerPathParams, + db_conn: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager, - ) -> Result; + ) -> HttpResponse; async fn update_resource( &self, - params: HashMap, - data: web::Json, - db_connection: &mut DbConnection, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, nats_cli: Arc, - event_manager: EventManager, - ) -> Result; - - async fn getone_resource( - &self, - params: HashMap, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result; + ) -> HttpResponse; - async fn listall_resource( + async fn get_resource( &self, - params: HashMap, - _data: web::Query, - db_connection: &mut DbConnection, + params: APIServerPathParams, + _query: Value, + db_conn: &mut DbConnection, _nats_cli: Arc, - ) -> Result; - - async fn watchall_resource( - &self, - params: HashMap, - _data: web::Query, - event_manager: EventManager, - ) -> Result; + ) -> HttpResponse; - async fn watchone_resource( - &self, - params: HashMap, - _data: web::Query, - event_manager: EventManager, - ) -> Result; + async fn patch_resource(&self, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, + nats_cli: Arc, + ) -> HttpResponse; // 不满足以上请求路径的处理 fn default(&self) -> DefaultHandler; @@ -112,667 +77,67 @@ pub trait Handler { #[derive(Clone)] pub struct DefaultHandler { - + service: ApiServerService, } impl DefaultHandler { pub fn new() -> Self { - DefaultHandler {} + Self { + service: ApiServerService {} + } } } -// 发送请求并等待响应 -async fn send_request( - message: Message, - nats_cli: Arc, -) -> std::result::Result<(), Box> +fn api_server_result_to_response(res: APIServerResult) -> HttpResponse where - T: Serialize + for<'de> Deserialize<'de> + Debug + Clone + Send + Sync + 'static, + T: Serialize, { - match nats_cli - .request(message, time::Duration::from_secs(100)) - .await - { - Ok(response) => { - if response.contains("Error handling reply") { - Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "Error found in the eventbus reply!")) as Box) - } else { - Ok(()) - } - } - Err(e) => { - Err(Box::new(e) as Box) - } + match res { + Ok(data) => HttpResponse::Ok().json(data), + Err(err) => HttpResponse::Ok().json(err), } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ApiServerMessage { - pub content: Value, -} - -// watch的事件类型 -#[derive(Clone, Debug, Serialize)] -pub enum EventType { - Create, - Update, - Delete, -} - -// 事件结构,包含事件类型和相关信息 -#[derive(Clone, Debug, Serialize)] -pub struct Event { - pub event_type: EventType, - pub message: Value -} - -// 事件管理器,管理不同资源类型的事件频道 -#[derive(Clone)] -pub struct EventManager { - // 使用 HashMap 来管理不同资源类型的广播通道 - resource_channels: Arc>>>, -} +pub struct HttpResponseWrapper(pub APIServerResult); -impl EventManager { - pub fn new() -> Self { - EventManager { - resource_channels: Arc::new(Mutex::new(HashMap::new())), +impl From> for HttpResponse +where + T: Serialize, +{ + fn from(wrapper: HttpResponseWrapper) -> HttpResponse { + match wrapper.0 { + Ok(data) => HttpResponse::Ok().json(data), + Err(err) => HttpResponse::Ok().json(err), } } - - // 获取资源类型对应的广播通道 - fn get_channel(&self, resource_type: &str) -> broadcast::Sender { - let mut channels = self.resource_channels.lock().unwrap(); - // 如果不存在该资源类型的通道,创建一个新的 - channels.entry(resource_type.to_string()).or_insert_with(|| { - let (tx, _) = broadcast::channel(100); // 创建一个容量为 100 的广播通道 - tx - }).clone() - } - - // 向指定资源类型广播事件 - pub async fn send_event(&self, resource_type: &str, event_type: EventType, message: Value) { - let channel = self.get_channel(resource_type); - - // 创建一个事件并发送到广播通道 - let event = Event { - event_type, - message, - }; - let _ = channel.send(event); // 发送事件 - } } #[async_trait] impl Handler for DefaultHandler { - async fn create_resource( - &self, - params: HashMap, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - let data = data.into_inner(); - // 获取 path 参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - - if plural == "crds" { - // 处理 CRD 资源注册 - let item_kind = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("plural")) - .and_then(|plural| plural.as_str()) - .unwrap_or("error"); - - let kind_upper = data - .get("spec") - .and_then(|spec| spec.get("names")) - .and_then(|names| names.get("kind")) - .and_then(|kind| kind.as_str()) - .unwrap_or("error"); - - let metadata_exists = check_metadata(db_connection, item_kind, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 CRD 资源已存在,无需重复注册" }))); - } - - insert_metadata(db_connection, item_kind, &ver, namespace.is_some(), &data) - .await - .map_err(ErrorInternalServerError)?; - - insert_key_value(item_kind, kind_upper); - } else { - // 检查 metadata 是否存在 - let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" - }))); - } - - // 检查资源是否已存在 - let item_name = data - .get("metadata") - .and_then(|metadata| metadata.get("name")) - .and_then(|name| name.as_str()) - .unwrap_or("error"); - - let kine_exists = check_kine( - db_connection, - plural, - item_name, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)?; - - if kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ - "error": "该资源已存在,请勿重复创建" - }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(plural).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - // 创建请求消息 - let request_message = Message::new( - "CREATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: data.clone(), - }), - None, - ); - - send_request(request_message, nats_cli) - .await - .map_err(ErrorInternalServerError)?; - - // 发送事件 - let resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}", ver, namespace, plural) - } else { - format!("{}/{}", ver, plural) - }; - let watchone_resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}/{}", ver, namespace, plural, item_name) - } else { - format!("{}/{}/{}", ver, plural, item_name) - }; - - event_manager - .send_event(&resource_type, EventType::Create, data.clone()) - .await; - event_manager - .send_event(&watchone_resource_type, EventType::Create, data.clone()) - .await; - - insert_kine( - db_connection, - plural, - item_name, - &data, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)?; - } - Ok(HttpResponse::Ok().json(data)) + async fn create_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { + HttpResponse::from(HttpResponseWrapper(self.service.create_resource(params, data, db_conn, nats_cli).await)) } - async fn delete_resource( - &self, - params: HashMap, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - let name = params.get("name").unwrap(); // 必须存在 - - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - - // 检查 metadata - let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" - }))); - } - - // 检查资源是否存在 - let kine_exists = check_kine( - db_connection, - plural, - name, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ - "error": "指定数据不存在" - }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(plural).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": ver.clone(), - "Namespaces": namespace.unwrap_or(""), - "Plural": plural.clone(), - "Name": name.clone(), - }); - - // 创建请求消息 - let request_message = Message::new( - "DELETE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - - send_request(request_message, nats_cli) - .await - .map_err(ErrorInternalServerError)?; - - // 发送事件 - let resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}", ver, namespace, plural) - } else { - format!("{}/{}", ver, plural) - }; - let watchone_resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}/{}", ver, namespace, plural, name) - } else { - format!("{}/{}/{}", ver, plural, name) - }; - - event_manager - .send_event(&resource_type, EventType::Delete, request_data.clone()) - .await; - event_manager - .send_event(&watchone_resource_type, EventType::Delete, request_data) - .await; - - // 删除资源 - let deleted = delete_from_kine( - db_connection, - plural, - name, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)?; - - if !deleted { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "指定数据不存在" - }))); - } - - Ok(HttpResponse::Ok().json(json!({ - "status": "资源删除成功" - }))) + async fn delete_resource(&self, params: APIServerPathParams, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { + HttpResponse::from(HttpResponseWrapper(self.service.delete_resource(params, db_conn, nats_cli).await)) } - async fn update_resource( - &self, - params: HashMap, - data: web::Json, - db_connection: &mut DbConnection, - nats_cli: Arc, - event_manager: EventManager, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - let name = params.get("name").unwrap(); // 必须存在 - let data = data.into_inner(); - - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - - // 检查 metadata 是否存在 - let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" - }))); - } - - // 检查资源是否存在 - let kine_exists = check_kine( - db_connection, - plural, - name, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)?; - - if !kine_exists { - return Ok(HttpResponse::InternalServerError().json(json!({ - "error": "指定数据不存在" - }))); - } - - let mut model_map = HashMap::new(); - let kind = get_value(plural).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - let request_data = json!({ - "apiVersion": ver.clone(), - "Namespaces": namespace.unwrap_or(""), - "Plural": plural.clone(), - "Name": name.clone(), - "Data": data.clone() - }); - - // 创建请求消息 - let request_message = Message::new( - "UPDATE".to_string(), - message::NativeEventAction::Other, - Some(model_map), - Some(ApiServerMessage { - content: request_data.clone(), - }), - None, - ); - send_request(request_message, nats_cli) - .await - .map_err(ErrorInternalServerError)?; - - // 发送事件 - let resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}", ver, namespace, plural) - } else { - format!("{}/{}", ver, plural) - }; - let watchone_resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}/{}", ver, namespace, plural, name) - } else { - format!("{}/{}/{}", ver, plural, name) - }; - - event_manager - .send_event(&resource_type, EventType::Update, request_data.clone()) - .await; - event_manager - .send_event(&watchone_resource_type, EventType::Update, request_data) - .await; - - // 更新资源 - let updated = update_data_in_kine( - db_connection, - plural, - name, - &ver, - namespace, - &data, - ) - .await - .map_err(ErrorInternalServerError)?; - - if !updated { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "指定数据不存在" - }))); - } - - Ok(HttpResponse::Ok().json(data)) + async fn update_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { + HttpResponse::from(HttpResponseWrapper(self.service.update_resource(params, data, db_conn, nats_cli).await)) } - async fn getone_resource( - &self, - params: HashMap, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - let name = params.get("name").unwrap(); // 必须存在 - - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - - // 检查 metadata 是否存在 - let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" - }))); - } - - // 获取资源数据 - if let Some(data) = get_data_from_kine( - db_connection, - plural, - name, - &ver, - namespace, - ) - .await - .map_err(ErrorInternalServerError)? { - // 将字符串解析为 JSON 格式 - match serde_json::from_str::(&data) { - Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), - Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ - "error": "数据格式错误,无法解析为 JSON" - }))), - } - } - - // 如果资源不存在 - Ok(HttpResponse::NotFound().json(json!({ - "error": "指定数据不存在" - }))) - } - - async fn listall_resource( - &self, - params: HashMap, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - - // 组合版本号 - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - - // 检查 metadata 是否存在 - let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) - .await - .map_err(ErrorInternalServerError)?; - - if !metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ - "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" - }))); - } - - // 获取资源列表 - let data_list = get_all_data_from_kine(db_connection, plural, &ver, namespace) - .await - .map_err(ErrorInternalServerError)?; - - // 将所有字符串解析为 JSON 格式并收集到数组中 - let json_array: Vec = data_list - .into_iter() - .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 - .collect(); - - // 返回结果 - Ok(HttpResponse::Ok().json(json_array)) + async fn get_resource(&self, params: APIServerPathParams, query: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { + HttpResponse::from(HttpResponseWrapper(self.service.get_resource(params, query, db_conn, nats_cli).await)) } - async fn watchall_resource( - &self, - params: HashMap, - _data: web::Query, - event_manager: EventManager, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - - // 生成 resource_type - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - let resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}", ver, namespace, plural) - } else { - format!("{}/{}", ver, plural) - }; - - // 监听事件 - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } - } - }; - - // 返回 SSE 流 - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) + async fn patch_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { + HttpResponse::from(HttpResponseWrapper(self.service.patch_resource(params, data, db_conn, nats_cli).await)) } - async fn watchone_resource( - &self, - params: HashMap, - _data: web::Query, - event_manager: EventManager, - ) -> Result { - // 解析路径参数 - let group = params.get("group").map(String::as_str); - let version = params.get("version").unwrap(); // 必须存在 - let namespace = params.get("namespace").map(String::as_str); - let plural = params.get("plural").unwrap(); // 必须存在 - let name = params.get("name").unwrap(); // 必须存在 - - // 生成 resource_type - let ver = if let Some(group) = group { - format!("{}/{}", group, version) - } else { - version.to_string() - }; - let resource_type = if let Some(namespace) = namespace { - format!("{}/{}/{}/{}", ver, namespace, plural, name) - } else { - format!("{}/{}/{}", ver, plural, name) - }; - - // 订阅事件 - let channel = event_manager.get_channel(&resource_type); - let mut rx = channel.subscribe(); - - let stream = async_stream::stream! { - loop { - match rx.recv().await { - Ok(event) => { - let json_event = serde_json::to_string(&event).unwrap(); - yield Ok::(Bytes::from(json_event)); - } - Err(_) => { - yield Err(actix_web::error::ErrorInternalServerError("Error occurred").into()); - } - } + fn default(&self) -> Self { + Self { + service: ApiServerService {} } - }; - - // 返回 SSE 流 - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - fn default(&self) -> DefaultHandler { - DefaultHandler {} } } + diff --git a/src/cores/mod.rs b/src/cores/mod.rs index 2a299bc..b33b880 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -1,3 +1,12 @@ +use crate::cores::apiserver::{APIServer, AppState}; +use crate::cores::handlers::DefaultHandler; +use crate::db::db::DbPool; +use feventbus::impls::nats::nats::NatsCli; +use feventbus::traits::consumer::Consumer; +use feventbus::traits::controller::EventBus; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + /** * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn @@ -6,6 +15,42 @@ **/ pub mod apiserver; -pub mod config; pub mod handlers; pub mod checker; +mod events; +mod services; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ResourcesMessage { + group: Option, + version: String, + namespace: Option, + plural: String, +} + +pub async fn prepare_app_state(database_url: &str) -> anyhow::Result { + let db_pool = Arc::new(DbPool::new(database_url)?); + let handler: DefaultHandler = DefaultHandler::new(); + let nats_cli = Arc::new(NatsCli::new().await?); + Ok(AppState { + db_pool, + handler: Arc::new(handler), + nats_cli, + }) +} + +/// 启动 Web 服务器 +/// +/// # 参数 +/// - `database_url`: 数据库连接字符串 +/// - `address`: 服务监听地址(如 "0.0.0.0:8080") +/// +/// # 返回值 +/// - 异步运行结果 +pub async fn start_server(database_url: &str, address: &str) -> anyhow::Result<()> { + let app_state = Arc::new(prepare_app_state(database_url).await?); + + APIServer::new().start(address, app_state.clone()).await?; + Ok(()) +} + diff --git a/src/cores/services.rs b/src/cores/services.rs new file mode 100644 index 0000000..cab0d7a --- /dev/null +++ b/src/cores/services.rs @@ -0,0 +1,454 @@ +use crate::cores::apiserver::APIServerPathParams; +use crate::db::check_exist::{check_kine, check_metadata}; +use crate::db::db::DbConnection; +use crate::db::delete::delete_from_kine; +use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; +use crate::db::insert::{insert_kine, insert_metadata}; +use crate::db::update::update_data_in_kine; +use feventbus::impls::nats::nats::NatsCli; +use fleetmod::utils::APIVersion; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize, Serializer}; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::sync::{Arc, LazyLock, Mutex}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum APIServerErrorType { + InternalError, + NotFound, + BadRequest, + Duplicated, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct APIServerError { + pub error_type: APIServerErrorType, + pub error_msg: String, +} + +impl APIServerError { + pub fn new(t: APIServerErrorType, msg: &str) -> Self { + Self { + error_type: t, + error_msg: msg.to_string(), + } + } + + pub fn internal_error(message: &str) -> Self { + Self::new(APIServerErrorType::InternalError, message) + } + + pub fn not_found(message: &str) -> APIServerError { + Self::new(APIServerErrorType::NotFound, message) + } + + pub fn bad_request(message: &str) -> APIServerError { + Self::new(APIServerErrorType::BadRequest, message) + } + + pub fn duplicated(message: &str) -> APIServerError { + Self::new(APIServerErrorType::Duplicated, message) + } +} + +impl Display for APIServerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self, f) + } +} + +impl Error for APIServerError {} + +impl From for APIServerError { + fn from(error: diesel::result::Error) -> Self { + Self::internal_error(error.to_string().as_str()) + } +} + +pub type APIServerResult = Result; + +// 定义全局哈希表来获取model名 +static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { + let mut map = HashMap::new(); + map.insert("pods".to_string(), "POD".to_string()); + map.insert("nodes".to_string(), "NODE".to_string()); + map.insert("jobs".to_string(), "JOB".to_string()); + Mutex::new(map) +}); + +// 添加键值对 +fn insert_key_value(key: &str, value: &str) { + let mut map = GLOBAL_HASHMAP.lock().unwrap(); + map.insert(key.to_string(), value.to_string()); +} + +// 获取键对应的值 +fn get_value(key: &str) -> Option { + let map = GLOBAL_HASHMAP.lock().unwrap(); + map.get(key).cloned() +} + +#[derive(Clone)] +pub struct ApiServerService {} + +impl ApiServerService { + pub async fn create_resource( + &self, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, + nats_cli: Arc, + ) -> APIServerResult { + let ServiceCtx { api_version_str, plural, namespace, .. } = Self::prepare_ctx(params, Some(false))?; + + if plural == "crds" { + // 处理 CRD 资源注册 + let item_kind = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + let kind_upper = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("kind")) + .and_then(|kind| kind.as_str()) + .unwrap_or("error"); + + let metadata_exists = check_metadata(db_conn, item_kind, api_version_str.as_str(), namespace.is_some()) + .await?; + + if metadata_exists { + return Err(APIServerError::duplicated("该CRD资源已存在,无需重复注册")); + } + + insert_metadata(db_conn, item_kind, api_version_str.as_str(), namespace.is_some(), &data) + .await?; + + insert_key_value(item_kind, kind_upper); + } else { + // 检查 metadata 是否存在 + Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + .await?; + + // 检查资源是否已存在 + let item_name = data + .get("metadata") + .and_then(|metadata| metadata.get("name")) + .and_then(|name| name.as_str()) + .unwrap_or("error"); + + let kine_exists = check_kine( + db_conn, + plural.as_str(), + item_name, + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + + if kine_exists { + return Err(APIServerError::duplicated("该资源已存在,请勿重复创建")); + } + + let mut model_map = HashMap::new(); + let kind = get_value(plural.as_str()).unwrap(); + model_map.insert("MODEL".to_string(), kind); + + // // 创建请求消息 + // let request_message = Message::new( + // "CREATE".to_string(), + // message::NativeEventAction::Other, + // Some(model_map), + // Some(ApiServerMessage { + // content: data.clone(), + // }), + // None, + // ); + + // send_request(request_message, nats_cli) + // .await + // .map_err(ErrorInternalServerError)?; + + insert_kine( + db_conn, + plural.as_str(), + item_name, + &data, + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + } + Ok(data) + } + + pub async fn delete_resource( + &self, + params: APIServerPathParams, + db_conn: &mut DbConnection, + nats_cli: Arc, + ) -> APIServerResult { + let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(true))?; + let name = name.unwrap(); + + // 检查 metadata + Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + .await?; + + // 检查资源是否存在 + let kine_exists = check_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + + if !kine_exists { + return Err(APIServerError::internal_error("指定资源不存在")); + } + + let mut model_map = HashMap::new(); + let kind = get_value(plural.as_str()).unwrap(); + model_map.insert("MODEL".to_string(), kind); + + // let request_data = json!({ + // "apiVersion": ver.clone(), + // "Namespaces": namespace.unwrap_or(""), + // "Plural": plural.clone(), + // "Name": name.clone(), + // }); + // + // // 创建请求消息 + // let request_message = Message::new( + // "DELETE".to_string(), + // message::NativeEventAction::Other, + // Some(model_map), + // Some(ApiServerMessage { + // content: request_data.clone(), + // }), + // None, + // ); + + // crate::cores::handlers::send_request(request_message, nats_cli) + // .await + // .map_err(ErrorInternalServerError)?; + + // 删除资源 + let deleted = delete_from_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + + if !deleted { + return Err(APIServerError::internal_error("指定资源不存在")); + } + + Ok(json!({"status": "资源删除成功"})) + } + + pub async fn update_resource( + &self, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, + nats_cli: Arc, + ) -> APIServerResult { + let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(true))?; + let name = name.unwrap(); + + // 检查 metadata 是否存在 + Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + .await?; + + // 检查资源是否存在 + let kine_exists = check_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + + if !kine_exists { + return Err(APIServerError::internal_error("指定资源不存在")); + } + + let mut model_map = HashMap::new(); + let kind = get_value(plural.as_str()).unwrap(); + model_map.insert("MODEL".to_string(), kind); + + // let request_data = json!({ + // "apiVersion": ver.clone(), + // "Namespaces": namespace.unwrap_or(""), + // "Plural": plural.clone(), + // "Name": name.clone(), + // "Data": data.clone() + // }); + + // // 创建请求消息 + // let request_message = Message::new( + // "UPDATE".to_string(), + // message::NativeEventAction::Other, + // Some(model_map), + // Some(ApiServerMessage { + // content: request_data.clone(), + // }), + // None, + // ); + // crate::cores::handlers::send_request(request_message, nats_cli) + // .await + // .map_err(ErrorInternalServerError)?; + + // 更新资源 + let updated = update_data_in_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + &data, + ).await?; + + if !updated { + return Err(APIServerError::internal_error("更新资源失败")); + } + + Ok(data) + } + + pub async fn get_resource( + &self, + params: APIServerPathParams, + _query: Value, + db_conn: &mut DbConnection, + _nats_cli: Arc, + ) -> APIServerResult> { + let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(false))?; + // 检查 metadata 是否存在 + Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + .await?; + + if name.is_some() { + // 如果name不为空,查询单个resource数据 + if let Some(data) = get_data_from_kine( + db_conn, + plural.as_str(), + name.unwrap().as_str(), + api_version_str.as_str(), + namespace.as_deref(), + ).await? { + // 将字符串解析为 JSON 格式 + return match serde_json::from_str::(&data) { + Ok(json_data) => Ok(vec![json_data]), + Err(_) => Err(APIServerError::internal_error("资源格式错误,无法解析为 JSON")), + }; + } + // 如果资源不存在 + Err(APIServerError::not_found("指定资源不存在")) + } else { + // 如果name为空,获取资源列表 + let data_list = get_all_data_from_kine(db_conn, plural.as_str(), api_version_str.as_str(), namespace.as_deref()) + .await?; + + // 将所有字符串解析为 JSON 格式并收集到数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + // 返回结果 + Ok(json_array) + } + } + + pub async fn patch_resource(&self, + params: APIServerPathParams, + data: Value, + db_conn: &mut DbConnection, + nats_cli: Arc, + ) -> APIServerResult { + let ServiceCtx { api_version_str, plural, namespace, name } = Self::prepare_ctx(params, Some(true))?; + let name = name.unwrap(); + Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + .await?; + + let mut curr_resource = { + let curr_resource = get_data_from_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + ).await?; + if curr_resource.is_none() { + return Err(APIServerError::not_found("指定资源不存在")); + } + // 将字符串解析为 JSON 格式 + match serde_json::from_str::(&curr_resource.unwrap()) { + Ok(json_data) => json_data, + Err(_) => return Err(APIServerError::internal_error("现有资源数据格式错误,无法解析为JSON")), + } + }; + let patch_json = data; + json_patch::merge(&mut curr_resource, &patch_json); + let updated = update_data_in_kine( + db_conn, + plural.as_str(), + name.as_str(), + api_version_str.as_str(), + namespace.as_deref(), + &curr_resource, + ) + .await?; + if !updated { + return Err(APIServerError::internal_error("在Patch时更新资源失败")); + } + Ok(curr_resource) + } +} + +struct ServiceCtx { + api_version_str: String, + plural: String, + namespace: Option, + name: Option, +} + +impl ApiServerService { + fn prepare_ctx(params: APIServerPathParams, name_required: Option) -> APIServerResult { + // 获取 path 参数 + let APIServerPathParams { group, version, plural, namespace, name } = params; + if name_required.is_some_and(|r| r) && name.is_none() { + return Err(APIServerError::bad_request("name参数未指定")); + } + if !name_required.is_some_and(|r| !r) && name.is_some() { + return Err(APIServerError::bad_request("name参数不应指定")); + } + Ok(ServiceCtx { + api_version_str: APIVersion { + group, + version, + }.to_string(), + plural, + namespace, + name, + }) + } + + async fn check_metadata_exists(db_conn: &mut DbConnection, plural: &str, api_version_str: &str, namespaced: bool) -> APIServerResult<()> { + let metadata_exists = check_metadata(db_conn, plural, api_version_str, namespaced) + .await?; + + if !metadata_exists { + return Err(APIServerError::not_found("该plural不存在,请检查plural版本以及是否需要namespace")); + } + Ok(()) + } +} diff --git a/src/db/db.rs b/src/db/db.rs index fb8aa9d..3deead4 100644 --- a/src/db/db.rs +++ b/src/db/db.rs @@ -16,6 +16,7 @@ use serde_json::{json, Value}; use diesel::connection::Connection; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); +#[derive(Debug)] pub enum DbPool { Pg(Pool>), Sqlite(Pool>), diff --git a/src/lib.rs b/src/lib.rs index 826987f..5368cf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,143 +9,4 @@ pub mod cores; pub mod schema; pub mod db; -use std::fmt::Debug; -use cores::apiserver::ApiServer; -use cores::config::DefaultConfig; -use cores::handlers::DefaultHandler; -use db::db::DbPool; -use std::sync::Arc; -use feventbus::err::Error; -use feventbus::impls::nats::nats::NatsCli; -use feventbus::message::Message; -use feventbus::traits::consumer::{Consumer, MessageHandler}; -use feventbus::traits::controller::EventBus; -use serde::{Deserialize, Serialize}; -use reqwest::Client; -#[derive(Serialize, Deserialize, Debug, Clone)] -struct ResourcesMessage { - group: Option, - version: String, - namespace: Option, - plural: String -} - -async fn reply_to_topic( - topic: &str, - nats_cli: Arc, - handler: MessageHandler, -) -> Result<(), Box> -{ - nats_cli - .reply(topic, handler) - .await - .map_err(|e| Box::new(e) as Box)?; - Ok(()) -} -/// 启动 Web 服务器 -/// -/// # 参数 -/// - `database_url`: 数据库连接字符串 -/// - `address`: 服务监听地址(如 "0.0.0.0:8080") -/// -/// # 返回值 -/// - 异步运行结果 -pub async fn start_server(database_url: &str, address: &str) -> Result<(), Box> { - // 创建数据库连接池 - let db_pool = Arc::new(DbPool::new(database_url)?); - - - - // 配置和启动服务器 - let config = Box::new(DefaultConfig::new()); - let server = ApiServer::new(config); - let handler: DefaultHandler = DefaultHandler::new(); - // 初始化 NatsCli 实例 - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - // 定义 HTTP 客户端 - let http_client = Arc::new(Client::new()); - - let reply_get_handler: MessageHandler = Arc::new(move |msg: Message| { - let http_client = Arc::clone(&http_client); - Box::pin(async move { - match msg.body { - Some(body) => { - if let Ok(resource_message) = serde_json::from_value::(body.clone()) { - let route_path = match resource_message.group { - Some(group) => { - match resource_message.namespace { - Some(namespace) => { - format!( - "/apis/{}/{}/namespaces/{}/{}", - group, resource_message.version, namespace, resource_message.plural - ) - } - None => { - format!( - "/apis/{}/{}/{}", - group, resource_message.version, resource_message.plural - ) - } - } - } - None => { - match resource_message.namespace { - Some(namespace) => { - format!( - "/api/{}/namespaces/{}/{}", - resource_message.version, namespace, resource_message.plural - ) - } - None => { - format!("/api/{}/{}", resource_message.version, resource_message.plural) - } - } - } - }; - - let base_url = "http://localhost:8080"; - let full_url = format!("{}{}", base_url, route_path); - match http_client.get(&full_url).send().await { - Ok(response) => { - if response.status().is_success() { - let text = response.text().await.unwrap_or_default(); - Ok(text) - } else { - Err(Error::MessageHandling(format!( - "---Sub get resources topic failed: HTTP error {}---", - response.status() - ))) - } - } - Err(err) => Err(Error::MessageHandling(format!( - "---Sub get resources topic failed: Request error: {}---", - err - ))), - } - } else { - Err(Error::MessageHandling( - "---Sub get resources topic failed: message body is null---".to_string(), - )) - } - } - None => { - Err(Error::MessageHandling( - "---Sub get resources topic failed: message body is null---".to_string(), - )) - } - } - }) - }); - - tokio::spawn(reply_to_topic( - "GET", - Arc::clone(&nats_cli), - reply_get_handler, - )); - - let _ = Arc::new(server).start(address, handler, db_pool, nats_cli).await?; - - - Ok(()) -} - +pub use cores::{prepare_app_state, start_server}; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 07fc105..0000000 --- a/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use std::env; -use dotenv::dotenv; -use fleet_apiserver::start_server; - -#[actix_web::main] -async fn main() { - dotenv().ok(); - - // 从环境变量读取配置 - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let address = "0.0.0.0:8080"; - - // 启动服务 - if let Err(e) = start_server(&database_url, address).await { - eprintln!("Failed to start server: {}", e); - } -} diff --git a/tests/route_tests.rs b/tests/route_tests.rs index befa906..c302d08 100644 --- a/tests/route_tests.rs +++ b/tests/route_tests.rs @@ -1,1400 +1,1400 @@ -/** - * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences - * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn - * since: 0.1.0 - * -**/ - -use actix_web::error::ErrorInternalServerError; -use feventbus::impls::nats::nats::NatsCli; -use serde_json::Value; -use fleet_apiserver::cores::db::DbPool; -use fleet_apiserver::cores::handlers::{DefaultHandler, EventManager}; - -#[cfg(test)] -mod tests { - use super::*; - use actix_web::{test, web, App}; - use serde_json::json; - use crate::DbPool; - use std::sync::Arc; - use feventbus::traits::controller::EventBus; - use once_cell::sync::Lazy; - use crate::EventManager; - use serial_test::serial; - use fleet_apiserver::cores::handlers::Handler; - - // 使用 Lazy 来初始化静态的共享资源 - static DB_POOL: Lazy> = Lazy::new(|| { - let pool = DbPool::new_in_memory().expect("Failed to create in-memory database pool"); - Arc::new(pool) - }); - - static HANDLER: Lazy> = Lazy::new(|| Arc::new(DefaultHandler::new())); - - - - #[actix_web::test] - #[serial] - async fn test_api_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - // 初始化事件管理器 - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_api_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/namespaces/{namespace}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_api_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - - - let create_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/api/v1/namespaces/global/cargos") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/api/v1/namespaces/global/cargos") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-01", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-01", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }, - { - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/api/v1/namespaces/global/cargos/cargo-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "v1", - "kind": "Cargo", - "metadata": { - "name": "cargo-case-02", - "annotations": { - "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" - } - }, - "spec": { - "containers": [ - { - "name": "cargo-case-02", - "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req2 = test::TestRequest::get() - .uri("/api/v1/namespaces/global/cargos") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - - #[actix_web::test] - #[serial] - async fn test_api_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/api/{version}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - let create_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/api/v1/nodes") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/api/v1/nodes/node-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/api/v1/nodes/node-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/api/v1/nodes") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-01", - }, - "spec": { - "containers": [ - { - "name": "node-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/api/v1/nodes") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/api/v1/nodes/node-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/api/v1/nodes") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - - - #[actix_web::test] - #[serial] - async fn test_apis_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_apis_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_apis_with_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - - let create_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/apis/batch/v1/namespaces/global/jobs") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/apis/batch/v1/namespaces/global/jobs") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } - - - #[actix_web::test] - #[serial] - async fn test_crd_and_apis_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - let event_manager = EventManager::new(); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/{plural}", web::post().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::put().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - move |path| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - let event_manager = event_manager.clone(); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}/{name}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.getone_apis_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/apis/{group}/{version}/{plural}", web::get().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path, data| { - let handler = HANDLER.clone(); - let db_pool = DB_POOL.clone(); - let nats_cli = Arc::clone(&nats_cli); - - async move { - match db_pool.get_connection() { - Ok(mut conn) => handler.listall_apis_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - ).await; - - - let crd_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "CustomResourceDefinition", - "metadata": { - "name": "myjob.iscas.cn" - }, - "spec": { - "names": - { - "kind": "Myjob", - "listKind": "MyjobList", - "plural": "myjobs", - "singular": "myjob" - } - } - }); - - let crd_req = test::TestRequest::post() - .uri("/apis/batch/v1/crds") - .set_json(&crd_req_data) - .to_request(); - - let crd_resp = test::call_service(&app, crd_req).await; - - assert!(crd_resp.status().is_success()); - - let crd_resp_body = test::read_body_json::(crd_resp).await; - assert_eq!(crd_resp_body, crd_req_data); - - - let create_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req = test::TestRequest::post() - .uri("/apis/batch/v1/myjobs") - .set_json(&create_req_data) - .to_request(); - - let create_resp = test::call_service(&app, create_req).await; - - assert!(create_resp.status().is_success()); - - let create_resp_body = test::read_body_json::(create_resp).await; - assert_eq!(create_resp_body, create_req_data); - - - - - - - - let update_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let update_req = test::TestRequest::put() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .set_json(&update_req_data) - .to_request(); - - let update_resp = test::call_service(&app, update_req).await; - - assert!(update_resp.status().is_success()); - - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); - - - - - let get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); - - let get_req = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .to_request(); - - let get_resp = test::call_service(&app, get_req).await; - - assert!(get_resp.status().is_success()); - - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); - - - - - let create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); - - let create_req2 = test::TestRequest::post() - .uri("/apis/batch/v1/myjobs") - .set_json(&create_req_data2) - .to_request(); - - let create_resp2 = test::call_service(&app, create_req2).await; - - assert!(create_resp2.status().is_success()); - - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); - - - - let list_req_data = json!([{ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }, - { - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - - let list_req = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs") - .to_request(); - - let list_resp = test::call_service(&app, list_req).await; - - assert!(list_resp.status().is_success()); - - let list_resp_body = test::read_body_json::(list_resp).await; - assert_eq!(list_resp_body, list_req_data); - - - - let delete_req = test::TestRequest::delete() - .uri("/apis/batch/v1/myjobs/myjob-case-01") - .to_request(); - - let delete_resp = test::call_service(&app, delete_req).await; - - assert!(delete_resp.status().is_success()); - - let list_req_data2 = json!([ - { - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }]); - let list_req2 = test::TestRequest::get() - .uri("/apis/batch/v1/myjobs") - .to_request(); - - let list_resp2 = test::call_service(&app, list_req2).await; - - assert!(list_resp2.status().is_success()); - - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); - - } -} +// /** +// * Copyright (2024, ) Institute of Software, Chinese Academy of Sciences +// * author: chenhongyu23@otcaix.iscas.ac.cn, wuheng@iscas.ac.cn +// * since: 0.1.0 +// * +// **/ +// +// use actix_web::error::ErrorInternalServerError; +// use feventbus::impls::nats::nats::NatsCli; +// use serde_json::Value; +// use fleet_apiserver::cores::db::DbPool; +// use fleet_apiserver::cores::handlers::{DefaultHandler, EventManager}; +// +// #[cfg(test)] +// mod tests { +// use super::*; +// use actix_web::{test, web, App}; +// use serde_json::json; +// use crate::DbPool; +// use std::sync::Arc; +// use feventbus::traits::controller::EventBus; +// use once_cell::sync::Lazy; +// use crate::EventManager; +// use serial_test::serial; +// use fleet_apiserver::cores::handlers::Handler; +// +// // 使用 Lazy 来初始化静态的共享资源 +// static DB_POOL: Lazy> = Lazy::new(|| { +// let pool = DbPool::new_in_memory().expect("Failed to create in-memory database pool"); +// Arc::new(pool) +// }); +// +// static HANDLER: Lazy> = Lazy::new(|| Arc::new(DefaultHandler::new())); +// +// +// +// #[actix_web::test] +// #[serial] +// async fn test_api_with_namespace() { +// let nats_cli = Arc::new(NatsCli::new().await.unwrap()); +// // 初始化事件管理器 +// let event_manager = EventManager::new(); +// +// let app = test::init_service( +// App::new() +// .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.create_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.update_api_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.delete_api_with_namespace(path, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.getone_api_with_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/namespaces/{namespace}/{plural}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.listall_api_with_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// ).await; +// +// +// +// +// let create_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-01", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-01", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req = test::TestRequest::post() +// .uri("/api/v1/namespaces/global/cargos") +// .set_json(&create_req_data) +// .to_request(); +// +// let create_resp = test::call_service(&app, create_req).await; +// +// assert!(create_resp.status().is_success()); +// +// let create_resp_body = test::read_body_json::(create_resp).await; +// assert_eq!(create_resp_body, create_req_data); +// +// +// +// +// +// +// +// let update_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-01", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-01", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let update_req = test::TestRequest::put() +// .uri("/api/v1/namespaces/global/cargos/cargo-case-01") +// .set_json(&update_req_data) +// .to_request(); +// +// let update_resp = test::call_service(&app, update_req).await; +// +// assert!(update_resp.status().is_success()); +// +// let update_resp_body = test::read_body_json::(update_resp).await; +// assert_eq!(update_resp_body, update_req_data); +// +// +// +// +// let get_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-01", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-01", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let get_req = test::TestRequest::get() +// .uri("/api/v1/namespaces/global/cargos/cargo-case-01") +// .to_request(); +// +// let get_resp = test::call_service(&app, get_req).await; +// +// assert!(get_resp.status().is_success()); +// +// let get_resp_body = test::read_body_json::(get_resp).await; +// assert_eq!(get_resp_body, get_req_data); +// +// +// +// +// let create_req_data2 = json!({ +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-02", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-02", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req2 = test::TestRequest::post() +// .uri("/api/v1/namespaces/global/cargos") +// .set_json(&create_req_data2) +// .to_request(); +// +// let create_resp2 = test::call_service(&app, create_req2).await; +// +// assert!(create_resp2.status().is_success()); +// +// let create_resp_body2 = test::read_body_json::(create_resp2).await; +// assert_eq!(create_resp_body2, create_req_data2); +// +// +// +// let list_req_data = json!([{ +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-01", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-01", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:123", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }, +// { +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-02", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-02", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// +// let list_req = test::TestRequest::get() +// .uri("/api/v1/namespaces/global/cargos") +// .to_request(); +// +// let list_resp = test::call_service(&app, list_req).await; +// +// assert!(list_resp.status().is_success()); +// +// let list_resp_body = test::read_body_json::(list_resp).await; +// assert_eq!(list_resp_body, list_req_data); +// +// +// +// let delete_req = test::TestRequest::delete() +// .uri("/api/v1/namespaces/global/cargos/cargo-case-01") +// .to_request(); +// +// let delete_resp = test::call_service(&app, delete_req).await; +// +// assert!(delete_resp.status().is_success()); +// +// let list_req_data2 = json!([ +// { +// "apiVersion": "v1", +// "kind": "Cargo", +// "metadata": { +// "name": "cargo-case-02", +// "annotations": { +// "k8s.v1.cni.cncf.io/networks": "testns1/macvlan-conf-1" +// } +// }, +// "spec": { +// "containers": [ +// { +// "name": "cargo-case-02", +// "image": "g-ubjg5602-docker.pkg.coding.net/iscas-system/containers/busybox:latest", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// +// let list_req2 = test::TestRequest::get() +// .uri("/api/v1/namespaces/global/cargos") +// .to_request(); +// +// let list_resp2 = test::call_service(&app, list_req2).await; +// +// assert!(list_resp2.status().is_success()); +// +// let list_resp_body2 = test::read_body_json::(list_resp2).await; +// assert_eq!(list_resp_body2, list_req_data2); +// +// } +// +// +// +// #[actix_web::test] +// #[serial] +// async fn test_api_without_namespace() { +// let nats_cli = Arc::new(NatsCli::new().await.unwrap()); +// let event_manager = EventManager::new(); +// +// let app = test::init_service( +// App::new() +// .route("/api/{version}/{plural}", web::post().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.create_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/{plural}/{name}", web::put().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.update_api_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/{plural}/{name}", web::delete().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.delete_api_without_namespace(path, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/{plural}/{name}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.getone_api_without_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/api/{version}/{plural}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.listall_api_without_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// ).await; +// +// +// let create_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-01", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req = test::TestRequest::post() +// .uri("/api/v1/nodes") +// .set_json(&create_req_data) +// .to_request(); +// +// let create_resp = test::call_service(&app, create_req).await; +// +// assert!(create_resp.status().is_success()); +// +// let create_resp_body = test::read_body_json::(create_resp).await; +// assert_eq!(create_resp_body, create_req_data); +// +// +// +// +// +// +// +// let update_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let update_req = test::TestRequest::put() +// .uri("/api/v1/nodes/node-case-01") +// .set_json(&update_req_data) +// .to_request(); +// +// let update_resp = test::call_service(&app, update_req).await; +// +// assert!(update_resp.status().is_success()); +// +// let update_resp_body = test::read_body_json::(update_resp).await; +// assert_eq!(update_resp_body, update_req_data); +// +// +// +// +// let get_req_data = json!({ +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let get_req = test::TestRequest::get() +// .uri("/api/v1/nodes/node-case-01") +// .to_request(); +// +// let get_resp = test::call_service(&app, get_req).await; +// +// assert!(get_resp.status().is_success()); +// +// let get_resp_body = test::read_body_json::(get_resp).await; +// assert_eq!(get_resp_body, get_req_data); +// +// +// +// +// let create_req_data2 = json!({ +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req2 = test::TestRequest::post() +// .uri("/api/v1/nodes") +// .set_json(&create_req_data2) +// .to_request(); +// +// let create_resp2 = test::call_service(&app, create_req2).await; +// +// assert!(create_resp2.status().is_success()); +// +// let create_resp_body2 = test::read_body_json::(create_resp2).await; +// assert_eq!(create_resp_body2, create_req_data2); +// +// +// +// let list_req_data = json!([{ +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }, +// { +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// +// let list_req = test::TestRequest::get() +// .uri("/api/v1/nodes") +// .to_request(); +// +// let list_resp = test::call_service(&app, list_req).await; +// +// assert!(list_resp.status().is_success()); +// +// let list_resp_body = test::read_body_json::(list_resp).await; +// assert_eq!(list_resp_body, list_req_data); +// +// +// +// let delete_req = test::TestRequest::delete() +// .uri("/api/v1/nodes/node-case-01") +// .to_request(); +// +// let delete_resp = test::call_service(&app, delete_req).await; +// +// assert!(delete_resp.status().is_success()); +// +// let list_req_data2 = json!([ +// { +// "apiVersion": "v1", +// "kind": "Node", +// "metadata": { +// "name": "node-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "node-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// let list_req2 = test::TestRequest::get() +// .uri("/api/v1/nodes") +// .to_request(); +// +// let list_resp2 = test::call_service(&app, list_req2).await; +// +// assert!(list_resp2.status().is_success()); +// +// let list_resp_body2 = test::read_body_json::(list_resp2).await; +// assert_eq!(list_resp_body2, list_req_data2); +// +// } +// +// +// +// +// #[actix_web::test] +// #[serial] +// async fn test_apis_with_namespace() { +// let nats_cli = Arc::new(NatsCli::new().await.unwrap()); +// let event_manager = EventManager::new(); +// +// let app = test::init_service( +// App::new() +// .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.create_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::put().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.update_apis_with_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::delete().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.delete_apis_with_namespace(path, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.getone_apis_with_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.listall_apis_with_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// ).await; +// +// +// +// let create_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-01", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req = test::TestRequest::post() +// .uri("/apis/batch/v1/namespaces/global/jobs") +// .set_json(&create_req_data) +// .to_request(); +// +// let create_resp = test::call_service(&app, create_req).await; +// +// assert!(create_resp.status().is_success()); +// +// let create_resp_body = test::read_body_json::(create_resp).await; +// assert_eq!(create_resp_body, create_req_data); +// +// +// +// +// +// +// +// let update_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let update_req = test::TestRequest::put() +// .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") +// .set_json(&update_req_data) +// .to_request(); +// +// let update_resp = test::call_service(&app, update_req).await; +// +// assert!(update_resp.status().is_success()); +// +// let update_resp_body = test::read_body_json::(update_resp).await; +// assert_eq!(update_resp_body, update_req_data); +// +// +// +// +// let get_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let get_req = test::TestRequest::get() +// .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") +// .to_request(); +// +// let get_resp = test::call_service(&app, get_req).await; +// +// assert!(get_resp.status().is_success()); +// +// let get_resp_body = test::read_body_json::(get_resp).await; +// assert_eq!(get_resp_body, get_req_data); +// +// +// +// +// let create_req_data2 = json!({ +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req2 = test::TestRequest::post() +// .uri("/apis/batch/v1/namespaces/global/jobs") +// .set_json(&create_req_data2) +// .to_request(); +// +// let create_resp2 = test::call_service(&app, create_req2).await; +// +// assert!(create_resp2.status().is_success()); +// +// let create_resp_body2 = test::read_body_json::(create_resp2).await; +// assert_eq!(create_resp_body2, create_req_data2); +// +// +// +// let list_req_data = json!([{ +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }, +// { +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// +// let list_req = test::TestRequest::get() +// .uri("/apis/batch/v1/namespaces/global/jobs") +// .to_request(); +// +// let list_resp = test::call_service(&app, list_req).await; +// +// assert!(list_resp.status().is_success()); +// +// let list_resp_body = test::read_body_json::(list_resp).await; +// assert_eq!(list_resp_body, list_req_data); +// +// +// +// let delete_req = test::TestRequest::delete() +// .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") +// .to_request(); +// +// let delete_resp = test::call_service(&app, delete_req).await; +// +// assert!(delete_resp.status().is_success()); +// +// let list_req_data2 = json!([ +// { +// "apiVersion": "batch/v1", +// "kind": "Job", +// "metadata": { +// "name": "job-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "job-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// let list_req2 = test::TestRequest::get() +// .uri("/apis/batch/v1/namespaces/global/jobs") +// .to_request(); +// +// let list_resp2 = test::call_service(&app, list_req2).await; +// +// assert!(list_resp2.status().is_success()); +// +// let list_resp_body2 = test::read_body_json::(list_resp2).await; +// assert_eq!(list_resp_body2, list_req_data2); +// +// } +// +// +// #[actix_web::test] +// #[serial] +// async fn test_crd_and_apis_without_namespace() { +// let nats_cli = Arc::new(NatsCli::new().await.unwrap()); +// let event_manager = EventManager::new(); +// +// let app = test::init_service( +// App::new() +// .route("/apis/{group}/{version}/{plural}", web::post().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.create_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/{plural}/{name}", web::put().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.update_apis_without_namespace(path, data, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/{plural}/{name}", web::delete().to({ +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// move |path| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// let event_manager = event_manager.clone(); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.delete_apis_without_namespace(path, &mut conn, nats_cli, event_manager).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/{plural}/{name}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.getone_apis_without_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// .route("/apis/{group}/{version}/{plural}", web::get().to({ +// let nats_cli = Arc::clone(&nats_cli); +// +// move |path, data| { +// let handler = HANDLER.clone(); +// let db_pool = DB_POOL.clone(); +// let nats_cli = Arc::clone(&nats_cli); +// +// async move { +// match db_pool.get_connection() { +// Ok(mut conn) => handler.listall_apis_without_namespace(path, data, &mut conn, nats_cli).await, +// Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), +// } +// } +// } +// })) +// ).await; +// +// +// let crd_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "CustomResourceDefinition", +// "metadata": { +// "name": "myjob.iscas.cn" +// }, +// "spec": { +// "names": +// { +// "kind": "Myjob", +// "listKind": "MyjobList", +// "plural": "myjobs", +// "singular": "myjob" +// } +// } +// }); +// +// let crd_req = test::TestRequest::post() +// .uri("/apis/batch/v1/crds") +// .set_json(&crd_req_data) +// .to_request(); +// +// let crd_resp = test::call_service(&app, crd_req).await; +// +// assert!(crd_resp.status().is_success()); +// +// let crd_resp_body = test::read_body_json::(crd_resp).await; +// assert_eq!(crd_resp_body, crd_req_data); +// +// +// let create_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-01", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req = test::TestRequest::post() +// .uri("/apis/batch/v1/myjobs") +// .set_json(&create_req_data) +// .to_request(); +// +// let create_resp = test::call_service(&app, create_req).await; +// +// assert!(create_resp.status().is_success()); +// +// let create_resp_body = test::read_body_json::(create_resp).await; +// assert_eq!(create_resp_body, create_req_data); +// +// +// +// +// +// +// +// let update_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let update_req = test::TestRequest::put() +// .uri("/apis/batch/v1/myjobs/myjob-case-01") +// .set_json(&update_req_data) +// .to_request(); +// +// let update_resp = test::call_service(&app, update_req).await; +// +// assert!(update_resp.status().is_success()); +// +// let update_resp_body = test::read_body_json::(update_resp).await; +// assert_eq!(update_resp_body, update_req_data); +// +// +// +// +// let get_req_data = json!({ +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }); +// +// let get_req = test::TestRequest::get() +// .uri("/apis/batch/v1/myjobs/myjob-case-01") +// .to_request(); +// +// let get_resp = test::call_service(&app, get_req).await; +// +// assert!(get_resp.status().is_success()); +// +// let get_resp_body = test::read_body_json::(get_resp).await; +// assert_eq!(get_resp_body, get_req_data); +// +// +// +// +// let create_req_data2 = json!({ +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }); +// +// let create_req2 = test::TestRequest::post() +// .uri("/apis/batch/v1/myjobs") +// .set_json(&create_req_data2) +// .to_request(); +// +// let create_resp2 = test::call_service(&app, create_req2).await; +// +// assert!(create_resp2.status().is_success()); +// +// let create_resp_body2 = test::read_body_json::(create_resp2).await; +// assert_eq!(create_resp_body2, create_req_data2); +// +// +// +// let list_req_data = json!([{ +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-01", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-01", +// "command": [ +// "sleep", +// "1" +// ] +// } +// ] +// } +// }, +// { +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// +// let list_req = test::TestRequest::get() +// .uri("/apis/batch/v1/myjobs") +// .to_request(); +// +// let list_resp = test::call_service(&app, list_req).await; +// +// assert!(list_resp.status().is_success()); +// +// let list_resp_body = test::read_body_json::(list_resp).await; +// assert_eq!(list_resp_body, list_req_data); +// +// +// +// let delete_req = test::TestRequest::delete() +// .uri("/apis/batch/v1/myjobs/myjob-case-01") +// .to_request(); +// +// let delete_resp = test::call_service(&app, delete_req).await; +// +// assert!(delete_resp.status().is_success()); +// +// let list_req_data2 = json!([ +// { +// "apiVersion": "batch/v1", +// "kind": "Myjob", +// "metadata": { +// "name": "myjob-case-02", +// }, +// "spec": { +// "containers": [ +// { +// "name": "myjob-case-02", +// "command": [ +// "sleep", +// "3600" +// ] +// } +// ] +// } +// }]); +// let list_req2 = test::TestRequest::get() +// .uri("/apis/batch/v1/myjobs") +// .to_request(); +// +// let list_resp2 = test::call_service(&app, list_req2).await; +// +// assert!(list_resp2.status().is_success()); +// +// let list_resp_body2 = test::read_body_json::(list_resp2).await; +// assert_eq!(list_resp_body2, list_req_data2); +// +// } +// } diff --git a/tests/server_tests.rs b/tests/server_tests.rs new file mode 100644 index 0000000..b0131cd --- /dev/null +++ b/tests/server_tests.rs @@ -0,0 +1,60 @@ +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App, HttpResponse}; + use dotenv::dotenv; + use env_logger::{Builder, Target}; + use feventbus::traits::controller::EventBus; + use fleet_apiserver::cores::apiserver::K8sStyleRoute; + use fleet_apiserver::cores::handlers::Handler; + use fleet_apiserver::{prepare_app_state, start_server}; + use log::log; + use once_cell::sync::Lazy; + use serde_json::json; + use serial_test::serial; + use std::env; + use std::sync::atomic::AtomicBool; + use std::sync::Arc; + use std::thread::spawn; + + const ADDRESS: &str = "0.0.0.0:8000"; + const URL_PREFIX: &str = "http://localhost:8000"; + + const DATABASE_URL: &str = "DATABASE_URL"; + + async fn setup() { + let mut builder = Builder::from_default_env(); + builder.target(Target::Stdout); + builder.init(); + + dotenv().ok(); + } + + #[actix_web::test] + #[serial] + async fn test_server() { + setup().await; + let database_url = env::var(DATABASE_URL).expect("DATABASE_URL must be set"); + log::info!("Setting DATABASE_URL to {}", &database_url); + let app_state = prepare_app_state(database_url.as_str()).await; + if let Err(e) = app_state { + log::error!("Failed to prepare app state: {:?}", e); + return; + } + let app = App::new().app_data(web::Data::new(app_state.unwrap())); + let routes = K8sStyleRoute::get_routes(); + let app = routes.iter().fold(app, |app, route| { + app.route(route.get_path().as_str(), route.get_web_route()) + }); + let app = app.default_service(web::to(move || async { + Ok::( + HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) + })); + + let app = test::init_service(app).await; + + let req = test::TestRequest::get().uri("/apis/group1/v1/namespaces/ns1/pods/pod1").to_request(); + let res = test::call_service(&app, req).await; + log::info!("Response: {:?}", test::read_body(res).await); + } +} -- Gitee From c8a08a3d1eca83debf27316dc444e69791e74f55 Mon Sep 17 00:00:00 2001 From: yzc1114 <1021777674@qq.com> Date: Fri, 20 Dec 2024 02:20:09 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E9=87=8D=E6=9E=84apiserver=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0eventbus=E7=95=8C=E9=9D=A2=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构apiserver,添加eventbus界面相关代码 --- .gitignore | 3 +- Cargo.lock | 27 +++ Cargo.toml | 7 +- src/cores/apiserver.rs | 83 +++++--- src/cores/checker.rs | 4 +- src/cores/events.rs | 474 +++++++++++++++++++++++++++++++++++------ src/cores/handlers.rs | 131 +++++------- src/cores/mod.rs | 17 +- src/cores/services.rs | 418 ++++++++++++++++++++++-------------- tests/server_tests.rs | 330 +++++++++++++++++++++++++--- 10 files changed, 1122 insertions(+), 372 deletions(-) diff --git a/.gitignore b/.gitignore index e13928c..fc336a8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk -database.sqlite \ No newline at end of file +database.sqlite +test-database.sqlite \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7cb95fa..48f50a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1048,6 +1048,7 @@ checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "feventbus" version = "0.3.1" +source = "git+https://gitee.com/iscas-system/eventbus.git#b38c27f76ebfb8888f7927bb5c36939f45c76eea" dependencies = [ "async-nats", "async-trait", @@ -1094,6 +1095,7 @@ dependencies = [ "env_logger", "feventbus", "fleetmod", + "futures", "itertools 0.13.0", "json-patch", "jsonschema", @@ -1106,7 +1108,9 @@ dependencies = [ "schemars", "serde", "serde_json", + "serde_yaml", "serial_test", + "strum", "tokio", ] @@ -1123,6 +1127,7 @@ dependencies = [ [[package]] name = "fleetmod" version = "0.1.1" +source = "git+https://gitee.com/iscas-system/fleetmod.git#9f1dd10055f6fcbb8cf544be2027a9947473cd5a" dependencies = [ "anyhow", "chrono", @@ -3111,6 +3116,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.90", +] + [[package]] name = "subslice" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index d5c4462..e251f16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ readme = "README.md" [dependencies] #feventbus = "0.3.0" -feventbus = { path = "../eventbus" } -fleetmod = { path = "../fleetmod" } +feventbus = { git = "https://gitee.com/iscas-system/eventbus.git" } +fleetmod = { git = "https://gitee.com/iscas-system/fleetmod.git" } r2d2 = "0.8.10" dotenv = "0.15.0" diesel = { version = "2.2.0", features = ["sqlite", "postgres", "r2d2"] } @@ -39,3 +39,6 @@ log = "0.4.22" itertools = "0.13.0" anyhow = "1.0.94" json-patch = "3.0.1" +strum = { version = "0.26.3", features = ["derive"] } +serde_yaml = "0.9.34+deprecated" +futures = "0.3.31" diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index be6bf9b..af1d955 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -1,11 +1,12 @@ -use crate::cores::handlers::{Handler, HttpResponseWrapper}; +use crate::cores::events::WatchEventPublisher; +use crate::cores::handlers::{APIServerResponse, Handler}; use crate::cores::services::{APIServerError, APIServerResult}; use crate::db::db::DbPool; use actix_web::dev::Server; use actix_web::web::route; -use actix_web::Result; -use actix_web::{http, web, App, HttpResponse, HttpServer}; +use actix_web::{http, web, App, Error, HttpResponse, HttpServer}; use feventbus::impls::nats::nats::NatsCli; +use futures::StreamExt; use http::Method; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -18,6 +19,7 @@ pub struct AppState { pub db_pool: Arc, pub nats_cli: Arc, pub handler: Arc, + pub watch_event_publisher: Arc, } impl APIServer { @@ -25,16 +27,17 @@ impl APIServer { APIServer {} } - pub fn start(&self, addr: &str, app_state: Arc) -> Server { + pub fn start(&self, addr: &str, app_state: AppState) -> Server { let server = HttpServer::new(move || { let app = App::new() .app_data(web::Data::new(app_state.clone())); let routes = K8sStyleRoute::get_routes(); let app = routes.iter().fold(app, |app, route| { + log::info!("register route: {} {}", route.get_method(), route.get_path()); app.route(route.get_path().as_str(), route.get_web_route()) }); let app = app.default_service(web::to(move || async { - Ok::( + Ok::( HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) })); app @@ -46,7 +49,7 @@ impl APIServer { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct APIServerPathParams { +pub struct K8sStylePathParams { pub group: Option, pub version: String, pub namespace: Option, @@ -75,12 +78,11 @@ impl APIServerRoute for K8sStyleRoute { // apis/{group}/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] // api/{version}/[namespaces/{namespace}: 可选]/{plural}/[{name}: 可选] fn get_path(&self) -> String { - let prefix = if self.with_group { "apis/{group}" } else { "api" }; - let version = "{version}"; - let namespace = if self.with_namespace { "namespaces/{namespace}" } else { "" }; - let plural = "{plural}"; - let name = if self.with_name { "{name}" } else { "" }; - format!("{}/{}/{}/{}/{}", prefix, version, namespace, plural, name) + let prefix = if self.with_group { "apis/{group}/" } else { "api/" }; + let version = "{version}/"; + let namespace = if self.with_namespace { "namespaces/{namespace}/" } else { "" }; + let plural_and_name = if self.with_name { "{plural}/{name}" } else { "{plural}" }; + format!("{}{}{}{}", prefix, version, namespace, plural_and_name) } fn get_web_route(&self) -> actix_web::Route { @@ -96,33 +98,62 @@ impl K8sStyleRoute { }).collect() } - async fn handler(method: Method, query: web::Query, path_params: web::Path, state: web::Data, body: web::Json) - -> HttpResponse { - log::info!("ApiServerHandler: method: {:?}, path_params: {:?}, body: {:?}", method, path_params, body); - let mut res = state.db_pool.get_connection(); - if let Err(e) = res { - log::error!("error getting db conn: {}", e); - return HttpResponse::from(HttpResponseWrapper::(Err(APIServerError::internal_error("DB pool error")))); + async fn parse_body(mut payload: web::Payload) -> APIServerResult { + // payload is a stream of Bytes objects + let mut body = web::BytesMut::new(); + let parse_chunk_error = APIServerError::bad_request("parse payload chunk error"); + while let Some(chunk) = payload.next().await { + let chunk = chunk; + if chunk.is_err() { + return Err(parse_chunk_error); + } + let chunk = chunk.unwrap(); + // limit max size of in-memory payload + if (body.len() + chunk.len()) > 262_144 { + return Err(parse_chunk_error); + } + body.extend_from_slice(&chunk); } - let mut conn = res.unwrap(); + + let body = serde_json::from_slice::(&body); + if body.is_err() { + return Err(APIServerError::bad_request("payload is not a valid json")); + } + Ok(body.unwrap()) + } + + async fn handler(method: Method, query: web::Query, path_params: web::Path, state: web::Data, payload: web::Payload) + -> HttpResponse { + let body = Self::parse_body(payload).await; + log::info!("ApiServerHandler: method: {:?}, path_params: {:?}", method, path_params); let handler = state.handler.clone(); + match method { + Method::POST | Method::PUT | Method::PATCH => { + if body.is_err() { + return HttpResponse::from(APIServerResponse::from(body)); + } + } + _ => {} + } match method { Method::GET => { - handler.get_resource(path_params.into_inner(), query.into_inner(), &mut conn, state.nats_cli.clone()).await + handler.get_resource(path_params.into_inner(), query.into_inner(), state.into_inner()).await } Method::POST => { - handler.create_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await + handler.create_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await } Method::PUT => { - handler.update_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await + handler.update_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await } Method::DELETE => { - handler.delete_resource(path_params.into_inner(), &mut conn, state.nats_cli.clone()).await + handler.delete_resource(path_params.into_inner(), state.into_inner()).await } Method::PATCH => { - handler.patch_resource(path_params.into_inner(), body.into_inner(), &mut conn, state.nats_cli.clone()).await + handler.patch_resource(path_params.into_inner(), body.unwrap(), state.into_inner()).await + } + _ => { + HttpResponse::from(APIServerResponse::from(Err::(APIServerError::bad_request("unsupported method")))) } - _ => todo!() } } } diff --git a/src/cores/checker.rs b/src/cores/checker.rs index e47d071..a176dc3 100644 --- a/src/cores/checker.rs +++ b/src/cores/checker.rs @@ -7,15 +7,15 @@ use jsonschema::Validator; + +use actix_web::Result; use serde_json::Value; -use actix_web::{Result}; use std::error::Error; use std::fs::File; use std::io::Read; // TODO,未来能自动从openapi中获取Schema pub fn validate_pod_json(json_str: &str) -> Result> { - let mut file = File::open("schemas/pod-schema.json")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; diff --git a/src/cores/events.rs b/src/cores/events.rs index c709688..5b06c08 100644 --- a/src/cores/events.rs +++ b/src/cores/events.rs @@ -1,10 +1,12 @@ -use crate::cores::apiserver::{APIServerPathParams, AppState}; -use crate::cores::services::APIServerResult; +use crate::cores::apiserver::AppState; +use crate::cores::services::{APIServerError, APIServerResult, APIServerService, APIServerServiceParams}; +use anyhow::Result; use feventbus::err::Error as FEventBusError; use feventbus::impls::nats::nats::NatsCli; -use feventbus::message::Message; +use feventbus::message::{Message, NativeEventAction}; use feventbus::traits::consumer::{Consumer, MessageHandler}; -use fleetmod::utils::APIVersion; +use feventbus::traits::producer::Producer; +use fleetmod::utils::{APIVersion, ResourceCommon}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::error::Error as SerdeError; @@ -12,23 +14,13 @@ use serde_json::Value; use std::fmt::{Debug, Display, Formatter}; use std::future::Future; use std::sync::Arc; +use std::time; +use strum::{EnumIter, IntoEnumIterator}; +use tokio::sync::{mpsc, Mutex}; +use tokio::time::sleep; +use fleetmod::FleetResource; -async fn reply_to_topic( - topic: String, - nats_cli: Arc, - handler: MessageHandler, -) -> anyhow::Result<(), Box> -where - T: Serialize + for<'de> Deserialize<'de> + Send + 'static + Clone + Sync + Debug, -{ - nats_cli - .reply(topic.as_str(), handler) - .await - .map_err(|e| Box::new(e) as Box)?; - Ok(()) -} - -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum EventTopic { // P2P events表示其他组件向api server发送的点对点事件 P2P(P2PEventTopic), @@ -45,23 +37,23 @@ impl Display for EventTopic { } } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, EnumIter, PartialEq)] pub enum P2PEventTopic { Create, Update, Patch, Delete, List, - StartsWatch(ResourceCollectionIdentifier), - EndsWatch(ResourceCollectionIdentifier), + StartsWatch, + EndsWatch, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum PubSubEventTopic { Watch(ResourceCollectionIdentifier), } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct ResourceCollectionIdentifier { pub api_version: APIVersion, pub kind: String, @@ -69,10 +61,31 @@ pub struct ResourceCollectionIdentifier { // todo! 未来支持更多字段的过滤,如label,annotation等 } +impl ResourceCollectionIdentifier { + // 如果value是一个合法的FleetResource,并且符合当前identifier的筛选规则,则返回true + pub fn fits(&self, value: Value) -> bool { + // ResourceCommon包含api_version, kind, metadata等所有资源的共有字段 + let resource_common: ResourceCommon = match serde_json::from_value(value) { + Ok(v) => v, + Err(_) => return false, + }; + if resource_common.api_version != self.api_version { + return false; + } + if resource_common.kind != self.kind { + return false; + } + if resource_common.metadata.namespace != self.namespace { + return false; + } + true + } +} + // Create, Update, Patch使用WriteEventRequest #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WriteEventRequest { - pub params: APIServerPathParams, + pub params: APIServerServiceParams, pub data: Value, } @@ -81,79 +94,139 @@ pub struct WriteEventResponse { pub res: APIServerResult, } -// Delete 使用DeleteEventRequest #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeleteEventRequest { - pub params: APIServerPathParams, + pub params: APIServerServiceParams, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeleteEventResponse { - pub res: APIServerResult<()>, + pub res: APIServerResult, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ReadEventRequest { - pub params: APIServerPathParams, + pub params: APIServerServiceParams, pub query: Value, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ReadEventResponse { - pub res: APIServerResult>, + pub res: APIServerResult, } -pub struct EventServer { +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StartsWatchRequest { + pub ri: ResourceCollectionIdentifier, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StartsWatchResponse {} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EndsWatchRequest { + pub ri: ResourceCollectionIdentifier, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EndsWatchResponse {} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WatchEventMessage { + pub values: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum WatchEventMessageValue { + Created(Value), + Updated(Value), + Deleted(Value), +} + +impl WatchEventMessageValue { + pub fn get_value(&self) -> &Value { + match self { + WatchEventMessageValue::Created(v) => v, + WatchEventMessageValue::Updated(v) => v, + WatchEventMessageValue::Deleted(v) => v, + } + } +} + +pub struct P2PEventServer { app_state: Arc, + service: Arc, } -impl EventServer { +impl P2PEventServer { pub fn new(app_state: Arc) -> Self { Self { app_state, + service: Arc::new(APIServerService::new()), } } pub fn start(&self) { let nats_cli = self.app_state.nats_cli.clone(); + self.start_reply(nats_cli.clone()); } - pub async fn reply_list(req: ReadEventRequest) -> ReadEventResponse { - todo!() + pub async fn reply_list(service: Arc, app_state: Arc, req: ReadEventRequest) -> ReadEventResponse { + let res = service.get_resource(req.params, req.query, app_state.clone()).await; + ReadEventResponse { res } } - pub async fn reply_create(req: WriteEventRequest) -> ReadEventResponse { - todo!() + pub async fn reply_create(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { + let res = service.create_resource(req.params, req.data, app_state.clone()).await; + WriteEventResponse { res } } - pub async fn reply_update(req: WriteEventRequest) -> ReadEventResponse { - todo!() + pub async fn reply_update(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { + let res = service.update_resource(req.params, req.data, app_state.clone()).await; + WriteEventResponse { res } } - pub async fn reply_patch(req: WriteEventRequest) -> ReadEventResponse { - todo!() + pub async fn reply_patch(service: Arc, app_state: Arc, req: WriteEventRequest) -> WriteEventResponse { + let res = service.patch_resource(req.params, req.data, app_state).await; + WriteEventResponse { res } + } + + pub async fn reply_delete(service: Arc, app_state: Arc, req: DeleteEventRequest) -> DeleteEventResponse { + let res = service.delete_resource(req.params, app_state).await; + DeleteEventResponse { res } } - pub async fn reply_delete(req: DeleteEventRequest) -> ReadEventResponse { + pub async fn reply_starts_watch(_: Arc, app_state: Arc, req: StartsWatchRequest) -> StartsWatchResponse { + let watch_event = PubSubEventTopic::Watch(req.ri); + app_state.watch_event_publisher.add_pub_sub_event_topic(watch_event).await; + StartsWatchResponse {} + } + + pub async fn reply_ends_watch(_service: Arc, _app_state: Arc, _req: EndsWatchRequest) -> EndsWatchResponse { + // 暂时先不实现取消watch:需要维持观察者的状态,以便在取消watch时取消订阅,可以通过心跳来实现 todo!() } - pub async fn do_reply(body: Value, reply: F) -> Result + pub async fn do_reply(service: Arc, app_state: Arc, body: Value, reply: F) -> Result where I: DeserializeOwned, O: Serialize, - F: Fn(I) -> Fut, + F: Fn(Arc, Arc, I) -> Fut, Fut: Future, { let req: I = serde_json::from_value(body).map_err(|e: SerdeError| FEventBusError::MessageHandling(e.to_string()))?; - let res = reply(req).await; + let res = reply(service, app_state, req).await; Ok(serde_json::to_string(&res).unwrap()) } - pub fn get_reply_handler(event_topic: P2PEventTopic) -> MessageHandler + pub fn get_reply_handler(&self, event_topic: P2PEventTopic) -> MessageHandler { + let service = self.service.clone(); + let app_state = self.app_state.clone(); let reply_handler: MessageHandler = Arc::new(move |msg: Message| { let event_topic = event_topic.clone(); + let service = service.clone(); + let app_state = app_state.clone(); Box::pin(async move { if msg.body.is_none() { return Err(FEventBusError::MessageHandling( @@ -162,42 +235,303 @@ impl EventServer { } let body = msg.body.unwrap(); match event_topic { - P2PEventTopic::List => Self::do_reply(body, EventServer::reply_list).await, - P2PEventTopic::Create => Self::do_reply(body, EventServer::reply_create).await, - P2PEventTopic::Update => Self::do_reply(body, EventServer::reply_update).await, - P2PEventTopic::Patch => Self::do_reply(body, EventServer::reply_update).await, - P2PEventTopic::Delete => Self::do_reply(body, EventServer::reply_delete).await, - _ => Err(FEventBusError::MessageHandling( - "Unsupported event topic".to_string(), - )), + P2PEventTopic::List => Self::do_reply(service, app_state, body, P2PEventServer::reply_list).await, + P2PEventTopic::Create => Self::do_reply(service, app_state, body, P2PEventServer::reply_create).await, + P2PEventTopic::Update => Self::do_reply(service, app_state, body, P2PEventServer::reply_update).await, + P2PEventTopic::Patch => Self::do_reply(service, app_state, body, P2PEventServer::reply_patch).await, + P2PEventTopic::Delete => Self::do_reply(service, app_state, body, P2PEventServer::reply_delete).await, + P2PEventTopic::StartsWatch => Self::do_reply(service, app_state, body, P2PEventServer::reply_starts_watch).await, + P2PEventTopic::EndsWatch => Self::do_reply(service, app_state, body, P2PEventServer::reply_ends_watch).await, } }) }); reply_handler } - pub fn start_reply(nats_cli: Arc) { - let p2p_topics = vec![ - EventTopic::P2P(P2PEventTopic::List), - EventTopic::P2P(P2PEventTopic::Create), - EventTopic::P2P(P2PEventTopic::Update), - EventTopic::P2P(P2PEventTopic::Patch), - EventTopic::P2P(P2PEventTopic::Delete), - ]; - for t in p2p_topics { + pub fn start_reply(&self, nats_cli: Arc) { + let mut topics = Vec::new(); + for p2p in P2PEventTopic::iter() { + topics.push(EventTopic::P2P(p2p.clone())); + } + for t in topics { let topic_str = t.to_string(); let p2p_topic = match t { EventTopic::P2P(topic) => topic, _ => unreachable!(), }; - let reply_handler = Self::get_reply_handler(p2p_topic); - actix_web::rt::spawn(reply_to_topic( - topic_str, - Arc::clone(&nats_cli), - reply_handler, - )); + let reply_handler = self.get_reply_handler(p2p_topic); + let nats_cli = nats_cli.clone(); + tokio::spawn(async move { + log::info!("Registering reply handler for topic {}", topic_str); + if let Err(e) = nats_cli + .reply(topic_str.as_str(), reply_handler) + .await { + log::error!("Failed to register reply handler for topic {}: {}", topic_str, e); + } + }); + } + } +} + +pub struct WatchEventPublisher { + nats_cli: Arc, + pub_sub_event_topics: Arc>>, + sender: mpsc::Sender, + receiver: Arc>>, +} + +impl WatchEventPublisher { + pub fn new(nats_cli: Arc) -> Self { + let (sx, rx) = mpsc::channel(100); + Self { + nats_cli, + // todo! pub_sub_event_topics在重启后会清空,需要持久化 + pub_sub_event_topics: Arc::new(Mutex::new(Vec::new())), + sender: sx, + receiver: Arc::new(Mutex::new(rx)), + } + } + + pub async fn add_pub_sub_event_topic(&self, topic: PubSubEventTopic) { + match topic { + PubSubEventTopic::Watch(_) => {} + // _ => return anyhow::bail!("WatchEventPublisher不支持Watch以外的事件类型"), + }; + let mut topics = self.pub_sub_event_topics.lock().await; + if topics.contains(&topic) { + return; + } + topics.push(topic); + // todo! 持久化 + } + + pub async fn remove_pub_sub_event_topic(&self, topic: PubSubEventTopic) { + let mut topics = self.pub_sub_event_topics.lock().await; + if let Some(index) = topics.iter().position(|x| *x == topic) { + topics.remove(index); + } + } + + pub fn start(&self) { + let rx = self.receiver.clone(); + let nats_cli = self.nats_cli.clone(); + let pub_sub_event_topics = self.pub_sub_event_topics.clone(); + tokio::spawn(async move { + log::info!("WatchEventPublisher started receiving messages"); + let mut rx = rx.lock().await; // 锁定接收器 + let buffer_limit = 32; + let mut buffer = Vec::with_capacity(buffer_limit); + loop { + sleep(time::Duration::from_millis(500)).await; + let received_cnt = rx.recv_many(&mut buffer, buffer_limit).await; + if received_cnt == 0 { + continue; + } + log::info!("WatchEventPublisher Received {} messages", received_cnt); + Self::publish_events_to_topics(nats_cli.clone(), pub_sub_event_topics.clone(), &buffer).await; + } + }); + } + + pub async fn publish_events_to_topics(nats_cli: Arc, pub_sub_event_topics: Arc>>, msg_values: &Vec) { + for topic in pub_sub_event_topics.lock().await.iter() { + let mut identified_values = Vec::new(); + for msg_value in msg_values.iter() { + let identifier = match topic { + PubSubEventTopic::Watch(identifier) => { + identifier.clone() + } + }; + let v = msg_value.get_value(); + if identifier.fits(v.clone()) { + identified_values.push(msg_value.clone()); + } + } + if identified_values.len() > 0 { + let to_publish = Message::new( + EventTopic::PubSub(topic.clone()).to_string(), + NativeEventAction::Other, // 该字段暂时无用 + None, // metadata + Some(identified_values), // body + None, // created_at + ); + if let Err(e) = nats_cli.publish(to_publish).await { + log::error!("WatchEventPublisher Failed to publish event: {}", e); + } + } + } + } + + pub async fn publish_create_event(&self, data: Value) { + if let Err(e) = self.sender.send(WatchEventMessageValue::Created(data)).await { + log::error!("Failed to publish create event: {}", e); + } + } + + pub async fn publish_update_event(&self, data: Value) { + if let Err(e) = self.sender.send(WatchEventMessageValue::Updated(data)).await { + log::error!("Failed to publish update event: {}", e); + } + } + + pub async fn publish_delete_event(&self, data: Value) { + if let Err(e) = self.sender.send(WatchEventMessageValue::Deleted(data)).await { + log::error!("Failed to publish delete event: {}", e); } } } -pub struct EventClient {} \ No newline at end of file +pub struct APIServerEventClient { + nats_cli: Arc, + timeout: time::Duration, +} + +impl APIServerEventClient { + pub fn new(nats_cli: Arc, timeout: Option) -> Self { + Self { + nats_cli, + timeout: timeout.unwrap_or(time::Duration::from_secs(60)), + } + } + + fn new_message(topic: P2PEventTopic, req: R) -> Message + where + R: Clone + Serialize + DeserializeOwned, + { + let msg = Message::new( + EventTopic::P2P(topic).to_string(), + NativeEventAction::Other, + None, + Some(req), + None, + ); + msg + } + + fn map_serde_err(e: SerdeError) -> APIServerError { + APIServerError::internal_error(format!("err serde processing event msg: {}", e).as_str()) + } + + fn data_to_value(data: T) -> APIServerResult + where + T: Serialize, + { + Ok(serde_json::to_value(data).map_err(Self::map_serde_err)?) + } + + fn value_to_data(value: Value) -> APIServerResult + where + T: DeserializeOwned, + { + Ok(serde_json::from_value(value).map_err(Self::map_serde_err)?) + } + + fn parse_msg_raw_string(res: String) -> APIServerResult + where + T: DeserializeOwned, + { + let res: T = serde_json::from_str(&res).map_err(Self::map_serde_err)?; + Ok(res) + } + + async fn write_event(&self, topic: P2PEventTopic, params: APIServerServiceParams, data: Value) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + let msg = Self::new_message(topic, WriteEventRequest { params, data }); + let resp_str = self.nats_cli.request(msg, self.timeout).await?; + let resp: WriteEventResponse = Self::parse_msg_raw_string(resp_str)?; + Self::value_to_data(resp.res?) + } + + pub async fn create(&self, params: APIServerServiceParams, data: T) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + self.write_event(P2PEventTopic::Create, params, Self::data_to_value(data)?).await + } + + pub async fn create_by_resource(&self, data: T) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone + FleetResource, + { + let mut params = APIServerServiceParams::from_resource(&data); + params.name = None; // 创建资源时在路径参数中不要包含name + self.create(params, data).await + } + + pub async fn delete(&self, params: APIServerServiceParams) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + let msg = Self::new_message(P2PEventTopic::Delete, DeleteEventRequest { params }); + let resp_str = self.nats_cli.request(msg, self.timeout).await?; + let resp: WriteEventResponse = Self::parse_msg_raw_string(resp_str)?; + Self::value_to_data(resp.res?) + } + + pub async fn delete_by_resource(&self, data: T) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone + FleetResource, + { + let params = APIServerServiceParams::from_resource(&data); + self.delete(params).await + } + + pub async fn update(&self, params: APIServerServiceParams, data: T) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone + FleetResource, + { + self.write_event(P2PEventTopic::Update, params, Self::data_to_value(data)?).await + } + + pub async fn update_by_resource(&self, data: T) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone + FleetResource, + { + let params = APIServerServiceParams::from_resource(&data); + self.write_event(P2PEventTopic::Update, params, Self::data_to_value(data)?).await + } + + pub async fn patch(&self, params: APIServerServiceParams, data: Value) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + self.write_event(P2PEventTopic::Patch, params, data).await + } + + async fn read_event(&self, topic: P2PEventTopic, params: APIServerServiceParams, query: Value) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + let msg = Self::new_message(topic, ReadEventRequest { params, query }); + let resp_str = self.nats_cli.request(msg, self.timeout).await?; + let resp: ReadEventResponse = Self::parse_msg_raw_string(resp_str)?; + Self::value_to_data(resp.res?) + } + + pub async fn get(&self, params: APIServerServiceParams, query: Value) -> APIServerResult + where + T: Serialize + DeserializeOwned + Clone, + { + self.read_event(P2PEventTopic::List, params, query).await + } + + pub async fn list(&self, params: APIServerServiceParams, query: Value) -> APIServerResult> + where + T: Serialize + DeserializeOwned + Clone, + { + self.read_event(P2PEventTopic::List, params, query).await + } + + pub async fn starts_watch(&self, ri: ResourceCollectionIdentifier) -> APIServerResult<()> { + let msg = Self::new_message(P2PEventTopic::StartsWatch, StartsWatchRequest { ri }); + let resp_str = self.nats_cli.request(msg, self.timeout).await?; + let _resp: StartsWatchResponse = Self::parse_msg_raw_string(resp_str)?; + Ok(()) + } + + pub async fn ends_watch(&self, _ri: ResourceCollectionIdentifier) -> APIServerResult<()> { + todo!() + } +} \ No newline at end of file diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index c268d95..41357b8 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -1,74 +1,46 @@ -use crate::cores::apiserver::APIServerPathParams; -use crate::cores::services::{APIServerResult, ApiServerService}; -use crate::db::db::DbConnection; +use crate::cores::apiserver::{AppState, K8sStylePathParams}; +use crate::cores::services::{APIServerResult, APIServerService, APIServerStatusCode}; use actix_web::HttpResponse; use async_trait::async_trait; -use feventbus::impls::nats::nats::NatsCli; -use serde::Serialize; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::collections::HashMap; -use std::sync::{Arc, LazyLock, Mutex}; - -// 定义全局哈希表来获取model名 -static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { - let mut map = HashMap::new(); - map.insert("pods".to_string(), "POD".to_string()); - map.insert("nodes".to_string(), "NODE".to_string()); - map.insert("jobs".to_string(), "JOB".to_string()); - Mutex::new(map) -}); - -// 添加键值对 -fn insert_key_value(key: &str, value: &str) { - let mut map = GLOBAL_HASHMAP.lock().unwrap(); - map.insert(key.to_string(), value.to_string()); -} - -// 获取键对应的值 -fn get_value(key: &str) -> Option { - let map = GLOBAL_HASHMAP.lock().unwrap(); - map.get(key).cloned() -} - +use std::sync::Arc; #[async_trait] pub trait Handler { async fn create_resource( &self, - params: APIServerPathParams, + params: K8sStylePathParams, data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + app_state: Arc, ) -> HttpResponse; async fn delete_resource( &self, - params: APIServerPathParams, - db_conn: &mut DbConnection, - nats_cli: Arc, + params: K8sStylePathParams, + app_state: Arc, ) -> HttpResponse; async fn update_resource( &self, - params: APIServerPathParams, + params: K8sStylePathParams, data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + app_state: Arc, ) -> HttpResponse; async fn get_resource( &self, - params: APIServerPathParams, + params: K8sStylePathParams, _query: Value, - db_conn: &mut DbConnection, - _nats_cli: Arc, + app_state: Arc, ) -> HttpResponse; - async fn patch_resource(&self, - params: APIServerPathParams, - data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + async fn patch_resource( + &self, + params: K8sStylePathParams, + data: Value, + app_state: Arc, ) -> HttpResponse; // 不满足以上请求路径的处理 @@ -77,66 +49,79 @@ pub trait Handler { #[derive(Clone)] pub struct DefaultHandler { - service: ApiServerService, + service: APIServerService, } impl DefaultHandler { pub fn new() -> Self { Self { - service: ApiServerService {} + service: APIServerService::new() } } } -fn api_server_result_to_response(res: APIServerResult) -> HttpResponse +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APIServerResponse +{ + pub status_code: APIServerStatusCode, + pub message: String, + pub data: Option, +} + +impl From> for APIServerResponse where - T: Serialize, + T: Serialize + DeserializeOwned + Clone, { - match res { - Ok(data) => HttpResponse::Ok().json(data), - Err(err) => HttpResponse::Ok().json(err), + fn from(result: APIServerResult) -> Self { + match result { + Ok(data) => APIServerResponse { + status_code: APIServerStatusCode::OK, + message: "Success".to_string(), + data: Some(data), + }, + Err(err) => APIServerResponse { + status_code: err.status_code, + message: err.message, + data: None, + } + } } } -pub struct HttpResponseWrapper(pub APIServerResult); - -impl From> for HttpResponse +impl From> for HttpResponse where - T: Serialize, + T: Serialize + DeserializeOwned + Clone, { - fn from(wrapper: HttpResponseWrapper) -> HttpResponse { - match wrapper.0 { - Ok(data) => HttpResponse::Ok().json(data), - Err(err) => HttpResponse::Ok().json(err), - } + fn from(response: APIServerResponse) -> Self { + HttpResponse::Ok().json(response) } } #[async_trait] impl Handler for DefaultHandler { - async fn create_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { - HttpResponse::from(HttpResponseWrapper(self.service.create_resource(params, data, db_conn, nats_cli).await)) + async fn create_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { + HttpResponse::from(APIServerResponse::from(self.service.create_resource(params.into(), data, app_state).await)) } - async fn delete_resource(&self, params: APIServerPathParams, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { - HttpResponse::from(HttpResponseWrapper(self.service.delete_resource(params, db_conn, nats_cli).await)) + async fn delete_resource(&self, params: K8sStylePathParams, app_state: Arc) -> HttpResponse { + HttpResponse::from(APIServerResponse::from(self.service.delete_resource(params.into(), app_state).await)) } - async fn update_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { - HttpResponse::from(HttpResponseWrapper(self.service.update_resource(params, data, db_conn, nats_cli).await)) + async fn update_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { + HttpResponse::from(APIServerResponse::from(self.service.update_resource(params.into(), data, app_state).await)) } - async fn get_resource(&self, params: APIServerPathParams, query: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { - HttpResponse::from(HttpResponseWrapper(self.service.get_resource(params, query, db_conn, nats_cli).await)) + async fn get_resource(&self, params: K8sStylePathParams, query: Value, app_state: Arc) -> HttpResponse { + HttpResponse::from(APIServerResponse::from(self.service.get_resource(params.into(), query, app_state).await)) } - async fn patch_resource(&self, params: APIServerPathParams, data: Value, db_conn: &mut DbConnection, nats_cli: Arc) -> HttpResponse { - HttpResponse::from(HttpResponseWrapper(self.service.patch_resource(params, data, db_conn, nats_cli).await)) + async fn patch_resource(&self, params: K8sStylePathParams, data: Value, app_state: Arc) -> HttpResponse { + HttpResponse::from(APIServerResponse::from(self.service.patch_resource(params.into(), data, app_state).await)) } fn default(&self) -> Self { Self { - service: ApiServerService {} + service: APIServerService::new() } } } diff --git a/src/cores/mod.rs b/src/cores/mod.rs index b33b880..ea4b623 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -1,8 +1,8 @@ use crate::cores::apiserver::{APIServer, AppState}; +use crate::cores::events::{P2PEventServer, WatchEventPublisher}; use crate::cores::handlers::DefaultHandler; use crate::db::db::DbPool; use feventbus::impls::nats::nats::NatsCli; -use feventbus::traits::consumer::Consumer; use feventbus::traits::controller::EventBus; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -17,8 +17,8 @@ use std::sync::Arc; pub mod apiserver; pub mod handlers; pub mod checker; -mod events; -mod services; +pub mod events; +pub mod services; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ResourcesMessage { @@ -32,10 +32,12 @@ pub async fn prepare_app_state(database_url: &str) -> anyhow::Result { let db_pool = Arc::new(DbPool::new(database_url)?); let handler: DefaultHandler = DefaultHandler::new(); let nats_cli = Arc::new(NatsCli::new().await?); + let watch_event_publisher = Arc::new(WatchEventPublisher::new(nats_cli.clone())); Ok(AppState { db_pool, handler: Arc::new(handler), nats_cli, + watch_event_publisher, }) } @@ -48,9 +50,12 @@ pub async fn prepare_app_state(database_url: &str) -> anyhow::Result { /// # 返回值 /// - 异步运行结果 pub async fn start_server(database_url: &str, address: &str) -> anyhow::Result<()> { - let app_state = Arc::new(prepare_app_state(database_url).await?); - - APIServer::new().start(address, app_state.clone()).await?; + let app_state = prepare_app_state(database_url).await?; + // 启动watch相关事件监听协程 + app_state.clone().watch_event_publisher.start(); + // 启动P2P事件监听协程 + P2PEventServer::new(Arc::from(app_state.clone())).start(); + APIServer::new().start(address, app_state).await?; Ok(()) } diff --git a/src/cores/services.rs b/src/cores/services.rs index cab0d7a..9e671c5 100644 --- a/src/cores/services.rs +++ b/src/cores/services.rs @@ -1,56 +1,79 @@ -use crate::cores::apiserver::APIServerPathParams; +use crate::cores::apiserver::{AppState, K8sStylePathParams}; +use crate::cores::events::WatchEventPublisher; use crate::db::check_exist::{check_kine, check_metadata}; use crate::db::db::DbConnection; use crate::db::delete::delete_from_kine; use crate::db::get::{get_all_data_from_kine, get_data_from_kine}; use crate::db::insert::{insert_kine, insert_metadata}; use crate::db::update::update_data_in_kine; -use feventbus::impls::nats::nats::NatsCli; +use feventbus::err::Error as FEventBusError; use fleetmod::utils::APIVersion; -use serde::ser::SerializeStruct; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; +use std::cell::RefCell; use std::collections::HashMap; use std::error::Error; use std::fmt::{Display, Formatter}; use std::sync::{Arc, LazyLock, Mutex}; +use fleetmod::FleetResource; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum APIServerErrorType { - InternalError, - NotFound, - BadRequest, - Duplicated, +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum APIServerStatusCode { + // 正常流程 + OK = 20000, + + // 内部错误流程 + InternalError = 50000, + + // 请求方错误流程 + NotFound = 40004, + BadRequest = 40000, + Duplicated = 40001, + Timeout = 40002, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct APIServerError { - pub error_type: APIServerErrorType, - pub error_msg: String, + pub status_code: APIServerStatusCode, + pub message: String, } +impl From for APIServerError { + fn from(err: FEventBusError) -> Self { + match err { + FEventBusError::Timeout(msg) => APIServerError::timeout(msg.as_str()), + _ => APIServerError::internal_error(err.to_string().as_str()), + } + } +} + +// todo! 使用macro实现 impl APIServerError { - pub fn new(t: APIServerErrorType, msg: &str) -> Self { + pub fn new(t: APIServerStatusCode, msg: &str) -> Self { Self { - error_type: t, - error_msg: msg.to_string(), + status_code: t, + message: msg.to_string(), } } pub fn internal_error(message: &str) -> Self { - Self::new(APIServerErrorType::InternalError, message) + Self::new(APIServerStatusCode::InternalError, message) } pub fn not_found(message: &str) -> APIServerError { - Self::new(APIServerErrorType::NotFound, message) + Self::new(APIServerStatusCode::NotFound, message) } pub fn bad_request(message: &str) -> APIServerError { - Self::new(APIServerErrorType::BadRequest, message) + Self::new(APIServerStatusCode::BadRequest, message) } pub fn duplicated(message: &str) -> APIServerError { - Self::new(APIServerErrorType::Duplicated, message) + Self::new(APIServerStatusCode::Duplicated, message) + } + + pub fn timeout(message: &str) -> APIServerError { + Self::new(APIServerStatusCode::Timeout, message) } } @@ -70,39 +93,161 @@ impl From for APIServerError { pub type APIServerResult = Result; +// todo! 之后修改成缓存模块 // 定义全局哈希表来获取model名 static GLOBAL_HASHMAP: LazyLock>> = LazyLock::new(|| { let mut map = HashMap::new(); - map.insert("pods".to_string(), "POD".to_string()); - map.insert("nodes".to_string(), "NODE".to_string()); - map.insert("jobs".to_string(), "JOB".to_string()); + map.insert("pods".to_string(), "Pod".to_string()); + map.insert("nodes".to_string(), "Node".to_string()); + map.insert("jobs".to_string(), "Job".to_string()); Mutex::new(map) }); -// 添加键值对 -fn insert_key_value(key: &str, value: &str) { - let mut map = GLOBAL_HASHMAP.lock().unwrap(); - map.insert(key.to_string(), value.to_string()); -} +struct MetadataCache {} + +impl MetadataCache { + fn insert_key_value(key: &str, value: &str) { + let mut map = GLOBAL_HASHMAP.lock().unwrap(); + map.insert(key.to_string(), value.to_string()); + } -// 获取键对应的值 -fn get_value(key: &str) -> Option { - let map = GLOBAL_HASHMAP.lock().unwrap(); - map.get(key).cloned() + fn get_map() -> HashMap { + let map = GLOBAL_HASHMAP.lock().unwrap(); + map.clone() + } } + #[derive(Clone)] -pub struct ApiServerService {} +pub struct APIServerService {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PluralOrKind { + Plural(String), + Kind(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct APIServerServiceParams { + pub group: Option, + pub plural_or_kind: PluralOrKind, + pub version: String, + pub namespace: Option, + pub name: Option, +} + +impl APIServerServiceParams { + pub fn from_resource(resource: &T) -> APIServerServiceParams + where + T: FleetResource, + { + let kind = resource.get_kind(); + let metadata = resource.get_metadata(); + let api_version = resource.get_api_version(); + let mut builder = APIServerServiceParamsBuilder::new() + .kind(kind.to_string()) + .version(api_version.version.clone()) + .name(metadata.name.clone()); + if metadata.namespace.is_some() { + builder = builder.namespace(metadata.namespace.clone().unwrap()); + } + if api_version.group.is_some() { + builder = builder.group(api_version.group.clone().unwrap()); + } + builder.build().unwrap() + } +} + +impl From for APIServerServiceParams { + fn from(params: K8sStylePathParams) -> Self { + APIServerServiceParams { + group: params.group, + plural_or_kind: PluralOrKind::Plural(params.plural), + version: params.version, + namespace: params.namespace, + name: params.name, + } + } +} + +pub struct APIServerServiceParamsBuilder { + pub group: Option, + pub plural_or_kind: Option, + pub version: Option, + pub namespace: Option, + pub name: Option, +} + +impl APIServerServiceParamsBuilder { + pub fn new() -> Self { + Self { + group: None, + plural_or_kind: None, + version: None, + namespace: None, + name: None, + } + } + + pub fn group(mut self, group: String) -> Self { + self.group = Some(group); + self + } + + pub fn plural(mut self, plural: String) -> Self { + self.plural_or_kind = Some(PluralOrKind::Plural(plural)); + self + } + + pub fn kind(mut self, kind: String) -> Self { + self.plural_or_kind = Some(PluralOrKind::Kind(kind)); + self + } + + pub fn version(mut self, version: String) -> Self { + self.version = Some(version); + self + } + + pub fn namespace(mut self, namespace: String) -> Self { + self.namespace = Some(namespace); + self + } + + pub fn name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn build(self) -> anyhow::Result { + if self.plural_or_kind.is_none() { + return Err(anyhow::anyhow!("plural_or_kind is required")); + } + if self.version.is_none() { + return Err(anyhow::anyhow!("version is required")); + } + Ok(APIServerServiceParams { + group: self.group, + plural_or_kind: self.plural_or_kind.unwrap(), + version: self.version.unwrap(), + namespace: self.namespace, + name: self.name, + }) + } +} + +impl APIServerService { + pub fn new() -> Self { + Self {} + } -impl ApiServerService { pub async fn create_resource( &self, - params: APIServerPathParams, + params: APIServerServiceParams, data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + app_state: Arc, ) -> APIServerResult { - let ServiceCtx { api_version_str, plural, namespace, .. } = Self::prepare_ctx(params, Some(false))?; + let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, .. } = Self::prepare_ctx(params, app_state, Some(false))?; if plural == "crds" { // 处理 CRD 资源注册 @@ -120,20 +265,20 @@ impl ApiServerService { .and_then(|kind| kind.as_str()) .unwrap_or("error"); - let metadata_exists = check_metadata(db_conn, item_kind, api_version_str.as_str(), namespace.is_some()) + let metadata_exists = check_metadata(db_conn.get_mut(), item_kind, api_version_str.as_str(), namespace.is_some()) .await?; if metadata_exists { return Err(APIServerError::duplicated("该CRD资源已存在,无需重复注册")); } - insert_metadata(db_conn, item_kind, api_version_str.as_str(), namespace.is_some(), &data) + insert_metadata(db_conn.get_mut(), item_kind, api_version_str.as_str(), namespace.is_some(), &data) .await?; - insert_key_value(item_kind, kind_upper); + MetadataCache::insert_key_value(item_kind, kind_upper); } else { // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.is_some()) .await?; // 检查资源是否已存在 @@ -144,7 +289,7 @@ impl ApiServerService { .unwrap_or("error"); let kine_exists = check_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), item_name, api_version_str.as_str(), @@ -155,27 +300,8 @@ impl ApiServerService { return Err(APIServerError::duplicated("该资源已存在,请勿重复创建")); } - let mut model_map = HashMap::new(); - let kind = get_value(plural.as_str()).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - // // 创建请求消息 - // let request_message = Message::new( - // "CREATE".to_string(), - // message::NativeEventAction::Other, - // Some(model_map), - // Some(ApiServerMessage { - // content: data.clone(), - // }), - // None, - // ); - - // send_request(request_message, nats_cli) - // .await - // .map_err(ErrorInternalServerError)?; - insert_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), item_name, &data, @@ -183,64 +309,41 @@ impl ApiServerService { namespace.as_deref(), ).await?; } + watch_event_publisher.publish_create_event(data.clone()).await; Ok(data) } pub async fn delete_resource( &self, - params: APIServerPathParams, - db_conn: &mut DbConnection, - nats_cli: Arc, + params: APIServerServiceParams, + app_state: Arc, ) -> APIServerResult { - let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(true))?; + let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; let name = name.unwrap(); // 检查 metadata - Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.is_some()) .await?; // 检查资源是否存在 - let kine_exists = check_kine( - db_conn, + let resource_json_string = get_data_from_kine( + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), namespace.as_deref(), ).await?; - - if !kine_exists { - return Err(APIServerError::internal_error("指定资源不存在")); + if resource_json_string.is_none() { + return Err(APIServerError::not_found("指定资源不存在")); } - - let mut model_map = HashMap::new(); - let kind = get_value(plural.as_str()).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - // let request_data = json!({ - // "apiVersion": ver.clone(), - // "Namespaces": namespace.unwrap_or(""), - // "Plural": plural.clone(), - // "Name": name.clone(), - // }); - // - // // 创建请求消息 - // let request_message = Message::new( - // "DELETE".to_string(), - // message::NativeEventAction::Other, - // Some(model_map), - // Some(ApiServerMessage { - // content: request_data.clone(), - // }), - // None, - // ); - - // crate::cores::handlers::send_request(request_message, nats_cli) - // .await - // .map_err(ErrorInternalServerError)?; + let resource = match serde_json::from_str::(resource_json_string.unwrap().as_str()) { + Ok(json_data) => Ok(json_data), + Err(_) => Err(APIServerError::internal_error("资源格式错误,无法解析为 JSON")), + }?; // 删除资源 let deleted = delete_from_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), @@ -251,26 +354,26 @@ impl ApiServerService { return Err(APIServerError::internal_error("指定资源不存在")); } - Ok(json!({"status": "资源删除成功"})) + watch_event_publisher.publish_delete_event(resource.clone()).await; + Ok(resource) } pub async fn update_resource( &self, - params: APIServerPathParams, + params: APIServerServiceParams, data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + app_state: Arc, ) -> APIServerResult { - let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(true))?; + let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; let name = name.unwrap(); // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.is_some()) .await?; // 检查资源是否存在 let kine_exists = check_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), @@ -281,35 +384,9 @@ impl ApiServerService { return Err(APIServerError::internal_error("指定资源不存在")); } - let mut model_map = HashMap::new(); - let kind = get_value(plural.as_str()).unwrap(); - model_map.insert("MODEL".to_string(), kind); - - // let request_data = json!({ - // "apiVersion": ver.clone(), - // "Namespaces": namespace.unwrap_or(""), - // "Plural": plural.clone(), - // "Name": name.clone(), - // "Data": data.clone() - // }); - - // // 创建请求消息 - // let request_message = Message::new( - // "UPDATE".to_string(), - // message::NativeEventAction::Other, - // Some(model_map), - // Some(ApiServerMessage { - // content: request_data.clone(), - // }), - // None, - // ); - // crate::cores::handlers::send_request(request_message, nats_cli) - // .await - // .map_err(ErrorInternalServerError)?; - // 更新资源 let updated = update_data_in_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), @@ -321,25 +398,25 @@ impl ApiServerService { return Err(APIServerError::internal_error("更新资源失败")); } + watch_event_publisher.publish_update_event(data.clone()).await; Ok(data) } pub async fn get_resource( &self, - params: APIServerPathParams, + params: APIServerServiceParams, _query: Value, - db_conn: &mut DbConnection, - _nats_cli: Arc, - ) -> APIServerResult> { - let ServiceCtx { api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, Some(false))?; + app_state: Arc, + ) -> APIServerResult { + let ServiceCtx { mut db_conn, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, None)?; // 检查 metadata 是否存在 - Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.is_some()) .await?; if name.is_some() { // 如果name不为空,查询单个resource数据 if let Some(data) = get_data_from_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.unwrap().as_str(), api_version_str.as_str(), @@ -347,7 +424,7 @@ impl ApiServerService { ).await? { // 将字符串解析为 JSON 格式 return match serde_json::from_str::(&data) { - Ok(json_data) => Ok(vec![json_data]), + Ok(json_data) => Ok(json_data), Err(_) => Err(APIServerError::internal_error("资源格式错误,无法解析为 JSON")), }; } @@ -355,7 +432,7 @@ impl ApiServerService { Err(APIServerError::not_found("指定资源不存在")) } else { // 如果name为空,获取资源列表 - let data_list = get_all_data_from_kine(db_conn, plural.as_str(), api_version_str.as_str(), namespace.as_deref()) + let data_list = get_all_data_from_kine(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.as_deref()) .await?; // 将所有字符串解析为 JSON 格式并收集到数组中 @@ -364,24 +441,24 @@ impl ApiServerService { .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 .collect(); // 返回结果 - Ok(json_array) + Ok(json!(json_array)) } } - pub async fn patch_resource(&self, - params: APIServerPathParams, - data: Value, - db_conn: &mut DbConnection, - nats_cli: Arc, + pub async fn patch_resource( + &self, + params: APIServerServiceParams, + data: Value, + app_state: Arc, ) -> APIServerResult { - let ServiceCtx { api_version_str, plural, namespace, name } = Self::prepare_ctx(params, Some(true))?; + let ServiceCtx { mut db_conn, watch_event_publisher, api_version_str, plural, namespace, name, .. } = Self::prepare_ctx(params, app_state, Some(true))?; let name = name.unwrap(); - Self::check_metadata_exists(db_conn, plural.as_str(), api_version_str.as_str(), namespace.is_some()) + Self::check_metadata_exists(db_conn.get_mut(), plural.as_str(), api_version_str.as_str(), namespace.is_some()) .await?; let mut curr_resource = { let curr_resource = get_data_from_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), @@ -399,39 +476,64 @@ impl ApiServerService { let patch_json = data; json_patch::merge(&mut curr_resource, &patch_json); let updated = update_data_in_kine( - db_conn, + db_conn.get_mut(), plural.as_str(), name.as_str(), api_version_str.as_str(), namespace.as_deref(), &curr_resource, - ) - .await?; + ).await?; if !updated { return Err(APIServerError::internal_error("在Patch时更新资源失败")); } + watch_event_publisher.publish_update_event(curr_resource.clone()).await; Ok(curr_resource) } } struct ServiceCtx { + db_conn: RefCell, + watch_event_publisher: Arc, api_version_str: String, plural: String, namespace: Option, name: Option, } -impl ApiServerService { - fn prepare_ctx(params: APIServerPathParams, name_required: Option) -> APIServerResult { +impl APIServerService { + fn prepare_ctx(params: APIServerServiceParams, app_state: Arc, name_required: Option) -> APIServerResult { // 获取 path 参数 - let APIServerPathParams { group, version, plural, namespace, name } = params; + let APIServerServiceParams { group, version, plural_or_kind, namespace, name } = params; if name_required.is_some_and(|r| r) && name.is_none() { return Err(APIServerError::bad_request("name参数未指定")); } - if !name_required.is_some_and(|r| !r) && name.is_some() { + if name_required.is_some_and(|r| !r) && name.is_some() { return Err(APIServerError::bad_request("name参数不应指定")); } + let res = app_state.db_pool.get_connection(); + if let Err(e) = res { + log::error!("error getting db conn: {}", e); + return Err(APIServerError::internal_error("DB pool error")); + } + let conn = res.unwrap(); + let watch_event_publisher = app_state.watch_event_publisher.clone(); + let plural_kind_map = MetadataCache::get_map(); + let kind_plural_map = plural_kind_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect::>(); + let plural = match plural_or_kind { + PluralOrKind::Plural(p) => { + p.to_string() + } + PluralOrKind::Kind(k) => { + let plural = kind_plural_map.get(k.as_str()); + if plural.is_none() { + return Err(APIServerError::bad_request("未知的资源类型")); + } + plural.unwrap().clone() + } + }; Ok(ServiceCtx { + db_conn: RefCell::new(conn), + watch_event_publisher, api_version_str: APIVersion { group, version, diff --git a/tests/server_tests.rs b/tests/server_tests.rs index b0131cd..0e0abce 100644 --- a/tests/server_tests.rs +++ b/tests/server_tests.rs @@ -1,60 +1,322 @@ #[cfg(test)] mod tests { use super::*; - use actix_web::{test, web, App, HttpResponse}; + use actix_service::ServiceFactory; + use actix_web::dev::ServiceRequest; + use actix_web::{test, web, App, Error, HttpResponse}; + use anyhow::Result; use dotenv::dotenv; use env_logger::{Builder, Target}; use feventbus::traits::controller::EventBus; - use fleet_apiserver::cores::apiserver::K8sStyleRoute; - use fleet_apiserver::cores::handlers::Handler; + use fleet_apiserver::cores::apiserver::{AppState, K8sStylePathParams, K8sStyleRoute}; + use fleet_apiserver::cores::events::{APIServerEventClient, P2PEventServer}; + use fleet_apiserver::cores::handlers::{APIServerResponse, Handler}; + use fleet_apiserver::cores::services::{APIServerResult, APIServerServiceParams, APIServerServiceParamsBuilder, APIServerStatusCode}; use fleet_apiserver::{prepare_app_state, start_server}; + use fleetmod::pod::Pod; use log::log; use once_cell::sync::Lazy; - use serde_json::json; + use serde_json::{json, Value}; use serial_test::serial; use std::env; use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::thread::spawn; + use tokio::time::sleep; - const ADDRESS: &str = "0.0.0.0:8000"; - const URL_PREFIX: &str = "http://localhost:8000"; - const DATABASE_URL: &str = "DATABASE_URL"; + const DATABASE_URL: &str = "sqlite://./test-database.sqlite"; - async fn setup() { + #[macro_export] + macro_rules! setup_test_app {() => { + { + let app_state = setup().await.unwrap(); + let app = App::new().app_data(web::Data::new(app_state.clone())); + let routes = K8sStyleRoute::get_routes(); + let app = routes.iter().fold(app, |app, route| { + log::info!("register route: {} {}", route.get_method(), route.get_path()); + app.route(route.get_path().as_str(), route.get_web_route()) + }); + let app = app.default_service(web::to(move || async { + log::error!("invalid url"); + Ok::( + HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) + })); + let app = test::init_service(app).await; + (app, app_state) + }}; + } + + async fn setup() -> Result { let mut builder = Builder::from_default_env(); builder.target(Target::Stdout); builder.init(); - dotenv().ok(); - } - - #[actix_web::test] - #[serial] - async fn test_server() { - setup().await; - let database_url = env::var(DATABASE_URL).expect("DATABASE_URL must be set"); - log::info!("Setting DATABASE_URL to {}", &database_url); - let app_state = prepare_app_state(database_url.as_str()).await; + log::info!("Setting DATABASE_URL to {}", DATABASE_URL); + let app_state = prepare_app_state(DATABASE_URL).await; if let Err(e) = app_state { log::error!("Failed to prepare app state: {:?}", e); - return; + return Err(e); } - let app = App::new().app_data(web::Data::new(app_state.unwrap())); - let routes = K8sStyleRoute::get_routes(); - let app = routes.iter().fold(app, |app, route| { - app.route(route.get_path().as_str(), route.get_web_route()) - }); - let app = app.default_service(web::to(move || async { - Ok::( - HttpResponse::NotFound().body("invalid url, see https://gitee.com/iscas-system/apiserver/wikis/pages?sort_id=12661944&doc_id=6135774")) - })); - - let app = test::init_service(app).await; - - let req = test::TestRequest::get().uri("/apis/group1/v1/namespaces/ns1/pods/pod1").to_request(); - let res = test::call_service(&app, req).await; - log::info!("Response: {:?}", test::read_body(res).await); + let app_state = app_state?; + // 启动watch相关事件监听协程 + app_state.clone().watch_event_publisher.start(); + // 启动P2P事件监听协程 + P2PEventServer::new(Arc::from(app_state.clone())).start(); + // 等待启动 + sleep(std::time::Duration::from_secs(1)).await; + Ok(app_state.clone()) + } + + #[tokio::test] + #[serial] + async fn test_pod_basic() { + let (app, app_state) = setup_test_app!(); + + let name = "test-create-pod".to_string(); + let namespace = "ns1".to_string(); + let version = "v1".to_string(); + let plural = "pods".to_string(); + let pod_value = mock_pod(name.clone(), Some(namespace.clone())); + let pod: Pod = serde_json::from_value(pod_value.clone()).unwrap(); + let with_name_params = K8sStylePathParams { group: None, version: version.clone(), namespace: Some(namespace.clone()), name: Some(name.clone()), plural: plural.clone() }; + let without_name_params = K8sStylePathParams { group: None, version: version.clone(), namespace: Some(namespace.clone()), name: None, plural: plural.clone() }; + + // delete it first + let pod_delete_url = get_url(with_name_params.clone()); + log::info!("pod_delete_url: {}", pod_delete_url); + let req = test::TestRequest::delete().uri(pod_delete_url.as_str()).to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("delete res {:?}", res); + + // test create + let pod_create_url = get_url(without_name_params.clone()); + let req = test::TestRequest::post() + .uri(pod_create_url.as_str()) + .set_json(&pod_value) + .to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("create res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::OK); + let pod_from_create = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(pod, pod_from_create); + + // test get one + let pod_getone_url = get_url(with_name_params.clone()); + let req = test::TestRequest::get().uri(pod_getone_url.as_str()).to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("getone res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::OK); + assert!(res.data.is_some()); + let pod_from_getone: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(pod, pod_from_getone); + + // test list + let pod_list_url = get_url(without_name_params.clone()); + let req = test::TestRequest::get().uri(pod_list_url.as_str()).to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("list res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::OK); + assert!(res.data.is_some()); + let pods_from_list: Vec = serde_json::from_value(res.data.unwrap()).unwrap(); + let found = pods_from_list.iter().any(|p| p.metadata.name == name); + assert!(found); + let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == name).unwrap(); + assert_eq!(pod, *pod_from_list); + + // test patch + let pod_patch_url = get_url(with_name_params.clone()); + let req = test::TestRequest::patch() + .uri(pod_patch_url.as_str()) + .set_json(&json!({"metadata": {"labels": {"app": "my-app-patched", "patch-new-key": "patch-new-value"}}})) + .to_request(); + let mut target_pod = pod.clone(); + target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); + target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("patch res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::OK); + let pod_patched: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(target_pod, pod_patched); + + // test delete + let pod_delete_url = get_url(with_name_params.clone()); + let req = test::TestRequest::delete().uri(pod_delete_url.as_str()).to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("delete res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::OK); + let pod_deleted: Pod = serde_json::from_value(res.data.unwrap()).unwrap(); + assert_eq!(target_pod, pod_deleted); + let req = test::TestRequest::get().uri(pod_getone_url.as_str()).to_request(); + let res: APIServerResponse = test::call_and_read_body_json(&app, req).await; + log::info!("get res {:?}", res); + assert_eq!(res.status_code, APIServerStatusCode::NotFound); + + // test by eventbus + let event_cli = APIServerEventClient::new(app_state.nats_cli, None); + let with_name_params = APIServerServiceParams::from(with_name_params.clone()); + let without_name_params = APIServerServiceParams::from(without_name_params.clone()); + + // test create + let res = event_cli + .create_by_resource(pod.clone()) + .await; + log::info!("create res {:?}", res); + assert_eq!(pod, res.ok().unwrap()); + + // test get one + let res = event_cli + .get(with_name_params.clone(), Value::Null) + .await; + log::info!("getone res {:?}", res); + assert!(res.is_ok()); + assert_eq!(pod, res.ok().unwrap()); + + // test list + let res = event_cli + .get(without_name_params.clone(), Value::Null) + .await; + log::info!("list res {:?}", res); + assert!(res.is_ok()); + let pods_from_list: Vec = res.ok().unwrap(); + let found = pods_from_list.iter().any(|p| p.metadata.name == name); + assert!(found); + let pod_from_list = pods_from_list.iter().find(|p| p.metadata.name == name).unwrap(); + assert_eq!(pod, *pod_from_list); + + // test patch + let mut target_pod = pod.clone(); + target_pod.metadata.labels.as_mut().unwrap().insert("app".to_string(), "my-app-patched".to_string()); + target_pod.metadata.labels.as_mut().unwrap().insert("patch-new-key".to_string(), "patch-new-value".to_string()); + let res = event_cli + .patch(with_name_params.clone(), json!({"metadata": {"labels": {"app": "my-app-patched", "patch-new-key": "patch-new-value"}}})).await; + log::info!("patch res {:?}", res); + assert!(res.is_ok()); + assert_eq!(target_pod, res.ok().unwrap()); + + // test delete + let res = event_cli + .delete(with_name_params.clone()) + .await; + log::info!("delete res {:?}", res); + assert_eq!(target_pod, res.ok().unwrap()); + let res: APIServerResult = event_cli + .get(with_name_params.clone(), Value::Null) + .await; + log::info!("get res {:?}", res); + assert!(res.is_err()); + assert_eq!(res.err().unwrap().status_code, APIServerStatusCode::NotFound); + } + + fn get_url(params: K8sStylePathParams) -> String { + let with_group = params.group.is_some(); + let with_namespace = params.namespace.is_some(); + let with_name = params.name.is_some(); + let prefix = if with_group { + format!("/apis/{}/", params.group.unwrap()) + } else { + "/api/".to_string() + }; + let version_and_namespace = if with_namespace { + format!("{}/namespaces/{}/", params.version, params.namespace.unwrap()) + } else { + format!("{}/", params.version) + }; + let plural_and_name = if with_name { + format!("{}/{}", params.plural, params.name.unwrap()) + } else { + format!("{}", params.plural) + }; + format!("{}{}{}", prefix, version_and_namespace, plural_and_name) + } + + fn mock_pod(name: String, namespace: Option) -> Value { + let pod_yaml = r#" +apiVersion: v1 +kind: Pod +metadata: + name: example-pod + namespace: default + labels: + app: my-app + creationTimestamp: 2024-12-10T08:00:00+08:00 +spec: + nodeName: my-node + hostname: my-hostname + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "my-local-host" + - "another-host" + containers: + - name: example-container + image: example-image:latest + imagePullPolicy: IfNotPresent + command: ["nginx"] + args: ["-g", "daemon off;"] + workingDir: /usr/share/nginx/html + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + env: + - name: ENV_MODE + value: production + - name: ENV_VERSION + valueFrom: + fieldRef: + fieldPath: metadata.name + resources: + requests: + memory: "128Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "1" + volumeMounts: + - name: config-volume + mountPath: /etc/nginx/conf.d + readOnly: true + - name: data-volume + mountPath: /usr/share/nginx/html + - name: data-host-volume + mountPath: /usr/share/nginx/a.txt + volumeDevices: + - name: device-volume + devicePath: /dev/sdb + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + readOnlyRootFilesystem: true + allowPrivilegeEscalation: true + privileged: true + volumes: + - name: example-volume + configMap: + name: nginx-config + - name: data-volume + emptyDir: {} + - name: device-volume + hostPath: + path: /dev/sdb + type: Directory + - name: device-volume + hostPath: + path: /dev/sdb + type: Directory +status: + phase: Pending + message: begin handle + podIP: 10.42.0.9 + podIPs: + - ip: 10.42.0.9 +"#; + let mut pod: Pod = serde_yaml::from_str(pod_yaml).expect("Failed to parse YAML"); + pod.metadata.name = name.to_string(); + pod.metadata.namespace = namespace.map(|s| s.to_string()); + serde_json::to_value(pod).unwrap() } } -- Gitee