From 93adfafe60b8f3b473b5097c2b80497abe8f5300 Mon Sep 17 00:00:00 2001 From: Yuhang Wei Date: Thu, 23 Nov 2023 19:49:16 +0800 Subject: [PATCH] feat(Rust os-agent, proxy): add rust version os-agent and proxy To decrease the memory usage, we develop os-agent and proxy in Rust Signed-off-by: Yuhang Wei --- .gitignore | 7 + KubeOS-Rust/Cargo.lock | 2841 +++++++++++++++++ KubeOS-Rust/Cargo.toml | 15 + KubeOS-Rust/agent/Cargo.toml | 20 + KubeOS-Rust/agent/src/function.rs | 36 + KubeOS-Rust/agent/src/main.rs | 75 + KubeOS-Rust/agent/src/rpc/agent.rs | 32 + KubeOS-Rust/agent/src/rpc/agent_impl.rs | 221 ++ KubeOS-Rust/agent/src/rpc/mod.rs | 19 + KubeOS-Rust/cli/Cargo.toml | 15 + KubeOS-Rust/cli/src/client.rs | 77 + KubeOS-Rust/cli/src/lib.rs | 14 + KubeOS-Rust/cli/src/method/callable_method.rs | 26 + KubeOS-Rust/cli/src/method/cleanup.rs | 34 + KubeOS-Rust/cli/src/method/configure.rs | 41 + KubeOS-Rust/cli/src/method/mod.rs | 19 + KubeOS-Rust/cli/src/method/prepare_upgrade.rs | 41 + KubeOS-Rust/cli/src/method/request.rs | 58 + KubeOS-Rust/cli/src/method/rollback.rs | 34 + KubeOS-Rust/cli/src/method/upgrade.rs | 34 + KubeOS-Rust/manager/Cargo.toml | 22 + KubeOS-Rust/manager/src/api/agent_status.rs | 49 + KubeOS-Rust/manager/src/api/mod.rs | 17 + KubeOS-Rust/manager/src/api/types.rs | 67 + KubeOS-Rust/manager/src/lib.rs | 15 + KubeOS-Rust/manager/src/sys_mgmt/config.rs | 723 +++++ .../manager/src/sys_mgmt/containerd_image.rs | 359 +++ KubeOS-Rust/manager/src/sys_mgmt/mod.rs | 21 + KubeOS-Rust/manager/src/sys_mgmt/values.rs | 35 + KubeOS-Rust/manager/src/utils/common.rs | 312 ++ .../manager/src/utils/container_image.rs | 271 ++ KubeOS-Rust/manager/src/utils/executor.rs | 101 + .../manager/src/utils/image_manager.rs | 260 ++ KubeOS-Rust/manager/src/utils/mod.rs | 23 + KubeOS-Rust/manager/src/utils/partition.rs | 110 + KubeOS-Rust/proxy/Cargo.toml | 31 + KubeOS-Rust/proxy/src/controller/apiclient.rs | 172 + .../proxy/src/controller/controller.rs | 502 +++ KubeOS-Rust/proxy/src/controller/crd.rs | 77 + KubeOS-Rust/proxy/src/controller/drain.rs | 650 ++++ KubeOS-Rust/proxy/src/controller/mod.rs | 23 + KubeOS-Rust/proxy/src/controller/utils.rs | 155 + KubeOS-Rust/proxy/src/controller/values.rs | 49 + KubeOS-Rust/proxy/src/main.rs | 52 + 44 files changed, 7755 insertions(+) create mode 100644 KubeOS-Rust/Cargo.lock create mode 100644 KubeOS-Rust/Cargo.toml create mode 100644 KubeOS-Rust/agent/Cargo.toml create mode 100644 KubeOS-Rust/agent/src/function.rs create mode 100644 KubeOS-Rust/agent/src/main.rs create mode 100644 KubeOS-Rust/agent/src/rpc/agent.rs create mode 100644 KubeOS-Rust/agent/src/rpc/agent_impl.rs create mode 100644 KubeOS-Rust/agent/src/rpc/mod.rs create mode 100644 KubeOS-Rust/cli/Cargo.toml create mode 100644 KubeOS-Rust/cli/src/client.rs create mode 100644 KubeOS-Rust/cli/src/lib.rs create mode 100644 KubeOS-Rust/cli/src/method/callable_method.rs create mode 100644 KubeOS-Rust/cli/src/method/cleanup.rs create mode 100644 KubeOS-Rust/cli/src/method/configure.rs create mode 100644 KubeOS-Rust/cli/src/method/mod.rs create mode 100644 KubeOS-Rust/cli/src/method/prepare_upgrade.rs create mode 100644 KubeOS-Rust/cli/src/method/request.rs create mode 100644 KubeOS-Rust/cli/src/method/rollback.rs create mode 100644 KubeOS-Rust/cli/src/method/upgrade.rs create mode 100644 KubeOS-Rust/manager/Cargo.toml create mode 100644 KubeOS-Rust/manager/src/api/agent_status.rs create mode 100644 KubeOS-Rust/manager/src/api/mod.rs create mode 100644 KubeOS-Rust/manager/src/api/types.rs create mode 100644 KubeOS-Rust/manager/src/lib.rs create mode 100644 KubeOS-Rust/manager/src/sys_mgmt/config.rs create mode 100644 KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs create mode 100644 KubeOS-Rust/manager/src/sys_mgmt/mod.rs create mode 100644 KubeOS-Rust/manager/src/sys_mgmt/values.rs create mode 100644 KubeOS-Rust/manager/src/utils/common.rs create mode 100644 KubeOS-Rust/manager/src/utils/container_image.rs create mode 100644 KubeOS-Rust/manager/src/utils/executor.rs create mode 100644 KubeOS-Rust/manager/src/utils/image_manager.rs create mode 100644 KubeOS-Rust/manager/src/utils/mod.rs create mode 100644 KubeOS-Rust/manager/src/utils/partition.rs create mode 100644 KubeOS-Rust/proxy/Cargo.toml create mode 100644 KubeOS-Rust/proxy/src/controller/apiclient.rs create mode 100644 KubeOS-Rust/proxy/src/controller/controller.rs create mode 100644 KubeOS-Rust/proxy/src/controller/crd.rs create mode 100644 KubeOS-Rust/proxy/src/controller/drain.rs create mode 100644 KubeOS-Rust/proxy/src/controller/mod.rs create mode 100644 KubeOS-Rust/proxy/src/controller/utils.rs create mode 100644 KubeOS-Rust/proxy/src/controller/values.rs create mode 100644 KubeOS-Rust/proxy/src/main.rs diff --git a/.gitignore b/.gitignore index 5e56e040..4d173c5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ +# vscode settings +.vscode + +# rust dependencies +target/ + +# KubeOS bin /bin diff --git a/KubeOS-Rust/Cargo.lock b/KubeOS-Rust/Cargo.lock new file mode 100644 index 00000000..f6906039 --- /dev/null +++ b/KubeOS-Rust/Cargo.lock @@ -0,0 +1,2841 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.10", + "instant", + "rand 0.8.5", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "jsonrpc", + "log", + "manager", + "serde", + "serde_json", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset 0.5.6", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags 1.3.2", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes 1.5.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio 1.14.0", + "tokio-util 0.7.2", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes 1.5.0", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes 1.5.0", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes 1.5.0", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio 1.14.0", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio 1.14.0", + "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 1.5.0", + "hyper", + "native-tls", + "tokio 1.14.0", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonrpc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8d6b3f301ba426b30feca834a2a18d48d5b54e5065496b5c1b05537bee3639" +dependencies = [ + "base64", + "serde", + "serde_json", +] + +[[package]] +name = "jsonrpc-core" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0745a6379e3edc893c84ec203589790774e4247420033e71a76d3ab4687991fa" +dependencies = [ + "futures 0.1.31", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-derive" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a847f9ec7bb52149b2786a17c9cb260d6effc6b8eeb8c16b343a487a7563a3" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpc-ipc-server" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf50e53e4eea8f421a7316c5f63e395f7bc7c4e786a6dc54d76fab6ff7aa7ce7" +dependencies = [ + "jsonrpc-core", + "jsonrpc-server-utils", + "log", + "parity-tokio-ipc", + "parking_lot 0.10.2", + "tokio-service", +] + +[[package]] +name = "jsonrpc-server-utils" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f1f3990650c033bd8f6bd46deac76d990f9bbfb5f8dc8c4767bf0a00392176" +dependencies = [ + "bytes 0.4.12", + "globset", + "jsonrpc-core", + "lazy_static", + "log", + "tokio 0.1.22", + "tokio-codec", + "unicase", +] + +[[package]] +name = "k8s-openapi" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8de9873b904e74b3533f77493731ee26742418077503683db44e1b3c54aa5c" +dependencies = [ + "base64", + "bytes 1.5.0", + "chrono", + "http", + "percent-encoding", + "serde", + "serde-value", + "serde_json", + "url", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "kube" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4b96944d327b752df4f62f3a31d8694892af06fb585747c0b5e664927823d1a" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232db1af3d3680f9289cf0b4db51b2b9fee22550fc65d25869e39b23e0aaa696" +dependencies = [ + "base64", + "bytes 1.5.0", + "chrono", + "dirs-next", + "either", + "futures 0.3.29", + "http", + "http-body", + "hyper", + "hyper-timeout", + "hyper-tls", + "jsonpath_lib", + "k8s-openapi", + "kube-core", + "openssl", + "pem", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio 1.14.0", + "tokio-native-tls", + "tokio-util 0.6.10", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de491f8c9ee97117e0b47a629753e939c2392d5d0a40f6928e582a5fba328098" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "once_cell", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbb86bb3607245a67c8ad3a52aff41108f36b0d1e9e3e82ffb5760bfd84b965" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "kube-runtime" +version = "0.66.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710729592eb30219b4e84898e91dc991fe09ccafe2c17fec4e45c3426c61abe0" +dependencies = [ + "backoff", + "dashmap", + "derivative", + "futures 0.3.29", + "json-patch", + "k8s-openapi", + "kube-client", + "pin-project", + "serde", + "serde_json", + "smallvec 1.11.1", + "thiserror", + "tokio 1.14.0", + "tokio-util 0.6.10", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4dcd960cc540667f619483fc99102f88d6118b87730e24e8fbe8054b7445e4" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "manager" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "lazy_static", + "log", + "mockall", + "nix", + "predicates", + "regex", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log", + "mio 0.6.23", + "miow 0.3.7", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "mockall" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "net2" +version = "0.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.0", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.3", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "openssl" +version = "0.10.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +dependencies = [ + "bitflags 2.4.0", + "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.37", +] + +[[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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os-agent" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "jsonrpc-core", + "jsonrpc-derive", + "jsonrpc-ipc-server", + "lazy_static", + "log", + "manager", + "nix", + "serde", + "serde_json", +] + +[[package]] +name = "parity-tokio-ipc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e57fea504fea33f9fbb5f49f378359030e7e026a6ab849bb9e8f0787376f1bf" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "libc", + "log", + "mio-named-pipes", + "miow 0.3.7", + "rand 0.7.3", + "tokio 0.1.22", + "tokio-named-pipes", + "tokio-uds", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core 0.6.3", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core 0.7.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "rustc_version", + "smallvec 0.6.14", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93f386bb233083c799e6e642a9d73db98c24a5deeb95ffc85bf281255dffc98" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "smallvec 1.11.1", + "winapi 0.3.9", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "predicates" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3d91237f5de3bcd9d927e24d03b495adb6135097b001cea7403e2d573d00a9" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "cli", + "env_logger", + "futures 0.3.29", + "h2", + "k8s-openapi", + "kube", + "log", + "manager", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "snafu", + "socket2", + "thiserror", + "thread_local", + "tokio 1.14.0", + "tokio-retry", + "tracing", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom 0.2.10", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64", + "bytes 1.5.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio 1.14.0", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +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 = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[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.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "mio 0.6.23", + "num_cpus", + "tokio-codec", + "tokio-current-thread", + "tokio-executor", + "tokio-fs", + "tokio-io", + "tokio-reactor", + "tokio-sync", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer", + "tokio-udp", + "tokio-uds", +] + +[[package]] +name = "tokio" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" +dependencies = [ + "autocfg", + "bytes 1.5.0", + "libc", + "memchr", + "mio 0.7.14", + "num_cpus", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-codec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "tokio-io", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" +dependencies = [ + "futures 0.1.31", + "tokio-executor", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils", + "futures 0.1.31", +] + +[[package]] +name = "tokio-fs" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" +dependencies = [ + "futures 0.1.31", + "tokio-io", + "tokio-threadpool", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio 1.14.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tokio-named-pipes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d282d483052288b2308ba5ee795f5673b159c9bdf63c385a05609da782a5eae" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "mio 0.6.23", + "mio-named-pipes", + "tokio 0.1.22", +] + +[[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 1.14.0", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils", + "futures 0.1.31", + "lazy_static", + "log", + "mio 0.6.23", + "num_cpus", + "parking_lot 0.9.0", + "slab", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio 1.14.0", +] + +[[package]] +name = "tokio-service" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +dependencies = [ + "futures 0.1.31", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures 0.1.31", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "mio 0.6.23", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures 0.1.31", + "lazy_static", + "log", + "num_cpus", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-timer" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils", + "futures 0.1.31", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-udp" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", + "mio 0.6.23", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-uds" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "libc", + "log", + "mio 0.6.23", + "mio-uds", + "tokio-codec", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes 1.5.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "slab", + "tokio 1.14.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes 1.5.0", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio 1.14.0", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio 1.14.0", + "tokio-util 0.7.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8" +dependencies = [ + "base64", + "bitflags 1.3.2", + "bytes 1.5.0", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "web-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/KubeOS-Rust/Cargo.toml b/KubeOS-Rust/Cargo.toml new file mode 100644 index 00000000..64ad4be8 --- /dev/null +++ b/KubeOS-Rust/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +members = [ + "manager", + "agent", + "cli", + "proxy", +] + +[profile.release] +opt-level = 's' +debug = false +rpath = false +debug-assertions = false +overflow-checks = false +lto = true diff --git a/KubeOS-Rust/agent/Cargo.toml b/KubeOS-Rust/agent/Cargo.toml new file mode 100644 index 00000000..fd7d3905 --- /dev/null +++ b/KubeOS-Rust/agent/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "os-agent" +version = "0.1.0" +edition = "2021" +description = "KubeOS os-agent" +license = "MulanPSL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +manager = { package = "manager", path = "../manager" } +jsonrpc-core = { version = "15.1" } +jsonrpc-derive = { version = "15.1" } +jsonrpc-ipc-server = { version = "15.1" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +log = { version = "= 0.4.15" } +anyhow = { version = "1.0" } +env_logger = { version = "0.9" } +lazy_static = { version = "1.4" } +nix = { version = "0.26.2" } diff --git a/KubeOS-Rust/agent/src/function.rs b/KubeOS-Rust/agent/src/function.rs new file mode 100644 index 00000000..8fb8a4e3 --- /dev/null +++ b/KubeOS-Rust/agent/src/function.rs @@ -0,0 +1,36 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub use jsonrpc_core::Result as RpcResult; +use jsonrpc_core::{Error, ErrorCode}; +pub use jsonrpc_derive::rpc; +use log::error; + +const RPC_OP_ERROR: i64 = -1; + +pub struct RpcFunction; + +impl RpcFunction { + pub fn call(f: F) -> RpcResult + where + F: FnOnce() -> anyhow::Result, + { + (f)().map_err(|e| { + error!("{:?}", e); + Error { + code: ErrorCode::ServerError(RPC_OP_ERROR), + message: format!("{:?}", e), + data: None, + } + }) + } +} diff --git a/KubeOS-Rust/agent/src/main.rs b/KubeOS-Rust/agent/src/main.rs new file mode 100644 index 00000000..2201c202 --- /dev/null +++ b/KubeOS-Rust/agent/src/main.rs @@ -0,0 +1,75 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + fs::{self, DirBuilder, Permissions}, + os::unix::fs::{DirBuilderExt, PermissionsExt}, + path::Path, +}; + +use env_logger::{Builder, Env, Target}; +use jsonrpc_core::{IoHandler, IoHandlerExtension}; +use jsonrpc_ipc_server::ServerBuilder; + +mod function; +mod rpc; + +use log::info; +use rpc::{Agent, AgentImpl}; + +const SOCK_PATH: &str = "/run/os-agent/os-agent.sock"; +const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); + +fn start_and_run(sock_path: &str) { + let socket_path = Path::new(sock_path); + + // Create directory for socket if it doesn't exist + if let Some(dir_path) = socket_path.parent() { + if !dir_path.exists() { + DirBuilder::new() + .mode(0o750) + .create(dir_path) + .expect("Couldn't create directory for socket"); + } + } + + // Add RPC methods to IoHandler + let mut io = IoHandler::new(); + AgentImpl::default().to_delegate().augment(&mut io); + + // Build and start server + let builder = ServerBuilder::new(io); + let server = builder.start(sock_path).expect("Couldn't open socket"); + + let gid = nix::unistd::getgid(); + nix::unistd::chown(socket_path, Some(nix::unistd::ROOT), Some(gid)) + .expect("Couldn't set socket group"); + + // Set socket permissions to 0640 + let socket_permissions = Permissions::from_mode(0o640); + fs::set_permissions(socket_path, socket_permissions).expect("Couldn't set socket permissions"); + + info!("os-agent started, waiting for requests..."); + server.wait(); +} + +fn main() { + Builder::from_env(Env::default().default_filter_or("info")) + .target(Target::Stdout) + .init(); + + info!( + "os-agent version is: {}", + CARGO_PKG_VERSION.unwrap_or("NOT FOUND") + ); + start_and_run(SOCK_PATH); +} diff --git a/KubeOS-Rust/agent/src/rpc/agent.rs b/KubeOS-Rust/agent/src/rpc/agent.rs new file mode 100644 index 00000000..97eb4566 --- /dev/null +++ b/KubeOS-Rust/agent/src/rpc/agent.rs @@ -0,0 +1,32 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::function::{rpc, RpcResult}; +use manager::api::{ConfigureRequest, Response, UpgradeRequest}; + +#[rpc(server)] +pub trait Agent { + #[rpc(name = "prepare_upgrade")] + fn prepare_upgrade(&self, req: UpgradeRequest) -> RpcResult; + + #[rpc(name = "upgrade")] + fn upgrade(&self) -> RpcResult; + + #[rpc(name = "cleanup")] + fn cleanup(&self) -> RpcResult; + + #[rpc(name = "configure")] + fn configure(&self, req: ConfigureRequest) -> RpcResult; + + #[rpc(name = "rollback")] + fn rollback(&self) -> RpcResult; +} diff --git a/KubeOS-Rust/agent/src/rpc/agent_impl.rs b/KubeOS-Rust/agent/src/rpc/agent_impl.rs new file mode 100644 index 00000000..c752ffac --- /dev/null +++ b/KubeOS-Rust/agent/src/rpc/agent_impl.rs @@ -0,0 +1,221 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{sync::Mutex, thread, time::Duration}; + +use anyhow::{anyhow, Result}; +use log::{debug, error, info}; +use nix::{sys::reboot::RebootMode, unistd::sync}; + +use super::{ + agent::Agent, + function::{RpcFunction, RpcResult}, +}; +use manager::{ + api::{AgentStatus, ConfigureRequest, ImageType, Response, UpgradeRequest}, + sys_mgmt::{CtrImageHandler, CONFIG_TEMPLATE}, + utils::{ + clean_env, get_partition_info, switch_boot_menuentry, PreparePath, RealCommandExecutor, + UpgradeImageManager, + }, +}; + +pub struct AgentImpl { + mutex: Mutex<()>, + disable_reboot: bool, +} + +impl Agent for AgentImpl { + fn prepare_upgrade(&self, req: UpgradeRequest) -> RpcResult { + RpcFunction::call(|| self.prepare_upgrade_impl(req)) + } + + fn upgrade(&self) -> RpcResult { + RpcFunction::call(|| self.upgrade_impl()) + } + + fn cleanup(&self) -> RpcResult { + RpcFunction::call(|| self.cleanup_impl()) + } + + fn configure(&self, req: ConfigureRequest) -> RpcResult { + RpcFunction::call(|| self.configure_impl(req)) + } + + fn rollback(&self) -> RpcResult { + RpcFunction::call(|| self.rollback_impl()) + } +} + +impl Default for AgentImpl { + fn default() -> Self { + Self { + mutex: Mutex::new(()), + disable_reboot: false, + } + } +} + +impl AgentImpl { + pub fn prepare_upgrade_impl(&self, req: UpgradeRequest) -> Result { + let _lock = self.mutex.lock().unwrap(); + debug!("Received an 'prepare upgrade' request: {:?}", req); + info!("Start to upgrade to version: {}", req.version); + + let handler: Box> = match req.image_type.as_str() { + "containerd" => Box::new(ImageType::Containerd(CtrImageHandler::default())), + _ => return Err(anyhow!("Invalid image type \"{}\"", req.image_type)), + }; + + let image_manager = handler.download_image(&req)?; + info!( + "Ready to install image: {:?}", + image_manager.paths.image_path.display() + ); + + Ok(Response { + status: AgentStatus::UpgradeReady, + }) + } + + pub fn upgrade_impl(&self) -> Result { + let _lock = self.mutex.lock().unwrap(); + info!("Start to upgrade"); + let command_executor = RealCommandExecutor {}; + let (_, next_partition_info) = get_partition_info(&command_executor)?; + let image_manager = UpgradeImageManager::new( + PreparePath::default(), + next_partition_info, + command_executor, + ); + image_manager.install()?; + self.reboot()?; + Ok(Response { + status: AgentStatus::Upgraded, + }) + } + + pub fn cleanup_impl(&self) -> Result { + let _lock = self.mutex.lock().unwrap(); + info!("Start to cleanup"); + let paths = PreparePath::default(); + clean_env(paths.update_path, paths.mount_path, paths.image_path)?; + Ok(Response { + status: AgentStatus::NotApplied, + }) + } + + pub fn configure_impl(&self, mut req: ConfigureRequest) -> Result { + let _lock = self.mutex.lock().unwrap(); + debug!("Received a 'configure' request: {:?}", req); + info!("Start to configure"); + let config_map = &*CONFIG_TEMPLATE; + for config in req.configs.iter_mut() { + let config_type = &config.model; + if let Some(configuration) = config_map.get(config_type) { + debug!("Found configuration type: \"{}\"", config_type); + configuration.set_config(config)?; + } else { + error!("Unknown configuration type: \"{}\"", config_type); + Err(anyhow!("Unknown configuration type: \"{}\"", config_type))?; + } + } + Ok(Response { + status: AgentStatus::Configured, + }) + } + + pub fn rollback_impl(&self) -> Result { + let _lock = self.mutex.lock().unwrap(); + info!("Start to rollback"); + let command_executor = RealCommandExecutor {}; + let (_, next_partition_info) = get_partition_info(&command_executor)?; + switch_boot_menuentry( + &command_executor, + manager::sys_mgmt::DEFAULT_GRUBENV_PATH, + &next_partition_info.menuentry, + )?; + info!( + "Switch to boot partition: {}, device: {}", + next_partition_info.menuentry, next_partition_info.device + ); + self.reboot()?; + Ok(Response { + status: AgentStatus::Rollbacked, + }) + } + + pub fn reboot(&self) -> Result<()> { + info!("Wait to reboot"); + thread::sleep(Duration::from_secs(1)); + sync(); + if self.disable_reboot { + return Ok(()); + } + nix::sys::reboot::reboot(RebootMode::RB_AUTOBOOT)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use manager::api::Sysconfig; + use std::collections::HashMap; + + #[test] + fn configure_impl_tests() { + let agent = AgentImpl::default(); + let req = ConfigureRequest { + configs: vec![Sysconfig { + model: "kernel.sysctl".to_string(), + config_path: "".to_string(), + contents: HashMap::new(), + }], + }; + let res = agent.configure_impl(req).unwrap(); + assert_eq!( + res, + Response { + status: AgentStatus::Configured, + } + ); + + let req = ConfigureRequest { + configs: vec![Sysconfig { + model: "invalid".to_string(), + config_path: "".to_string(), + contents: HashMap::new(), + }], + }; + let res = agent.configure_impl(req); + assert!(res.is_err()); + } + + #[test] + fn upgrade_impl_tests() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + let agent = AgentImpl::default(); + let req = UpgradeRequest { + version: "v2".into(), + check_sum: "xxx".into(), + image_type: "xxx".into(), + container_image: "xxx".into(), + }; + let res = agent.prepare_upgrade_impl(req); + assert!(res.is_err()); + } +} diff --git a/KubeOS-Rust/agent/src/rpc/mod.rs b/KubeOS-Rust/agent/src/rpc/mod.rs new file mode 100644 index 00000000..976356be --- /dev/null +++ b/KubeOS-Rust/agent/src/rpc/mod.rs @@ -0,0 +1,19 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::function; + +mod agent; +mod agent_impl; + +pub use agent::*; +pub use agent_impl::*; diff --git a/KubeOS-Rust/cli/Cargo.toml b/KubeOS-Rust/cli/Cargo.toml new file mode 100644 index 00000000..18ea908a --- /dev/null +++ b/KubeOS-Rust/cli/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cli" +version = "0.1.0" +edition = "2021" +description = "KubeOS os-agent client" +license = "MulanPSL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +kubeos-manager = { package = "manager", path = "../manager" } +jsonrpc = { version = "0.13", features = ["simple_uds"] } +log = { version = "0.4" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +anyhow = { version = "1.0" } diff --git a/KubeOS-Rust/cli/src/client.rs b/KubeOS-Rust/cli/src/client.rs new file mode 100644 index 00000000..71121fe0 --- /dev/null +++ b/KubeOS-Rust/cli/src/client.rs @@ -0,0 +1,77 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::path::Path; + +use jsonrpc::{ + simple_uds::UdsTransport, Client as JsonRPCClient, Request as JsonRPCRequest, + Response as JsonRPCResponse, +}; +use serde_json::value::RawValue; + +pub struct Client { + json_rpc_client: JsonRPCClient, +} + +pub struct Request<'a>(JsonRPCRequest<'a>); + +impl<'a> Request<'a> {} + +impl Client { + pub fn new>(socket_path: P) -> Self { + let client = Client { + json_rpc_client: JsonRPCClient::with_transport(UdsTransport::new(socket_path)), + }; + client + } + + pub fn build_request<'a>( + &self, + command: &'a str, + params: &'a Vec>, + ) -> Request<'a> { + let json_rpc_request = self.json_rpc_client.build_request(command, ¶ms); + let request = Request(json_rpc_request); + request + } + + pub fn send_request(&self, request: Request) -> Result { + let response = self.json_rpc_client.send_request(request.0); + response + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::method::{callable_method::RpcMethod, configure::ConfigureMethod}; + use kubeos_manager::api; + + #[test] + #[ignore] + fn test_client() { + let socket_path = "/home/yuhang/os-agent-rust.sock"; + let cli = Client::new(socket_path); + + let configured = api::AgentStatus::Configured; + let resp = api::Response { status: configured }; + let config_request = api::ConfigureRequest { + configs: vec![api::Sysconfig { + model: "kernel.sysctl".into(), + config_path: "".into(), + contents: std::collections::hash_map::HashMap::new(), + }], + }; + let config_resp = ConfigureMethod::new(config_request).call(&cli).unwrap(); + assert_eq!(resp, config_resp); + } +} diff --git a/KubeOS-Rust/cli/src/lib.rs b/KubeOS-Rust/cli/src/lib.rs new file mode 100644 index 00000000..cd66d72f --- /dev/null +++ b/KubeOS-Rust/cli/src/lib.rs @@ -0,0 +1,14 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub mod client; +pub mod method; diff --git a/KubeOS-Rust/cli/src/method/callable_method.rs b/KubeOS-Rust/cli/src/method/callable_method.rs new file mode 100644 index 00000000..d59ebd62 --- /dev/null +++ b/KubeOS-Rust/cli/src/method/callable_method.rs @@ -0,0 +1,26 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::RawValue; + +use super::request::{parse_error, request}; +use crate::client::Client; + +pub trait RpcMethod { + type Response: serde::de::DeserializeOwned; + fn command_name(&self) -> &'static str; + fn command_params(&self) -> Vec>; + fn call(&self, client: &Client) -> Result { + let response = request(client, self.command_name(), self.command_params())?; + response.result().map_err(|e| parse_error(e)) + } +} diff --git a/KubeOS-Rust/cli/src/method/cleanup.rs b/KubeOS-Rust/cli/src/method/cleanup.rs new file mode 100644 index 00000000..48a03bc8 --- /dev/null +++ b/KubeOS-Rust/cli/src/method/cleanup.rs @@ -0,0 +1,34 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::RawValue; + +use crate::method::callable_method::RpcMethod; +use kubeos_manager::api; + +pub struct CleanupMethod {} + +impl CleanupMethod { + pub fn new() -> Self { + CleanupMethod {} + } +} + +impl RpcMethod for CleanupMethod { + type Response = api::Response; + fn command_name(&self) -> &'static str { + "cleanup" + } + fn command_params(&self) -> Vec> { + vec![] + } +} diff --git a/KubeOS-Rust/cli/src/method/configure.rs b/KubeOS-Rust/cli/src/method/configure.rs new file mode 100644 index 00000000..ddfeb05f --- /dev/null +++ b/KubeOS-Rust/cli/src/method/configure.rs @@ -0,0 +1,41 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::{to_raw_value, RawValue}; + +use crate::method::callable_method::RpcMethod; +use kubeos_manager::api; + +pub struct ConfigureMethod { + req: api::ConfigureRequest, +} + +impl ConfigureMethod { + pub fn new(req: api::ConfigureRequest) -> Self { + ConfigureMethod { req } + } + + pub fn set_configure_request(&mut self, req: api::ConfigureRequest) -> &Self { + self.req = req; + self + } +} + +impl RpcMethod for ConfigureMethod { + type Response = api::Response; + fn command_name(&self) -> &'static str { + "configure" + } + fn command_params(&self) -> Vec> { + vec![to_raw_value(&self.req).unwrap()] + } +} diff --git a/KubeOS-Rust/cli/src/method/mod.rs b/KubeOS-Rust/cli/src/method/mod.rs new file mode 100644 index 00000000..b04b0fd8 --- /dev/null +++ b/KubeOS-Rust/cli/src/method/mod.rs @@ -0,0 +1,19 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub mod callable_method; +pub mod cleanup; +pub mod configure; +pub mod prepare_upgrade; +pub mod request; +pub mod rollback; +pub mod upgrade; diff --git a/KubeOS-Rust/cli/src/method/prepare_upgrade.rs b/KubeOS-Rust/cli/src/method/prepare_upgrade.rs new file mode 100644 index 00000000..dd3157df --- /dev/null +++ b/KubeOS-Rust/cli/src/method/prepare_upgrade.rs @@ -0,0 +1,41 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::{to_raw_value, RawValue}; + +use crate::method::callable_method::RpcMethod; +use kubeos_manager::api; + +pub struct PrepareUpgradeMethod { + req: api::UpgradeRequest, +} + +impl PrepareUpgradeMethod { + pub fn new(req: api::UpgradeRequest) -> Self { + PrepareUpgradeMethod { req } + } + + pub fn set_prepare_upgrade_request(&mut self, req: api::UpgradeRequest) -> &Self { + self.req = req; + self + } +} + +impl RpcMethod for PrepareUpgradeMethod { + type Response = api::Response; + fn command_name(&self) -> &'static str { + "prepare_upgrade" + } + fn command_params(&self) -> Vec> { + vec![to_raw_value(&self.req).unwrap()] + } +} diff --git a/KubeOS-Rust/cli/src/method/request.rs b/KubeOS-Rust/cli/src/method/request.rs new file mode 100644 index 00000000..4e3dbec6 --- /dev/null +++ b/KubeOS-Rust/cli/src/method/request.rs @@ -0,0 +1,58 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use anyhow::anyhow; +use jsonrpc::{Error, Response}; +use log::debug; +use serde_json::value::RawValue; + +use crate::client::Client; + +pub fn request( + client: &Client, + command: &str, + params: Vec>, +) -> Result { + let request = client.build_request(command, ¶ms); + let response = client.send_request(request).map_err(|e| parse_error(e)); + debug!("{:#?}", response); + response +} + +pub fn parse_error(error: Error) -> anyhow::Error { + match error { + Error::Transport(e) => { + anyhow!( + "Cannot connect to KubeOS os-agent unix socket, {}", + e.source() + .map(|e| e.to_string()) + .unwrap_or_else(|| "Connection timeout".to_string()) + ) + } + Error::Json(e) => { + debug!("Json parse error: {:?}", e); + anyhow!("Failed to parse response") + } + Error::Rpc(ref e) => match e.message == "Method not found" { + true => { + anyhow!("Method is unimplemented") + } + false => { + anyhow!("{}", e.message) + } + }, + _ => { + debug!("{:?}", error); + anyhow!("Response is invalid") + } + } +} diff --git a/KubeOS-Rust/cli/src/method/rollback.rs b/KubeOS-Rust/cli/src/method/rollback.rs new file mode 100644 index 00000000..5b9b0fde --- /dev/null +++ b/KubeOS-Rust/cli/src/method/rollback.rs @@ -0,0 +1,34 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::RawValue; + +use crate::method::callable_method::RpcMethod; +use kubeos_manager::api; + +pub struct RollbackMethod {} + +impl RollbackMethod { + pub fn new() -> Self { + RollbackMethod {} + } +} + +impl RpcMethod for RollbackMethod { + type Response = api::Response; + fn command_name(&self) -> &'static str { + "rollback" + } + fn command_params(&self) -> Vec> { + vec![] + } +} diff --git a/KubeOS-Rust/cli/src/method/upgrade.rs b/KubeOS-Rust/cli/src/method/upgrade.rs new file mode 100644 index 00000000..9098e197 --- /dev/null +++ b/KubeOS-Rust/cli/src/method/upgrade.rs @@ -0,0 +1,34 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde_json::value::RawValue; + +use crate::method::callable_method::RpcMethod; +use kubeos_manager::api; + +pub struct UpgradeMethod {} + +impl UpgradeMethod { + pub fn new() -> Self { + UpgradeMethod {} + } +} + +impl RpcMethod for UpgradeMethod { + type Response = api::Response; + fn command_name(&self) -> &'static str { + "upgrade" + } + fn command_params(&self) -> Vec> { + vec![] + } +} diff --git a/KubeOS-Rust/manager/Cargo.toml b/KubeOS-Rust/manager/Cargo.toml new file mode 100644 index 00000000..39f49efd --- /dev/null +++ b/KubeOS-Rust/manager/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "manager" +version = "0.1.0" +edition = "2021" +description = "KubeOS os-agent manager" +license = "MulanPSL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dev-dependencies] +tempfile = "3.2" +mockall = { version = "=0.11.3" } +predicates = "=2.0.1" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +log = { version = "0.4" } +anyhow = { version = "1.0" } +env_logger = { version = "0.9" } +lazy_static = { version = "1.4" } +regex = { version = "1.7.3" } +nix = { version = "0.26.2" } diff --git a/KubeOS-Rust/manager/src/api/agent_status.rs b/KubeOS-Rust/manager/src/api/agent_status.rs new file mode 100644 index 00000000..7a2edf7d --- /dev/null +++ b/KubeOS-Rust/manager/src/api/agent_status.rs @@ -0,0 +1,49 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use serde::{Deserialize, Serialize}; + +const AGENT_STATUS_UNKNOWN: &str = "UNKNOWN"; +const AGENT_STATUS_NOT_APPLIED: &str = "NOT-APPLIED"; +const AGENT_STATUS_UPGRADEREADY: &str = "UPGRADE-READY"; +const AGENT_STATUS_UPGRADED: &str = "UPGRADED"; +const AGENT_STATUS_ROLLBACKED: &str = "ROLLBACKED"; +const AGENT_STATUS_CONFIGURED: &str = "CONFIGURED"; + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum AgentStatus { + Unknown, + NotApplied, + UpgradeReady, + Upgraded, + Rollbacked, + Configured, +} + +impl Default for AgentStatus { + fn default() -> Self { + Self::Unknown + } +} + +impl std::fmt::Display for AgentStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + AgentStatus::Unknown => AGENT_STATUS_UNKNOWN, + AgentStatus::NotApplied => AGENT_STATUS_NOT_APPLIED, + AgentStatus::UpgradeReady => AGENT_STATUS_UPGRADEREADY, + AgentStatus::Upgraded => AGENT_STATUS_UPGRADED, + AgentStatus::Rollbacked => AGENT_STATUS_ROLLBACKED, + AgentStatus::Configured => AGENT_STATUS_CONFIGURED, + }) + } +} diff --git a/KubeOS-Rust/manager/src/api/mod.rs b/KubeOS-Rust/manager/src/api/mod.rs new file mode 100644 index 00000000..01c9df1a --- /dev/null +++ b/KubeOS-Rust/manager/src/api/mod.rs @@ -0,0 +1,17 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +mod agent_status; +mod types; + +pub use agent_status::*; +pub use types::*; diff --git a/KubeOS-Rust/manager/src/api/types.rs b/KubeOS-Rust/manager/src/api/types.rs new file mode 100644 index 00000000..e21f55bf --- /dev/null +++ b/KubeOS-Rust/manager/src/api/types.rs @@ -0,0 +1,67 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::agent_status::*; +use crate::{ + sys_mgmt::CtrImageHandler, + utils::{CommandExecutor, UpgradeImageManager}, +}; + +#[derive(Deserialize, Serialize, Debug)] +pub struct UpgradeRequest { + pub version: String, + pub check_sum: String, + pub image_type: String, + pub container_image: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct KeyInfo { + pub value: String, + pub operation: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Sysconfig { + pub model: String, + pub config_path: String, + pub contents: HashMap, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ConfigureRequest { + pub configs: Vec, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct Response { + pub status: AgentStatus, +} + +pub enum ImageType { + Containerd(CtrImageHandler), +} + +impl ImageType { + pub fn download_image(&self, req: &UpgradeRequest) -> anyhow::Result> { + match self { + ImageType::Containerd(handler) => handler.download_image(req), + } + } +} +pub trait ImageHandler { + fn download_image(&self, req: &UpgradeRequest) -> anyhow::Result>; +} diff --git a/KubeOS-Rust/manager/src/lib.rs b/KubeOS-Rust/manager/src/lib.rs new file mode 100644 index 00000000..b45cab99 --- /dev/null +++ b/KubeOS-Rust/manager/src/lib.rs @@ -0,0 +1,15 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub mod api; +pub mod sys_mgmt; +pub mod utils; diff --git a/KubeOS-Rust/manager/src/sys_mgmt/config.rs b/KubeOS-Rust/manager/src/sys_mgmt/config.rs new file mode 100644 index 00000000..01a09d70 --- /dev/null +++ b/KubeOS-Rust/manager/src/sys_mgmt/config.rs @@ -0,0 +1,723 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + collections::HashMap, + fs::{self, File}, + io::{self, BufRead, BufWriter, Write}, + os::unix::fs::PermissionsExt, + path::{Path, PathBuf}, + string::String, +}; + +use anyhow::{anyhow, Context, Ok, Result}; +use lazy_static::lazy_static; +use log::{debug, info, trace, warn}; +use regex::Regex; + +use super::{api::*, values}; +use crate::utils::*; + +lazy_static! { + pub static ref CONFIG_TEMPLATE: HashMap> = { + let mut config_map = HashMap::new(); + config_map.insert( + values::KERNEL_SYSCTL.to_string(), + Box::new(KernelSysctl::new(values::DEFAULT_PROC_PATH)) as Box, + ); + config_map.insert( + values::KERNEL_SYSCTL_PERSIST.to_string(), + Box::new(KernelSysctlPersist) as Box, + ); + config_map.insert( + values::GRUB_CMDLINE_CURRENT.to_string(), + Box::new(GrubCmdline { + grub_path: values::DEFAULT_GRUB_CFG_PATH.to_string(), + is_cur_partition: true, + }) as Box, + ); + config_map.insert( + values::GRUB_CMDLINE_NEXT.to_string(), + Box::new(GrubCmdline { + grub_path: values::DEFAULT_GRUB_CFG_PATH.to_string(), + is_cur_partition: false, + }) as Box, + ); + config_map + }; +} + +pub trait Configuration { + fn set_config(&self, config: &mut Sysconfig) -> Result<()>; +} + +pub struct KernelSysctl { + pub proc_path: String, +} +pub struct KernelSysctlPersist; +pub struct GrubCmdline { + pub grub_path: String, + pub is_cur_partition: bool, +} + +impl Configuration for KernelSysctl { + fn set_config(&self, config: &mut Sysconfig) -> Result<()> { + info!("Start set kernel.sysctl"); + for (key, key_info) in config.contents.iter() { + let proc_path = self.get_proc_path(key); + if key_info.operation == "delete" { + warn!("Failed to delete kernel.sysctl config with key \"{}\"", key); + } else if key_info.value != "" && key_info.operation == "" { + fs::write(&proc_path, format!("{}\n", &key_info.value).as_bytes()).with_context( + || format!("Failed to write kernel.sysctl with key: \"{}\"", key), + )?; + info!("Configured kernel.sysctl {}={}", key, key_info.value); + } else { + warn!( + "Failed to parse kernel.sysctl, key: \"{}\", value: \"{}\", operation: \"{}\"", + key, key_info.value, key_info.operation + ); + } + } + Ok(()) + } +} + +impl KernelSysctl { + fn new(proc_path: &str) -> Self { + Self { + proc_path: String::from(proc_path), + } + } + + fn get_proc_path(&self, key: &str) -> PathBuf { + let path_str = format!("{}{}", self.proc_path, key.replace(".", "/")); + Path::new(&path_str).to_path_buf() + } +} + +impl Configuration for KernelSysctlPersist { + fn set_config(&self, config: &mut Sysconfig) -> Result<()> { + info!("Start set kernel.sysctl.persist"); + let mut config_path = &values::DEFAULT_KERNEL_CONFIG_PATH.to_string(); + if config.config_path != "" { + config_path = &config.config_path; + } + debug!("kernel.sysctl.persist config_path: \"{}\"", config_path); + create_config_file(config_path)?; + let configs = get_and_set_configs(&mut config.contents, config_path)?; + write_configs_to_file(config_path, &configs)?; + Ok(()) + } +} + +fn create_config_file(config_path: &str) -> Result<()> { + if !is_file_exist(config_path) { + let f = fs::File::create(config_path)?; + let metadata = f.metadata()?; + let mut permissions = metadata.permissions(); + permissions.set_mode(values::DEFAULT_KERNEL_CONFIG_PERM); + debug!("Create file {} with permission 0644", config_path); + } + Ok(()) +} + +fn get_and_set_configs( + expect_configs: &mut HashMap, + config_path: &str, +) -> Result> { + let f = File::open(config_path)?; + let mut configs_write = Vec::new(); + for line in io::BufReader::new(f).lines() { + let line = line?; + // if line is a comment or blank + if line.starts_with("#") || line.starts_with(";") || line.trim().is_empty() { + configs_write.push(line); + continue; + } + let config_kv: Vec<&str> = line.splitn(2, '=').map(|s| s.trim()).collect(); + // if config_kv is not a key-value pair + if config_kv.len() != 2 { + return Err(anyhow!("could not parse sysctl config {}", line)); + } + let new_key_info = expect_configs.get(config_kv[0]); + let new_config = match new_key_info { + Some(new_key_info) if new_key_info.operation == "delete" => { + handle_delete_key(&config_kv, new_key_info) + } + Some(new_key_info) => handle_update_key(&config_kv, new_key_info), + None => config_kv.join("="), + }; + configs_write.push(new_config); + expect_configs.remove(config_kv[0]); + } + let new_config = handle_add_key(expect_configs, false); + configs_write.extend(new_config); + Ok(configs_write) +} + +fn write_configs_to_file(config_path: &str, configs: &Vec) -> Result<()> { + info!("Write configuration to file \"{}\"", config_path); + let f = File::create(config_path)?; + let mut w = BufWriter::new(f); + for line in configs { + if line == "" { + continue; + } + writeln!(w, "{}", line.as_str())?; + } + w.flush() + .with_context(|| format!("Failed to flush file {}", config_path))?; + w.get_mut() + .sync_all() + .with_context(|| format!("Failed to sync"))?; + debug!("Write configuration to file \"{}\" success", config_path); + Ok(()) +} + +fn handle_delete_key(config_kv: &Vec<&str>, new_config_info: &KeyInfo) -> String { + let key = config_kv[0]; + if config_kv.len() == 1 && new_config_info.value == "" { + info!("Delete configuration key: \"{}\"", key); + return String::from(""); + } else if config_kv.len() == 1 && new_config_info.value != "" { + warn!( + "Failed to delete key \"{}\" with inconsistent values \"nil\" and \"{}\"", + key, new_config_info.value + ); + return key.to_string(); + } + let old_value = config_kv[1]; + if old_value != new_config_info.value { + warn!( + "Failed to delete key \"{}\" with inconsistent values \"{}\" and \"{}\"", + key, old_value, new_config_info.value + ); + return config_kv.join("="); + } + info!("Delete configuration {}={}", key, old_value); + String::from("") +} + +fn handle_update_key(config_kv: &Vec<&str>, new_config_info: &KeyInfo) -> String { + let key = config_kv[0]; + if new_config_info.operation != "" { + warn!( + "Unknown operation \"{}\", updating key \"{}\" with value \"{}\" by default", + new_config_info.operation, key, new_config_info.value + ); + } + if config_kv.len() == values::ONLY_KEY && new_config_info.value == "" { + return key.to_string(); + } + let new_value = new_config_info.value.trim(); + if config_kv.len() == values::ONLY_KEY && new_config_info.value != "" { + info!("Update configuration \"{}={}\"", key, new_value); + return format!("{}={}", key, new_value); + } + if new_config_info.value == "" { + warn!("Failed to update key \"{}\" with \"null\" value", key); + return config_kv.join("="); + } + info!("Update configuration \"{}={}\"", key, new_value); + format!("{}={}", key, new_value) +} + +fn handle_add_key( + expect_configs: &HashMap, + is_only_key_valid: bool, +) -> Vec { + let mut configs_write = Vec::new(); + for (key, config_info) in expect_configs.iter() { + if config_info.operation == "delete" { + warn!("Failed to delete inexistent key: \"{}\"", key); + continue; + } + if key == "" || key.contains("=") { + warn!( + "Failed to add \"null\" key or key containing \"=\", key: \"{}\"", + key + ); + continue; + } + if config_info.operation != "" { + warn!( + "Unknown operation \"{}\", adding key \"{}\" with value \"{}\" by default", + config_info.operation, key, config_info.value + ); + } + let (k, v) = (key.trim(), config_info.value.trim()); + if v == "" && is_only_key_valid { + info!("Add configuration \"{}\"", k); + configs_write.push(k.to_string()); + } else if v == "" { + warn!("Failed to add key \"{}\" with \"null\" value", k); + } else { + info!("Add configuration \"{}={}\"", k, v); + configs_write.push(format!("{}={}", k, v)); + } + } + configs_write +} + +impl Configuration for GrubCmdline { + fn set_config(&self, config: &mut Sysconfig) -> Result<()> { + if self.is_cur_partition { + info!("Start set grub.cmdline.current configuration"); + } else { + info!("Start set grub.cmdline.next configuration"); + } + if !is_file_exist(&self.grub_path) { + return Err(anyhow!("Failed to find grub.cfg file")); + } + if cfg!(test) {} + let config_partition = if cfg!(test) { + self.is_cur_partition + } else { + self.get_config_partition(RealCommandExecutor {})? + }; + debug!( + "Config_partition: {} (false means partition A, true means partition B)", + config_partition + ); + let configs = get_and_set_grubcfg(&mut config.contents, &self.grub_path, config_partition)?; + write_configs_to_file(&self.grub_path, &configs)?; + Ok(()) + } +} + +impl GrubCmdline { + // get_config_partition returns false if the menuentry to be configured is A, true for menuentry B + fn get_config_partition(&self, executor: T) -> Result { + let (_, next_partition) = get_partition_info(&executor)?; + let mut flag = false; + if next_partition.menuentry == "B" { + flag = true + } + Ok(self.is_cur_partition != flag) + } +} + +fn get_and_set_grubcfg( + expect_configs: &mut HashMap, + grub_path: &str, + config_partition: bool, +) -> Result> { + let f = File::open(grub_path)?; + let re_find_cur_linux = r"^\s*linux.*root=.*"; + let re = Regex::new(re_find_cur_linux)?; + let mut configs_write = Vec::new(); + let mut match_config_partition = false; + for line in io::BufReader::new(f).lines() { + let mut line = line?; + if re.is_match(&line) { + if match_config_partition == config_partition { + line = modify_boot_cfg(expect_configs, &line)?; + } + match_config_partition = true; + } + configs_write.push(line); + } + Ok(configs_write) +} + +fn modify_boot_cfg(expect_configs: &mut HashMap, line: &String) -> Result { + trace!( + "Match partition that need to be configured, entering modify_boot_cfg, linux line: {}", + line + ); + let mut new_configs = vec![" ".to_string()]; + let olg_configs: Vec<&str> = line.split(' ').collect(); + for old_config in olg_configs { + if old_config == "" { + continue; + } + // At most 2 substrings can be returned to satisfy the case like root=UUID=xxxx + let config = old_config.splitn(2, "=").collect::>(); + if config.len() != values::ONLY_KEY && config.len() != values::KV_PAIR { + return Err(anyhow!( + "Failed to parse grub.cfg linux line {}", + old_config + )); + } + let new_key_info = expect_configs.get(config[0]); + let new_config = match new_key_info { + Some(new_key_info) if new_key_info.operation == "delete" => { + handle_delete_key(&config, new_key_info) + } + Some(new_key_info) => handle_update_key(&config, new_key_info), + None => config.join("="), + }; + if !new_config.is_empty() { + new_configs.push(new_config); + } + expect_configs.remove(config[0]); + } + let new_config = handle_add_key(expect_configs, true); + new_configs.extend(new_config); + Ok(new_configs.join(" ")) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sys_mgmt::{ + GRUB_CMDLINE_CURRENT, GRUB_CMDLINE_NEXT, KERNEL_SYSCTL, KERNEL_SYSCTL_PERSIST, + }; + use mockall::{mock, predicate::*}; + use std::fs; + use tempfile::{NamedTempFile, TempDir}; + + // Mock the CommandExecutor trait + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_get_config_partition() { + init(); + let mut grub_cmdline = GrubCmdline { + grub_path: String::from(""), + is_cur_partition: true, + }; + let mut executor = MockCommandExec::new(); + + // the output shows that current root menuentry is A + let command_output1 = + "sda\nsda1 /boot/efi vfat\nsda2 / ext4\nsda3 ext4\nsda4 /persist ext4\nsr0 iso9660\n"; + executor + .expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output1.to_string())); + + let result = grub_cmdline.get_config_partition(executor).unwrap(); + // it should return false because the current root menuentry is A and we want to configure current partition + assert_eq!(result, false); + + let mut executor = MockCommandExec::new(); + + // the output shows that current root menuentry is A + let command_output1 = + "sda\nsda1 /boot/efi vfat\nsda2 / ext4\nsda3 ext4\nsda4 /persist ext4\nsr0 iso9660\n"; + executor + .expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output1.to_string())); + grub_cmdline.is_cur_partition = false; + let result = grub_cmdline.get_config_partition(executor).unwrap(); + // it should return true because the current root menuentry is A and we want to configure next partition + assert_eq!(result, true); + } + + #[test] + fn test_kernel_sysctl() { + init(); + let tmp_dir = TempDir::new().unwrap(); + assert_eq!(tmp_dir.path().exists(), true); + let kernel_sysctl = KernelSysctl::new(tmp_dir.path().to_str().unwrap()); + + let config_detail = HashMap::from([ + ( + "a".to_string(), + KeyInfo { + value: "1".to_string(), + operation: "".to_string(), + }, + ), + ( + "b".to_string(), + KeyInfo { + value: "2".to_string(), + operation: "delete".to_string(), + }, + ), + ( + "c".to_string(), + KeyInfo { + value: "3".to_string(), + operation: "add".to_string(), + }, + ), + ( + "d".to_string(), + KeyInfo { + value: "".to_string(), + operation: "".to_string(), + }, + ), + ( + "e".to_string(), + KeyInfo { + value: "".to_string(), + operation: "delete".to_string(), + }, + ), + ]); + + let mut config = Sysconfig { + model: KERNEL_SYSCTL.to_string(), + config_path: String::from(""), + contents: config_detail, + }; + kernel_sysctl.set_config(&mut config).unwrap(); + + let result = + fs::read_to_string(format!("{}{}", tmp_dir.path().to_str().unwrap(), "a")).unwrap(); + assert_eq!(result, "1\n"); + } + + #[test] + fn test_kernel_sysctl_persist() { + init(); + let comment = r"# This file is managed by KubeOS for unit testing."; + // create a tmp file with comment + let mut tmp_file = tempfile::NamedTempFile::new().unwrap(); + writeln!(tmp_file, "{}", comment).unwrap(); + writeln!(tmp_file, "a=0").unwrap(); + let kernel_sysctl_persist = KernelSysctlPersist {}; + let config_detail = HashMap::from([ + ( + "a".to_string(), + KeyInfo { + value: "1".to_string(), + operation: "".to_string(), + }, + ), + ( + "b".to_string(), + KeyInfo { + value: "2".to_string(), + operation: "delete".to_string(), + }, + ), + ( + "c".to_string(), + KeyInfo { + value: "3".to_string(), + operation: "add".to_string(), + }, + ), + ]); + let mut config = Sysconfig { + model: KERNEL_SYSCTL_PERSIST.to_string(), + config_path: String::from(tmp_file.path().to_str().unwrap()), + contents: config_detail, + }; + kernel_sysctl_persist.set_config(&mut config).unwrap(); + let result = fs::read_to_string(tmp_file.path().to_str().unwrap()).unwrap(); + let expected_res = format!("{}\n{}\n{}\n", comment, "a=1", "c=3"); + assert_eq!(result, expected_res); + + // test config_path is empty + // remember modify DEFAULT_KERNEL_CONFIG_PATH first + // let config_detail = HashMap::from([ + // ( + // "aaa".to_string(), + // KeyInfo { + // value: "3".to_string(), + // operation: "add".to_string(), + // }, + // ), + // ( + // "bbb".to_string(), + // KeyInfo { + // value: "1".to_string(), + // operation: "delete".to_string(), + // }, + // ), + // ]); + // config.config_path = "".to_string(); + // config.contents = config_detail; + // kernel_sysctl_persist.set_config(&mut config).unwrap(); + // let result = fs::read_to_string(crate::sys_mgmt::DEFAULT_KERNEL_CONFIG_PATH).unwrap(); + // let expected_res = format!("{}\n", "aaa=3",); + // assert_eq!(result, expected_res); + } + + #[test] + fn write_configs_to_file_tests() { + init(); + let path = "/home/yuhang/abc.txt"; + let configs = vec!["a=1".to_string(), "b=2".to_string()]; + write_configs_to_file(&path.to_string(), &configs).unwrap(); + } + + #[test] + fn test_grub_cmdline() { + init(); + let mut tmp_file = NamedTempFile::new().unwrap(); + let mut grub_cmdline = GrubCmdline { + grub_path: tmp_file.path().to_str().unwrap().to_string(), + is_cur_partition: true, + }; + let grub_cfg = r"menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt2' + linux /boot/vmlinuz root=UUID=1 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +} + +menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt3' + linux /boot/vmlinuz root=UUID=2 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=3 + initrd /boot/initramfs.img +}"; + writeln!(tmp_file, "{}", grub_cfg).unwrap(); + let config_first_part = HashMap::from([ + ( + "debug".to_string(), + KeyInfo { + value: "".to_string(), + operation: "".to_string(), + }, + ), + ( + "quiet".to_string(), + KeyInfo { + value: "".to_string(), + operation: "delete".to_string(), + }, + ), + ( + "panic".to_string(), + KeyInfo { + value: "5".to_string(), + operation: "".to_string(), + }, + ), + ( + "nomodeset".to_string(), + KeyInfo { + value: "".to_string(), + operation: "update".to_string(), + }, + ), + ( + "oops".to_string(), + KeyInfo { + value: "".to_string(), + operation: "".to_string(), + }, + ), + ( + "".to_string(), + KeyInfo { + value: "test".to_string(), + operation: "".to_string(), + }, + ), + ( + "selinux".to_string(), + KeyInfo { + value: "1".to_string(), + operation: "delete".to_string(), + }, + ), + ( + "acpi".to_string(), + KeyInfo { + value: "off".to_string(), + operation: "delete".to_string(), + }, + ), + ( + "ro".to_string(), + KeyInfo { + value: "1".to_string(), + operation: "".to_string(), + }, + ), + ]); + let mut config = Sysconfig { + model: GRUB_CMDLINE_CURRENT.to_string(), + config_path: String::new(), + contents: config_first_part, + }; + grub_cmdline.set_config(&mut config).unwrap(); + grub_cmdline.is_cur_partition = false; + let config_second = HashMap::from([ + ( + "pci".to_string(), + KeyInfo { + value: "nomis".to_string(), + operation: "".to_string(), + }, + ), + ( + "panic".to_string(), + KeyInfo { + value: "5".to_string(), + operation: "".to_string(), + }, + ), + ]); + config.contents = config_second; + config.model = GRUB_CMDLINE_NEXT.to_string(); + grub_cmdline.set_config(&mut config).unwrap(); + let result = fs::read_to_string(tmp_file.path().to_str().unwrap()).unwrap(); + let expected_res = r"menuentry 'A' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-A' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt2' + linux /boot/vmlinuz root=UUID=1 ro rootfstype=ext4 nomodeset quiet oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=5 pci=nomis + initrd /boot/initramfs.img +} +menuentry 'B' --class KubeOS --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'KubeOS-B' { + load_video + set gfxpayload=keep + insmod gzio + insmod part_gpt + insmod ext2 + set root='hd0,gpt3' + linux /boot/vmlinuz root=UUID=2 ro=1 rootfstype=ext4 nomodeset oops=panic softlockup_panic=1 nmi_watchdog=1 rd.shell=0 selinux=0 crashkernel=256M panic=5 debug + initrd /boot/initramfs.img +} +"; + assert_eq!(result, expected_res); + } + + #[test] + fn test_create_config_file() { + init(); + let tmp_file = "/tmp/kubeos-test-create-config-file.txt"; + create_config_file(&tmp_file).unwrap(); + assert!(is_file_exist(&tmp_file)); + fs::remove_file(tmp_file).unwrap(); + } +} diff --git a/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs b/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs new file mode 100644 index 00000000..b4bdd2c8 --- /dev/null +++ b/KubeOS-Rust/manager/src/sys_mgmt/containerd_image.rs @@ -0,0 +1,359 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{fs, os::unix::fs::PermissionsExt, path::Path}; + +use anyhow::{anyhow, Result}; +use log::{debug, info}; + +use super::api::{ImageHandler, UpgradeRequest}; +use crate::sys_mgmt::{IMAGE_PERMISSION, NEED_GB_SIZE, PERSIST_DIR}; +use crate::utils::*; + +pub struct CtrImageHandler { + pub paths: PreparePath, + pub executor: T, +} + +const DEFAULT_NAMESPACE: &str = "k8s.io"; + +impl ImageHandler for CtrImageHandler { + fn download_image(&self, req: &UpgradeRequest) -> Result> { + perpare_env(&self.paths, NEED_GB_SIZE, PERSIST_DIR, IMAGE_PERMISSION)?; + self.get_image(req)?; + self.get_rootfs_archive(req, IMAGE_PERMISSION)?; + + let (_, next_partition_info) = get_partition_info(&self.executor)?; + let img_manager = UpgradeImageManager::new( + self.paths.clone(), + next_partition_info, + self.executor.clone(), + ); + img_manager.create_os_image(IMAGE_PERMISSION) + } +} + +impl Default for CtrImageHandler { + fn default() -> Self { + Self { + paths: PreparePath::default(), + executor: RealCommandExecutor {}, + } + } +} + +impl CtrImageHandler { + #[cfg(test)] + fn new(paths: PreparePath, executor: T) -> Self { + Self { paths, executor } + } + + fn get_image(&self, req: &UpgradeRequest) -> Result<()> { + let image_name = &req.container_image; + is_valid_image_name(image_name)?; + info!("Start pull image {}", image_name); + let containerd_command: String; + if is_command_available("crictl", &self.executor) { + containerd_command = "crictl".to_string(); + } else { + containerd_command = "ctr".to_string(); + } + pull_image(&containerd_command, image_name, &self.executor)?; + info!("Start check image digest"); + check_oci_image_digest_match( + &containerd_command, + image_name, + &req.check_sum, + &self.executor, + )?; + Ok(()) + } + + fn get_rootfs_archive(&self, req: &UpgradeRequest, permission: u32) -> Result<()> { + let image_name = &req.container_image; + let mount_path = &self.paths.mount_path.to_str().ok_or_else(|| { + anyhow!( + "Failed to get mount path: {}", + self.paths.mount_path.display() + ) + })?; + info!("Start get rootfs {}", image_name); + self.check_and_unmount(mount_path)?; + self.executor.run_command( + "ctr", + &[ + "-n", + DEFAULT_NAMESPACE, + "images", + "mount", + "--rw", + image_name, + mount_path, + ], + )?; + // copy os.tar from mount_path to its partent dir + self.copy_file( + &self.paths.mount_path.join(&self.paths.rootfs_file), + &self.paths.tar_path, + permission, + )?; + self.check_and_unmount(mount_path)?; + Ok(()) + } + + fn check_and_unmount(&self, mount_path: &str) -> Result<()> { + let ctr_snapshot_cmd = format!( + "ctr -n={} snapshots ls | grep {} | awk '{{print $1}}'", + DEFAULT_NAMESPACE, mount_path + ); + let exist_snapshot = self + .executor + .run_command_with_output("bash", &["-c", &ctr_snapshot_cmd])?; + if !exist_snapshot.is_empty() { + self.executor.run_command( + "ctr", + &["-n", DEFAULT_NAMESPACE, "images", "unmount", mount_path], + )?; + self.executor.run_command( + "ctr", + &["-n", DEFAULT_NAMESPACE, "snapshots", "remove", mount_path], + )?; + } + Ok(()) + } + + fn copy_file, Q: AsRef>( + &self, + src: P, + dst: Q, + permission: u32, + ) -> Result<()> { + let copied_bytes = fs::copy(src.as_ref(), dst.as_ref())?; + debug!( + "Copy {} to {}, total bytes: {}", + src.as_ref().display(), + dst.as_ref().display(), + copied_bytes + ); + fs::set_permissions(dst, fs::Permissions::from_mode(permission))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::mock; + use std::io::Write; + use std::path::Path; + use std::path::PathBuf; + use tempfile::NamedTempFile; + + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_get_image() { + init(); + let mut mock_executor = MockCommandExec::new(); + let image_name = "docker.io/library/busybox:latest"; + let req = UpgradeRequest { + version: "KubeOS v2".to_string(), + image_type: "containerd".to_string(), + container_image: image_name.to_string(), + check_sum: "22222".to_string(), + }; + // mock is_command_available + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "/bin/sh" && args.contains(&"command -v crictl")) // simplified with a closure + .times(1) + .returning(|_, _| Ok(())); + // mock pull_image + mock_executor + .expect_run_command() + .withf(|cmd, args| { + cmd == "crictl" + && args.contains(&"pull") + && args.contains(&"docker.io/library/busybox:latest") + }) + .times(1) + .returning(|_, _| Ok(())); + // mock get_oci_image_digest + let command_output2 = "[docker.io/library/busybox:latest@sha256:22222]"; + mock_executor + .expect_run_command_with_output() + .withf(|cmd, args| { + cmd == "crictl" + && args.contains(&"inspecti") + && args.contains(&"{{.status.repoDigests}}") + }) + .times(1) + .returning(|_, _| Ok(command_output2.to_string())); + let ctr = CtrImageHandler::new(PreparePath::default(), mock_executor); + let result = ctr.get_image(&req); + assert!(result.is_ok()); + } + + #[test] + fn test_get_rootfs_archive() { + init(); + let mut mock_executor = MockCommandExec::new(); + let image_name = "docker.io/library/busybox:latest"; + let req = UpgradeRequest { + version: "KubeOS v2".to_string(), + image_type: "containerd".to_string(), + container_image: image_name.to_string(), + check_sum: "22222".to_string(), + }; + + // mock check_and_unmount + mock_executor + .expect_run_command_with_output() + .withf(|cmd, args| cmd == "bash" && args.len() == 2 && args[0] == "-c") // simplified with a closure + .times(1) + .returning(|_, _| Ok("".to_string())); + + // mock ctr mount rw + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "ctr" && args.len() == 7 && args[4] == "--rw") // simplified with a closure + .times(1) + .returning(|_, _| Ok(())); + + // create temp file for copy + let mut tmp_file = NamedTempFile::new().expect("Failed to create temporary file."); + writeln!(tmp_file, "Hello, world!").expect("Failed to write to temporary file."); + + // Get the path of the temporary file and the path where it should be copied. + let src_dir = tmp_file.path().parent().unwrap(); + let src_file_name = tmp_file + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + let dst_file = NamedTempFile::new().expect("Failed to create destination temporary file."); + let dst_path = dst_file.path().to_path_buf(); + + let paths = PreparePath { + update_path: PathBuf::new(), + image_path: PathBuf::new(), + mount_path: src_dir.to_path_buf(), + rootfs_file: src_file_name.clone(), + tar_path: dst_path.clone(), + }; + + // mock check_and_unmount + mock_executor + .expect_run_command_with_output() + .withf(|cmd, args| cmd == "bash" && args.len() == 2 && args[0] == "-c") // simplified with a closure + .times(1) + .returning(|_, _| Ok("".to_string())); + + let ctr = CtrImageHandler::new(paths, mock_executor); + let result = ctr.get_rootfs_archive(&req, IMAGE_PERMISSION); + assert!(result.is_ok()); + } + + #[test] + fn test_copy_file() { + // Setup: Create a temporary file and write some data to it. + let mut tmp_file = NamedTempFile::new().expect("Failed to create temporary file."); + writeln!(tmp_file, "Hello, world!").expect("Failed to write to temporary file."); + + // Get the path of the temporary file and the path where it should be copied. + let src_path = tmp_file.path().to_str().unwrap().to_string(); + let dst_file = NamedTempFile::new().expect("Failed to create destination temporary file."); + let dst_path = dst_file.path().to_str().unwrap().to_string(); + + let ctr = CtrImageHandler::default(); + let result = ctr.copy_file(&src_path, &dst_path, IMAGE_PERMISSION); + + assert!(result.is_ok()); + + let expected_content = "Hello, world!\n"; + let actual_content = + fs::read_to_string(&dst_path).expect("Failed to read destination file."); + assert_eq!(expected_content, actual_content); + + // Assert the file permission + let metadata = fs::metadata(&dst_path).expect("Failed to read destination file."); + let expected_permission = 0o100600; + assert_eq!(metadata.permissions().mode(), expected_permission); + } + + #[test] + fn test_check_and_unmount() { + let mut mock_executor = MockCommandExec::new(); + + // When `run_command_with_output` is called with "bash" and the specific args, it will return Ok("snapshot_exists"). + mock_executor + .expect_run_command_with_output() + .withf(|cmd, args| cmd == "bash" && args.len() == 2 && args[0] == "-c") + .times(1) + .returning(|_, _| Ok("snapshot_exists".to_string())); + + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "ctr" && args.contains(&"images")) + .times(1) + .returning(|_, _| Ok(())); + + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "ctr" && args.contains(&"snapshots")) + .times(1) + .returning(|_, _| Ok(())); + + let result = CtrImageHandler::new(PreparePath::default(), mock_executor) + .check_and_unmount("test_mount_path"); + + assert!(result.is_ok()); + } + + #[test] + #[ignore] + fn test_download_image() { + init(); + let ctr = CtrImageHandler { + paths: PreparePath::default(), + executor: RealCommandExecutor {}, + }; + let update_req = UpgradeRequest { + version: "KubeOS v2".to_string(), + image_type: "containerd".to_string(), + container_image: "docker.io/library/busybox:latest".to_string(), + check_sum: "".to_string(), + }; + ctr.download_image(&update_req).unwrap(); + let tar_path = "/persist/KubeOS-Update/os.tar"; + assert_eq!(true, Path::new(tar_path).exists()); + } +} diff --git a/KubeOS-Rust/manager/src/sys_mgmt/mod.rs b/KubeOS-Rust/manager/src/sys_mgmt/mod.rs new file mode 100644 index 00000000..446d072c --- /dev/null +++ b/KubeOS-Rust/manager/src/sys_mgmt/mod.rs @@ -0,0 +1,21 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::api; + +mod config; +mod containerd_image; +mod values; + +pub use config::*; +pub use containerd_image::*; +pub use values::*; diff --git a/KubeOS-Rust/manager/src/sys_mgmt/values.rs b/KubeOS-Rust/manager/src/sys_mgmt/values.rs new file mode 100644 index 00000000..3452b4ad --- /dev/null +++ b/KubeOS-Rust/manager/src/sys_mgmt/values.rs @@ -0,0 +1,35 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub const KERNEL_SYSCTL: &str = "kernel.sysctl"; +pub const KERNEL_SYSCTL_PERSIST: &str = "kernel.sysctl.persist"; +pub const GRUB_CMDLINE_CURRENT: &str = "grub.cmdline.current"; +pub const GRUB_CMDLINE_NEXT: &str = "grub.cmdline.next"; + +pub const DEFAULT_PROC_PATH: &str = "/proc/sys/"; +pub const DEFAULT_KERNEL_CONFIG_PATH: &str = "/etc/sysctl.conf"; +pub const DEFAULT_GRUB_CFG_PATH: &str = "/boot/efi/EFI/openEuler/grub.cfg"; +pub const DEFAULT_GRUBENV_PATH: &str = "/boot/efi/EFI/openEuler/grubenv"; + +pub const PERSIST_DIR: &str = "/persist"; +pub const ROOTFS_ARCHIVE: &str = "os.tar"; +pub const UPDATE_DIR: &str = "KubeOS-Update"; +pub const MOUNT_DIR: &str = "kubeos-update"; +pub const OS_IMAGE_NAME: &str = "update.img"; + +pub const DEFAULT_KERNEL_CONFIG_PERM: u32 = 0o644; +pub const DEFAULT_GRUB_CFG_PERM: u32 = 0o751; +pub const IMAGE_PERMISSION: u32 = 0o600; + +pub const ONLY_KEY: usize = 1; +pub const KV_PAIR: usize = 2; +pub const NEED_GB_SIZE: i64 = 3; diff --git a/KubeOS-Rust/manager/src/utils/common.rs b/KubeOS-Rust/manager/src/utils/common.rs new file mode 100644 index 00000000..cc826dd4 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/common.rs @@ -0,0 +1,312 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + fs, + os::linux::fs::MetadataExt, + os::unix::fs::DirBuilderExt, + path::{Path, PathBuf}, +}; + +use anyhow::{anyhow, Result}; +use log::{debug, info}; +use nix::{mount, mount::MntFlags}; + +use super::executor::CommandExecutor; +use crate::sys_mgmt::{MOUNT_DIR, OS_IMAGE_NAME, PERSIST_DIR, ROOTFS_ARCHIVE, UPDATE_DIR}; + +#[derive(Clone)] +pub struct PreparePath { + pub update_path: PathBuf, // update_path: /persist/KubeOS-Update + pub mount_path: PathBuf, // mount_path: /persist/KubeOS-Update/kubeos-update + pub tar_path: PathBuf, // tar_path: /persist/KubeOS-Update/os.tar + pub image_path: PathBuf, // image_path: /persist/update.img + pub rootfs_file: String, // rootfs_file: os.tar +} + +impl Default for PreparePath { + fn default() -> Self { + let update_pathbuf = Path::new(PERSIST_DIR).join(UPDATE_DIR); + let persist_dir = Path::new(PERSIST_DIR); + Self { + update_path: update_pathbuf.clone(), + mount_path: update_pathbuf.join(MOUNT_DIR), + tar_path: update_pathbuf.join(ROOTFS_ARCHIVE), + image_path: persist_dir.join(OS_IMAGE_NAME), + rootfs_file: ROOTFS_ARCHIVE.to_string(), + } + } +} + +pub fn is_file_exist>(path: P) -> bool { + path.as_ref().exists() +} + +pub fn perpare_env( + prepare_path: &PreparePath, + need_gb: i64, + persist_path: &str, + permission: u32, +) -> Result<()> { + info!("Prepare environment to upgrade"); + check_disk_size(need_gb, persist_path)?; + clean_env( + &prepare_path.update_path, + &prepare_path.mount_path, + &prepare_path.image_path, + )?; + fs::DirBuilder::new() + .recursive(true) + .mode(permission) + .create(&prepare_path.mount_path)?; + Ok(()) +} + +pub fn check_disk_size(need_gb: i64, path: &str) -> Result<()> { + info!("Check if there is enough disk space to upgrade"); + let kb = 1024; + let fs_stat = nix::sys::statfs::statfs(path)?; + let need_disk_size = need_gb * kb * kb * kb; + let available_blocks = i64::try_from(fs_stat.blocks_available())?; + let available_space = available_blocks * fs_stat.block_size(); + if available_space < need_disk_size { + return Err(anyhow!("Space is not enough for downloading")); + } + Ok(()) +} + +// clean_env will umount the mount path and delete all files in /persist/KubeOS-Update and update.img +pub fn clean_env

