diff --git a/Cargo.lock b/Cargo.lock index f509f09ec3dee0bbb9b3d6803d7094a369546c9e..8ee884607c2e16f9b3b90b3e6e9932cfaf2bbf80 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", @@ -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]] @@ -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", @@ -356,7 +356,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -367,9 +367,15 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "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" @@ -668,7 +684,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.89", ] [[package]] @@ -679,7 +695,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 +746,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.89", ] [[package]] @@ -746,7 +775,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -766,7 +795,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 +825,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -816,7 +845,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -899,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" @@ -910,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" @@ -958,9 +1003,11 @@ dependencies = [ "lazy_static", "once_cell", "r2d2", + "reqwest", "schemars", "serde", "serde_json", + "serial_test", "tokio", ] @@ -981,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" @@ -1056,7 +1118,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1135,6 +1197,31 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" @@ -1225,6 +1312,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.7", "http 1.1.0", "http-body", "httparse", @@ -1235,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" @@ -1392,7 +1513,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1436,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1543,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" @@ -1634,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]] @@ -1768,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" @@ -2020,7 +2208,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2079,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", @@ -2134,7 +2330,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2152,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" @@ -2164,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" @@ -2173,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" @@ -2209,7 +2466,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.89", ] [[package]] @@ -2228,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" @@ -2261,7 +2541,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2272,7 +2552,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2304,7 +2584,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2328,6 +2608,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" @@ -2413,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]] @@ -2464,6 +2769,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 +2808,41 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "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]] @@ -2512,7 +2862,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2586,7 +2936,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2597,7 +2947,17 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "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]] @@ -2606,11 +2966,21 @@ 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" @@ -2839,7 +3209,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -2873,7 +3243,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2983,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" @@ -3088,7 +3467,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3110,7 +3489,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3130,7 +3509,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3151,7 +3530,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3173,7 +3552,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1e3061b018d99dd817f175371e6844b2e50a29a9..1f7f07667c8b9ee1d39fa5b18e44de41f631f2bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,6 @@ 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" +reqwest = "0.12.9" \ No newline at end of file diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index 1a0597a33f92e6c4f648c7ec5e67164958fefdde..7b709af3e646d013329e6c0f3983d58d168d6299 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -1,16 +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 feventbus::traits::controller::EventBus; -use crate::cores::db::{DbPool}; +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(); + + 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, } @@ -22,14 +49,10 @@ 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(); @@ -43,695 +66,236 @@ 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| { - 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_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::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| { + 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_apis_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", )), } } - })); - } + }, + ), + ); } - // 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| { + 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_api_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", )), } } - })); - } 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| { - 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_with_namespace(path, &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); - - 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| { - 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_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() - .getone_apis_with_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", )), } } - })); - } + }, + ), + ); } + 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| { + 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_api_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", )), } } - })); - } 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| { - 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_with_namespace(path, 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(); - - 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| { - let handler = Arc::clone(&handler); - let event_manager = event_manager.clone(); - - async move { - handler - .default() - .watchone_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| { - 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(); + 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 |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_apis_with_namespace(path, data, event_manager) + .watchone_resource(params, data, event_manager) .await } - })); - } + }, + ), + ); } // TODO 与Kubernetes的状态一致 diff --git a/src/cores/checker.rs b/src/cores/checker.rs index ab13881beb46ea7871a81d52ffe50387151c2ad4..e47d0715f0cb4b16bc202ce9a5c6b52c6bb86a24 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/config.rs b/src/cores/config.rs index f741fcaad6e9c7380b723e998dbb51ff7aab1db5..50e3f368bd876c91de2d888de813289631629aad 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 4aca85807939fca3dedb41034df4ba2a33930c9b..e46f02e0ae09b1b1a32c6775cf1eae3d4590fc7f 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -1,17 +1,20 @@ +/** + * 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}; +use std::{time, vec::Vec}; use async_trait::async_trait; -use actix_web::{HttpResponse, Result, web, Error}; -use chrono::Utc; -use diesel::{QueryResult, RunQueryDsl, QueryDsl, ExpressionMethods, OptionalExtension, QueryableByName, PgConnection, sql_query, SqliteConnection, Connection}; +use actix_web::{HttpResponse, Result, web}; 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 actix_web::web::{Path, Query}; use feventbus::impls::nats::nats::NatsCli; use feventbus::message::Message; use feventbus::message; @@ -19,11 +22,15 @@ 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(); - 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) @@ -43,248 +50,60 @@ fn get_value(key: &str) -> Option { #[async_trait] pub trait Handler { - // /api/{version}/{plural} - async fn create_api_without_namespace( - &self, - info: web::Path<(String, String)>, - 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( + async fn create_resource( &self, - info: web::Path<(String, String, String, String)>, + params: HashMap, 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; // 不满足以上请求路径的处理 @@ -302,3680 +121,658 @@ 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)?; +// 发送请求并等待响应 +async fn send_request( + message: Message, + nats_cli: Arc, +) -> std::result::Result<(), Box> +where + T: Serialize + for<'de> Deserialize<'de> + Debug + Clone + Send + Sync + 'static, +{ + 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 { - 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)?; + Ok(()) } } + Err(e) => { + Err(Box::new(e) as Box) + } } - 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(()) +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ApiServerMessage { + pub content: Value, } - -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(()) +// watch的事件类型 +#[derive(Clone, Debug, Serialize)] +pub enum EventType { + Create, + Update, + Delete, } - -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(()) +// 事件结构,包含事件类型和相关信息 +#[derive(Clone, Debug, Serialize)] +pub struct Event { + pub event_type: EventType, + pub message: Value } -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(()) +// 事件管理器,管理不同资源类型的事件频道 +#[derive(Clone)] +pub struct EventManager { + // 使用 HashMap 来管理不同资源类型的广播通道 + resource_channels: Arc>>>, } +impl EventManager { + pub fn new() -> Self { + EventManager { + resource_channels: Arc::new(Mutex::new(HashMap::new())), + } + } -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)?; + // 获取资源类型对应的广播通道 + 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() } - Ok(()) -} + // 向指定资源类型广播事件 + pub async fn send_event(&self, resource_type: &str, event_type: EventType, message: Value) { + let channel = self.get_channel(resource_type); -// 在 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(()) + // 创建一个事件并发送到广播通道 + let event = Event { + event_type, + message, + }; + let _ = channel.send(event); // 发送事件 + } } -// 从 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 - ), - } +#[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 { - 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 - ), - } + version.to_string() }; - // 执行删除 - 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)? - } - } - }; + 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"); - total_rows_affected += rows_affected; - } + 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"); - // 如果至少有两个表进行了删除,则返回 true - Ok(total_rows_affected > 1) -} + 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)?; -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 - ), - } + insert_key_value(item_kind, kind_upper); } 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 - ), - } - }; + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; - // 执行更新操作 - 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)? - } + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" + }))); } - }; - 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 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; - } - } - // 筛选出出现次数大于等于 2 的数据 - let filtered_results: Vec = unique_results - .into_iter() - .filter(|(_, count)| *count >= 2) // 过滤条件 - .map(|(data, _)| data) // 提取数据部分 - .collect(); - - Ok(filtered_results) -} - - - - -// 发送请求并等待响应 -async fn send_request( - message: Message, - nats_cli: Arc, -) -> std::result::Result<(), Box> -where - T: Serialize + for<'de> Deserialize<'de> + Debug + Clone + Send + Sync + 'static, -{ - 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) - } - } -} - -#[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>>>, -} - -impl EventManager { - pub fn new() -> Self { - EventManager { - resource_channels: Arc::new(Mutex::new(HashMap::new())), - } - } - - // 获取资源类型对应的广播通道 - 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 { - - // /api/{version}/{plural} - async fn create_api_without_namespace( - &self, - info: web::Path<(String, String)>, - 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(); - - //判断是正常的插入函数还是 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,false).await.map_err(ErrorInternalServerError)?; - - if metadata_exists { - return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); - } - - insert_metadata(db_connection, item_kind, &version, false, &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)?; - - 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, &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); - - - // 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!("{}/{}", 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; - - insert_kine(db_connection, &plural, item_name, &data, &version, None) - .await - .map_err(ErrorInternalServerError)?; - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /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 { - 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); - } else { - // 调用check_metadata函数 - 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" }))); - } - - // 从 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, 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!("{}/{}/{}", version, namespace, plural); - let watchone_resource_type = format!("{}/{}/{}/{}", version, 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, &version, Some(&namespace)) - .await - .map_err(ErrorInternalServerError)?; - } - - Ok(HttpResponse::Ok().json(data)) - } - - // /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 { - let (group, version, 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, 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); - } else { - // 调用check_metadata函数 - 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" }))); - } - - // 从 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, 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); - - // 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 request_data = json!({ - "apiVersion": ver, - "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!("{}/{}/{}", 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) - .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 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" }))); - } - - 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": "指定数据不存在" }))) - } - - // /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 { - 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" }))); - } - - if let Some(data) = get_data_from_kine(db_connection, &plural, &name, &ver, 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": "指定数据不存在" }))) - } - - // /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)) - } - - // /apis/{group}/{version}/{plural} - async fn listall_apis_without_namespace( - &self, - info: web::Path<(String, String, String)>, - _data: web::Query, - db_connection: &mut DbConnection, - _nats_cli: Arc, - ) -> Result { - let (group, version, plural) = 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 data_list = get_all_data_from_kine(db_connection, &plural, &ver, 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)) - } - - // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn listall_apis_with_namespace( - &self, - info: web::Path<(String, String, String, String)>, - _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 - ) -> 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - 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()); - } - } - } - }; - - Ok(HttpResponse::Ok() - .content_type("text/event-stream") - .streaming(stream)) - } - - - fn default(&self) -> 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; - - // 使用 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] - async fn test_api_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - - let app = test::init_service( - App::new() - .route("/api/{version}/namespaces/{namespace}/{plural}", web::post().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.create_api_with_namespace(path, data, &mut conn, nats_cli).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); - 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.update_api_with_namespace(path, data, &mut conn, nats_cli).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); - move |path| { - 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.delete_api_with_namespace(path, &mut conn, nats_cli).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] - async fn test_api_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - - let app = test::init_service( - App::new() - .route("/api/{version}/{plural}", web::post().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.create_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::put().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.update_api_without_namespace(path, data, &mut conn, nats_cli).await, - Err(_) => Err(ErrorInternalServerError("Failed to get database connection")), - } - } - } - })) - .route("/api/{version}/{plural}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path| { - 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.delete_api_without_namespace(path, &mut conn, nats_cli).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; + // 检查资源是否已存在 + let item_name = data + .get("metadata") + .and_then(|metadata| metadata.get("name")) + .and_then(|name| name.as_str()) + .unwrap_or("error"); - assert!(delete_resp.status().is_success()); + let kine_exists = check_kine( + db_connection, + plural, + item_name, + &ver, + namespace, + ) + .await + .map_err(ErrorInternalServerError)?; - let list_req_data2 = json!([ - { - "apiVersion": "v1", - "kind": "Node", - "metadata": { - "name": "node-case-02", - }, - "spec": { - "containers": [ - { - "name": "node-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] + if kine_exists { + return Ok(HttpResponse::InternalServerError().json(json!({ + "error": "该资源已存在,请勿重复创建" + }))); } - }]); - let list_req2 = test::TestRequest::get() - .uri("/api/v1/nodes") - .to_request(); - let list_resp2 = test::call_service(&app, list_req2).await; + let mut model_map = HashMap::new(); + let kind = get_value(plural).unwrap(); + model_map.insert("MODEL".to_string(), kind); - assert!(list_resp2.status().is_success()); + // 创建请求消息 + let request_message = Message::new( + "CREATE".to_string(), + message::NativeEventAction::Other, + Some(model_map), + Some(ApiServerMessage { + content: data.clone(), + }), + None, + ); - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); + 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 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" + }))); + } - #[actix_web::test] - async fn test_apis_with_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwrap()); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/namespaces/{namespace}/{plural}", web::post().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.create_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}/{name}", web::put().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.update_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}/{name}", web::delete().to({ - let nats_cli = Arc::clone(&nats_cli); - - move |path| { - 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.delete_apis_with_namespace(path, &mut conn, nats_cli).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 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 update_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } + let request_data = json!({ + "apiVersion": ver.clone(), + "Namespaces": namespace.unwrap_or(""), + "Plural": plural.clone(), + "Name": name.clone(), }); - 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 request_message = Message::new( + "DELETE".to_string(), + message::NativeEventAction::Other, + Some(model_map), + Some(ApiServerMessage { + content: request_data.clone(), + }), + None, + ); - let update_resp_body = test::read_body_json::(update_resp).await; - assert_eq!(update_resp_body, update_req_data); + 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": "指定数据不存在" + }))); + } - let get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-01", - }, - "spec": { - "containers": [ - { - "name": "job-case-01", - "command": [ - "sleep", - "1" - ] - } - ] - } - }); + Ok(HttpResponse::Ok().json(json!({ + "status": "资源删除成功" + }))) + } - let get_req = test::TestRequest::get() - .uri("/apis/batch/v1/namespaces/global/jobs/job-case-01") - .to_request(); + 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 get_resp = test::call_service(&app, get_req).await; + let ver = if let Some(group) = group { + format!("{}/{}", group, version) + } else { + version.to_string() + }; - assert!(get_resp.status().is_success()); + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; - let get_resp_body = test::read_body_json::(get_resp).await; - assert_eq!(get_resp_body, get_req_data); + 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 create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Job", - "metadata": { - "name": "job-case-02", - }, - "spec": { - "containers": [ - { - "name": "job-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } + let request_data = json!({ + "apiVersion": ver.clone(), + "Namespaces": namespace.unwrap_or(""), + "Plural": plural.clone(), + "Name": name.clone(), + "Data": data.clone() }); - 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 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 list_resp2 = test::call_service(&app, list_req2).await; + // 发送事件 + 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) + }; - assert!(list_resp2.status().is_success()); + 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)?; - let list_resp_body2 = test::read_body_json::(list_resp2).await; - assert_eq!(list_resp_body2, list_req_data2); + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "指定数据不存在" + }))); + } + Ok(HttpResponse::Ok().json(data)) } + 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() + }; - #[actix_web::test] - async fn test_crd_and_apis_without_namespace() { - let nats_cli = Arc::new(NatsCli::new().await.unwarp()); - - let app = test::init_service( - App::new() - .route("/apis/{group}/{version}/{plural}", web::post().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.create_apis_without_namespace(path, data, &mut conn, nats_cli).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); - - 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.update_apis_without_namespace(path, data, &mut conn, nats_cli).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); - - move |path| { - 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.delete_apis_without_namespace(path, &mut conn, nats_cli).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); - - + // 检查 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 get_req_data = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-01", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-01", - "command": [ - "sleep", - "1" - ] - } - ] + // 获取资源数据 + 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" + }))), } - }); - - 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); - + } + // 如果资源不存在 + 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() + }; - let create_req_data2 = json!({ - "apiVersion": "batch/v1", - "kind": "Myjob", - "metadata": { - "name": "myjob-case-02", - }, - "spec": { - "containers": [ - { - "name": "myjob-case-02", - "command": [ - "sleep", - "3600" - ] - } - ] - } - }); + // 检查 metadata 是否存在 + let metadata_exists = check_metadata(db_connection, plural, &ver, namespace.is_some()) + .await + .map_err(ErrorInternalServerError)?; - let create_req2 = test::TestRequest::post() - .uri("/apis/batch/v1/myjobs") - .set_json(&create_req_data2) - .to_request(); + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ + "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" + }))); + } - let create_resp2 = test::call_service(&app, create_req2).await; + // 获取资源列表 + let data_list = get_all_data_from_kine(db_connection, plural, &ver, namespace) + .await + .map_err(ErrorInternalServerError)?; - assert!(create_resp2.status().is_success()); + // 将所有字符串解析为 JSON 格式并收集到数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); - let create_resp_body2 = test::read_body_json::(create_resp2).await; - assert_eq!(create_resp_body2, create_req_data2); + // 返回结果 + Ok(HttpResponse::Ok().json(json_array)) + } + 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 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 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 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(); + // 返回 SSE 流 + Ok(HttpResponse::Ok() + .content_type("text/event-stream") + .streaming(stream)) + } - let delete_resp = test::call_service(&app, delete_req).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) + }; - assert!(delete_resp.status().is_success()); + // 订阅事件 + let channel = event_manager.get_channel(&resource_type); + let mut rx = channel.subscribe(); - 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 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 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); + // 返回 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 a34795b8c2f5dec2be64c9063eade14e5abae24e..2a299bc9df107174ec70bfb4aad94410db29dbea 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -1,11 +1,11 @@ /** * 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; 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 0000000000000000000000000000000000000000..c04cf76795074161eeff4cebc2ef46f21d145626 --- /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 97% rename from src/cores/db.rs rename to src/db/db.rs index 6e9fd6adbd82175cc54d2144199bfa68cdb021fc..fb8aa9d4bd5a3437d93b3122554f41812ebe2185 100644 --- a/src/cores/db.rs +++ b/src/db/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; @@ -7,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 { @@ -24,12 +30,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/db/delete.rs b/src/db/delete.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc646b73fb8f52287a977b252529c109ea753c12 --- /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 0000000000000000000000000000000000000000..b07433a5cd8480a4d4397a757a76e6bc5e9c88e8 --- /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 0000000000000000000000000000000000000000..c0b9e3da6cfb2d1e1598fb844ce0520741d9332c --- /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 0000000000000000000000000000000000000000..129c1fdb1efc45f5b022a38c488fee83174daebc --- /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 0000000000000000000000000000000000000000..d957223f23695895b6bab854fb55bbea8664ce4f --- /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 2f3e95c06ca21fcdd490e77673878eafe1fdaa3c..826987fc4f0f4ceec0cefd9726939ffde1f36c2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,47 @@ +/** + * 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; +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; +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 服务器 /// /// # 参数 @@ -25,7 +60,92 @@ 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(); + 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(()) } diff --git a/src/main.rs b/src/main.rs index d20e3b95831f74a016a61a7de2a915196a84d47d..07fc1057378e07fedc5cd49b2451056acfeef8cc 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 new file mode 100644 index 0000000000000000000000000000000000000000..befa9067317853ff28a8511bd646221c1ca20902 --- /dev/null +++ b/tests/route_tests.rs @@ -0,0 +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); + + } +}