From 1b967fb527a3a2c70f5591e2862328ff371de635 Mon Sep 17 00:00:00 2001 From: baojinri Date: Tue, 6 Feb 2024 15:25:15 +0800 Subject: [PATCH 1/6] impl horaectl --- Cargo.lock | 336 +++++++++++++++++++++++---- Cargo.toml | 1 + horaectl/Cargo.toml | 42 ++++ horaectl/src/cmd/cluster.rs | 45 ++++ horaectl/src/cmd/cluster_diagnose.rs | 24 ++ horaectl/src/cmd/cluster_list.rs | 22 ++ horaectl/src/cmd/cluster_schedule.rs | 48 ++++ horaectl/src/cmd/mod.rs | 120 ++++++++++ horaectl/src/cmd/quit.rs | 25 ++ horaectl/src/main.rs | 27 +++ horaectl/src/operation/cluster.rs | 168 ++++++++++++++ horaectl/src/operation/mod.rs | 80 +++++++ horaectl/src/util/mod.rs | 55 +++++ 13 files changed, 943 insertions(+), 50 deletions(-) create mode 100644 horaectl/Cargo.toml create mode 100644 horaectl/src/cmd/cluster.rs create mode 100644 horaectl/src/cmd/cluster_diagnose.rs create mode 100644 horaectl/src/cmd/cluster_list.rs create mode 100644 horaectl/src/cmd/cluster_schedule.rs create mode 100644 horaectl/src/cmd/mod.rs create mode 100644 horaectl/src/cmd/quit.rs create mode 100644 horaectl/src/main.rs create mode 100644 horaectl/src/operation/cluster.rs create mode 100644 horaectl/src/operation/mod.rs create mode 100644 horaectl/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fca1cab21f..4006555dff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1330,9 +1330,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1340,7 +1340,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.1", + "windows-targets 0.52.0", ] [[package]] @@ -1437,6 +1437,18 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "clap_lex" version = "0.7.0" @@ -1571,7 +1583,7 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ - "encode_unicode", + "encode_unicode 0.3.6", "lazy_static", "libc", "windows-sys 0.45.0", @@ -1647,11 +1659,21 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +[[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.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpp_demangle" @@ -2411,6 +2433,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -2573,6 +2601,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.1.0" @@ -3019,6 +3062,21 @@ dependencies = [ "digest", ] +[[package]] +name = "horaectl" +version = "1.2.6-alpha" +dependencies = [ + "chrono", + "clap 4.4.18", + "once_cell", + "package", + "prettytable", + "reqwest", + "serde", + "shell-words", + "tokio", +] + [[package]] name = "horaedb" version = "2.0.0" @@ -3185,15 +3243,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", "http", "hyper", - "rustls 0.20.8", + "rustls 0.21.6", "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls 0.24.1", ] [[package]] @@ -3208,6 +3267,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyperloglog" version = "1.0.2" @@ -3551,9 +3623,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -4204,6 +4276,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "neli" version = "0.6.4" @@ -4574,6 +4664,50 @@ dependencies = [ "tokio", ] +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.3.3", + "cfg-if 1.0.0", + "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.48", +] + +[[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.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.0" @@ -4589,6 +4723,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "package" +version = "0.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f576c01f4729e6e9fdd95c9fe0d9e6be1c66182b8d6b15b830feac95aec402d2" + [[package]] name = "panic_ext" version = "2.0.0" @@ -5084,6 +5224,20 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "prettytable" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +dependencies = [ + "csv", + "encode_unicode 1.0.0", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -5815,9 +5969,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.0", "bytes", @@ -5829,20 +5983,25 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.20.8", + "rustls 0.21.6", "rustls-pemfile 1.0.2", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", - "tokio-rustls 0.23.4", + "tokio-native-tls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -5850,7 +6009,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.22.6", + "webpki-roots 0.25.4", "winreg", ] @@ -6195,6 +6354,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.7" @@ -6251,6 +6419,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -6268,9 +6459,9 @@ checksum = "e6b44e8fc93a14e66336d230954dda83d18b4605ccace8fe09bc7514a71ad0bc" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -6286,9 +6477,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -6432,6 +6623,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -6879,6 +7076,27 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system_catalog" version = "2.0.0" @@ -7209,6 +7427,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -7220,6 +7448,16 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.6", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.25.0" @@ -7872,9 +8110,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7882,24 +8120,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7909,9 +8147,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7919,28 +8157,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-streams" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", @@ -7951,9 +8189,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -7971,21 +8209,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ - "webpki", + "rustls-webpki 0.100.2", ] [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.2", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "which" @@ -8323,11 +8558,12 @@ dependencies = [ [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if 1.0.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ec6fcd4e23..963027e5bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ license = "Apache-2.0" resolver = "2" # In alphabetical order members = [ + "horaectl", "integration_tests", "integration_tests/sdk/rust", "src/analytic_engine", diff --git a/horaectl/Cargo.toml b/horaectl/Cargo.toml new file mode 100644 index 0000000000..033884712a --- /dev/null +++ b/horaectl/Cargo.toml @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "horaectl" + +[package.license] +workspace = true + +[package.version] +workspace = true + +[package.authors] +workspace = true + +[package.edition] +workspace = true + +[dependencies] +chrono = "0.4.33" +clap = { version = "4.4.18", features = ["derive"] } +once_cell = "1.19.0" +package = "0.0.0" +prettytable = "0.10.0" +reqwest = {version = "0.11.24", features = ["json"]} +serde = { version = "1.0.196", features = ["derive"] } +shell-words = "1.1.0" +tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] } \ No newline at end of file diff --git a/horaectl/src/cmd/cluster.rs b/horaectl/src/cmd/cluster.rs new file mode 100644 index 0000000000..b392b6d0ba --- /dev/null +++ b/horaectl/src/cmd/cluster.rs @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::{ArgMatches, Command}; + +use crate::{ + cmd::{ + cluster_diagnose::diagnose, + cluster_list::list, + cluster_schedule::{schedule, schedule_resolve}, + }, + operation::cluster::{clusters_diagnose, clusters_list}, +}; + +pub fn cluster() -> Command { + Command::new("cluster") + .about("Operations on cluster") + .alias("c") + .subcommand(list()) + .subcommand(diagnose()) + .subcommand(schedule()) +} + +pub async fn cluster_resolve(arg_matches: &ArgMatches) { + match arg_matches.subcommand() { + Some(("list", _)) => clusters_list().await, + Some(("diagnose", _)) => clusters_diagnose().await, + Some(("schedule", sub_matches)) => schedule_resolve(sub_matches).await, + _ => {} + } +} diff --git a/horaectl/src/cmd/cluster_diagnose.rs b/horaectl/src/cmd/cluster_diagnose.rs new file mode 100644 index 0000000000..e02dd98327 --- /dev/null +++ b/horaectl/src/cmd/cluster_diagnose.rs @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::Command; + +pub fn diagnose() -> Command { + Command::new("diagnose") + .about("Cluster diagnose") + .alias("d") +} diff --git a/horaectl/src/cmd/cluster_list.rs b/horaectl/src/cmd/cluster_list.rs new file mode 100644 index 0000000000..89f4a9014a --- /dev/null +++ b/horaectl/src/cmd/cluster_list.rs @@ -0,0 +1,22 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::Command; + +pub fn list() -> Command { + Command::new("list").about("Cluster list").alias("l") +} diff --git a/horaectl/src/cmd/cluster_schedule.rs b/horaectl/src/cmd/cluster_schedule.rs new file mode 100644 index 0000000000..4e9d72735c --- /dev/null +++ b/horaectl/src/cmd/cluster_schedule.rs @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::{Arg, ArgMatches, Command}; + +use crate::operation::cluster::{clusters_schedule_get, clusters_schedule_set}; + +pub fn schedule() -> Command { + Command::new("schedule") + .about("Cluster schedule") + .alias("s") + .subcommand(Command::new("get").about("Get the schedule status")) + .subcommand( + Command::new("set").about("Set the schedule status").arg( + Arg::new("enable") + .help("Enable or disable schedule") + .long("enable") + .short('e') + .default_value("false") + .value_parser(clap::value_parser!(bool)), + ), + ) +} + +pub async fn schedule_resolve(arg_matches: &ArgMatches) { + match arg_matches.subcommand() { + Some(("get", _)) => clusters_schedule_get().await, + Some(("set", sub_matches)) => { + let enable = sub_matches.get_one::("enable").copied().unwrap(); + clusters_schedule_set(enable).await; + } + _ => {} + } +} diff --git a/horaectl/src/cmd/mod.rs b/horaectl/src/cmd/mod.rs new file mode 100644 index 0000000000..f7e6850391 --- /dev/null +++ b/horaectl/src/cmd/mod.rs @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +mod cluster; +mod cluster_diagnose; +mod cluster_list; +mod cluster_schedule; +pub mod quit; + +use std::{io, io::Write}; + +use clap::{Arg, Command}; + +use crate::{ + cmd::{ + cluster::{cluster, cluster_resolve}, + quit::quit, + }, + util::{CLUSTER_NAME, META_ADDR}, +}; + +fn cmd() -> Command { + let horaectl = Command::new("horaectl") + .about("horaectl is a command line tool for HoraeDB") + .arg( + Arg::new("meta_addr") + .short('m') + .long("meta") + .value_name("META_ADDR") + .default_value("127.0.0.1:8080") + .help("meta addr is used to connect to meta server"), + ) + .arg( + Arg::new("cluster_name") + .short('c') + .long("cluster") + .value_name("CLUSTER_NAME") + .default_value("defaultCluster") + .help("Cluster to connect to"), + ); + + let matches = horaectl.clone().get_matches(); + META_ADDR + .set(matches.get_one::("meta_addr").unwrap().to_string()) + .unwrap(); + CLUSTER_NAME + .set( + matches + .get_one::("cluster_name") + .unwrap() + .to_string(), + ) + .unwrap(); + + horaectl.subcommand(cluster()).subcommand(quit()) +} + +pub async fn execute() { + let horaectl = cmd(); + loop { + print_prompt(META_ADDR.get().unwrap(), CLUSTER_NAME.get().unwrap()); + + let args = match read_args() { + Ok(args) => args, + Err(e) => { + println!("{}", e); + continue; + } + }; + + match horaectl.clone().try_get_matches_from(args) { + Ok(arg_matches) => match arg_matches.subcommand() { + Some(("quit", _)) => { + println!("bye"); + break; + } + Some(("cluster", sub_matches)) => cluster_resolve(sub_matches).await, + _ => {} + }, + Err(e) => { + println!("{}", e) + } + } + } +} + +fn read_args() -> Result, String> { + io::stdout().flush().unwrap(); + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .map_err(|e| e.to_string())?; + + let input = input.trim(); + if input.is_empty() { + return Err("No arguments provided".into()); + } + + let mut args = vec!["horaectl".to_string()]; + args.extend(shell_words::split(input).map_err(|e| e.to_string())?); + Ok(args) +} + +fn print_prompt(address: &str, cluster: &str) { + print!("{}({}) > ", address, cluster); +} diff --git a/horaectl/src/cmd/quit.rs b/horaectl/src/cmd/quit.rs new file mode 100644 index 0000000000..c5bcf9f094 --- /dev/null +++ b/horaectl/src/cmd/quit.rs @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use clap::Command; + +pub fn quit() -> Command { + Command::new("quit") + .about("Quit horaectl") + .alias("q") + .alias("exit") +} diff --git a/horaectl/src/main.rs b/horaectl/src/main.rs new file mode 100644 index 0000000000..99a7493461 --- /dev/null +++ b/horaectl/src/main.rs @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +mod cmd; +mod operation; +mod util; + +use crate::cmd::execute; + +#[tokio::main] +async fn main() { + execute().await; +} diff --git a/horaectl/src/operation/cluster.rs b/horaectl/src/operation/cluster.rs new file mode 100644 index 0000000000..dc15a3b22d --- /dev/null +++ b/horaectl/src/operation/cluster.rs @@ -0,0 +1,168 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use prettytable::row; + +use crate::{ + operation::{ + ClusterResponse, DiagnoseShardResponse, EnableScheduleRequest, EnableScheduleResponse, + }, + util::{ + format_time_milli, table_writer, API, CLUSTERS, CLUSTERS_DIAGNOSE_HEADER, + CLUSTERS_ENABLE_SCHEDULE_HEADER, CLUSTERS_LIST_HEADER, CLUSTER_NAME, DEBUG, HTTP, + META_ADDR, + }, +}; + +fn list_url() -> String { + HTTP.to_string() + META_ADDR.get().unwrap() + API + CLUSTERS +} + +fn diagnose_url() -> String { + HTTP.to_string() + + META_ADDR.get().unwrap() + + DEBUG + + "/diagnose" + + "/" + + CLUSTER_NAME.get().unwrap() + + "/shards" +} + +fn schedule_url() -> String { + HTTP.to_string() + + META_ADDR.get().unwrap() + + DEBUG + + CLUSTERS + + "/" + + CLUSTER_NAME.get().unwrap() + + "/enableSchedule" +} + +pub async fn clusters_list() { + let res = match reqwest::get(list_url()).await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let response: ClusterResponse = match res.json().await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + + let mut table = table_writer(&CLUSTERS_LIST_HEADER); + for cluster in response.data { + table.add_row(row![ + cluster.id, + cluster.name, + cluster.shard_total.to_string(), + cluster.topology_type, + cluster.procedure_executing_batch_size.to_string(), + format_time_milli(cluster.created_at), + format_time_milli(cluster.modified_at) + ]); + } + table.printstd(); +} + +pub async fn clusters_diagnose() { + let res = match reqwest::get(diagnose_url()).await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let response: DiagnoseShardResponse = match res.json().await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let mut table = table_writer(&CLUSTERS_DIAGNOSE_HEADER); + table.add_row(row![response + .data + .unregistered_shards + .iter() + .map(|shard_id| shard_id.to_string()) + .collect::>() + .join(", ")]); + for (shard_id, data) in response.data.unready_shards { + table.add_row(row!["", shard_id, data.node_name, data.status]); + } + table.printstd(); +} + +pub async fn clusters_schedule_get() { + let res = match reqwest::get(schedule_url()).await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let response: EnableScheduleResponse = match res.json().await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); + let row = match response.data { + Some(data) => row![data], + None => row!["topology should in dynamic mode"], + }; + table.add_row(row); + table.printstd(); +} + +pub async fn clusters_schedule_set(enable: bool) { + let request = EnableScheduleRequest { enable }; + + let res = match reqwest::Client::new() + .put(schedule_url()) + .json(&request) + .send() + .await + { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let response: EnableScheduleResponse = match res.json().await { + Ok(res) => res, + Err(e) => { + println!("{}", e); + return; + } + }; + let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); + let row = match response.data { + Some(data) => row![data], + None => row!["topology should in dynamic mode"], + }; + table.add_row(row); + table.printstd(); +} diff --git a/horaectl/src/operation/mod.rs b/horaectl/src/operation/mod.rs new file mode 100644 index 0000000000..f872346700 --- /dev/null +++ b/horaectl/src/operation/mod.rs @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +pub mod cluster; + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +#[derive(Deserialize, Debug)] +pub struct Cluster { + #[serde(rename = "ID")] + id: u32, + #[serde(rename = "Name")] + name: String, + #[serde(rename = "ShardTotal")] + shard_total: u32, + #[serde(rename = "TopologyType")] + topology_type: String, + #[serde(rename = "ProcedureExecutingBatchSize")] + procedure_executing_batch_size: u32, + #[serde(rename = "CreatedAt")] + created_at: i64, + #[serde(rename = "ModifiedAt")] + modified_at: i64, +} + +#[derive(Deserialize, Debug)] +pub struct ClusterResponse { + #[allow(unused)] + status: String, + data: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct DiagnoseShardStatus { + #[serde(rename = "nodeName")] + node_name: String, + status: String, +} + +#[derive(Deserialize, Debug)] +pub struct DiagnoseShard { + #[serde(rename = "unregisteredShards")] + unregistered_shards: Vec, + #[serde(rename = "unreadyShards")] + unready_shards: HashMap, +} + +#[derive(Deserialize, Debug)] +pub struct DiagnoseShardResponse { + #[allow(unused)] + status: String, + data: DiagnoseShard, +} + +#[derive(Serialize)] +pub struct EnableScheduleRequest { + enable: bool, +} + +#[derive(Deserialize)] +pub struct EnableScheduleResponse { + #[allow(unused)] + status: String, + data: Option, +} diff --git a/horaectl/src/util/mod.rs b/horaectl/src/util/mod.rs new file mode 100644 index 0000000000..0dbc337477 --- /dev/null +++ b/horaectl/src/util/mod.rs @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use chrono::{TimeZone, Utc}; +use once_cell::sync::OnceCell; +use prettytable::{Cell, Row, Table}; + +pub const HTTP: &str = "http://"; +pub const API: &str = "/api/v1"; +pub const DEBUG: &str = "/debug"; +pub const CLUSTERS: &str = "/clusters"; +pub static CLUSTERS_LIST_HEADER: [&str; 7] = [ + "ID", + "Name", + "ShardTotal", + "TopologyType", + "ProcedureExecutingBatchSize", + "CreatedAt", + "ModifiedAt", +]; +pub static CLUSTERS_DIAGNOSE_HEADER: [&str; 4] = [ + "unregistered_shards", + "unready_shards:shard_id", + "unready_shards:node_name", + "unready_shards:status", +]; +pub static CLUSTERS_ENABLE_SCHEDULE_HEADER: [&str; 1] = ["enable_schedule"]; +pub static META_ADDR: OnceCell = OnceCell::new(); +pub static CLUSTER_NAME: OnceCell = OnceCell::new(); + +pub fn table_writer(header: &[&str]) -> Table { + let mut table = Table::new(); + let header_row = Row::from_iter(header.iter().map(|&entry| Cell::new(entry))); + table.add_row(header_row); + table +} + +pub fn format_time_milli(milli: i64) -> String { + let datetime = Utc.timestamp_millis_opt(milli).single().unwrap(); + datetime.format("%Y-%m-%d %H:%M:%S%.3f").to_string() +} From 4adb53b067546fc01386bd194c85814e7846c260 Mon Sep 17 00:00:00 2001 From: baojinri Date: Sun, 18 Feb 2024 11:01:42 +0800 Subject: [PATCH 2/6] fmt --- Cargo.lock | 142 -------------------------------------------- horaectl/Cargo.toml | 16 ++--- 2 files changed, 6 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4006555dff..d289f121a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2601,21 +2601,6 @@ 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.1.0" @@ -3069,7 +3054,6 @@ dependencies = [ "chrono", "clap 4.4.18", "once_cell", - "package", "prettytable", "reqwest", "serde", @@ -3267,19 +3251,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyperloglog" version = "1.0.2" @@ -4276,24 +4247,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "neli" version = "0.6.4" @@ -4664,50 +4617,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "openssl" -version = "0.10.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" -dependencies = [ - "bitflags 2.3.3", - "cfg-if 1.0.0", - "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.48", -] - -[[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.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-float" version = "2.10.0" @@ -4723,12 +4632,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "package" -version = "0.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f576c01f4729e6e9fdd95c9fe0d9e6be1c66182b8d6b15b830feac95aec402d2" - [[package]] name = "panic_ext" version = "2.0.0" @@ -5983,12 +5886,10 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -6000,7 +5901,6 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", "tokio-util", "tower-service", @@ -6354,15 +6254,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scheduled-thread-pool" version = "0.2.7" @@ -6419,29 +6310,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.17" @@ -7427,16 +7295,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.23.4" diff --git a/horaectl/Cargo.toml b/horaectl/Cargo.toml index 033884712a..87699cda47 100644 --- a/horaectl/Cargo.toml +++ b/horaectl/Cargo.toml @@ -24,19 +24,15 @@ workspace = true [package.version] workspace = true -[package.authors] -workspace = true - [package.edition] workspace = true [dependencies] -chrono = "0.4.33" -clap = { version = "4.4.18", features = ["derive"] } -once_cell = "1.19.0" -package = "0.0.0" +chrono = { workspace = true } +clap = { version = "=4.4.18", features = ["derive"] } +once_cell = { workspace = true } prettytable = "0.10.0" -reqwest = {version = "0.11.24", features = ["json"]} -serde = { version = "1.0.196", features = ["derive"] } +reqwest = { workspace = true } +serde = { workspace = true } shell-words = "1.1.0" -tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] } \ No newline at end of file +tokio = { workspace = true } From 56bf68b4fb92ecb720b4635f6540416d3981c0bd Mon Sep 17 00:00:00 2001 From: baojinri Date: Wed, 13 Mar 2024 13:41:33 +0800 Subject: [PATCH 3/6] fix --- Cargo.lock | 26 ++----- Cargo.toml | 2 +- horaectl/Cargo.toml | 4 +- horaectl/src/cmd/cluster.rs | 48 ++++++++----- horaectl/src/cmd/cluster_diagnose.rs | 24 ------- horaectl/src/cmd/cluster_list.rs | 22 ------ horaectl/src/cmd/cluster_schedule.rs | 38 +++++----- horaectl/src/cmd/mod.rs | 104 ++++++++++++++------------- horaectl/src/cmd/quit.rs | 25 ------- horaectl/src/main.rs | 18 ++++- horaectl/src/operation/cluster.rs | 10 +-- horaectl/src/util/mod.rs | 11 ++- 12 files changed, 138 insertions(+), 194 deletions(-) delete mode 100644 horaectl/src/cmd/cluster_diagnose.rs delete mode 100644 horaectl/src/cmd/cluster_list.rs delete mode 100644 horaectl/src/cmd/quit.rs diff --git a/Cargo.lock b/Cargo.lock index d289f121a5..744b8cda0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,9 +1405,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -1415,9 +1415,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -1437,18 +1437,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "clap_derive" -version = "4.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "clap_lex" version = "0.7.0" @@ -3049,11 +3037,11 @@ dependencies = [ [[package]] name = "horaectl" -version = "1.2.6-alpha" +version = "2.0.0" dependencies = [ "chrono", - "clap 4.4.18", - "once_cell", + "clap", + "lazy_static", "prettytable", "reqwest", "serde", diff --git a/Cargo.toml b/Cargo.toml index 963027e5bb..3cf7537512 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ catalog_impls = { path = "src/catalog_impls" } horaedbproto = { git = "https://github.com/apache/incubator-horaedb-proto.git", rev = "19ece8f771fc0b3e8e734072cc3d8040de6c74cb" } codec = { path = "src/components/codec" } chrono = "0.4" -clap = "4.5.1" +clap = { version = "4.5.1", features = ["derive"] } clru = "0.6.1" cluster = { path = "src/cluster" } criterion = "0.5" diff --git a/horaectl/Cargo.toml b/horaectl/Cargo.toml index 87699cda47..1d36f08d78 100644 --- a/horaectl/Cargo.toml +++ b/horaectl/Cargo.toml @@ -29,8 +29,8 @@ workspace = true [dependencies] chrono = { workspace = true } -clap = { version = "=4.4.18", features = ["derive"] } -once_cell = { workspace = true } +clap = { workspace = true } +lazy_static = { workspace = true } prettytable = "0.10.0" reqwest = { workspace = true } serde = { workspace = true } diff --git a/horaectl/src/cmd/cluster.rs b/horaectl/src/cmd/cluster.rs index b392b6d0ba..89453ca9a6 100644 --- a/horaectl/src/cmd/cluster.rs +++ b/horaectl/src/cmd/cluster.rs @@ -15,31 +15,41 @@ // specific language governing permissions and limitations // under the License. -use clap::{ArgMatches, Command}; +use clap::Subcommand; use crate::{ - cmd::{ - cluster_diagnose::diagnose, - cluster_list::list, - cluster_schedule::{schedule, schedule_resolve}, - }, + cmd::cluster_schedule::{schedule_resolve, ScheduleCommands}, operation::cluster::{clusters_diagnose, clusters_list}, + util::CLUSTER_NAME, }; -pub fn cluster() -> Command { - Command::new("cluster") - .about("Operations on cluster") - .alias("c") - .subcommand(list()) - .subcommand(diagnose()) - .subcommand(schedule()) +#[derive(Subcommand)] +pub enum ClusterCommands { + #[clap(about = "Cluster list", long_about = None)] + #[clap(alias = "l")] + List, + + #[clap(about = "Cluster diagnose", long_about = None)] + #[clap(alias = "d")] + Diagnose, + + #[clap(about = "Cluster schedule", long_about = None)] + #[clap(alias = "s")] + Schedule { + #[clap(subcommand)] + commands: Option, + }, } -pub async fn cluster_resolve(arg_matches: &ArgMatches) { - match arg_matches.subcommand() { - Some(("list", _)) => clusters_list().await, - Some(("diagnose", _)) => clusters_diagnose().await, - Some(("schedule", sub_matches)) => schedule_resolve(sub_matches).await, - _ => {} +pub async fn cluster_resolve(cluster_name: Option, command: Option) { + if let Some(name) = cluster_name { + let mut cluster_name = CLUSTER_NAME.lock().unwrap(); + *cluster_name = name; + } + match command { + Some(ClusterCommands::List) => clusters_list().await, + Some(ClusterCommands::Diagnose) => clusters_diagnose().await, + Some(ClusterCommands::Schedule { commands }) => schedule_resolve(commands).await, + None => {} } } diff --git a/horaectl/src/cmd/cluster_diagnose.rs b/horaectl/src/cmd/cluster_diagnose.rs deleted file mode 100644 index e02dd98327..0000000000 --- a/horaectl/src/cmd/cluster_diagnose.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use clap::Command; - -pub fn diagnose() -> Command { - Command::new("diagnose") - .about("Cluster diagnose") - .alias("d") -} diff --git a/horaectl/src/cmd/cluster_list.rs b/horaectl/src/cmd/cluster_list.rs deleted file mode 100644 index 89f4a9014a..0000000000 --- a/horaectl/src/cmd/cluster_list.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use clap::Command; - -pub fn list() -> Command { - Command::new("list").about("Cluster list").alias("l") -} diff --git a/horaectl/src/cmd/cluster_schedule.rs b/horaectl/src/cmd/cluster_schedule.rs index 4e9d72735c..e67aa95e4d 100644 --- a/horaectl/src/cmd/cluster_schedule.rs +++ b/horaectl/src/cmd/cluster_schedule.rs @@ -15,34 +15,28 @@ // specific language governing permissions and limitations // under the License. -use clap::{Arg, ArgMatches, Command}; +use clap::Subcommand; use crate::operation::cluster::{clusters_schedule_get, clusters_schedule_set}; -pub fn schedule() -> Command { - Command::new("schedule") - .about("Cluster schedule") - .alias("s") - .subcommand(Command::new("get").about("Get the schedule status")) - .subcommand( - Command::new("set").about("Set the schedule status").arg( - Arg::new("enable") - .help("Enable or disable schedule") - .long("enable") - .short('e') - .default_value("false") - .value_parser(clap::value_parser!(bool)), - ), - ) +#[derive(Subcommand)] +pub enum ScheduleCommands { + #[clap(about = "Get the schedule status", long_about = None)] + Get, + + #[clap(about = "Set the schedule status", long_about = None)] + Set { + #[clap(long = "enable", short = 'e', default_value = "false", value_parser = clap::value_parser!(bool))] + enable: bool, + }, } -pub async fn schedule_resolve(arg_matches: &ArgMatches) { - match arg_matches.subcommand() { - Some(("get", _)) => clusters_schedule_get().await, - Some(("set", sub_matches)) => { - let enable = sub_matches.get_one::("enable").copied().unwrap(); +pub async fn schedule_resolve(command: Option) { + match command { + Some(ScheduleCommands::Get) => clusters_schedule_get().await, + Some(ScheduleCommands::Set { enable }) => { clusters_schedule_set(enable).await; } - _ => {} + None => {} } } diff --git a/horaectl/src/cmd/mod.rs b/horaectl/src/cmd/mod.rs index f7e6850391..8d67c3f219 100644 --- a/horaectl/src/cmd/mod.rs +++ b/horaectl/src/cmd/mod.rs @@ -16,80 +16,84 @@ // under the License. mod cluster; -mod cluster_diagnose; -mod cluster_list; mod cluster_schedule; -pub mod quit; - use std::{io, io::Write}; -use clap::{Arg, Command}; +use clap::{Parser, Subcommand}; use crate::{ - cmd::{ - cluster::{cluster, cluster_resolve}, - quit::quit, - }, + cmd::cluster::{cluster_resolve, ClusterCommands}, util::{CLUSTER_NAME, META_ADDR}, }; -fn cmd() -> Command { - let horaectl = Command::new("horaectl") - .about("horaectl is a command line tool for HoraeDB") - .arg( - Arg::new("meta_addr") - .short('m') - .long("meta") - .value_name("META_ADDR") - .default_value("127.0.0.1:8080") - .help("meta addr is used to connect to meta server"), - ) - .arg( - Arg::new("cluster_name") - .short('c') - .long("cluster") - .value_name("CLUSTER_NAME") - .default_value("defaultCluster") - .help("Cluster to connect to"), - ); +#[derive(Parser)] +#[clap(name = "horaectl")] +#[clap(about = "horaectl is a command line tool for HoraeDB", long_about = None)] +pub struct Horaectl { + #[clap( + short = 'm', + long = "meta", + default_value = "127.0.0.1:8080", + help = "meta addr is used to connect to meta server" + )] + pub meta_addr: String, + + #[clap( + short = 'c', + long = "cluster", + default_value = "defaultCluster", + help = "Cluster to connect to" + )] + pub cluster_name: String, + + #[clap(subcommand)] + pub commands: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + #[clap(about = "Quit horaectl", long_about = None)] + #[clap(alias = "q")] + #[clap(alias = "exit")] + Quit, + + #[clap(about = "Operations on cluster", long_about = None)] + #[clap(alias = "c")] + Cluster { + #[clap(short = 'c', long = "cluster", help = "Cluster to connect to")] + cluster_name: Option, - let matches = horaectl.clone().get_matches(); - META_ADDR - .set(matches.get_one::("meta_addr").unwrap().to_string()) - .unwrap(); - CLUSTER_NAME - .set( - matches - .get_one::("cluster_name") - .unwrap() - .to_string(), - ) - .unwrap(); - - horaectl.subcommand(cluster()).subcommand(quit()) + #[clap(subcommand)] + commands: Option, + }, } pub async fn execute() { - let horaectl = cmd(); loop { - print_prompt(META_ADDR.get().unwrap(), CLUSTER_NAME.get().unwrap()); + print_prompt( + META_ADDR.lock().unwrap().as_str(), + CLUSTER_NAME.lock().unwrap().as_str(), + ); let args = match read_args() { Ok(args) => args, Err(e) => { - println!("{}", e); + println!("Read input failed, err:{}", e); continue; } }; - match horaectl.clone().try_get_matches_from(args) { - Ok(arg_matches) => match arg_matches.subcommand() { - Some(("quit", _)) => { + match Horaectl::try_parse_from(args) { + Ok(horaectl) => match horaectl.commands { + Some(Commands::Quit) => { println!("bye"); break; } - Some(("cluster", sub_matches)) => cluster_resolve(sub_matches).await, - _ => {} + Some(Commands::Cluster { + cluster_name, + commands, + }) => cluster_resolve(cluster_name, commands).await, + None => {} }, Err(e) => { println!("{}", e) diff --git a/horaectl/src/cmd/quit.rs b/horaectl/src/cmd/quit.rs deleted file mode 100644 index c5bcf9f094..0000000000 --- a/horaectl/src/cmd/quit.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use clap::Command; - -pub fn quit() -> Command { - Command::new("quit") - .about("Quit horaectl") - .alias("q") - .alias("exit") -} diff --git a/horaectl/src/main.rs b/horaectl/src/main.rs index 99a7493461..d3fd939a25 100644 --- a/horaectl/src/main.rs +++ b/horaectl/src/main.rs @@ -19,9 +19,23 @@ mod cmd; mod operation; mod util; -use crate::cmd::execute; +use clap::Parser; + +use crate::{ + cmd::{execute, Horaectl}, + util::{CLUSTER_NAME, META_ADDR}, +}; #[tokio::main] async fn main() { - execute().await; + let horaectl = Horaectl::parse(); + { + let mut meta_addr = META_ADDR.lock().unwrap(); + *meta_addr = horaectl.meta_addr; + } + { + let mut cluster_name = CLUSTER_NAME.lock().unwrap(); + *cluster_name = horaectl.cluster_name; + } + execute().await } diff --git a/horaectl/src/operation/cluster.rs b/horaectl/src/operation/cluster.rs index dc15a3b22d..9b9b2b25ef 100644 --- a/horaectl/src/operation/cluster.rs +++ b/horaectl/src/operation/cluster.rs @@ -29,26 +29,26 @@ use crate::{ }; fn list_url() -> String { - HTTP.to_string() + META_ADDR.get().unwrap() + API + CLUSTERS + HTTP.to_string() + META_ADDR.lock().unwrap().as_str() + API + CLUSTERS } fn diagnose_url() -> String { HTTP.to_string() - + META_ADDR.get().unwrap() + + META_ADDR.lock().unwrap().as_str() + DEBUG + "/diagnose" + "/" - + CLUSTER_NAME.get().unwrap() + + CLUSTER_NAME.lock().unwrap().as_str() + "/shards" } fn schedule_url() -> String { HTTP.to_string() - + META_ADDR.get().unwrap() + + META_ADDR.lock().unwrap().as_str() + DEBUG + CLUSTERS + "/" - + CLUSTER_NAME.get().unwrap() + + CLUSTER_NAME.lock().unwrap().as_str() + "/enableSchedule" } diff --git a/horaectl/src/util/mod.rs b/horaectl/src/util/mod.rs index 0dbc337477..d6a4311365 100644 --- a/horaectl/src/util/mod.rs +++ b/horaectl/src/util/mod.rs @@ -15,10 +15,17 @@ // specific language governing permissions and limitations // under the License. +use std::sync::Mutex; + use chrono::{TimeZone, Utc}; -use once_cell::sync::OnceCell; +use lazy_static::lazy_static; use prettytable::{Cell, Row, Table}; +lazy_static! { + pub static ref META_ADDR: Mutex = Mutex::new(String::new()); + pub static ref CLUSTER_NAME: Mutex = Mutex::new(String::new()); +} + pub const HTTP: &str = "http://"; pub const API: &str = "/api/v1"; pub const DEBUG: &str = "/debug"; @@ -39,8 +46,6 @@ pub static CLUSTERS_DIAGNOSE_HEADER: [&str; 4] = [ "unready_shards:status", ]; pub static CLUSTERS_ENABLE_SCHEDULE_HEADER: [&str; 1] = ["enable_schedule"]; -pub static META_ADDR: OnceCell = OnceCell::new(); -pub static CLUSTER_NAME: OnceCell = OnceCell::new(); pub fn table_writer(header: &[&str]) -> Table { let mut table = Table::new(); From 98b00c2449a77c4aa1caf7c6bd1623f39e2500d7 Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Fri, 15 Mar 2024 15:59:37 +0800 Subject: [PATCH 4/6] refactor --- Cargo.lock | 1 + Cargo.toml | 1 + horaectl/Cargo.toml | 3 +- horaectl/src/cmd/cluster.rs | 28 +++------ horaectl/src/cmd/cluster_schedule.rs | 16 +++-- horaectl/src/cmd/mod.rs | 91 +++++++++++++++++----------- horaectl/src/main.rs | 25 ++++++-- horaectl/src/operation/cluster.rs | 84 +++++++------------------ src/tools/Cargo.toml | 2 +- 9 files changed, 117 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 744b8cda0b..d6c3ead858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3039,6 +3039,7 @@ dependencies = [ name = "horaectl" version = "2.0.0" dependencies = [ + "anyhow", "chrono", "clap", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 3cf7537512..77277e084f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ arrow = { version = "49.0.0", features = ["prettyprint"] } arrow_ipc = { version = "49.0.0" } arrow_ext = { path = "src/components/arrow_ext" } analytic_engine = { path = "src/analytic_engine" } +anyhow = { version = "1.0" } arena = { path = "src/components/arena" } async-stream = "0.3.4" async-trait = "0.1.72" diff --git a/horaectl/Cargo.toml b/horaectl/Cargo.toml index 1d36f08d78..580c0e7946 100644 --- a/horaectl/Cargo.toml +++ b/horaectl/Cargo.toml @@ -28,8 +28,9 @@ workspace = true workspace = true [dependencies] +anyhow = { workspace = true, features = ["backtrace"] } chrono = { workspace = true } -clap = { workspace = true } +clap = { workspace = true, features = ["env", "derive"] } lazy_static = { workspace = true } prettytable = "0.10.0" reqwest = { workspace = true } diff --git a/horaectl/src/cmd/cluster.rs b/horaectl/src/cmd/cluster.rs index 89453ca9a6..66e968b390 100644 --- a/horaectl/src/cmd/cluster.rs +++ b/horaectl/src/cmd/cluster.rs @@ -15,41 +15,33 @@ // specific language governing permissions and limitations // under the License. +use anyhow::Result; use clap::Subcommand; use crate::{ - cmd::cluster_schedule::{schedule_resolve, ScheduleCommands}, + cmd::cluster_schedule::{self, ScheduleCommands}, operation::cluster::{clusters_diagnose, clusters_list}, - util::CLUSTER_NAME, }; #[derive(Subcommand)] pub enum ClusterCommands { - #[clap(about = "Cluster list", long_about = None)] - #[clap(alias = "l")] + /// List cluster List, - #[clap(about = "Cluster diagnose", long_about = None)] - #[clap(alias = "d")] + /// Diagnose cluster Diagnose, - #[clap(about = "Cluster schedule", long_about = None)] - #[clap(alias = "s")] + /// Schedule cluster Schedule { #[clap(subcommand)] - commands: Option, + commands: ScheduleCommands, }, } -pub async fn cluster_resolve(cluster_name: Option, command: Option) { - if let Some(name) = cluster_name { - let mut cluster_name = CLUSTER_NAME.lock().unwrap(); - *cluster_name = name; - } +pub async fn run(command: ClusterCommands) -> Result<()> { match command { - Some(ClusterCommands::List) => clusters_list().await, - Some(ClusterCommands::Diagnose) => clusters_diagnose().await, - Some(ClusterCommands::Schedule { commands }) => schedule_resolve(commands).await, - None => {} + ClusterCommands::List => clusters_list().await, + ClusterCommands::Diagnose => clusters_diagnose().await, + ClusterCommands::Schedule { commands } => cluster_schedule::run(commands).await, } } diff --git a/horaectl/src/cmd/cluster_schedule.rs b/horaectl/src/cmd/cluster_schedule.rs index e67aa95e4d..b79a2dc7fe 100644 --- a/horaectl/src/cmd/cluster_schedule.rs +++ b/horaectl/src/cmd/cluster_schedule.rs @@ -15,28 +15,26 @@ // specific language governing permissions and limitations // under the License. +use anyhow::Result; use clap::Subcommand; use crate::operation::cluster::{clusters_schedule_get, clusters_schedule_set}; #[derive(Subcommand)] pub enum ScheduleCommands { - #[clap(about = "Get the schedule status", long_about = None)] + /// Get the schedule status Get, - #[clap(about = "Set the schedule status", long_about = None)] + /// Set the schedule status Set { - #[clap(long = "enable", short = 'e', default_value = "false", value_parser = clap::value_parser!(bool))] + #[clap(long, short, default_value = "false", value_parser = clap::value_parser!(bool))] enable: bool, }, } -pub async fn schedule_resolve(command: Option) { +pub async fn run(command: ScheduleCommands) -> Result<()> { match command { - Some(ScheduleCommands::Get) => clusters_schedule_get().await, - Some(ScheduleCommands::Set { enable }) => { - clusters_schedule_set(enable).await; - } - None => {} + ScheduleCommands::Get => clusters_schedule_get().await, + ScheduleCommands::Set { enable } => clusters_schedule_set(enable).await, } } diff --git a/horaectl/src/cmd/mod.rs b/horaectl/src/cmd/mod.rs index 8d67c3f219..28a3f19c7b 100644 --- a/horaectl/src/cmd/mod.rs +++ b/horaectl/src/cmd/mod.rs @@ -19,56 +19,69 @@ mod cluster; mod cluster_schedule; use std::{io, io::Write}; -use clap::{Parser, Subcommand}; +use anyhow::Result; +use clap::{Args, Parser, Subcommand}; use crate::{ - cmd::cluster::{cluster_resolve, ClusterCommands}, + cmd::cluster::ClusterCommands, util::{CLUSTER_NAME, META_ADDR}, }; #[derive(Parser)] #[clap(name = "horaectl")] -#[clap(about = "horaectl is a command line tool for HoraeDB", long_about = None)] -pub struct Horaectl { +#[clap(about = "HoraeCTL is a command line tool for HoraeDB", long_about = None)] +pub struct App { + #[clap(flatten)] + pub global_opts: GlobalOpts, + + /// Enter interactive mode + #[clap(short, long, default_value_t = false)] + pub interactive: bool, + + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Debug, Args)] +pub struct GlobalOpts { + /// Meta addr #[clap( - short = 'm', + short, long = "meta", - default_value = "127.0.0.1:8080", - help = "meta addr is used to connect to meta server" + global = true, + env = "HORAECTL_META_ADDR", + default_value = "127.0.0.1:8080" )] pub meta_addr: String, + /// Cluster name #[clap( - short = 'c', + short, long = "cluster", - default_value = "defaultCluster", - help = "Cluster to connect to" + global = true, + env = "HORAECTL_CLUSTER", + default_value = "defaultCluster" )] pub cluster_name: String, - - #[clap(subcommand)] - pub commands: Option, } #[derive(Subcommand)] -pub enum Commands { - #[clap(about = "Quit horaectl", long_about = None)] - #[clap(alias = "q")] - #[clap(alias = "exit")] - Quit, - - #[clap(about = "Operations on cluster", long_about = None)] +pub enum SubCommand { + /// Operations on cluster #[clap(alias = "c")] Cluster { - #[clap(short = 'c', long = "cluster", help = "Cluster to connect to")] - cluster_name: Option, - #[clap(subcommand)] - commands: Option, + commands: ClusterCommands, }, } -pub async fn execute() { +pub async fn run_command(cmd: SubCommand) -> Result<()> { + match cmd { + SubCommand::Cluster { commands } => cluster::run(commands).await, + } +} + +pub async fn repl_loop() { loop { print_prompt( META_ADDR.lock().unwrap().as_str(), @@ -83,20 +96,24 @@ pub async fn execute() { } }; - match Horaectl::try_parse_from(args) { - Ok(horaectl) => match horaectl.commands { - Some(Commands::Quit) => { - println!("bye"); - break; + if let Some(cmd) = args.get(1) { + if ["quit", "exit", "q"].iter().any(|v| v == cmd) { + break; + } + } + + match App::try_parse_from(args) { + Ok(horaectl) => { + if let Some(cmd) = horaectl.command { + if let Err(e) = match cmd { + SubCommand::Cluster { commands } => cluster::run(commands).await, + } { + println!("Run command failed, err:{e}"); + } } - Some(Commands::Cluster { - cluster_name, - commands, - }) => cluster_resolve(cluster_name, commands).await, - None => {} - }, + } Err(e) => { - println!("{}", e) + println!("Parse command failed, err:{e}"); } } } diff --git a/horaectl/src/main.rs b/horaectl/src/main.rs index d3fd939a25..24658ab626 100644 --- a/horaectl/src/main.rs +++ b/horaectl/src/main.rs @@ -19,23 +19,36 @@ mod cmd; mod operation; mod util; -use clap::Parser; +use clap::{CommandFactory, Parser}; use crate::{ - cmd::{execute, Horaectl}, + cmd::{repl_loop, run_command, App}, util::{CLUSTER_NAME, META_ADDR}, }; #[tokio::main] async fn main() { - let horaectl = Horaectl::parse(); + let app = App::parse(); { let mut meta_addr = META_ADDR.lock().unwrap(); - *meta_addr = horaectl.meta_addr; + *meta_addr = app.global_opts.meta_addr; } { let mut cluster_name = CLUSTER_NAME.lock().unwrap(); - *cluster_name = horaectl.cluster_name; + *cluster_name = app.global_opts.cluster_name; + } + + if app.interactive { + repl_loop().await; + return; + } + + if let Some(cmd) = app.command { + if let Err(e) = run_command(cmd).await { + println!("Run command failed, err:{e}"); + std::process::exit(1); + } + } else { + App::command().print_help().expect("print help failed"); } - execute().await } diff --git a/horaectl/src/operation/cluster.rs b/horaectl/src/operation/cluster.rs index 9b9b2b25ef..9f6f81518c 100644 --- a/horaectl/src/operation/cluster.rs +++ b/horaectl/src/operation/cluster.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use anyhow::Result; use prettytable::row; use crate::{ @@ -52,21 +53,9 @@ fn schedule_url() -> String { + "/enableSchedule" } -pub async fn clusters_list() { - let res = match reqwest::get(list_url()).await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; - let response: ClusterResponse = match res.json().await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; +pub async fn clusters_list() -> Result<()> { + let res = reqwest::get(list_url()).await?; + let response: ClusterResponse = res.json().await?; let mut table = table_writer(&CLUSTERS_LIST_HEADER); for cluster in response.data { @@ -81,23 +70,13 @@ pub async fn clusters_list() { ]); } table.printstd(); + + Ok(()) } -pub async fn clusters_diagnose() { - let res = match reqwest::get(diagnose_url()).await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; - let response: DiagnoseShardResponse = match res.json().await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; +pub async fn clusters_diagnose() -> Result<()> { + let res = reqwest::get(diagnose_url()).await?; + let response: DiagnoseShardResponse = res.json().await?; let mut table = table_writer(&CLUSTERS_DIAGNOSE_HEADER); table.add_row(row![response .data @@ -110,23 +89,13 @@ pub async fn clusters_diagnose() { table.add_row(row!["", shard_id, data.node_name, data.status]); } table.printstd(); + + Ok(()) } -pub async fn clusters_schedule_get() { - let res = match reqwest::get(schedule_url()).await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; - let response: EnableScheduleResponse = match res.json().await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; +pub async fn clusters_schedule_get() -> Result<()> { + let res = reqwest::get(schedule_url()).await?; + let response: EnableScheduleResponse = res.json().await?; let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); let row = match response.data { Some(data) => row![data], @@ -134,30 +103,19 @@ pub async fn clusters_schedule_get() { }; table.add_row(row); table.printstd(); + + Ok(()) } -pub async fn clusters_schedule_set(enable: bool) { +pub async fn clusters_schedule_set(enable: bool) -> Result<()> { let request = EnableScheduleRequest { enable }; - let res = match reqwest::Client::new() + let res = reqwest::Client::new() .put(schedule_url()) .json(&request) .send() - .await - { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; - let response: EnableScheduleResponse = match res.json().await { - Ok(res) => res, - Err(e) => { - println!("{}", e); - return; - } - }; + .await?; + let response: EnableScheduleResponse = res.json().await?; let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); let row = match response.data { Some(data) => row![data], @@ -165,4 +123,6 @@ pub async fn clusters_schedule_set(enable: bool) { }; table.add_row(row); table.printstd(); + + Ok(()) } diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index fb3910665e..1a3231cb8f 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -32,7 +32,7 @@ workspace = true [dependencies] analytic_engine = { workspace = true } -anyhow = { version = "1.0", features = ["backtrace"] } +anyhow = { workspace = true, features = ["backtrace"] } clap = { workspace = true, features = ["derive"] } common_types = { workspace = true } futures = { workspace = true } From 71381249d55588dc30d193149e9a633f83fc1c3e Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Fri, 15 Mar 2024 16:29:06 +0800 Subject: [PATCH 5/6] remove unused files --- horaectl/src/cmd/cluster.rs | 41 +++++++++++++++++++++------- horaectl/src/cmd/cluster_schedule.rs | 40 --------------------------- horaectl/src/cmd/mod.rs | 7 ++--- 3 files changed, 34 insertions(+), 54 deletions(-) delete mode 100644 horaectl/src/cmd/cluster_schedule.rs diff --git a/horaectl/src/cmd/cluster.rs b/horaectl/src/cmd/cluster.rs index 66e968b390..9fdad21bd5 100644 --- a/horaectl/src/cmd/cluster.rs +++ b/horaectl/src/cmd/cluster.rs @@ -18,13 +18,12 @@ use anyhow::Result; use clap::Subcommand; -use crate::{ - cmd::cluster_schedule::{self, ScheduleCommands}, - operation::cluster::{clusters_diagnose, clusters_list}, +use crate::operation::cluster::{ + clusters_diagnose, clusters_list, clusters_schedule_get, clusters_schedule_set, }; #[derive(Subcommand)] -pub enum ClusterCommands { +pub enum ClusterCommand { /// List cluster List, @@ -34,14 +33,36 @@ pub enum ClusterCommands { /// Schedule cluster Schedule { #[clap(subcommand)] - commands: ScheduleCommands, + cmd: Option, }, } -pub async fn run(command: ClusterCommands) -> Result<()> { - match command { - ClusterCommands::List => clusters_list().await, - ClusterCommands::Diagnose => clusters_diagnose().await, - ClusterCommands::Schedule { commands } => cluster_schedule::run(commands).await, +#[derive(Subcommand)] +pub enum ScheduleCommand { + /// Get the schedule status + Get, + + /// Enable schedule + On, + + /// Disable schedule + Off, +} + +pub async fn run(cmd: ClusterCommand) -> Result<()> { + match cmd { + ClusterCommand::List => clusters_list().await, + ClusterCommand::Diagnose => clusters_diagnose().await, + ClusterCommand::Schedule { cmd } => { + if let Some(cmd) = cmd { + match cmd { + ScheduleCommand::Get => clusters_schedule_get().await, + ScheduleCommand::On => clusters_schedule_set(true).await, + ScheduleCommand::Off => clusters_schedule_set(false).await, + } + } else { + clusters_schedule_get().await + } + } } } diff --git a/horaectl/src/cmd/cluster_schedule.rs b/horaectl/src/cmd/cluster_schedule.rs deleted file mode 100644 index b79a2dc7fe..0000000000 --- a/horaectl/src/cmd/cluster_schedule.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -use anyhow::Result; -use clap::Subcommand; - -use crate::operation::cluster::{clusters_schedule_get, clusters_schedule_set}; - -#[derive(Subcommand)] -pub enum ScheduleCommands { - /// Get the schedule status - Get, - - /// Set the schedule status - Set { - #[clap(long, short, default_value = "false", value_parser = clap::value_parser!(bool))] - enable: bool, - }, -} - -pub async fn run(command: ScheduleCommands) -> Result<()> { - match command { - ScheduleCommands::Get => clusters_schedule_get().await, - ScheduleCommands::Set { enable } => clusters_schedule_set(enable).await, - } -} diff --git a/horaectl/src/cmd/mod.rs b/horaectl/src/cmd/mod.rs index 28a3f19c7b..5906ef1822 100644 --- a/horaectl/src/cmd/mod.rs +++ b/horaectl/src/cmd/mod.rs @@ -16,20 +16,19 @@ // under the License. mod cluster; -mod cluster_schedule; use std::{io, io::Write}; use anyhow::Result; use clap::{Args, Parser, Subcommand}; use crate::{ - cmd::cluster::ClusterCommands, + cmd::cluster::ClusterCommand, util::{CLUSTER_NAME, META_ADDR}, }; #[derive(Parser)] #[clap(name = "horaectl")] -#[clap(about = "HoraeCTL is a command line tool for HoraeDB", long_about = None)] +#[clap(about = "HoraeCTL is a command line tool for HoraeDB", version)] pub struct App { #[clap(flatten)] pub global_opts: GlobalOpts, @@ -71,7 +70,7 @@ pub enum SubCommand { #[clap(alias = "c")] Cluster { #[clap(subcommand)] - commands: ClusterCommands, + commands: ClusterCommand, }, } From 79e6d1ad9896ce060fa69edcceeb02468feb2acb Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Fri, 15 Mar 2024 16:49:36 +0800 Subject: [PATCH 6/6] wrap http client --- horaectl/src/cmd/cluster.rs | 17 ++-- horaectl/src/operation/cluster.rs | 149 +++++++++++++++++------------- 2 files changed, 92 insertions(+), 74 deletions(-) diff --git a/horaectl/src/cmd/cluster.rs b/horaectl/src/cmd/cluster.rs index 9fdad21bd5..cd22c0ccc3 100644 --- a/horaectl/src/cmd/cluster.rs +++ b/horaectl/src/cmd/cluster.rs @@ -18,9 +18,7 @@ use anyhow::Result; use clap::Subcommand; -use crate::operation::cluster::{ - clusters_diagnose, clusters_list, clusters_schedule_get, clusters_schedule_set, -}; +use crate::operation::cluster::ClusterOp; #[derive(Subcommand)] pub enum ClusterCommand { @@ -50,18 +48,19 @@ pub enum ScheduleCommand { } pub async fn run(cmd: ClusterCommand) -> Result<()> { + let op = ClusterOp::try_new()?; match cmd { - ClusterCommand::List => clusters_list().await, - ClusterCommand::Diagnose => clusters_diagnose().await, + ClusterCommand::List => op.list().await, + ClusterCommand::Diagnose => op.diagnose().await, ClusterCommand::Schedule { cmd } => { if let Some(cmd) = cmd { match cmd { - ScheduleCommand::Get => clusters_schedule_get().await, - ScheduleCommand::On => clusters_schedule_set(true).await, - ScheduleCommand::Off => clusters_schedule_set(false).await, + ScheduleCommand::Get => op.get_schedule_status().await, + ScheduleCommand::On => op.update_schedule_status(true).await, + ScheduleCommand::Off => op.update_schedule_status(false).await, } } else { - clusters_schedule_get().await + op.get_schedule_status().await } } } diff --git a/horaectl/src/operation/cluster.rs b/horaectl/src/operation/cluster.rs index 9f6f81518c..709d44be26 100644 --- a/horaectl/src/operation/cluster.rs +++ b/horaectl/src/operation/cluster.rs @@ -15,8 +15,11 @@ // specific language governing permissions and limitations // under the License. +use std::time::Duration; + use anyhow::Result; use prettytable::row; +use reqwest::Client; use crate::{ operation::{ @@ -53,76 +56,92 @@ fn schedule_url() -> String { + "/enableSchedule" } -pub async fn clusters_list() -> Result<()> { - let res = reqwest::get(list_url()).await?; - let response: ClusterResponse = res.json().await?; - - let mut table = table_writer(&CLUSTERS_LIST_HEADER); - for cluster in response.data { - table.add_row(row![ - cluster.id, - cluster.name, - cluster.shard_total.to_string(), - cluster.topology_type, - cluster.procedure_executing_batch_size.to_string(), - format_time_milli(cluster.created_at), - format_time_milli(cluster.modified_at) - ]); +pub struct ClusterOp { + http_client: Client, +} + +impl ClusterOp { + pub fn try_new() -> Result { + let hc = Client::builder() + .timeout(Duration::from_secs(30)) + .user_agent("horaectl") + .build()?; + + Ok(Self { http_client: hc }) } - table.printstd(); - Ok(()) -} + pub async fn list(&self) -> Result<()> { + let res = self.http_client.get(list_url()).send().await?; + let response: ClusterResponse = res.json().await?; + + let mut table = table_writer(&CLUSTERS_LIST_HEADER); + for cluster in response.data { + table.add_row(row![ + cluster.id, + cluster.name, + cluster.shard_total.to_string(), + cluster.topology_type, + cluster.procedure_executing_batch_size.to_string(), + format_time_milli(cluster.created_at), + format_time_milli(cluster.modified_at) + ]); + } + table.printstd(); -pub async fn clusters_diagnose() -> Result<()> { - let res = reqwest::get(diagnose_url()).await?; - let response: DiagnoseShardResponse = res.json().await?; - let mut table = table_writer(&CLUSTERS_DIAGNOSE_HEADER); - table.add_row(row![response - .data - .unregistered_shards - .iter() - .map(|shard_id| shard_id.to_string()) - .collect::>() - .join(", ")]); - for (shard_id, data) in response.data.unready_shards { - table.add_row(row!["", shard_id, data.node_name, data.status]); + Ok(()) } - table.printstd(); - Ok(()) -} + pub async fn diagnose(&self) -> Result<()> { + let res = self.http_client.get(diagnose_url()).send().await?; + let response: DiagnoseShardResponse = res.json().await?; + let mut table = table_writer(&CLUSTERS_DIAGNOSE_HEADER); + table.add_row(row![response + .data + .unregistered_shards + .iter() + .map(|shard_id| shard_id.to_string()) + .collect::>() + .join(", ")]); + for (shard_id, data) in response.data.unready_shards { + table.add_row(row!["", shard_id, data.node_name, data.status]); + } + table.printstd(); -pub async fn clusters_schedule_get() -> Result<()> { - let res = reqwest::get(schedule_url()).await?; - let response: EnableScheduleResponse = res.json().await?; - let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); - let row = match response.data { - Some(data) => row![data], - None => row!["topology should in dynamic mode"], - }; - table.add_row(row); - table.printstd(); - - Ok(()) -} + Ok(()) + } + + pub async fn get_schedule_status(&self) -> Result<()> { + let res = self.http_client.get(schedule_url()).send().await?; + let response: EnableScheduleResponse = res.json().await?; + let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); + let row = match response.data { + Some(data) => row![data], + None => row!["topology should in dynamic mode"], + }; + table.add_row(row); + table.printstd(); + + Ok(()) + } -pub async fn clusters_schedule_set(enable: bool) -> Result<()> { - let request = EnableScheduleRequest { enable }; - - let res = reqwest::Client::new() - .put(schedule_url()) - .json(&request) - .send() - .await?; - let response: EnableScheduleResponse = res.json().await?; - let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); - let row = match response.data { - Some(data) => row![data], - None => row!["topology should in dynamic mode"], - }; - table.add_row(row); - table.printstd(); - - Ok(()) + pub async fn update_schedule_status(&self, enable: bool) -> Result<()> { + let request = EnableScheduleRequest { enable }; + + let res = self + .http_client + .put(schedule_url()) + .json(&request) + .send() + .await?; + let response: EnableScheduleResponse = res.json().await?; + let mut table = table_writer(&CLUSTERS_ENABLE_SCHEDULE_HEADER); + let row = match response.data { + Some(data) => row![data], + None => row!["topology should in dynamic mode"], + }; + table.add_row(row); + table.printstd(); + + Ok(()) + } }