(update_path: P, mount_path: P, image_path: P) -> Result<()> +where + P: AsRef, +{ + info!("Clean upgrade environment"); + if is_mounted(&mount_path)? { + debug!("Umount {}", mount_path.as_ref().display()); + if let Err(errno) = mount::umount2(mount_path.as_ref(), MntFlags::MNT_FORCE) { + return Err(anyhow!( + "Failed to umount {} in clean_env: {}", + mount_path.as_ref().display(), + errno + )); + } + } + // losetup -D? + delete_file_or_dir(update_path)?; + delete_file_or_dir(image_path)?; + Ok(()) +} + +pub fn delete_file_or_dir>(path: P) -> Result<()> { + if is_file_exist(&path) { + if fs::metadata(&path)?.is_file() { + debug!("Delete file {}", path.as_ref().display()); + fs::remove_file(&path)?; + } else { + debug!("Delete directory {}", path.as_ref().display()); + fs::remove_dir_all(&path)?; + } + } + Ok(()) +} + +pub fn is_command_available(command: &str, command_executor: &T) -> bool { + match command_executor.run_command( + "/bin/sh", + &["-c", format!("command -v {}", command).as_str()], + ) { + Ok(_) => { + debug!("command {} is available", command); + true + } + Err(_) => { + debug!("command {} is not available", command); + false + } + } +} + +pub fn is_mounted>(mount_path: P) -> Result { + if !is_file_exist(&mount_path) { + return Ok(false); + } + // Get device ID of mountPath + let mount_meta = fs::symlink_metadata(&mount_path)?; + let dev = mount_meta.st_dev(); + + // Get device ID of mountPath's parent directory + let parent = mount_path.as_ref().parent().ok_or_else(|| { + anyhow!( + "Failed to get parent directory of {}", + mount_path.as_ref().display() + ) + })?; + let parent_meta = fs::symlink_metadata(parent)?; + let dev_parent = parent_meta.st_dev(); + Ok(dev != dev_parent) +} + +pub fn switch_boot_menuentry( + command_executor: &T, + grub_env_path: &str, + next_menuentry: &str, +) -> Result<()> { + if get_boot_mode() == "uefi" { + command_executor.run_command( + "grub2-editenv", + &[ + grub_env_path, + "set", + format!("saved_entry={}", next_menuentry).as_str(), + ], + )?; + } else { + command_executor.run_command("grub2-set-default", &[next_menuentry])?; + } + Ok(()) +} + +pub fn get_boot_mode() -> String { + if is_file_exist("/sys/firmware/efi") { + "uefi".into() + } else { + "bios".into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::{mock, predicate::*}; + use tempfile::NamedTempFile; + use tempfile::TempDir; + + // Mock the CommandExecutor trait + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_is_file_exist() { + init(); + let path = "/tmp/test_is_file_exist"; + assert_eq!(is_file_exist(path), false); + + let file = NamedTempFile::new().unwrap(); + assert_eq!(is_file_exist(file.path().to_str().unwrap()), true); + + let tmp_dir = TempDir::new().unwrap(); + assert_eq!(is_file_exist(tmp_dir.path().to_str().unwrap()), true); + } + + #[test] + fn test_prepare_env() { + init(); + let paths = PreparePath { + update_path: PathBuf::from("/tmp/test_prepare_env"), + mount_path: PathBuf::from("/tmp/test_prepare_env/kubeos-update"), + tar_path: PathBuf::from("/tmp/test_prepare_env/os.tar"), + image_path: PathBuf::from("/tmp/test_prepare_env/update.img"), + rootfs_file: "os.tar".to_string(), + }; + perpare_env(&paths, 1, "/home", 0o700).unwrap(); + } + + #[test] + fn test_check_disk_size() { + init(); + let path = "/home"; + let need_gb = 1; + let result = check_disk_size(need_gb, path); + assert!(result.is_ok()); + let need_gb = 1000; + let result = check_disk_size(need_gb, path); + assert!(result.is_err()); + } + + #[test] + fn test_clean_env() { + init(); + let update_path = "/tmp/test_clean_env"; + let mount_path = "/tmp/test_clean_env/kubeos-update"; + let image_path = "/tmp/test_clean_env/update.img"; + clean_env( + &update_path.to_string(), + &mount_path.to_string(), + &image_path.to_string(), + ) + .unwrap(); + } + + #[test] + fn test_delete_file_or_dir() { + init(); + let path = "/tmp/test_delete_file"; + fs::File::create(path).unwrap(); + assert_eq!(Path::new(path).exists(), true); + delete_file_or_dir(&path.to_string()).unwrap(); + assert_eq!(Path::new(path).exists(), false); + + let path = "/tmp/test_dir"; + fs::create_dir(path).unwrap(); + assert_eq!(Path::new(path).exists(), true); + delete_file_or_dir(&path.to_string()).unwrap(); + assert_eq!(Path::new(path).exists(), false); + + let path = "/tmp/nonexist"; + delete_file_or_dir(path).unwrap(); + + let path = PathBuf::new(); + delete_file_or_dir(path).unwrap(); + } + + #[test] + fn test_switch_boot_menuentry() { + init(); + let grubenv_path = "/boot/efi/EFI/openEuler/grubenv"; + let next_menuentry = "B"; + let mut mock = MockCommandExec::new(); + mock.expect_run_command() + .withf(move |name, args| { + name == "grub2-editenv" + && args[0] == grubenv_path + && args[2] == format!("saved_entry={}", next_menuentry).as_str() + }) + .times(1) // Expect it to be called once + .returning(move |_, _| Ok(())); + + switch_boot_menuentry(&mock, grubenv_path, next_menuentry).unwrap() + } + + #[test] + #[ignore] + fn test_get_boot_mode() { + init(); + let boot_mode = get_boot_mode(); + assert!(boot_mode == "uefi"); + } +} diff --git a/KubeOS-Rust/manager/src/utils/container_image.rs b/KubeOS-Rust/manager/src/utils/container_image.rs new file mode 100644 index 00000000..8c5bfaf7 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/container_image.rs @@ -0,0 +1,271 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use anyhow::{anyhow, Result}; +use log::{debug, trace}; +use regex::Regex; + +use super::executor::CommandExecutor; + +pub fn is_valid_image_name(image: &str) -> Result<()> { + let pattern = r"^(?P[a-z0-9\-.]+\.[a-z0-9\-]+:?[0-9]*)?/?((?P[a-zA-Z0-9-_]+?)|(?P[a-zA-Z0-9-_]+?)/(?P[a-zA-Z-_]+?))(?P(?::[\w_.-]+)?|(?:@sha256:[a-fA-F0-9]+)?)$"; + let reg_ex = Regex::new(pattern)?; + if !reg_ex.is_match(image) { + return Err(anyhow!("Invalid image name: {}", image)); + } + trace!("Image name {} is valid", image); + Ok(()) +} + +pub fn check_oci_image_digest_match( + container_runtime: &str, + image_name: &str, + check_sum: &str, + command_executor: &T, +) -> Result<()> { + let image_digests = get_oci_image_digest(container_runtime, image_name, command_executor)?; + if image_digests != check_sum { + return Err(anyhow!( + "Image digest mismatch, expect {}, got {}", + check_sum, + image_digests + )); + } + Ok(()) +} + +pub fn get_oci_image_digest( + container_runtime: &str, + image_name: &str, + executor: &T, +) -> Result { + let cmd_output: String; + match container_runtime { + "crictl" => { + cmd_output = executor.run_command_with_output( + "crictl", + &[ + "inspecti", + "--output", + "go-template", + "--template", + "{{.status.repoDigests}}", + image_name, + ], + )?; + } + "docker" => { + cmd_output = executor.run_command_with_output( + "docker", + &["inspect", "--format", "{{.RepoDigests}}", image_name], + )?; + } + "ctr" => { + cmd_output = executor.run_command_with_output( + "ctr", + &[ + "-n", + "k8s.io", + "images", + "ls", + &format!("name=={}", image_name), + ], + )?; + // Split by whitespaces, we get vec like [REF TYPE DIGEST SIZE PLATFORMS LABELS x x x x x x] + // get the 8th element, and split by ':' to get the digest + let fields: Vec<&str> = cmd_output.split_whitespace().collect(); + if let Some(digest) = fields.get(8).and_then(|field| field.split(':').nth(1)) { + trace!("get_oci_image_digest: {}", digest); + return Ok(digest.to_string()); + } else { + return Err(anyhow!( + "Failed to get digest from ctr command output: {}", + cmd_output + )); + } + } + _ => { + return Err(anyhow!( + "Container runtime {} cannot be recognized", + container_runtime + )); + } + } + + // Parse the cmd_output to extract the digest + let parts: Vec<&str> = cmd_output.split('@').collect(); + if let Some(last_part) = parts.last() { + if last_part.starts_with("sha256") { + let parsed_parts: Vec<&str> = last_part.trim_matches(|c| c == ']').split(':').collect(); + // After spliiing by ':', we should get vec like [sha256, digests] + if parsed_parts.len() == 2 { + trace!("get_oci_image_digest: {}", parsed_parts[1]); + return Ok(parsed_parts[1].to_string()); // 1 is the index of digests + } + } + } + + Err(anyhow!( + "Failed to get digest from command output: {}", + cmd_output + )) +} + +pub fn pull_image(runtime: &str, image_name: &str, executor: &T) -> Result<()> { + debug!("Pull image {}", image_name); + match runtime { + "crictl" => { + executor.run_command("crictl", &["pull", image_name])?; + } + "ctr" => { + executor.run_command( + "ctr", + &[ + &"-n", + "k8s.io", + "images", + "pull", + "--hosts-dir", + "/etc/containerd/certs.d", + image_name, + ], + )?; + } + "docker" => { + executor.run_command("docker", &["pull", image_name])?; + } + _ => { + return Err(anyhow!( + "Container runtime {} cannot be recognized", + runtime + )); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::{mock, predicate::*}; + + // Mock the CommandExecutor trait + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_is_valid_image_name() { + init(); + let out = is_valid_image_name("nginx").unwrap(); + assert_eq!(out, ()); + let out = is_valid_image_name( + "docker.example.com:5000/gmr/alpine@sha256:11111111111111111111111111111111", + ) + .unwrap(); + assert_eq!(out, ()); + let out = is_valid_image_name( + "sosedoff/pgweb:latest@sha256:5a156ff125e5a12ac7ff43ee5120fa249cf62248337b6d04574c8", + ); + match out { + Ok(_) => assert_eq!(true, false), + Err(_) => assert_eq!(true, true), + } + } + + #[test] + fn test_get_oci_image_digest() { + init(); + let mut mock = MockCommandExec::new(); + let container_runtime = "ctr"; + let image_name = "docker.io/nginx:latest"; + let command_output1 = "REF TYPE DIGEST SIZE PLATFORMS LABELS\ndocker.io/nginx:latest text/html sha256:1111 132.5 KIB - -\n"; + mock.expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output1.to_string())); + let out1 = get_oci_image_digest(container_runtime, image_name, &mock).unwrap(); + let expect_output = "1111"; + assert_eq!(out1, expect_output); + + let container_runtime = "crictl"; + let command_output2 = "[docker.io/nginx@sha256:1111]"; + mock.expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output2.to_string())); + let out2 = get_oci_image_digest(container_runtime, image_name, &mock).unwrap(); + assert_eq!(out2, expect_output); + } + + #[test] + fn test_check_oci_image_digest_match() { + init(); + let mut mock = MockCommandExec::new(); + let image_name = "docker.io/nginx:latest"; + let container_runtime = "crictl"; + let command_output = "[docker.io/nginx@sha256:1111]"; + let check_sum = "1111"; + mock.expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output.to_string())); + let result = check_oci_image_digest_match(container_runtime, image_name, check_sum, &mock); + assert!(result.is_ok()); + } + + #[test] + fn test_pull_image() { + init(); + let mut mock_executor = MockCommandExec::new(); + + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "crictl" && args.len() == 2 && args[0] == "pull") // simplified with a closure + .times(1) + .returning(|_, _| Ok(())); + + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "ctr" && args.len() == 7 && args[3] == "pull") // simplified with a closure + .times(1) + .returning(|_, _| Ok(())); + + mock_executor + .expect_run_command() + .withf(|cmd, args| cmd == "docker" && args.len() == 2 && args[0] == "pull") // simplified with a closure + .times(1) + .returning(|_, _| Ok(())); + + let image_name = "docker.io/nginx:latest"; + let result = pull_image("crictl", image_name, &mock_executor); + assert!(result.is_ok()); + let result = pull_image("ctr", image_name, &mock_executor); + assert!(result.is_ok()); + let result = pull_image("docker", image_name, &mock_executor); + assert!(result.is_ok()); + let result = pull_image("aaa", image_name, &mock_executor); + assert!(result.is_err()); + } +} diff --git a/KubeOS-Rust/manager/src/utils/executor.rs b/KubeOS-Rust/manager/src/utils/executor.rs new file mode 100644 index 00000000..5c70c8e4 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/executor.rs @@ -0,0 +1,101 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::process::Command; + +use anyhow::{anyhow, Result}; +use log::trace; + +pub trait CommandExecutor: Clone { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; +} + +#[derive(Clone)] +pub struct RealCommandExecutor {} + +impl CommandExecutor for RealCommandExecutor { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()> { + let output = Command::new(name).args(args).output()?; + if !output.status.success() { + let error_message = String::from_utf8_lossy(&output.stderr); + return Err(anyhow!( + "Failed to run command: {} {:?}, stderr: {}", + name, + args, + error_message + )); + } + trace!("run_command: {} {:?} done", name, args); + Ok(()) + } + + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result { + let output = Command::new(name).args(args).output()?; + if !output.status.success() { + let error_message = String::from_utf8_lossy(&output.stderr); + return Err(anyhow!( + "Failed to run command: {} {:?}, stderr: {}", + name, + args, + error_message + )); + } + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + trace!("run_command_with_output: {} {:?} done", name, args); + Ok(stdout.trim_end_matches("\n").to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_run_command_with_output() { + init(); + let executor: RealCommandExecutor = RealCommandExecutor {}; + + // test run_command_with_output + let output = executor + .run_command_with_output("echo", &["hello", "world"]) + .unwrap(); + assert_eq!(output, "hello world"); + let out = executor + .run_command_with_output("sh", &["-c", format!("command -v {}", "cat").as_str()]) + .unwrap(); + assert_eq!(out, "/usr/bin/cat"); + let out = executor + .run_command_with_output("sh", &["-c", format!("command -v {}", "apple").as_str()]); + assert!(out.is_err()); + } + + #[test] + fn test_run_command() { + init(); + let executor: RealCommandExecutor = RealCommandExecutor {}; + // test run_command + let out = executor.run_command("sh", &["-c", format!("command -v {}", "apple").as_str()]); + assert!(out.is_err()); + + let out = executor.run_command("sh", &["-c", format!("command -v {}", "cat").as_str()]); + assert!(out.is_ok()); + } +} diff --git a/KubeOS-Rust/manager/src/utils/image_manager.rs b/KubeOS-Rust/manager/src/utils/image_manager.rs new file mode 100644 index 00000000..42b5a745 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/image_manager.rs @@ -0,0 +1,260 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + fs::{self, Permissions}, + os::unix::fs::PermissionsExt, + path::PathBuf, +}; + +use anyhow::{Context, Result}; +use log::{debug, info}; + +use super::{ + clean_env, + common::{delete_file_or_dir, PreparePath}, + executor::CommandExecutor, + partition::PartitionInfo, +}; +use crate::{sys_mgmt::DEFAULT_GRUBENV_PATH, utils::switch_boot_menuentry}; + +pub struct UpgradeImageManager { + pub paths: PreparePath, + pub next_partition: PartitionInfo, + pub executor: T, +} + +impl UpgradeImageManager { + pub fn new(paths: PreparePath, next_partition: PartitionInfo, executor: T) -> Self { + Self { + paths, + next_partition, + executor, + } + } + + fn image_path_str(&self) -> Result<&str> { + self.paths + .image_path + .to_str() + .context("Failed to convert image path to string") + } + + fn mount_path_str(&self) -> Result<&str> { + self.paths + .mount_path + .to_str() + .context("Failed to convert mount path to string") + } + + fn tar_path_str(&self) -> Result<&str> { + self.paths + .tar_path + .to_str() + .context("Failed to convert tar path to string") + } + + pub fn create_image_file(&self, permission: u32) -> Result<()> { + let image_str = self.image_path_str()?; + + debug!("Create image {}", image_str); + self.executor.run_command( + "dd", + &[ + "if=/dev/zero", + &format!("of={}", image_str), + "bs=2M", + "count=1024", + ], + )?; + fs::set_permissions(&self.paths.image_path, Permissions::from_mode(permission))?; + Ok(()) + } + + pub fn format_image(&self) -> Result<()> { + let image_str = self.image_path_str()?; + debug!("Format image {}", image_str); + self.executor.run_command( + format!("mkfs.{}", self.next_partition.fs_type).as_str(), + &[ + "-L", + format!("ROOT-{}", self.next_partition.menuentry).as_str(), + image_str, + ], + )?; + Ok(()) + } + + pub fn mount_image(&self) -> Result<()> { + let image_str = self.image_path_str()?; + let mount_str = self.mount_path_str()?; + debug!("Mount {} to {}", image_str, mount_str); + self.executor + .run_command("mount", &["-o", "loop", image_str, mount_str])?; + Ok(()) + } + + pub fn extract_tar_to_image(&self) -> Result<()> { + let tar_str = self.tar_path_str()?; + let mount_str = self.mount_path_str()?; + debug!("Extract {} to mounted path {}", tar_str, mount_str); + self.executor + .run_command("tar", &["-xvf", tar_str, "-C", mount_str])?; + Ok(()) + } + + pub fn create_os_image(self, permission: u32) -> Result { + self.create_image_file(permission)?; + self.format_image()?; + self.mount_image()?; + self.extract_tar_to_image()?; + // Pass empty image_path to clean_env to avoid delete image file + clean_env( + &self.paths.update_path, + &self.paths.mount_path, + &PathBuf::new(), + )?; + Ok(self) + } + + pub fn install(&self) -> Result<()> { + let image_str = self.image_path_str()?; + let device = self.next_partition.device.as_str(); + let menuentry = self.next_partition.menuentry.as_str(); + self.executor.run_command( + "dd", + &[ + format!("if={}", image_str).as_str(), + format!("of={}", device).as_str(), + "bs=8M", + ], + )?; + debug!("Install image {} to {} done", image_str, device); + // based on boot mode use different command to switch boot partition + switch_boot_menuentry(&self.executor, DEFAULT_GRUBENV_PATH, menuentry)?; + info!( + "Switch to boot partition: {}, device: {}", + menuentry, device + ); + delete_file_or_dir(image_str)?; + debug!("Remove image {}", image_str); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::{mock, predicate::*}; + use std::{fs, io::Write, path::Path}; + use tempfile::NamedTempFile; + + // Mock the CommandExecutor trait + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_update_image_manager() { + init(); + // create a dir in tmp dir + let tmp_dir = "/tmp/test_update_image_manager"; + let img_path = format!("{}/test_image", tmp_dir); + let mut temp_file = NamedTempFile::new().unwrap(); + write!(temp_file, "test content").unwrap(); // Writing s + fs::create_dir(tmp_dir).unwrap(); + let clone_img_path = img_path.clone(); + + let mut mock = MockCommandExec::new(); + //mock create_image_file + mock.expect_run_command() + .withf(|name, args| name == "dd" && args[0] == "if=/dev/zero") + .times(1) // Expect it to be called once + .returning(move |_, _| { + // simulate 'dd' by copying the contents of the temporary file + std::fs::copy(temp_file.path(), &clone_img_path).unwrap(); + Ok(()) + }); + + //mock format_image + mock.expect_run_command() + .withf(|name, args| name == "mkfs.ext4" && args[1] == "ROOT-B") + .times(1) // Expect it to be called once + .returning(|_, _| Ok(())); + + //mock mount_image + mock.expect_run_command() + .withf(|name, _| name == "mount") + .times(1) // Expect it to be called once + .returning(|_, _| Ok(())); + + //mock extract_tar_to_image + mock.expect_run_command() + .withf(|name, args| name == "tar" && args[0] == "-xvf") + .times(1) // Expect it to be called once + .returning(|_, _| Ok(())); + + //mock install->dd + mock.expect_run_command() + .withf(|name, _| name == "dd") + .times(1) // Expect it to be called once + .returning(|_, _| Ok(())); + + //mock install->grub2-set-default + mock.expect_run_command() + .withf(|name, args| { + name == "grub2-editenv" + && args[0] == "/boot/efi/EFI/openEuler/grubenv" + && args[1] == "set" + && args[2] == "saved_entry=B" + }) + .times(1) // Expect it to be called once + .returning(|_, _| Ok(())); + + let img_manager = UpgradeImageManager::new( + PreparePath { + update_path: tmp_dir.into(), + image_path: img_path.into(), + mount_path: "/tmp/update/mount".into(), + tar_path: "/tmp/update/image.tar".into(), + rootfs_file: "image.tar".into(), + }, + PartitionInfo { + device: "/dev/sda3".into(), + fs_type: "ext4".into(), + menuentry: "B".into(), + }, + mock, + ); + + let img_manager = img_manager.create_os_image(0o755).unwrap(); + let result = img_manager.install(); + assert!(result.is_ok()); + + assert_eq!(Path::new(&tmp_dir).exists(), false); + } +} diff --git a/KubeOS-Rust/manager/src/utils/mod.rs b/KubeOS-Rust/manager/src/utils/mod.rs new file mode 100644 index 00000000..caf406e3 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +mod common; +mod container_image; +mod executor; +mod image_manager; +mod partition; + +pub use common::*; +pub use container_image::*; +pub use executor::*; +pub use image_manager::*; +pub use partition::*; diff --git a/KubeOS-Rust/manager/src/utils/partition.rs b/KubeOS-Rust/manager/src/utils/partition.rs new file mode 100644 index 00000000..8d59e174 --- /dev/null +++ b/KubeOS-Rust/manager/src/utils/partition.rs @@ -0,0 +1,110 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use anyhow::{anyhow, Result}; +use log::{debug, trace}; + +use super::executor::CommandExecutor; + +#[derive(PartialEq, Debug, Default)] +pub struct PartitionInfo { + pub device: String, + pub menuentry: String, + pub fs_type: String, +} + +pub fn get_partition_info( + executor: &T, +) -> Result<(PartitionInfo, PartitionInfo), anyhow::Error> { + let lsblk = executor.run_command_with_output("lsblk", &["-lno", "NAME,MOUNTPOINTS,FSTYPE"])?; + // After split whitespace, the root directory line should have 3 elements, which are "sda2 / ext4". + let mut cur_partition = PartitionInfo::default(); + let mut next_partition = PartitionInfo::default(); + let splitted_len = 3; + trace!("get_partition_info lsblk command output:\n{}", lsblk); + for line in lsblk.lines() { + let res: Vec<&str> = line.split_whitespace().collect(); + if res.len() == splitted_len && res[1] == "/" { + debug!("root directory line: device={}, fs_type={}", res[0], res[2]); + cur_partition.device = format!("/dev/{}", res[0]).to_string(); + cur_partition.fs_type = res[2].to_string(); + next_partition.fs_type = res[2].to_string(); + if res[0].contains("2") { + cur_partition.menuentry = String::from("A"); + next_partition.menuentry = String::from("B"); + next_partition.device = format!("/dev/{}", res[0].replace("2", "3")).to_string(); + } else if res[0].contains("3") { + cur_partition.menuentry = String::from("B"); + next_partition.menuentry = String::from("A"); + next_partition.device = format!("/dev/{}", res[0].replace("3", "2")).to_string(); + } + } + } + if cur_partition.device.is_empty() { + return Err(anyhow!( + "Failed to get partition info, lsblk output: {}", + lsblk + )); + } + Ok((cur_partition, next_partition)) +} + +#[cfg(test)] +mod tests { + use super::*; + use mockall::{mock, predicate::*}; + + // Mock the CommandExecutor trait + mock! { + pub CommandExec{} + impl CommandExecutor for CommandExec { + fn run_command<'a>(&self, name: &'a str, args: &[&'a str]) -> Result<()>; + fn run_command_with_output<'a>(&self, name: &'a str, args: &[&'a str]) -> Result; + } + impl Clone for CommandExec { + fn clone(&self) -> Self; + } + } + + fn init() { + let _ = env_logger::builder() + .target(env_logger::Target::Stdout) + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + } + + #[test] + fn test_get_partition_info() { + init(); + let command_output1 = + "sda\nsda1 /boot/efi vfat\nsda2 / ext4\nsda3 ext4\nsda4 /persist ext4\nsr0 iso9660\n"; + let mut mock = MockCommandExec::new(); + mock.expect_run_command_with_output() + .times(1) + .returning(|_, _| Ok(command_output1.to_string())); + let res = get_partition_info(&mock).unwrap(); + let expect_res = ( + PartitionInfo { + device: "/dev/sda2".to_string(), + menuentry: "A".to_string(), + fs_type: "ext4".to_string(), + }, + PartitionInfo { + device: "/dev/sda3".to_string(), + menuentry: "B".to_string(), + fs_type: "ext4".to_string(), + }, + ); + assert_eq!(res, expect_res); + } +} diff --git a/KubeOS-Rust/proxy/Cargo.toml b/KubeOS-Rust/proxy/Cargo.toml new file mode 100644 index 00000000..58cb1f51 --- /dev/null +++ b/KubeOS-Rust/proxy/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "proxy" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +kube = { version = "0.66.0", features = ["runtime", "derive"] } +k8s-openapi = { version = "0.13.1", features = ["v1_22"] } +tokio = { version = "=1.14.0", features = ["rt-multi-thread", "macros"] } +anyhow = "1.0.44" +futures = "0.3.17" +serde = { version = "1.0.130", features = ["derive"] } +serde_json = "1.0.68" +thiserror = "1.0.29" +env_logger = "0.9.0" +tracing = "0.1.29" +schemars = "=0.8.10" +socket2 = "=0.4.9" +log = "=0.4.15" +thread_local = "=1.1.4" +async-trait = "0.1" +regex = "=1.7.3" +chrono = { version = "0.4", default-features = false, features = ["std"] } +snafu = "0.7" +h2 = "=0.3.16" +tokio-retry = "0.3" +reqwest = { version = "=0.11.10", default-features = false, features = [ "json" ] } +cli = { version = "0.1.0", path = "../cli" } +manager = { version = "0.1.0", path = "../manager" } diff --git a/KubeOS-Rust/proxy/src/controller/apiclient.rs b/KubeOS-Rust/proxy/src/controller/apiclient.rs new file mode 100644 index 00000000..5ee28004 --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/apiclient.rs @@ -0,0 +1,172 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::crd::{OSInstance, OSInstanceSpec, OSInstanceStatus}; +use super::values::{LABEL_OSINSTANCE, NODE_STATUS_IDLE, OSINSTANCE_API_VERSION, OSINSTANCE_KIND}; +use anyhow::Result; +use apiclient_error::Error; +use async_trait::async_trait; +use kube::{ + api::{Api, ObjectMeta, Patch, PatchParams, PostParams}, + Client, +}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Debug, Serialize, Deserialize)] +struct OSInstanceSpecPatch { + #[serde(rename = "apiVersion")] + api_version: String, + kind: String, + spec: OSInstanceSpec, +} + +impl Default for OSInstanceSpecPatch { + fn default() -> Self { + OSInstanceSpecPatch { + api_version: OSINSTANCE_API_VERSION.to_string(), + kind: OSINSTANCE_KIND.to_string(), + spec: OSInstanceSpec { + nodestatus: NODE_STATUS_IDLE.to_string(), + sysconfigs: None, + upgradeconfigs: None, + }, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct OSInstanceStatusPatch { + #[serde(rename = "apiVersion")] + api_version: String, + kind: String, + status: Option, +} + +impl Default for OSInstanceStatusPatch { + fn default() -> Self { + OSInstanceStatusPatch { + api_version: OSINSTANCE_API_VERSION.to_string(), + kind: OSINSTANCE_KIND.to_string(), + status: Some(OSInstanceStatus { + sysconfigs: None, + upgradeconfigs: None, + }), + } + } +} + +#[derive(Clone)] +pub struct ControllerClient { + pub client: Client, +} + +impl ControllerClient { + pub fn new(client: Client) -> Self { + ControllerClient { client } + } +} + +#[async_trait] +pub trait ApplyApi: Clone + Sized + Send + Sync { + async fn create_osinstance(&self, node_name: &str, namespace: &str) -> Result<(), Error>; + async fn update_osinstance_spec( + &self, + node_name: &str, + namespace: &str, + spec: &OSInstanceSpec, + ) -> Result<(), Error>; + async fn update_osinstance_status( + &self, + node_name: &str, + namespace: &str, + status: &Option, + ) -> Result<(), Error>; +} + +#[async_trait] +impl ApplyApi for ControllerClient { + async fn create_osinstance(&self, node_name: &str, namespace: &str) -> Result<(), Error> { + let mut labels = BTreeMap::new(); + labels.insert(LABEL_OSINSTANCE.to_string(), node_name.to_string()); + let osinstance = OSInstance { + metadata: ObjectMeta { + name: Some(node_name.to_string()), + namespace: Some(namespace.to_string()), + labels: Some(labels), + ..ObjectMeta::default() + }, + spec: OSInstanceSpec { + nodestatus: NODE_STATUS_IDLE.to_string(), + sysconfigs: None, + upgradeconfigs: None, + }, + status: None, + }; + let osi_api = Api::namespaced(self.client.clone(), namespace); + osi_api.create(&PostParams::default(), &osinstance).await?; + Ok(()) + } + + async fn update_osinstance_spec( + &self, + node_name: &str, + namespace: &str, + spec: &OSInstanceSpec, + ) -> Result<(), Error> { + let osi_api: Api = Api::namespaced(self.client.clone(), namespace); + let osi_spec_patch = OSInstanceSpecPatch { + spec: spec.clone(), + ..Default::default() + }; + osi_api + .patch( + node_name, + &PatchParams::default(), + &Patch::Merge(&osi_spec_patch), + ) + .await?; + Ok(()) + } + + async fn update_osinstance_status( + &self, + node_name: &str, + namespace: &str, + status: &Option, + ) -> Result<(), Error> { + let osi_api: Api = Api::namespaced(self.client.clone(), namespace); + let osi_status_patch = OSInstanceStatusPatch { + status: status.clone(), + ..Default::default() + }; + osi_api + .patch_status( + node_name, + &PatchParams::default(), + &Patch::Merge(&osi_status_patch), + ) + .await?; + Ok(()) + } +} +pub mod apiclient_error { + use thiserror::Error; + #[derive(Error, Debug)] + pub enum Error { + #[error("Kubernetes reported error: {source}")] + KubeError { + #[from] + source: kube::Error, + }, + } +} diff --git a/KubeOS-Rust/proxy/src/controller/controller.rs b/KubeOS-Rust/proxy/src/controller/controller.rs new file mode 100644 index 00000000..3a0f1a7c --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/controller.rs @@ -0,0 +1,502 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::crd::{Content, OSInstance, OS}; +use super::drain::drain_os; +use super::utils::{check_version, get_config_version, ConfigOperation, ConfigType}; +use super::values::{ + LABEL_UPGRADING, NODE_STATUS_CONFIG, NODE_STATUS_IDLE, OPERATION_TYPE_ROLLBACK, + OPERATION_TYPE_UPGRADE, REQUEUE_ERROR, REQUEUE_NORMAL, +}; +use super::{ + apiclient::{ApplyApi, ControllerClient}, + crd::Configs, +}; +use anyhow::Result; +use cli::{ + client::Client as AgentClient, + method::{ + callable_method::RpcMethod, cleanup::CleanupMethod, configure::ConfigureMethod, + prepare_upgrade::PrepareUpgradeMethod, rollback::RollbackMethod, upgrade::UpgradeMethod, + }, +}; +use k8s_openapi::api::core::v1::Node; +use kube::{ + api::{Api, PostParams}, + core::ErrorResponse, + runtime::controller::{Context, ReconcilerAction}, + Client, ResourceExt, +}; +use log::{debug, error, info}; +use manager::api::{ConfigureRequest, KeyInfo, Sysconfig as AgentSysconfig, UpgradeRequest}; +use reconciler_error::Error; +use std::collections::HashMap; +use std::env; + +pub async fn reconcile( + os: OS, + ctx: Context>, +) -> Result { + debug!("start reconcile"); + let proxy_controller = ctx.get_ref(); + let os_cr = &os; + let node_name = env::var("NODE_NAME")?; + let namespace: String = os_cr.namespace().ok_or(Error::MissingObjectKey { + resource: "os".to_string(), + value: "namespace".to_string(), + })?; + proxy_controller + .check_osi_exisit(&namespace, &node_name) + .await?; + let controller_res = proxy_controller + .get_resources(&namespace, &node_name) + .await?; + let node = controller_res.node; + let mut osinstance = controller_res.osinstance; + let node_os_image = &node + .status + .as_ref() + .ok_or(Error::MissingSubResource { + value: String::from("node.status"), + })? + .node_info + .as_ref() + .ok_or(Error::MissingSubResource { + value: String::from("node.status.node_info"), + })? + .os_image; + debug!( + "os expected osversion is {},actual osversion is {}", + os_cr.spec.osversion, node_os_image + ); + if check_version(&os_cr.spec.osversion, &node_os_image) { + match ConfigType::SysConfig.check_config_version(&os, &osinstance) { + ConfigOperation::Reassign => { + debug!("start reassign"); + proxy_controller + .refresh_node( + node, + osinstance, + &get_config_version(os_cr.spec.sysconfigs.as_ref()), + ConfigType::SysConfig, + ) + .await?; + return Ok(REQUEUE_NORMAL); + } + ConfigOperation::UpdateConfig => { + debug!("start update config"); + osinstance.spec.sysconfigs = os_cr.spec.sysconfigs.clone(); + proxy_controller + .controller_client + .update_osinstance_spec(&osinstance.name(), &namespace, &osinstance.spec) + .await?; + return Ok(REQUEUE_ERROR); + } + _ => {} + } + proxy_controller + .set_config(&mut osinstance, ConfigType::SysConfig) + .await?; + proxy_controller + .refresh_node( + node, + osinstance, + &get_config_version(os_cr.spec.sysconfigs.as_ref()), + ConfigType::SysConfig, + ) + .await?; + } else { + if os_cr.spec.opstype == NODE_STATUS_CONFIG { + return Err(Error::UpgradeBeforeConfig); + } + match ConfigType::UpgradeConfig.check_config_version(&os, &osinstance) { + ConfigOperation::Reassign => { + debug!("start reassign"); + proxy_controller + .refresh_node( + node, + osinstance, + &get_config_version(os_cr.spec.upgradeconfigs.as_ref()), + ConfigType::UpgradeConfig, + ) + .await?; + return Ok(REQUEUE_NORMAL); + } + _ => {} + } + if node.labels().contains_key(LABEL_UPGRADING) { + if osinstance.spec.nodestatus == NODE_STATUS_IDLE { + info!( + "node has upgrade label ,but osinstance.spec.nodestatus is idle. Operation:refesh node and wait reassgin" + ); + proxy_controller + .refresh_node( + node, + osinstance, + &get_config_version(os_cr.spec.upgradeconfigs.as_ref()), + ConfigType::UpgradeConfig, + ) + .await?; + return Ok(REQUEUE_NORMAL); + } + proxy_controller + .set_config(&mut osinstance, ConfigType::UpgradeConfig) + .await?; + proxy_controller.upgrade_node(os_cr, &node).await?; + } + } + Ok(REQUEUE_NORMAL) +} + +pub fn error_policy( + error: &Error, + _ctx: Context>, +) -> ReconcilerAction { + error!("Reconciliation error:{}", error.to_string()); + REQUEUE_ERROR +} + +struct ControllerResources { + osinstance: OSInstance, + node: Node, +} +pub struct ProxyController { + k8s_client: Client, + controller_client: T, + agent_client: AgentClient, +} + +impl ProxyController { + pub fn new(k8s_client: Client, controller_client: T, agent_client: AgentClient) -> Self { + ProxyController { + k8s_client, + controller_client, + agent_client, + } + } +} + +impl ProxyController { + async fn check_osi_exisit(&self, namespace: &str, node_name: &str) -> Result<(), Error> { + let osi_api: Api = Api::namespaced(self.k8s_client.clone(), namespace); + match osi_api.get(node_name).await { + Ok(osi) => { + debug!("osinstance is exist {:?}", osi.name()); + return Ok(()); + } + Err(kube::Error::Api(ErrorResponse { reason, .. })) if &reason == "NotFound" => { + info!("Create OSInstance {}", node_name); + self.controller_client + .create_osinstance(node_name, namespace) + .await?; + Ok(()) + } + Err(err) => Err(Error::KubeError { source: err }), + } + } + + async fn get_resources( + &self, + namespace: &str, + node_name: &str, + ) -> Result { + let osi_api: Api = Api::namespaced(self.k8s_client.clone(), namespace); + let osinstance_cr = osi_api.get(node_name).await?; + let node_api: Api = Api::all(self.k8s_client.clone()); + let node_cr = node_api.get(node_name).await?; + Ok(ControllerResources { + osinstance: osinstance_cr, + node: node_cr, + }) + } + + async fn refresh_node( + &self, + mut node: Node, + osinstance: OSInstance, + os_config_version: &str, + config_type: ConfigType, + ) -> Result<(), Error> { + debug!("start refresh_node"); + let node_api: Api = Api::all(self.k8s_client.clone()); + let labels = node.labels_mut(); + if labels.contains_key(LABEL_UPGRADING) { + labels.remove(LABEL_UPGRADING); + node = node_api + .replace(&node.name(), &PostParams::default(), &node) + .await?; + } + if let Some(node_spec) = &node.spec { + if let Some(node_unschedulable) = node_spec.unschedulable { + if node_unschedulable { + node_api.uncordon(&node.name()).await?; + info!("Uncordon successfully node{}", node.name()); + } + } + } + self.update_node_status(osinstance, os_config_version, config_type) + .await?; + Ok(()) + } + + async fn update_node_status( + &self, + mut osinstance: OSInstance, + os_config_version: &str, + config_type: ConfigType, + ) -> Result<(), Error> { + debug!("start update_node_status"); + if osinstance.spec.nodestatus == NODE_STATUS_IDLE { + return Ok(()); + } + let upgradeconfig_spec_version = + get_config_version(osinstance.spec.upgradeconfigs.as_ref()); + let sysconfig_spec_version = get_config_version(osinstance.spec.sysconfigs.as_ref()); + let sysconfig_status_version: String; + if let Some(osinstance_status) = osinstance.status.as_ref() { + sysconfig_status_version = get_config_version(osinstance_status.sysconfigs.as_ref()); + } else { + sysconfig_status_version = get_config_version(None); + } + if sysconfig_spec_version == sysconfig_status_version + || (config_type == ConfigType::SysConfig && os_config_version != sysconfig_spec_version) + || (config_type == ConfigType::UpgradeConfig + && os_config_version != upgradeconfig_spec_version) + { + let namespace = osinstance.namespace().ok_or(Error::MissingObjectKey { + resource: String::from("osinstance"), + value: String::from("namespace"), + })?; + osinstance.spec.nodestatus = NODE_STATUS_IDLE.to_string(); + self.controller_client + .update_osinstance_spec(&osinstance.name(), &namespace, &osinstance.spec) + .await?; + } + Ok(()) + } + + async fn update_osi_status( + &self, + osinstance: &mut OSInstance, + config_type: ConfigType, + ) -> Result<(), Error> { + debug!("start update_osi_status"); + config_type.set_osi_status_config(osinstance); + debug!("osinstance status is update to {:?}", osinstance.status); + let namespace = &osinstance.namespace().ok_or(Error::MissingObjectKey { + resource: "osinstance".to_string(), + value: "namespace".to_string(), + })?; + self.controller_client + .update_osinstance_status(&osinstance.name(), &namespace, &osinstance.status) + .await?; + Ok(()) + } + + async fn set_config( + &self, + osinstance: &mut OSInstance, + config_type: ConfigType, + ) -> Result<(), Error> { + debug!("start set_config"); + let config_info = config_type.check_config_start(osinstance); + if config_info.need_config { + match config_info.configs.and_then(convert_to_agent_config) { + Some(agent_configs) => { + let config_request = ConfigureRequest { + configs: agent_configs, + }; + match ConfigureMethod::new(config_request).call(&self.agent_client) { + Ok(_resp) => {} + Err(e) => { + return Err(Error::AgentError { source: e }); + } + } + } + None => { + info!("config is none, no need to config"); + } + }; + self.update_osi_status(osinstance, config_type).await?; + } + Ok(()) + } + + async fn upgrade_node(&self, os_cr: &OS, node: &Node) -> Result<(), Error> { + debug!("start upgrade node"); + + match os_cr.spec.opstype.as_str() { + OPERATION_TYPE_UPGRADE => { + let upgrade_request = UpgradeRequest { + version: os_cr.spec.osversion.clone(), + image_type: os_cr.spec.imagetype.clone(), + check_sum: os_cr.spec.checksum.clone(), + container_image: os_cr.spec.containerimage.clone(), + }; + match PrepareUpgradeMethod::new(upgrade_request).call(&self.agent_client) { + Ok(_resp) => {} + Err(e) => { + return Err(Error::AgentError { source: e }); + } + } + match self + .evict_node(&node.name(), os_cr.spec.evictpodforce) + .await + { + Ok(()) => {} + Err(e) => { + match CleanupMethod::new().call(&self.agent_client) { + Ok(_resp) => {} + Err(agent_error) => { + return Err(Error::AgentError { + source: agent_error, + }); + } + } + return Err(e); + } + } + match UpgradeMethod::new().call(&self.agent_client) { + Ok(_resp) => {} + Err(e) => { + return Err(Error::AgentError { source: e }); + } + } + } + OPERATION_TYPE_ROLLBACK => { + self.evict_node(&node.name(), os_cr.spec.evictpodforce) + .await?; + match RollbackMethod::new().call(&self.agent_client) { + Ok(_resp) => {} + Err(e) => { + return Err(Error::AgentError { source: e }); + } + } + } + _ => { + return Err(Error::OperationError { + value: os_cr.spec.opstype.clone(), + }); + } + } + Ok(()) + } + + async fn evict_node(&self, node_name: &str, evict_pod_force: bool) -> Result<(), Error> { + debug!("start evict_node"); + let node_api = Api::all(self.k8s_client.clone()); + node_api.cordon(node_name).await?; + info!("Cordon node Successfully{}, start drain nodes", node_name); + match self.drain_node(node_name, evict_pod_force).await { + Ok(()) => {} + Err(e) => { + node_api.uncordon(node_name).await?; + info!("Drain node {} error, uncordon node successfully", node_name); + return Err(e); + } + } + Ok(()) + } + + async fn drain_node(&self, node_name: &str, force: bool) -> Result<(), Error> { + use crate::controller::drain::error::DrainError::*; + match drain_os(&self.k8s_client.clone(), node_name, force).await { + Err(FindTargetPods { source, .. }) => Err(Error::KubeError { source: source }), + Err(DeletePodsError { errors, .. }) => Err(Error::DrainNodeError { + value: errors.join("; "), + }), + _ => Ok(()), + } + } +} + +fn convert_to_agent_config(configs: Configs) -> Option> { + let mut agent_configs: Vec = Vec::new(); + if let Some(config_list) = configs.configs { + for config in config_list.into_iter() { + match config.contents.and_then(convert_to_config_hashmap) { + Some(contents_tmp) => { + let config_tmp = AgentSysconfig { + model: config.model.unwrap_or_default(), + config_path: config.configpath.unwrap_or_default(), + contents: contents_tmp, + }; + agent_configs.push(config_tmp) + } + None => { + info!("model {} which has configpath {} do not has any contents no need to configure",config.model.unwrap_or_default(),config.configpath.unwrap_or_default()); + continue; + } + }; + } + if agent_configs.len() == 0 { + info!("no contents in all models, no need to configure"); + return None; + } + return Some(agent_configs); + } + return None; +} + +fn convert_to_config_hashmap(contents: Vec) -> Option> { + let mut contents_tmp: HashMap = HashMap::new(); + for content in contents.into_iter() { + let key_info = KeyInfo { + value: content.value.unwrap_or_default(), + operation: content.operation.unwrap_or_default(), + }; + contents_tmp.insert(content.key.unwrap_or_default(), key_info); + } + return Some(contents_tmp); +} + +pub mod reconciler_error { + use crate::controller::apiclient::apiclient_error; + use thiserror::Error; + #[derive(Error, Debug)] + pub enum Error { + #[error("Kubernetes reported error: {source}")] + KubeError { + #[from] + source: kube::Error, + }, + + #[error("Create/Patch OSInstance reported error: {source}")] + ApplyApiError { + #[from] + source: apiclient_error::Error, + }, + + #[error("Cannot get environment NODE_NAME, error: {source}")] + EnvError { + #[from] + source: std::env::VarError, + }, + + #[error("{}.metadata.{} is not exist", resource, value)] + MissingObjectKey { resource: String, value: String }, + + #[error("Cannot get {}, {} is None", value, value)] + MissingSubResource { value: String }, + + #[error("operation {} cannot be recognized", value)] + OperationError { value: String }, + + #[error("Expect OS Version is not same with Node OS Version, please upgrade first")] + UpgradeBeforeConfig, + + #[error("os-agent reported error:{source}")] + AgentError { source: anyhow::Error }, + #[error("Error when drain node, error reported: {}", value)] + DrainNodeError { value: String }, + } +} diff --git a/KubeOS-Rust/proxy/src/controller/crd.rs b/KubeOS-Rust/proxy/src/controller/crd.rs new file mode 100644 index 00000000..9f01a964 --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/crd.rs @@ -0,0 +1,77 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +#[derive(CustomResource, Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[kube( + group = "upgrade.openeuler.org", + version = "v1alpha1", + kind = "OS", + plural = "os", + singular = "os", + namespaced +)] +pub struct OSSpec { + pub osversion: String, + pub maxunavailable: i64, + pub checksum: String, + pub imagetype: String, + pub containerimage: String, + pub opstype: String, + pub evictpodforce: bool, + pub sysconfigs: Option, + pub upgradeconfigs: Option, +} + +#[derive(CustomResource, Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[kube( + group = "upgrade.openeuler.org", + version = "v1alpha1", + kind = "OSInstance", + plural = "osinstances", + singular = "osinstance", + status = "OSInstanceStatus", + namespaced +)] +pub struct OSInstanceSpec { + pub nodestatus: String, + pub sysconfigs: Option, + pub upgradeconfigs: Option, +} + +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct OSInstanceStatus { + pub sysconfigs: Option, + pub upgradeconfigs: Option, +} + +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct Configs { + pub version: Option, + pub configs: Option>, +} + +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct Config { + pub model: Option, + pub configpath: Option, + pub contents: Option>, +} + +#[derive(Clone, Deserialize, Serialize, Debug, Eq, PartialEq, JsonSchema)] +pub struct Content { + pub key: Option, + pub value: Option, + pub operation: Option, +} diff --git a/KubeOS-Rust/proxy/src/controller/drain.rs b/KubeOS-Rust/proxy/src/controller/drain.rs new file mode 100644 index 00000000..a586d949 --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/drain.rs @@ -0,0 +1,650 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use futures::{stream, StreamExt}; +use k8s_openapi::api::core::v1::{Pod, PodSpec, PodStatus}; +use kube::{ + api::{EvictParams, ListParams}, + core::ObjectList, + Api, Client, ResourceExt, +}; +use reqwest::StatusCode; +use tokio::time::{sleep, Duration, Instant}; +use tokio_retry::{ + strategy::{jitter, ExponentialBackoff}, + RetryIf, +}; +use tracing::{event, Level}; + +use self::error::DrainError; +use super::values::{ + EVERY_DELETION_CHECK, EVERY_EVICTION_RETRY, MAX_EVICT_POD_NUM, MAX_RETRIES_TIMES, + RETRY_BASE_DELAY, RETRY_MAX_DELAY, TIMEOUT, +}; + +pub(crate) async fn drain_os( + client: &Client, + node_name: &str, + force: bool, +) -> Result<(), error::DrainError> { + let pods_list = get_pods_deleted(client, node_name, force).await?; + + stream::iter(pods_list) + .for_each_concurrent(MAX_EVICT_POD_NUM, move |pod| { + let k8s_client = client.clone(); + async move { + if evict_pod(&k8s_client, &pod, force).await.is_ok() { + wait_for_deletion(&k8s_client, &pod).await.ok(); + } + } + }) + .await; + + Ok(()) +} + +async fn get_pods_deleted( + client: &Client, + node_name: &str, + force: bool, +) -> Result, error::DrainError> { + let lp = ListParams { + field_selector: Some(format!("spec.nodeName={}", node_name)), + ..Default::default() + }; + let pods_api: Api = Api::all(client.clone()); + let pods: ObjectList = match pods_api.list(&lp).await { + Ok(pods @ ObjectList { .. }) => pods, + Err(err) => { + return Err(DrainError::FindTargetPods { + source: err, + node_name: node_name.to_string(), + }); + } + }; + let mut filterd_pods_list: Vec = Vec::new(); + let mut filterd_err: Vec = Vec::new(); + let pod_filter = CombinedFilter::new(force); + for pod in pods.into_iter() { + let filter_result = pod_filter.filter(&pod); + if filter_result.status == PodDeleteStatus::Error { + filterd_err.push(filter_result.desc); + continue; + } + if filter_result.result { + filterd_pods_list.push(pod); + } + } + if filterd_err.len() > 0 { + return Err(error::DrainError::DeletePodsError { + errors: filterd_err, + }); + } + Ok(filterd_pods_list.into_iter()) +} + +async fn evict_pod( + k8s_client: &kube::Client, + pod: &Pod, + force: bool, +) -> Result<(), error::EvictionError> { + let pod_api: Api = get_pod_api_with_namespace(k8s_client, pod); + + let error_handling_strategy = if force { + ErrorHandleStrategy::RetryStrategy + } else { + ErrorHandleStrategy::TolerateStrategy + }; + + RetryIf::spawn( + error_handling_strategy.retry_strategy(), + || async { + loop { + let eviction_result = pod_api.evict(&pod.name_any(), &EvictParams::default()).await; + + match eviction_result { + Ok(_) => { + pod.name(); + event!(Level::INFO, "Successfully evicted Pod '{}'", pod.name_any()); + break; + } + Err(kube::Error::Api(e)) => { + let status_code = StatusCode::from_u16(e.code); + match status_code { + Ok(StatusCode::TOO_MANY_REQUESTS) => { + event!( + Level::ERROR, + "Too many requests when creating Eviction for Pod '{}': '{}'. This is likely due to respecting a Pod Disruption Budget. Retrying in {:.2}s.", + pod.name_any(), + e, + EVERY_EVICTION_RETRY.as_secs_f64() + ); + sleep(EVERY_EVICTION_RETRY).await; + continue; + } + Ok(StatusCode::INTERNAL_SERVER_ERROR) => { + event!( + Level::ERROR, + "Error when evicting Pod '{}': '{}'. Check for misconfigured PodDisruptionBudgets. Retrying in {:.2}s.", + pod.name_any(), + e, + EVERY_EVICTION_RETRY.as_secs_f64() + ); + sleep(EVERY_EVICTION_RETRY).await; + continue; + } + Ok(StatusCode::NOT_FOUND) => { + return Err(error::EvictionError::NonRetriableEviction { + source: kube::Error::Api(e.clone()), + pod_name: pod.name_any(), + }); + } + Ok(StatusCode::FORBIDDEN) => { + return Err(error::EvictionError::NonRetriableEviction { + source: kube::Error::Api(e.clone()), + pod_name: pod.name_any(), + }); + } + Ok(_) => { + event!( + Level::ERROR, + "Error when evicting Pod '{}': '{}'.", + pod.name_any(), + e + ); + return Err(error::EvictionError::RetriableEviction { + source: kube::Error::Api(e.clone()), + pod_name: pod.name_any(), + }); + } + Err(_) => { + event!( + Level::ERROR, + "Received invalid response code from Kubernetes API: '{}'", + e + ); + return Err(error::EvictionError::RetriableEviction { + source: kube::Error::Api(e.clone()), + pod_name: pod.name_any(), + }); + } + } + } + Err(e) => { + event!(Level::ERROR, "Eviction failed: '{}'. Retrying...", e); + return Err(error::EvictionError::RetriableEviction { + source: e, + pod_name: pod.name_any(), + }); + } + } + } + Ok(()) + }, + error_handling_strategy + ).await +} + +async fn wait_for_deletion(k8s_client: &kube::Client, pod: &Pod) -> Result<(), error::DrainError> { + let start_time = Instant::now(); + + let pod_api: Api = get_pod_api_with_namespace(k8s_client, pod); + loop { + match pod_api.get(&pod.name_any()).await { + Err(kube::Error::Api(e)) if e.code == 404 => { + event!(Level::INFO, "Pod {} deleted.", pod.name_any()); + break; + } + + Ok(p) if p.uid() != pod.uid() => { + let name = p + .metadata + .name + .clone() + .or_else(|| p.metadata.generate_name.clone()) + .unwrap_or_default(); + event!(Level::INFO, "Pod {} deleted.", name); + break; + } + + Ok(_) => { + event!( + Level::DEBUG, + "Pod '{}' not yet deleted. Waiting {}s.", + pod.name_any(), + EVERY_DELETION_CHECK.as_secs_f64() + ); + } + + Err(e) => { + event!( + Level::ERROR, + "Could not determine if Pod '{}' has been deleted: '{}'. Waiting {}s.", + pod.name_any(), + e, + EVERY_DELETION_CHECK.as_secs_f64() + ); + } + } + if start_time.elapsed() > TIMEOUT { + return Err(error::DrainError::WaitForDeletion { + pod_name: pod.name_any(), + max_wait: TIMEOUT, + }); + } else { + sleep(EVERY_DELETION_CHECK).await; + } + } + Ok(()) +} +fn get_pod_api_with_namespace(client: &kube::Client, pod: &Pod) -> Api { + match pod.metadata.namespace.as_ref() { + Some(namespace) => Api::namespaced(client.clone(), namespace), + None => Api::default_namespaced(client.clone()), + } +} +trait NameAny { + fn name_any(self: &Self) -> String; +} + +impl NameAny for &Pod { + fn name_any(self: &Self) -> String { + self.metadata + .name + .clone() + .or_else(|| self.metadata.generate_name.clone()) + .unwrap_or_default() + } +} +trait PodFilter { + fn filter(self: &Self, pod: &Pod) -> Box; +} + +struct FinishedOrFailedFilter {} +impl PodFilter for FinishedOrFailedFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + return match pod.status.as_ref() { + Some(PodStatus { + phase: Some(phase), .. + }) if phase == "Failed" || phase == "Succeeded" => { + FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay) + } + _ => FilterResult::create_filter_result(false, "", PodDeleteStatus::Okay), + }; + } +} +struct DaemonFilter { + finished_or_failed_filter: FinishedOrFailedFilter, + force: bool, +} +impl PodFilter for DaemonFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + if let FilterResult { result: true, .. } = + self.finished_or_failed_filter.filter(pod).as_ref() + { + return FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay); + } + + return match pod.metadata.owner_references.as_ref() { + Some(owner_references) + if owner_references.iter().any(|reference| { + reference.controller.unwrap_or(false) && reference.kind == "DaemonSet" + }) => + { + if self.force { + let description = format!( + "Ignore Pod '{}': Pod is member of a DaemonSet", + pod.name_any() + ); + Box::new(FilterResult { + result: false, + desc: description, + status: PodDeleteStatus::Warning, + }) + } else { + let description = format!( + "Cannot drain Pod '{}': Pod is member of a DaemonSet", + pod.name_any() + ); + Box::new(FilterResult { + result: false, + desc: description, + status: PodDeleteStatus::Error, + }) + } + } + _ => FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay), + }; + } +} +impl DaemonFilter { + fn new(force: bool) -> DaemonFilter { + return DaemonFilter { + finished_or_failed_filter: FinishedOrFailedFilter {}, + force: force, + }; + } +} + +struct MirrorFilter {} +impl PodFilter for MirrorFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + return match pod.metadata.annotations.as_ref() { + Some(annotations) if annotations.contains_key("kubernetes.io/config.mirror") => { + let description = format!( + "Ignore Pod '{}': Pod is a static Mirror Pod", + pod.name_any() + ); + FilterResult::create_filter_result( + false, + &description.to_string(), + PodDeleteStatus::Warning, + ) + } + _ => FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay), + }; + } +} + +struct LocalStorageFilter { + finished_or_failed_filter: FinishedOrFailedFilter, + force: bool, +} +impl PodFilter for LocalStorageFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + if let FilterResult { result: true, .. } = + self.finished_or_failed_filter.filter(pod).as_ref() + { + return FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay); + } + + return match pod.spec.as_ref() { + Some(PodSpec { + volumes: Some(volumes), + .. + }) if volumes.iter().any(|volume| volume.empty_dir.is_some()) => { + if self.force { + let description = format!( + "Force draining Pod '{}': Pod has local storage", + pod.name_any() + ); + Box::new(FilterResult { + result: true, + desc: description, + status: PodDeleteStatus::Warning, + }) + } else { + let description = format!( + "Cannot drain Pod '{}': Pod has local Storage", + pod.name_any() + ); + Box::new(FilterResult { + result: false, + desc: description, + status: PodDeleteStatus::Error, + }) + } + } + _ => FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay), + }; + } +} +impl LocalStorageFilter { + fn new(force: bool) -> LocalStorageFilter { + return LocalStorageFilter { + finished_or_failed_filter: FinishedOrFailedFilter {}, + force: force, + }; + } +} +struct UnreplicatedFilter { + finished_or_failed_filter: FinishedOrFailedFilter, + force: bool, +} +impl PodFilter for UnreplicatedFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + if let FilterResult { result: true, .. } = + self.finished_or_failed_filter.filter(pod).as_ref() + { + return FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay); + } + + let is_replicated = pod.metadata.owner_references.is_some(); + + if is_replicated { + return FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay); + } + + return if !is_replicated && self.force { + let description = format!("Force drain Pod '{}': Pod is unreplicated", pod.name_any()); + Box::new(FilterResult { + result: true, + desc: description, + status: PodDeleteStatus::Warning, + }) + } else { + let description = format!("Cannot drain Pod '{}': Pod is unreplicated", pod.name_any()); + Box::new(FilterResult { + result: false, + desc: description, + status: PodDeleteStatus::Error, + }) + }; + } +} +impl UnreplicatedFilter { + fn new(force: bool) -> UnreplicatedFilter { + return UnreplicatedFilter { + finished_or_failed_filter: FinishedOrFailedFilter {}, + force: force, + }; + } +} + +struct DeletedFilter { + delete_wait_timeout: Duration, +} +impl PodFilter for DeletedFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + let now = Instant::now().elapsed(); + return match pod.metadata.deletion_timestamp.as_ref() { + Some(time) + if time.0.timestamp() != 0 + && now - Duration::from_secs(time.0.timestamp() as u64) + >= self.delete_wait_timeout => + { + FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay) + } + _ => FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay), + }; + } +} + +struct CombinedFilter { + deleted_filter: DeletedFilter, + daemon_filter: DaemonFilter, + mirror_filter: MirrorFilter, + local_storage_filter: LocalStorageFilter, + unreplicated_filter: UnreplicatedFilter, +} +impl PodFilter for CombinedFilter { + fn filter(self: &Self, pod: &Pod) -> Box { + let mut filter_res = self.deleted_filter.filter(pod); + if !filter_res.result { + event!(Level::INFO, filter_res.desc); + return Box::new(FilterResult { + result: filter_res.result, + desc: filter_res.desc.clone(), + status: filter_res.status.clone(), + }); + } + filter_res = self.daemon_filter.filter(pod); + if !filter_res.result { + event!(Level::INFO, filter_res.desc); + return Box::new(FilterResult { + result: filter_res.result, + desc: filter_res.desc.clone(), + status: filter_res.status.clone(), + }); + } + filter_res = self.mirror_filter.filter(pod); + if !filter_res.result { + event!(Level::INFO, filter_res.desc); + return Box::new(FilterResult { + result: filter_res.result, + desc: filter_res.desc.clone(), + status: filter_res.status.clone(), + }); + } + filter_res = self.local_storage_filter.filter(pod); + if !filter_res.result { + event!(Level::INFO, filter_res.desc); + return Box::new(FilterResult { + result: filter_res.result, + desc: filter_res.desc.clone(), + status: filter_res.status.clone(), + }); + } + filter_res = self.unreplicated_filter.filter(pod); + if !filter_res.result { + event!(Level::INFO, filter_res.desc); + return Box::new(FilterResult { + result: filter_res.result, + desc: filter_res.desc.clone(), + status: filter_res.status.clone(), + }); + } + + return FilterResult::create_filter_result(true, "", PodDeleteStatus::Okay); + } +} +impl CombinedFilter { + fn new(force: bool) -> CombinedFilter { + return CombinedFilter { + deleted_filter: DeletedFilter { + delete_wait_timeout: TIMEOUT, + }, + daemon_filter: DaemonFilter::new(force), + mirror_filter: MirrorFilter {}, + local_storage_filter: LocalStorageFilter::new(force), + unreplicated_filter: UnreplicatedFilter::new(force), + }; + } +} + +#[derive(PartialEq, Clone, Copy)] +enum PodDeleteStatus { + Okay, + Warning, + Error, +} +struct FilterResult { + result: bool, + desc: String, + status: PodDeleteStatus, +} +impl FilterResult { + fn create_filter_result( + result: bool, + desc: &str, + status: PodDeleteStatus, + ) -> Box { + Box::new(FilterResult { + result: result, + desc: desc.to_string(), + status: status, + }) + } +} + +enum ErrorHandleStrategy { + RetryStrategy, + TolerateStrategy, +} + +impl ErrorHandleStrategy { + fn retry_strategy(&self) -> impl Iterator { + let backoff = ExponentialBackoff::from_millis(RETRY_BASE_DELAY.as_millis() as u64) + .max_delay(RETRY_MAX_DELAY) + .map(jitter); + + return match self { + Self::TolerateStrategy => { + return backoff.take(0); + } + + Self::RetryStrategy => backoff.take(MAX_RETRIES_TIMES), + }; + } +} + +impl tokio_retry::Condition for ErrorHandleStrategy { + fn should_retry(&mut self, error: &error::EvictionError) -> bool { + match self { + Self::TolerateStrategy => false, + Self::RetryStrategy => { + if let error::EvictionError::RetriableEviction { .. } = error { + true + } else { + false + } + } + } + } +} + +pub mod error { + use snafu::Snafu; + use tokio::time::Duration; + + #[derive(Debug, Snafu)] + #[snafu(visibility(pub))] + pub enum DrainError { + #[snafu(display("Unable to find drainable Pods for Node '{}': '{}'", node_name, source))] + FindTargetPods { + source: kube::Error, + node_name: String, + }, + + #[snafu( + display( + "Pod '{}' was not deleted in the time allocated ({:.2}s).", + pod_name, + max_wait.as_secs_f64() + ) + )] + WaitForDeletion { + pod_name: String, + max_wait: Duration, + }, + DeletePodsError { + errors: Vec, + }, + } + + #[derive(Debug, Snafu)] + #[snafu(visibility(pub))] + pub enum EvictionError { + #[snafu(display("Unable to evict Pod '{}': '{}'", pod_name, source))] + RetriableEviction { + source: kube::Error, + pod_name: String, + }, + + #[snafu(display("Unable to evict Pod '{}': '{}'", pod_name, source))] + /// A fatal error occurred while attempting to evict a Pod. This will not be retried. + NonRetriableEviction { + source: kube::Error, + pod_name: String, + }, + } +} diff --git a/KubeOS-Rust/proxy/src/controller/mod.rs b/KubeOS-Rust/proxy/src/controller/mod.rs new file mode 100644 index 00000000..e2e06493 --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +mod apiclient; +mod controller; +mod crd; +mod drain; +mod utils; +mod values; + +pub use apiclient::ControllerClient; +pub use controller::{error_policy, reconcile, reconciler_error::Error, ProxyController}; +pub use crd::OS; +pub use values::SOCK_PATH; diff --git a/KubeOS-Rust/proxy/src/controller/utils.rs b/KubeOS-Rust/proxy/src/controller/utils.rs new file mode 100644 index 00000000..26a0d18a --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/utils.rs @@ -0,0 +1,155 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use super::crd::{Configs, OSInstance, OSInstanceStatus, OS}; +use super::values::{NODE_STATUS_CONFIG, NODE_STATUS_IDLE, NODE_STATUS_UPGRADE}; +use log::{debug, info}; + +#[derive(PartialEq, Clone, Copy)] +pub enum ConfigType { + UpgradeConfig, + SysConfig, +} + +pub enum ConfigOperation { + DoNothing, + Reassign, + UpdateConfig, +} + +pub struct ConfigInfo { + pub need_config: bool, + pub configs: Option, +} + +impl ConfigType { + pub fn check_config_version(&self, os: &OS, osinstance: &OSInstance) -> ConfigOperation { + debug!("start check_config_version"); + let node_status = &osinstance.spec.nodestatus; + if node_status == NODE_STATUS_IDLE { + debug!("======node status is idle======"); + return ConfigOperation::DoNothing; + }; + match self { + ConfigType::UpgradeConfig => { + let os_config_version = get_config_version(os.spec.upgradeconfigs.as_ref()); + let osi_config_version = + get_config_version(osinstance.spec.upgradeconfigs.as_ref()); + debug!("=======os upgradeconfig version is{},osinstance spec upragdeconfig version is{}",os_config_version,osi_config_version); + if !check_version(&os_config_version, &osi_config_version) { + info!("os.spec.upgradeconfig.version is not equal to oninstance.spec.upragdeconfig.version, operation: reassgin upgrade to get newest upgradeconfigs"); + return ConfigOperation::Reassign; + } + } + ConfigType::SysConfig => { + let os_config_version = get_config_version(os.spec.sysconfigs.as_ref()); + let osi_config_version = get_config_version(osinstance.spec.sysconfigs.as_ref()); + debug!( + "=======os sysconfig version is{},osinstance spec sysconfig version is{}", + os_config_version, osi_config_version + ); + if !check_version(&os_config_version, &osi_config_version) { + if node_status == NODE_STATUS_CONFIG { + info!("os.spec.sysconfig.version is not equal to oninstance.spec.sysconfig.version, operation: reassgin config to get newest sysconfigs"); + return ConfigOperation::Reassign; + } + if node_status == NODE_STATUS_UPGRADE { + info!("os.spec.sysconfig.version is not equal to oninstance.spec.sysconfig.version, operation: update osinstance.spec.sysconfig and reconcile"); + return ConfigOperation::UpdateConfig; + } + } + } + }; + ConfigOperation::DoNothing + } + pub fn check_config_start(&self, osinstance: &OSInstance) -> ConfigInfo { + debug!("start check_config_start"); + let spec_config_version: String; + let status_config_version: String; + let configs: Option; + match self { + ConfigType::UpgradeConfig => { + spec_config_version = get_config_version(osinstance.spec.upgradeconfigs.as_ref()); + if let Some(osinstance_status) = osinstance.status.as_ref() { + status_config_version = + get_config_version(osinstance_status.upgradeconfigs.as_ref()); + } else { + status_config_version = get_config_version(None); + } + configs = osinstance.spec.upgradeconfigs.clone(); + } + ConfigType::SysConfig => { + spec_config_version = get_config_version(osinstance.spec.sysconfigs.as_ref()); + if let Some(osinstance_status) = osinstance.status.as_ref() { + status_config_version = + get_config_version(osinstance_status.sysconfigs.as_ref()); + } else { + status_config_version = get_config_version(None); + } + configs = osinstance.spec.sysconfigs.clone(); + } + } + debug!( + "=======osinstance soec config version is {},status config version is {}", + spec_config_version, status_config_version + ); + if spec_config_version != status_config_version + && osinstance.spec.nodestatus != NODE_STATUS_IDLE + { + return ConfigInfo { + need_config: true, + configs: configs, + }; + } + return ConfigInfo { + need_config: false, + configs: None, + }; + } + pub fn set_osi_status_config(&self, osinstance: &mut OSInstance) { + match self { + ConfigType::UpgradeConfig => { + if let Some(osi_status) = &mut osinstance.status { + osi_status.upgradeconfigs = osinstance.spec.upgradeconfigs.clone(); + } else { + osinstance.status = Some(OSInstanceStatus { + upgradeconfigs: osinstance.spec.upgradeconfigs.clone(), + sysconfigs: None, + }) + } + } + ConfigType::SysConfig => { + if let Some(osi_status) = &mut osinstance.status { + osi_status.sysconfigs = osinstance.spec.sysconfigs.clone(); + } else { + osinstance.status = Some(OSInstanceStatus { + upgradeconfigs: None, + sysconfigs: osinstance.spec.sysconfigs.clone(), + }) + } + } + } + } +} + +pub fn check_version(version_a: &str, version_b: &str) -> bool { + version_a.eq(version_b) +} + +pub fn get_config_version(configs: Option<&Configs>) -> String { + if let Some(configs) = configs { + if let Some(version) = configs.version.as_ref() { + return version.to_string(); + } + }; + String::from("") +} diff --git a/KubeOS-Rust/proxy/src/controller/values.rs b/KubeOS-Rust/proxy/src/controller/values.rs new file mode 100644 index 00000000..f5d41965 --- /dev/null +++ b/KubeOS-Rust/proxy/src/controller/values.rs @@ -0,0 +1,49 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use kube::runtime::controller::ReconcilerAction; +use tokio::time::Duration; + +pub const LABEL_OSINSTANCE: &str = "upgrade.openeuler.org/osinstance-node"; +pub const LABEL_UPGRADING: &str = "upgrade.openeuler.org/upgrading"; + +pub const OSINSTANCE_API_VERSION: &str = "upgrade.openeuler.org/v1alpha1"; +pub const OSINSTANCE_KIND: &str = "OSInstance"; + +pub const NODE_STATUS_IDLE: &str = "idle"; +pub const NODE_STATUS_UPGRADE: &str = "upgrade"; +pub const NODE_STATUS_CONFIG: &str = "config"; + +pub const OPERATION_TYPE_UPGRADE: &str = "upgrade"; +pub const OPERATION_TYPE_ROLLBACK: &str = "rollback"; + +pub const SOCK_PATH: &str = "/run/os-agent/os-agent.sock"; + +pub const REQUEUE_NORMAL: ReconcilerAction = ReconcilerAction { + requeue_after: Some(Duration::from_secs(15)), +}; + +pub const REQUEUE_ERROR: ReconcilerAction = ReconcilerAction { + requeue_after: Some(Duration::from_secs(1)), +}; + +pub const MAX_EVICT_POD_NUM: usize = 5; + +pub const EVERY_EVICTION_RETRY: Duration = Duration::from_secs(5); + +pub const EVERY_DELETION_CHECK: Duration = Duration::from_secs(5); + +pub const TIMEOUT: Duration = Duration::from_secs(u64::MAX); + +pub const RETRY_BASE_DELAY: Duration = Duration::from_millis(100); +pub const RETRY_MAX_DELAY: Duration = Duration::from_secs(20); +pub const MAX_RETRIES_TIMES: usize = 10; diff --git a/KubeOS-Rust/proxy/src/main.rs b/KubeOS-Rust/proxy/src/main.rs new file mode 100644 index 00000000..43610a64 --- /dev/null +++ b/KubeOS-Rust/proxy/src/main.rs @@ -0,0 +1,52 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved. + * KubeOS is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use anyhow::Result; +use env_logger::{Builder, Env, Target}; +use futures::StreamExt; +use kube::{ + api::{Api, ListParams}, + client::Client, + runtime::controller::{Context, Controller}, +}; +use log::{error, info}; +mod controller; +use cli::client::Client as AgentClient; +use controller::{error_policy, reconcile, ControllerClient, ProxyController, OS, SOCK_PATH}; + +const PROXY_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); +#[tokio::main] +async fn main() -> Result<()> { + Builder::from_env(Env::default().default_filter_or("info")) + .target(Target::Stdout) + .init(); + let client = Client::try_default().await?; + let os: Api = Api::all(client.clone()); + let controller_client = ControllerClient::new(client.clone()); + let agent_client = AgentClient::new(SOCK_PATH); + let proxy_controller = ProxyController::new(client, controller_client, agent_client); + info!( + "os-proxy version is {}, start renconcile", + PROXY_VERSION.unwrap_or("Not Found") + ); + Controller::new(os, ListParams::default()) + .run(reconcile, error_policy, Context::new(proxy_controller)) + .for_each(|res| async move { + match res { + Ok(_o) => {} + Err(e) => error!("reconcile failed: {}", e.to_string()), + } + }) + .await; + info!("os-proxy terminated"); + Ok(()) +} -- Gitee