diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ab48485..fd06b1b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,29 +2,32 @@ name: Kudo CI on: push: - branches: [ "main" ] + branches: ["main", "project-demo-202208"] pull_request: - branches: [ "main" ] + branches: ["main", "project-demo-202208"] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install clippy - run: rustup component add clippy - - name: Install rustfmt - run: rustup component add rustfmt - - name: Build - run: make kudo - - name: Lint - run: make lint - - name: Format - run: make format - - name: Tests - run: make check + - uses: actions/checkout@v3 + - name: Install clippy + run: rustup component add clippy + - name: Install rustfmt + run: rustup component add rustfmt + - name: Cargo setup + run: | + echo "[target.x86_64-unknown-linux-gnu]" >> ~/.cargo/config + echo "runner = 'sudo -E'" >> ~/.cargo/config + - name: Build + run: make kudo + - name: Lint + run: make lint + - name: Format + run: make format + - name: Tests + run: make check diff --git a/Cargo.lock b/Cargo.lock index a9828deb..040a166b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,9 +224,9 @@ dependencies = [ [[package]] name = "android_system_properties" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] @@ -474,9 +474,9 @@ checksum = "300bccc729b1ada84523246038aad61fead689ac362bb9d44beea6f6a188c34b" [[package]] name = "clap" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" +checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f" dependencies = [ "atty", "bitflags", @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck", "proc-macro-error", @@ -894,26 +894,59 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -922,25 +955,29 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1132,13 +1169,14 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", + "once_cell", "wasm-bindgen", "winapi", ] @@ -1387,8 +1425,20 @@ dependencies = [ name = "node-agent" version = "0.1.0" dependencies = [ + "async-stream", + "cidr", + "confy", + "env_logger 0.9.0", + "log", + "network", + "node_manager", "proto", + "serde", + "serde_derive", "tokio", + "tokio-stream", + "tonic", + "uuid", "workload_manager", ] @@ -1397,7 +1447,7 @@ name = "node_manager" version = "0.1.0" dependencies = [ "log", - "sysinfo", + "sysinfo 0.24.7", ] [[package]] @@ -1882,6 +1932,7 @@ name = "scheduler" version = "0.1.0" dependencies = [ "anyhow", + "async-stream", "confy", "env_logger 0.8.4", "log", @@ -2009,9 +2060,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +checksum = "5cf2781a4ca844dd4f9b608a1791eea19830df0ad3cdd9988cd05f1c66ccb63a" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -2090,6 +2141,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "sysinfo" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71eb43e528fdc239f08717ec2a378fdb017dddbc3412de15fff527554591a66c" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "system-configuration" version = "0.5.0" @@ -2493,6 +2559,28 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", + "rand", + "uuid-macro-internal", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548f7181a5990efa50237abb7ebca410828b57a8955993334679f8b50b35c97d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2746,10 +2834,16 @@ version = "0.1.0" dependencies = [ "anyhow", "bollard", + "cidr", + "futures", "futures-util", + "network", "proto", + "sysinfo 0.25.3", + "tokio", "tokio-test", "tonic", + "uuid", ] [[package]] diff --git a/controller/lib/Cargo.toml b/controller/lib/Cargo.toml index 91ad2406..02c2caab 100644 --- a/controller/lib/Cargo.toml +++ b/controller/lib/Cargo.toml @@ -13,6 +13,5 @@ tonic = "0.7.2" proto = { path = "../../proto" } log = "0.4.0" tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"] } - serde_json = "1.0" diff --git a/controller/lib/src/external_api/generic/filter.rs b/controller/lib/src/external_api/generic/filter.rs new file mode 100644 index 00000000..fd0b5b93 --- /dev/null +++ b/controller/lib/src/external_api/generic/filter.rs @@ -0,0 +1,59 @@ +use super::model::FilterError; +/// `FilterService` is a struct that can be used as a services to filter result. +pub struct FilterService {} + +impl Default for FilterService { + fn default() -> Self { + Self::new() + } +} + +impl FilterService { + pub fn new() -> Self { + FilterService {} + } + + /// It takes a vector of and a limit, and returns a vector that is limited to the + /// number specified by the limit + /// + /// # Arguments: + /// + /// * `vector`: A vector. + /// * `limit`: The number of elements in the vector to return. + /// + /// # Returns: + /// + /// A vector. + + pub fn limit(&mut self, vector: &Vec, mut limit: u32) -> Vec { + if limit > vector.len() as u32 { + limit = vector.len() as u32; + } + vector[0..limit as usize].to_vec() + } + + /// "Return a subset of the vector, starting at the offset index." + /// + /// The first thing we do is check if the offset is greater than the length of the vector. If + /// it is, we return an error + /// + /// # Arguments: + /// + /// * `vector`: A vector. + /// * `offset`: The offset to start from. + /// + /// # Returns: + /// + /// A vector. + + pub fn offset( + &mut self, + vector: &Vec, + offset: u32, + ) -> Result, FilterError> { + if offset > vector.len() as u32 { + return Err(FilterError::OutOfRange); + } + Ok(vector[offset as usize..].to_vec()) + } +} diff --git a/controller/lib/src/external_api/generic/mod.rs b/controller/lib/src/external_api/generic/mod.rs new file mode 100644 index 00000000..c5bc6204 --- /dev/null +++ b/controller/lib/src/external_api/generic/mod.rs @@ -0,0 +1,2 @@ +pub mod filter; +pub mod model; diff --git a/controller/lib/src/external_api/generic/model.rs b/controller/lib/src/external_api/generic/model.rs new file mode 100644 index 00000000..27fbc575 --- /dev/null +++ b/controller/lib/src/external_api/generic/model.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +pub enum FilterError { + OutOfRange, +} + +#[derive(Deserialize, Serialize)] +pub struct Pagination { + pub limit: u32, + pub offset: u32, +} diff --git a/controller/lib/src/external_api/interface.rs b/controller/lib/src/external_api/interface.rs index 9f2928ba..ad9ecfa5 100644 --- a/controller/lib/src/external_api/interface.rs +++ b/controller/lib/src/external_api/interface.rs @@ -1,3 +1,4 @@ +use super::namespace; use super::workload; use actix_web::middleware::Logger; use actix_web::{web, App, HttpResponse, HttpServer}; @@ -22,6 +23,7 @@ impl ExternalAPIInterface { .app_data(web::Data::new(ActixAppState { etcd_address })) .route("/health", web::get().to(HttpResponse::Ok)) .service(workload::controller::WorkloadController {}.services()) + .service(namespace::controller::NamespaceController {}.services()) .wrap(Logger::default()) }) .workers(num_workers) diff --git a/controller/lib/src/external_api/mod.rs b/controller/lib/src/external_api/mod.rs index 9859aa55..88a23bfa 100644 --- a/controller/lib/src/external_api/mod.rs +++ b/controller/lib/src/external_api/mod.rs @@ -1,2 +1,4 @@ +pub mod generic; pub mod interface; +mod namespace; mod workload; diff --git a/controller/lib/src/external_api/namespace/controller.rs b/controller/lib/src/external_api/namespace/controller.rs new file mode 100644 index 00000000..20276b84 --- /dev/null +++ b/controller/lib/src/external_api/namespace/controller.rs @@ -0,0 +1,113 @@ +use crate::external_api::generic::model::Pagination; +use crate::external_api::interface::ActixAppState; + +use super::model::NamespaceDTO; +use super::service::NamespaceService; +use actix_web::http::StatusCode; +use actix_web::{web, HttpResponse, Responder, Scope}; +pub struct NamespaceController {} +impl NamespaceController { + pub fn services(&self) -> Scope { + web::scope("/namespace") + .service( + web::resource("/{namespace_name}") + .route(web::delete().to(NamespaceController::delete_namespace)) + .route(web::get().to(NamespaceController::namespace)) + .route(web::patch().to(NamespaceController::patch_namespace)), + ) + .service( + web::resource("") + .route(web::put().to(NamespaceController::put_namespace)) + .route(web::get().to(NamespaceController::get_all_namespace)), + ) + } + + pub async fn namespace( + params: web::Path, + data: web::Data, + ) -> impl Responder { + let mut namespace_service = match NamespaceService::new(&data.etcd_address).await { + Ok(namespace) => namespace, + Err(e) => return e.to_http(), + }; + + let namespace_name = params.into_inner(); + + namespace_service + .namespace(&namespace_name) + .await + .map_or_else(|e| e.to_http(), |w| w.to_http()) + } + + pub async fn put_namespace( + body: web::Json, + data: web::Data, + ) -> impl Responder { + let mut namespace_service = match NamespaceService::new(&data.etcd_address).await { + Ok(namespace) => namespace, + Err(e) => return e.to_http(), + }; + let namespace_dto = body.into_inner(); + namespace_service + .create_namespace(namespace_dto) + .await + .map_or_else(|e| e.to_http(), |w| w.to_http()) + } + + pub async fn get_all_namespace( + pagination: Option>, + data: web::Data, + ) -> impl Responder { + let mut namespace_service = match NamespaceService::new(&data.etcd_address).await { + Ok(namespace) => namespace, + Err(e) => return e.to_http(), + }; + + match pagination { + Some(pagination) => { + let namespaces = namespace_service + .get_all_namespace(pagination.limit, pagination.offset) + .await; + namespaces.to_http() + } + None => { + let namespaces = namespace_service.get_all_namespace(0, 0).await; + namespaces.to_http() + } + } + } + + pub async fn patch_namespace( + params: web::Path, + body: web::Json, + data: web::Data, + ) -> impl Responder { + let mut namespace_service = match NamespaceService::new(&data.etcd_address).await { + Ok(namespace) => namespace, + Err(e) => return e.to_http(), + }; + + let namespace_name = params.into_inner(); + let namespace_dto = body.into_inner(); + + namespace_service + .update_namespace(namespace_dto, &namespace_name) + .await + .map_or_else(|e| e.to_http(), |w| w.to_http()) + } + + pub async fn delete_namespace( + params: web::Path, + data: web::Data, + ) -> impl Responder { + let mut namespace_service = match NamespaceService::new(&data.etcd_address).await { + Ok(namespace) => namespace, + Err(e) => return e.to_http(), + }; + + let namespace_name = params.into_inner(); + + namespace_service.delete_namespace(&namespace_name).await; + HttpResponse::build(StatusCode::NO_CONTENT).body("Remove successfully") + } +} diff --git a/controller/lib/src/external_api/namespace/mod.rs b/controller/lib/src/external_api/namespace/mod.rs new file mode 100644 index 00000000..9c5ae537 --- /dev/null +++ b/controller/lib/src/external_api/namespace/mod.rs @@ -0,0 +1,3 @@ +pub mod controller; +pub mod model; +pub mod service; diff --git a/controller/lib/src/external_api/namespace/model.rs b/controller/lib/src/external_api/namespace/model.rs new file mode 100644 index 00000000..41abcd0c --- /dev/null +++ b/controller/lib/src/external_api/namespace/model.rs @@ -0,0 +1,72 @@ +use actix_web::HttpResponse; +use serde::{Deserialize, Serialize}; + +pub enum NamespaceError { + NotFound, + Etcd(String), + NameAlreadyExists(String), + JsonToNamespace(String), + NamespaceToJson(String), +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Metadata {} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Namespace { + pub id: String, + pub name: String, + pub metadata: Metadata, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NamespaceDTO { + pub name: String, +} + +impl NamespaceError { + pub fn to_http(&self) -> HttpResponse { + match self { + NamespaceError::NotFound => HttpResponse::NotFound().body("Namespace not found"), + NamespaceError::Etcd(err) => { + HttpResponse::InternalServerError().body(format!("Etcd error: {} ", err)) + } + NamespaceError::NameAlreadyExists(name) => HttpResponse::Conflict() + .body(format!("Namespace with name {} already exists", name)), + NamespaceError::JsonToNamespace(err) => HttpResponse::InternalServerError().body( + format!("Error while converting JSON string to Namespace : {}", err), + ), + NamespaceError::NamespaceToJson(err) => HttpResponse::InternalServerError().body( + format!("Error while converting the Namespace to JSON: {}", err), + ), + } + } +} +impl Namespace { + pub fn to_http(&self) -> HttpResponse { + match serde_json::to_string(&self) { + Ok(json) => HttpResponse::Ok().body(json), + Err(err) => HttpResponse::InternalServerError().body(format!( + "Error while converting the Namespace to JSON: {}", + err + )), + } + } +} +#[derive(Deserialize, Serialize)] +pub struct NamespaceVector { + pub namespaces: Vec, +} +impl NamespaceVector { + pub fn new(namespaces: Vec) -> NamespaceVector { + NamespaceVector { namespaces } + } + pub fn to_http(&self) -> HttpResponse { + match serde_json::to_string(&self.namespaces) { + Ok(json) => HttpResponse::Ok().body(json), + Err(err) => HttpResponse::InternalServerError().body(format!( + "Error while converting the Namespace to JSON: {}", + err + )), + } + } +} diff --git a/controller/lib/src/external_api/namespace/service.rs b/controller/lib/src/external_api/namespace/service.rs new file mode 100644 index 00000000..d85e9ea6 --- /dev/null +++ b/controller/lib/src/external_api/namespace/service.rs @@ -0,0 +1,117 @@ +use crate::etcd::EtcdClient; +use crate::external_api::generic::filter::FilterService; +use serde_json; +use std::net::SocketAddr; + +use super::model::{Metadata, Namespace, NamespaceDTO, NamespaceError, NamespaceVector}; + +pub struct NamespaceService { + etcd_service: EtcdClient, + filter_service: FilterService, +} + +impl NamespaceService { + pub async fn new(etcd_address: &SocketAddr) -> Result { + let inner = NamespaceService { + etcd_service: EtcdClient::new(etcd_address.to_string()) + .await + .map_err(|err| NamespaceError::Etcd(err.to_string()))?, + filter_service: FilterService::new(), + }; + Ok(inner) + } + + pub async fn namespace(&mut self, namespace_name: &str) -> Result { + let id = self.id(namespace_name); + match self.etcd_service.get(&id).await { + Some(namespace) => { + let namespace: Namespace = serde_json::from_str(&namespace) + .map_err(|err| NamespaceError::JsonToNamespace(err.to_string()))?; + Ok(namespace) + } + None => Err(NamespaceError::NotFound), + } + } + + pub async fn get_all_namespace(&mut self, limit: u32, offset: u32) -> NamespaceVector { + let mut new_vec: Vec = Vec::new(); + match self.etcd_service.get_all().await { + Some(namespaces) => { + for namespace in namespaces { + // if namespace deserialize failed , we don't want to throw error , so we just don't add it to the vector + if let Ok(namespace) = serde_json::from_str::(&namespace) { + new_vec.push(namespace); + } + } + if offset > 0 { + match self.filter_service.offset(&new_vec, offset) { + Ok(namespaces) => new_vec = namespaces, + Err(_) => return NamespaceVector::new(vec![]), + } + } + if limit > 0 { + new_vec = self.filter_service.limit(&new_vec, limit); + } + NamespaceVector::new(new_vec) + } + None => NamespaceVector::new(vec![]), + } + } + + pub async fn create_namespace( + &mut self, + namespace_dto: NamespaceDTO, + ) -> Result { + let id = self.id(&namespace_dto.name); + match self.namespace(&namespace_dto.name).await { + Ok(namespace) => Err(NamespaceError::NameAlreadyExists(namespace.name)), + Err(err) => match err { + NamespaceError::NotFound => { + let namespace = Namespace { + id: id.to_string(), + name: namespace_dto.name, + metadata: Metadata {}, + }; + let json = serde_json::to_string(&namespace) + .map_err(|err| NamespaceError::NamespaceToJson(err.to_string()))?; + self.etcd_service + .put(&id, &json) + .await + .map_err(|err| NamespaceError::Etcd(err.to_string()))?; + Ok(namespace) + } + _ => Err(err), + }, + } + } + + pub async fn update_namespace( + &mut self, + namespace_dto: NamespaceDTO, + namespace_name: &str, + ) -> Result { + // we get the id before update , and the new id after update + let new_id = self.id(&namespace_dto.name); + self.namespace(namespace_name).await?; + let namespace = Namespace { + id: new_id.to_string(), + name: namespace_dto.name, + metadata: Metadata {}, + }; + let json = serde_json::to_string(&namespace) + .map_err(|err| NamespaceError::NamespaceToJson(err.to_string()))?; + self.etcd_service + .put(&new_id, &json) + .await + .map_err(|err| NamespaceError::Etcd(err.to_string()))?; + Ok(namespace) + } + + pub async fn delete_namespace(&mut self, namespace_name: &str) { + let id = self.id(namespace_name); + _ = self.etcd_service.delete(&id).await; + } + pub fn id(&mut self, namespace_name: &str) -> String { + format!("namespace.{}", namespace_name) + } +} diff --git a/controller/lib/src/external_api/workload/controller.rs b/controller/lib/src/external_api/workload/controller.rs index d94f4bc2..db09b0d7 100644 --- a/controller/lib/src/external_api/workload/controller.rs +++ b/controller/lib/src/external_api/workload/controller.rs @@ -1,7 +1,8 @@ use crate::external_api::interface::ActixAppState; +use super::model::WorkloadDTO; use super::service::WorkloadService; -use super::{model::Pagination, model::WorkloadDTO}; +use crate::external_api::generic::model::Pagination; use actix_web::http::StatusCode; use actix_web::{web, HttpResponse, Responder, Scope}; pub struct WorkloadController {} diff --git a/controller/lib/src/external_api/workload/filter.rs b/controller/lib/src/external_api/workload/filter.rs deleted file mode 100644 index 29ef2ba8..00000000 --- a/controller/lib/src/external_api/workload/filter.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::model::{Workload, WorkloadError}; - -/// `FilterService` is a struct that can be used as a service in the WorkloadService. -pub struct FilterService {} - -impl FilterService { - pub fn new() -> Self { - FilterService {} - } - - /// It takes a vector of workloads and a limit, and returns a vector of workloads that is limited to the - /// number of workloads specified by the limit - /// - /// # Arguments: - /// - /// * `workloads`: A vector of workloads to be limited. - /// * `limit`: The number of workloads to return. - /// - /// # Returns: - /// - /// A vector of workloads. - - pub fn limit(&mut self, workloads: &Vec, mut limit: u32) -> Vec { - if limit > workloads.len() as u32 { - limit = workloads.len() as u32; - } - workloads[0..limit as usize].to_vec() - } - - /// "Return a subset of the workloads vector, starting at the offset index." - /// - /// The first thing we do is check if the offset is greater than the length of the workloads vector. If - /// it is, we return an error - /// - /// # Arguments: - /// - /// * `workloads`: A vector of workloads to be filtered. - /// * `offset`: The offset to start from. - /// - /// # Returns: - /// - /// A vector of workloads - - pub fn offset( - &mut self, - workloads: &Vec, - offset: u32, - ) -> Result, WorkloadError> { - if offset > workloads.len() as u32 { - return Err(WorkloadError::OutOfRange); - } - Ok(workloads[offset as usize..].to_vec()) - } -} diff --git a/controller/lib/src/external_api/workload/mod.rs b/controller/lib/src/external_api/workload/mod.rs index 2426b69e..9c5ae537 100644 --- a/controller/lib/src/external_api/workload/mod.rs +++ b/controller/lib/src/external_api/workload/mod.rs @@ -1,4 +1,3 @@ pub mod controller; -pub mod filter; pub mod model; pub mod service; diff --git a/controller/lib/src/external_api/workload/model.rs b/controller/lib/src/external_api/workload/model.rs index a7b69c3b..a8d79797 100644 --- a/controller/lib/src/external_api/workload/model.rs +++ b/controller/lib/src/external_api/workload/model.rs @@ -5,9 +5,10 @@ pub enum WorkloadError { WorkloadNotFound, Etcd(String), NameAlreadyExists(String), - OutOfRange, JsonToWorkload(String), WorkloadToJson(String), + NamespaceService, + NamespaceNotFound, } impl WorkloadError { @@ -20,21 +21,20 @@ impl WorkloadError { WorkloadError::NameAlreadyExists(name) => { HttpResponse::Conflict().body(format!("Workload with name {} already exists", name)) } - WorkloadError::OutOfRange => HttpResponse::BadRequest().body("Out of range"), WorkloadError::JsonToWorkload(err) => HttpResponse::InternalServerError().body( format!("Error while converting JSON string to workload : {}", err), ), WorkloadError::WorkloadToJson(err) => HttpResponse::InternalServerError().body( format!("Error while converting the workload to JSON: {}", err), ), + WorkloadError::NamespaceService => HttpResponse::InternalServerError() + .body("Cannot create a NamespaceService instance"), + WorkloadError::NamespaceNotFound => { + HttpResponse::NotFound().body("Namespace not found") + } } } } -#[derive(Deserialize, Serialize)] -pub struct Pagination { - pub limit: u32, - pub offset: u32, -} #[derive(Deserialize, Serialize, Clone, Debug)] pub enum Type { Container = 0, @@ -78,6 +78,7 @@ pub struct WorkloadDTO { pub environment: Vec, pub ports: Vec, pub uri: String, + pub resources: Ressources, } #[derive(Deserialize, Serialize)] pub struct WorkloadVector { @@ -88,7 +89,7 @@ impl WorkloadVector { WorkloadVector { workloads } } pub fn to_http(&self) -> HttpResponse { - match serde_json::to_string(&self) { + match serde_json::to_string(&self.workloads) { Ok(json) => HttpResponse::Ok().body(json), Err(err) => HttpResponse::InternalServerError().body(format!( "Error while converting the workload to json: {}", diff --git a/controller/lib/src/external_api/workload/service.rs b/controller/lib/src/external_api/workload/service.rs index d74c246d..7e0fa942 100644 --- a/controller/lib/src/external_api/workload/service.rs +++ b/controller/lib/src/external_api/workload/service.rs @@ -1,9 +1,9 @@ -use std::net::SocketAddr; - -use super::filter::FilterService; use super::model::{Ressources, Type, Workload, WorkloadDTO, WorkloadError, WorkloadVector}; use crate::etcd::EtcdClient; +use crate::external_api::generic::filter::FilterService; +use crate::external_api::namespace::service::NamespaceService; use serde_json; +use std::net::SocketAddr; /// `WorkloadService` is a struct that inpired from Controllers Provider Modules architectures. It can be used as a service in the WorkloadController .A service can use other services. /// Properties: @@ -13,6 +13,7 @@ use serde_json; pub struct WorkloadService { etcd_service: EtcdClient, filter_service: FilterService, + namespace_service: NamespaceService, } impl WorkloadService { @@ -22,6 +23,9 @@ impl WorkloadService { .await .map_err(|err| WorkloadError::Etcd(err.to_string()))?, filter_service: FilterService::new(), + namespace_service: NamespaceService::new(etcd_address) + .await + .map_err(|_| WorkloadError::NamespaceService)?, }; Ok(inner) } @@ -31,6 +35,12 @@ impl WorkloadService { namespace: &str, ) -> Result { let id = self.id(workload_name, namespace); + //check if namespace exists + self.namespace_service + .namespace(namespace) + .await + .map_err(|_| WorkloadError::NamespaceNotFound)?; + match self.etcd_service.get(&id).await { Some(workload) => { let workload: Workload = serde_json::from_str(&workload) @@ -115,9 +125,9 @@ impl WorkloadService { uri: workload_dto.uri, environment: workload_dto.environment, resources: Ressources { - cpu: 0, - memory: 0, - disk: 0, + cpu: workload_dto.resources.cpu, + memory: workload_dto.resources.memory, + disk: workload_dto.resources.disk, }, ports: workload_dto.ports, namespace: namespace.to_string(), @@ -162,9 +172,9 @@ impl WorkloadService { uri: workload_dto.uri, environment: workload_dto.environment.to_vec(), resources: Ressources { - cpu: 0, - memory: 0, - disk: 0, + cpu: workload_dto.resources.cpu, + memory: workload_dto.resources.memory, + disk: workload_dto.resources.disk, }, ports: workload_dto.ports.to_vec(), namespace: namespace.to_string(), diff --git a/kudoctl/src/client/instance.rs b/kudoctl/src/client/instance.rs index 8b3be8cd..ee8e83e2 100644 --- a/kudoctl/src/client/instance.rs +++ b/kudoctl/src/client/instance.rs @@ -7,15 +7,22 @@ use crate::{client::types::IdResponse, resource::workload}; use super::request::Client; +#[derive(Debug, Serialize)] +struct CreateRequestBody { + pub workload_name: String, +} + /// Starts an instance on the cluster. /// /// Returns the id of the instance. -pub async fn create(client: &Client, workload_id: &String) -> anyhow::Result { +pub async fn create(client: &Client, namespace: &str, workload_id: &str) -> anyhow::Result { let response: IdResponse = (*client) - .send_json_request::( - &format!("/instance/?workloadId={}", workload_id), + .send_json_request( + &format!("/instance/{}", namespace), Method::PUT, - None, + Some(&CreateRequestBody { + workload_name: workload_id.to_owned(), + }), ) .await .context("Error creating instance")?; @@ -46,9 +53,13 @@ pub struct GetInstancesResponse { } /// List the instances in the cluster. -pub async fn list(client: &Client) -> anyhow::Result { +pub async fn list(client: &Client, namespace: &str) -> anyhow::Result { let response: GetInstancesResponse = (*client) - .send_json_request::("/instance", Method::GET, None) + .send_json_request::( + format!("/instance/{}", namespace).as_str(), + Method::GET, + None, + ) .await .context("Error getting instances")?; debug!( @@ -60,9 +71,13 @@ pub async fn list(client: &Client) -> anyhow::Result { } /// Get info about one instance. -pub async fn get(client: &Client, instance_id: &str) -> anyhow::Result { +pub async fn get(client: &Client, namespace: &str, instance_id: &str) -> anyhow::Result { let response: Instance = (*client) - .send_json_request::(&format!("/instance/{}", instance_id), Method::GET, None) + .send_json_request::( + &format!("/instance/{}/{}", namespace, instance_id), + Method::GET, + None, + ) .await .context("Error getting instance")?; debug!("Instance {} received", response.id); @@ -70,9 +85,13 @@ pub async fn get(client: &Client, instance_id: &str) -> anyhow::Result } /// Delete an instance with the given id. -pub async fn delete(client: &Client, instance_id: &str) -> anyhow::Result<()> { +pub async fn delete(client: &Client, namespace: &str, instance_id: &str) -> anyhow::Result<()> { (*client) - .send_json_request::<(), ()>(&format!("/instance/{}", instance_id), Method::DELETE, None) + .send_json_request::<(), ()>( + &format!("/instance/{}/{}", namespace, instance_id), + Method::DELETE, + None, + ) .await .context("Error deleting instance")?; debug!("Instance {} deleted", instance_id); diff --git a/kudoctl/src/client/namespace.rs b/kudoctl/src/client/namespace.rs index 14bc158b..11d82260 100644 --- a/kudoctl/src/client/namespace.rs +++ b/kudoctl/src/client/namespace.rs @@ -32,3 +32,13 @@ pub async fn list(client: &Client) -> Result { ); Ok(response) } + +/// Delete an namespace with the given id. +pub async fn delete(client: &Client, name: &str) -> anyhow::Result<()> { + (*client) + .send_json_request::<(), ()>(&format!("/namespace/{}", name), Method::DELETE, None) + .await + .context("Error deleting namespace")?; + debug!("Namespace {} deleted", name); + Ok(()) +} diff --git a/kudoctl/src/client/workload.rs b/kudoctl/src/client/workload.rs index 7ccabffc..4e7c1842 100644 --- a/kudoctl/src/client/workload.rs +++ b/kudoctl/src/client/workload.rs @@ -1,15 +1,32 @@ use anyhow::{Context, Result}; -use log::debug; +use log::{debug, warn}; use reqwest::Method; use serde::{Deserialize, Serialize}; use crate::{ client::types::IdResponse, - resource::{self, workload}, + resource::workload::{self, Resources}, }; use super::request::{Client, RequestError}; +/// Ports binding for the workload +#[derive(Debug, Deserialize, Serialize)] +pub struct PortBinding { + pub source: i32, + pub destination: i32, +} + +/// Workload, as stored in the controller +#[derive(Debug, Deserialize, Serialize)] +pub struct WorkloadBody { + pub name: String, + pub uri: String, + pub environment: Vec, + pub resources: Resources, + pub ports: Vec, +} + /// Creates a workload in the cluster. /// /// Returns the id of the workload. @@ -18,11 +35,46 @@ pub async fn create( namespace: &str, workload: &workload::Workload, ) -> std::result::Result { + let workload_body = WorkloadBody { + name: workload.name.clone(), + uri: workload.uri.clone(), + environment: workload.env.as_deref().unwrap_or_default().to_vec(), + resources: workload.resources.to_owned(), + ports: workload + .ports + .to_owned() + .unwrap_or_default() + .into_iter() + .map(|p| { + // parse the port binding from the string + + let mut splitted = p.split(':'); + let source = splitted.next().unwrap_or(&p).parse::().unwrap_or(0); + + if source == 0 { + warn!("Invalid port binding: {}", p); + } + + let destination = if let Some(s) = splitted.nth(1) { + debug!("No destination port specified, using source port"); + s.parse::().unwrap_or(source) + } else { + source + }; + + PortBinding { + source, + destination, + } + }) + .collect(), + }; + let response: IdResponse = (*client) .send_json_request( format!("/workload/{}", namespace).as_str(), Method::PUT, - Some(workload), + Some(&workload_body), ) .await?; debug!("Workload {} created", response.id); @@ -51,13 +103,9 @@ pub async fn update( /// Get info about a workload. /// /// Returns the workload info. -pub async fn get( - client: &Client, - namespace: &str, - workload_id: &str, -) -> Result { - let response: workload::Workload = (*client) - .send_json_request::( +pub async fn get(client: &Client, namespace: &str, workload_id: &str) -> Result { + let response: WorkloadBody = (*client) + .send_json_request::( &format!("/workload/{}/{}", namespace, workload_id), Method::GET, None, @@ -70,7 +118,7 @@ pub async fn get( #[derive(Debug, Deserialize, Serialize)] pub struct GetWorkloadResponse { pub count: u64, - pub workloads: Vec, + pub workloads: Vec, #[serde(skip)] pub show_header: bool, } diff --git a/kudoctl/src/main.rs b/kudoctl/src/main.rs index acdd77ad..ca5d7a1a 100644 --- a/kudoctl/src/main.rs +++ b/kudoctl/src/main.rs @@ -56,10 +56,7 @@ async fn main() -> Result<(), Box> { global_config.controller_url = host.to_string(); } - // Set namespace if defined - if let Some(namespace) = cli.namespace { - global_config.namespace = namespace; - } + global_config.namespace = cli.namespace.unwrap_or_else(|| "default".to_string()); subcommands::match_subcommand(cli.command, &global_config).await; diff --git a/kudoctl/src/resource/parse.rs b/kudoctl/src/resource/parse.rs index 40bfc5f7..bf8c511d 100644 --- a/kudoctl/src/resource/parse.rs +++ b/kudoctl/src/resource/parse.rs @@ -32,7 +32,6 @@ resources: assert_eq!(workload.resources.memory, 2); assert_eq!(workload.resources.disk, 3); } - _ => panic!("Unexpected resource type"), } } @@ -58,7 +57,6 @@ ports: assert_eq!(workload.ports.as_ref().unwrap()[0], "8080:8080"); assert_eq!(workload.ports.as_ref().unwrap()[1], "8081:8081"); } - _ => panic!("Unexpected resource type"), } } @@ -84,7 +82,6 @@ env: assert_eq!(workload.env.as_ref().unwrap()[0], "KEY1=VALUE1"); assert_eq!(workload.env.as_ref().unwrap()[1], "KEY2=VALUE2"); } - _ => panic!("Unexpected resource type"), } } diff --git a/kudoctl/src/resource/workload.rs b/kudoctl/src/resource/workload.rs index ea255018..ff42cc89 100644 --- a/kudoctl/src/resource/workload.rs +++ b/kudoctl/src/resource/workload.rs @@ -13,7 +13,7 @@ pub struct Workload { } // Resources assigned to a workload -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Resources { // CPU in milliCPU pub cpu: u64, diff --git a/kudoctl/src/subcommands/apply.rs b/kudoctl/src/subcommands/apply.rs index 55ee67dd..a1cd4f49 100644 --- a/kudoctl/src/subcommands/apply.rs +++ b/kudoctl/src/subcommands/apply.rs @@ -93,7 +93,8 @@ pub async fn execute(args: Apply, conf: &config::Config) -> Result { }; if needs_instance_create { - let instance_id = client::instance::create(&client, &workload_id).await?; + let instance_id = + client::instance::create(&client, &conf.namespace, &workload.name).await?; info!( "Workload {} created with id {} and started with instance {}", diff --git a/kudoctl/src/subcommands/delete/instance.rs b/kudoctl/src/subcommands/delete/instance.rs index 488ff21b..cdcb0b81 100644 --- a/kudoctl/src/subcommands/delete/instance.rs +++ b/kudoctl/src/subcommands/delete/instance.rs @@ -7,5 +7,5 @@ use anyhow::{Context, Result}; /// Request deletion of a resource on the cluster. pub async fn execute(conf: &config::Config, id: &str) -> Result<()> { let client = Client::new(conf).context("Error creating client")?; - client::instance::delete(&client, id).await + client::instance::delete(&client, &conf.namespace, id).await } diff --git a/kudoctl/src/subcommands/delete/mod.rs b/kudoctl/src/subcommands/delete/mod.rs index 3a871bcd..445821d1 100644 --- a/kudoctl/src/subcommands/delete/mod.rs +++ b/kudoctl/src/subcommands/delete/mod.rs @@ -2,6 +2,7 @@ use crate::config; use anyhow::Result; use clap::{Args, ValueEnum}; mod instance; +mod namespace; mod resource; #[derive(Debug, Args)] @@ -23,6 +24,9 @@ enum Subjects { /// instances Instance, + + /// namespaces + Namespace, } /// match the subcommand to get the correct info @@ -30,6 +34,7 @@ pub async fn execute(args: Subcommand, conf: &config::Config) -> Result match args.subject { Subjects::Resource => resource::execute(conf, args.id.as_str()).await, Subjects::Instance => instance::execute(conf, args.id.as_str()).await, + Subjects::Namespace => namespace::execute(conf, args.id.as_str()).await, }?; Ok(String::new()) diff --git a/kudoctl/src/subcommands/delete/namespace.rs b/kudoctl/src/subcommands/delete/namespace.rs new file mode 100644 index 00000000..27ab121a --- /dev/null +++ b/kudoctl/src/subcommands/delete/namespace.rs @@ -0,0 +1,11 @@ +use crate::{ + client::{self, request::Client}, + config, +}; +use anyhow::{Context, Result}; + +/// Request deletion of a resource on the cluster. +pub async fn execute(conf: &config::Config, id: &str) -> Result<()> { + let client = Client::new(conf).context("Error creating client")?; + client::namespace::delete(&client, id).await +} diff --git a/kudoctl/src/subcommands/get/instance.rs b/kudoctl/src/subcommands/get/instance.rs index 02420fd1..f776585f 100644 --- a/kudoctl/src/subcommands/get/instance.rs +++ b/kudoctl/src/subcommands/get/instance.rs @@ -19,7 +19,7 @@ pub async fn execute( let search = search.unwrap(); let client = Client::new(conf).context("Error creating client")?; - let result = client::instance::get(&client, search.as_str()).await?; + let result = client::instance::get(&client, &conf.namespace, search.as_str()).await?; output::format_output(result, format) } diff --git a/kudoctl/src/subcommands/get/instances.rs b/kudoctl/src/subcommands/get/instances.rs index 75970afb..ebd2af04 100644 --- a/kudoctl/src/subcommands/get/instances.rs +++ b/kudoctl/src/subcommands/get/instances.rs @@ -14,7 +14,7 @@ pub async fn execute( show_header: bool, ) -> Result { let client = Client::new(conf).context("Error creating client")?; - let mut result = client::instance::list(&client).await?; + let mut result = client::instance::list(&client, &conf.namespace).await?; result.show_header = show_header; output::format_output(result, format) } diff --git a/kudoctl/src/subcommands/get/resource.rs b/kudoctl/src/subcommands/get/resource.rs index e3e3cdfd..86099419 100644 --- a/kudoctl/src/subcommands/get/resource.rs +++ b/kudoctl/src/subcommands/get/resource.rs @@ -1,8 +1,7 @@ use super::output::{self, OutputFormat}; use crate::{ - client::{self, request::Client}, + client::{self, request::Client, workload::WorkloadBody}, config, - resource::workload::Workload, }; use anyhow::{bail, Context, Result}; use std::fmt::Display; @@ -25,29 +24,33 @@ pub async fn execute( output::format_output(result, format) } -impl Display for Workload { +impl Display for WorkloadBody { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "name : {}\n", self.name)?; writeln!(f, "uri : {}\n", self.uri)?; - if let Some(ports) = &self.ports { - // display ports - let ports_str = ports - .iter() - .fold(String::new(), |acc, port| acc + &format!("{} ", port)) - .trim() - .replace(' ', ",") - .replace(':', "->"); + // display ports + let ports_str = self + .ports + .iter() + .fold(String::new(), |acc, port| { + acc + &format!("{}->{} ", port.source, port.destination) + }) + .trim() + .replace(' ', ","); + + if !ports_str.is_empty() { writeln!(f, "ports : {} ", ports_str)?; } - if let Some(env) = &self.env { - // display environment variables - let env_vars_str = env - .iter() - .fold(String::new(), |acc, env_var| acc + &format!("{} ", env_var)) - .trim() - .replace(' ', ","); + // display environment variables + let env_vars_str = self + .environment + .iter() + .fold(String::new(), |acc, env_var| acc + &format!("{} ", env_var)) + .trim() + .replace(' ', ","); + if !env_vars_str.is_empty() { writeln!(f, "env variables : {} ", env_vars_str)?; } diff --git a/kudoctl/src/subcommands/get/resources.rs b/kudoctl/src/subcommands/get/resources.rs index 2edede0c..f2bc0808 100644 --- a/kudoctl/src/subcommands/get/resources.rs +++ b/kudoctl/src/subcommands/get/resources.rs @@ -1,6 +1,6 @@ use crate::{ client::{self, request::Client, workload::GetWorkloadResponse}, - config, resource, + config, }; use anyhow::{Context, Result}; use std::fmt::Display; @@ -26,11 +26,7 @@ impl Display for GetWorkloadResponse { } for r in &self.workloads { - match r { - resource::Resource::Workload(workload) => { - writeln!(f, "{}\tWorkload\n", workload.name)?; - } - } + writeln!(f, "{}\tWorkload\n", r.name)?; } Ok(()) } diff --git a/network/src/utils/mod.rs b/network/src/utils/mod.rs index d5d8f918..740a9620 100644 --- a/network/src/utils/mod.rs +++ b/network/src/utils/mod.rs @@ -8,7 +8,7 @@ use crate::error::KudoNetworkError; const IFACE_MAX_SIZE: usize = 12; -pub(crate) fn bridge_name(node_id: String) -> String { +pub fn bridge_name(node_id: String) -> String { format!("kbr{}", &node_id[..min(IFACE_MAX_SIZE, node_id.len())]) } diff --git a/node-agent/Cargo.lock b/node-agent/Cargo.lock index 216c1383..7ac90331 100644 --- a/node-agent/Cargo.lock +++ b/node-agent/Cargo.lock @@ -5,3 +5,1255 @@ version = 3 [[package]] name = "agent" version = "0.1.0" + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bollard" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d4b9e55620571c2200f4be87db2a9a69e2a107fc7d206a6accad58c3536cb" +dependencies = [ + "base64", + "bollard-stubs", + "bytes", + "chrono", + "futures-core", + "futures-util", + "hex", + "http", + "hyper", + "hyperlocal", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.42.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4295240332c78d04291f3ac857a281d5534a8e036f3dfcdaa294b22c0d424427" +dependencies = [ + "chrono", + "serde", + "serde_with", +] + +[[package]] +name = "bytes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time", + "winapi", +] + +[[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", +] + +[[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", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +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.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "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", + "tokio-io-timeout", +] + +[[package]] +name = "hyperlocal" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" +dependencies = [ + "futures-util", + "hex", + "hyper", + "pin-project", + "tokio", +] + +[[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.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "node-agent" +version = "0.1.0" +dependencies = [ + "workload_manager_lib", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "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_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[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.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[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", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "workload_manager_lib" +version = "0.1.0" +dependencies = [ + "bollard", + "futures", + "tokio", + "tonic", +] diff --git a/node-agent/Cargo.toml b/node-agent/Cargo.toml index 4321a7e0..b2373175 100644 --- a/node-agent/Cargo.toml +++ b/node-agent/Cargo.toml @@ -7,5 +7,17 @@ edition = "2021" [dependencies] proto = { path = "../proto" } -workload_manager= {path = "./workload_manager"} -tokio = { version = "1.0", features = ["full"] } \ No newline at end of file +workload_manager = { path = "./workload_manager" } +node_manager = { path = "./node_manager" } +network = { path = "../network" } +tonic = "0.7.2" +tokio = { version = "1.20.1", features = ["full"] } +tokio-stream = "0.1.9" +async-stream = "0.3.3" +env_logger = "0.9.0" +log = "0.4.0" +confy = "0.4.0" +serde = "1.0.142" +serde_derive = "1.0.142" +uuid = { version="1.1.2", features = ["v4"] } +cidr = "0.2.1" diff --git a/node-agent/src/config.rs b/node-agent/src/config.rs new file mode 100644 index 00000000..d5b23bec --- /dev/null +++ b/node-agent/src/config.rs @@ -0,0 +1,62 @@ +use serde_derive::{Deserialize, Serialize}; + +/// +/// NodeAgentConfig is a structure to define node agent configuration +/// +/// server: node agent grpc server config +/// client: scheduler grpc server config +/// +#[derive(Serialize, Deserialize, Debug)] +pub struct NodeAgentConfig { + pub server: GrpcServerConfig, + pub client: GrpcServerConfig, +} + +/// +/// GrpcServerConfig is a structure to define any grpc server configuration +/// +/// host: ip or address of the grpc server +/// port: grpc server's port +/// +#[derive(Serialize, Deserialize, Debug)] +pub struct GrpcServerConfig { + pub host: String, + pub port: u16, +} + +/// The default configuration for node agent +impl Default for NodeAgentConfig { + fn default() -> Self { + NodeAgentConfig { + server: GrpcServerConfig { + host: "127.0.0.1".to_string(), + port: 50053, + }, + client: GrpcServerConfig { + host: "127.0.0.1".to_string(), + port: 50052, + }, + } + } +} + +/// new function: allows you to define custom server and client configurations +impl NodeAgentConfig { + pub fn new( + server_host: String, + server_port: u16, + client_host: String, + client_port: u16, + ) -> Self { + Self { + server: GrpcServerConfig { + host: server_host, + port: server_port, + }, + client: GrpcServerConfig { + host: client_host, + port: client_port, + }, + } + } +} diff --git a/node-agent/src/main.rs b/node-agent/src/main.rs index e7a11a96..b9395dea 100644 --- a/node-agent/src/main.rs +++ b/node-agent/src/main.rs @@ -1,3 +1,314 @@ -fn main() { - println!("Hello, world!"); +use std::env; +use std::net::Ipv4Addr; +use std::str::FromStr; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; + +use cidr::Ipv4Inet; +use log::{debug, info, trace}; +use network::node::request::SetupNodeRequest; +use tokio::sync::mpsc::channel; +use tokio::sync::Mutex; +use tokio::time; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{transport::Server, Request, Response, Status}; +use uuid::Uuid; + +mod config; + +use config::{GrpcServerConfig, NodeAgentConfig}; +use network::node::setup_node; +use node_manager::NodeSystem; +use workload_manager::workload_manager::WorkloadManager; + +use proto::agent::{ + instance_service_server::InstanceService, instance_service_server::InstanceServiceServer, + Instance, InstanceStatus, SignalInstruction, +}; +use proto::scheduler::{ + node_service_client::NodeServiceClient, NodeRegisterRequest, NodeRegisterResponse, NodeStatus, + Resource, ResourceSummary, Status as SchedulerStatus, +}; + +const NUMBER_OF_CONNECTION_ATTEMPTS: u16 = 10; + +/// +/// This Struct implement the Instance service from Node Agent proto file +/// +pub struct InstanceServiceController { + workload_manager: Arc>, +} + +impl InstanceServiceController { + pub fn new(node_id: String) -> Self { + Self { + workload_manager: Arc::new(Mutex::new(WorkloadManager::new(node_id))), + } + } +} + +#[tonic::async_trait] +impl InstanceService for InstanceServiceController { + type createStream = ReceiverStream>; + + async fn create( + &self, + request: Request, + ) -> Result, Status> { + let instance = request.into_inner(); + let channel = channel(1024); + + // call workload_manager create function in an other thread + let workload_manager = self.workload_manager.clone(); + + tokio::spawn(async move { + workload_manager + .clone() + .lock() + .await + .create(instance, channel.0.clone()) + .await + .map_err(|err| Status::internal(format!("Workload creation error: {}", err))) + .ok(); + }); + + // send receiver to scheduler + Ok(Response::new(ReceiverStream::new(channel.1))) + } + + async fn signal(&self, request: Request) -> Result, Status> { + let signal_instruction = request.into_inner(); + + // call workload_manager signal function in an other thread + let workload_manager = self.workload_manager.clone(); + + tokio::spawn(async move { + workload_manager + .clone() + .lock() + .await + .signal(signal_instruction) + .await + .map_err(|_| Status::internal("Cannot send signal to the workload")) + .ok(); + }); + + Ok(Response::new(())) + } +} + +/// +/// This function starts the grpc server of the Node Agent. +/// The server listens and responds to requests from the Scheduler. +/// The default port is 50053. +/// +fn create_grpc_server(config: GrpcServerConfig, node_id: String) -> tokio::task::JoinHandle<()> { + let addr = format!("{}:{}", config.host, config.port).parse().unwrap(); + let instance_service_controller = InstanceServiceController::new(node_id); + + info!("Node Agent server listening on {}", addr); + + tokio::spawn(async move { + Server::builder() + .add_service(InstanceServiceServer::new(instance_service_controller)) + .serve(addr) + .await + .unwrap() + }) +} + +/// +/// This function allows you to connect to the scheduler's grpc server. +/// +async fn connect_to_scheduler( + addr: String, +) -> Option> { + NodeServiceClient::connect(addr.clone()).await.ok() +} + +/// +/// This function allows you to register to the scheduler's grpc server. +/// +async fn register_to_scheduler( + client: &mut NodeServiceClient, + certificate: String, +) -> Option> { + let register_request = tonic::Request::new(NodeRegisterRequest { certificate }); + + client.register(register_request).await.ok() +} + +/// +/// This function allows you to send node status to the scheduler's grpc server. +/// +async fn send_node_status_to_scheduler( + client: &mut NodeServiceClient, + node_system_arc: Arc>, + node_id: String, +) -> Option> { + let node_status_stream = async_stream::stream! { + let mut interval = time::interval(Duration::from_secs(1)); + + let cpu_limit = node_system_arc.lock().await.total_cpu(); + let memory_limit = node_system_arc.lock().await.total_memory(); + let disk_limit = node_system_arc.lock().await.total_disk(); + + loop { + interval.tick().await; + + let cpu_usage = node_system_arc.lock().await.used_cpu(); + let memory_usage = node_system_arc.lock().await.used_memory(); + let disk_usage = node_system_arc.lock().await.used_disk(); + + let node_status = NodeStatus { + id: node_id.clone(), + status: SchedulerStatus::Running as i32, + status_description: "".into(), + resource: Some(Resource { + limit: Some(ResourceSummary { + cpu: cpu_limit, + memory: memory_limit, + disk: disk_limit, + }), + usage: Some(ResourceSummary { + cpu: cpu_usage, + memory: memory_usage, + disk: disk_usage, + }), + }), + }; + + debug!("Node resources sent to the Scheduler"); + + yield node_status; + } + }; + + client.status(Request::new(node_status_stream)).await.ok() +} + +/// +/// This function launch the Node Agent grpc client. +/// First, the client registered to the Scheduler. +/// Secondaly, once connected to it, it's send node resources to the Scheduler. +/// +fn create_grpc_client(config: GrpcServerConfig, node_id: String) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + // Connection to the Scheduler's grpc server + + let addr = format!("http://{}:{}", config.host, config.port); + let mut connection = connect_to_scheduler(addr.clone()).await; + + let mut attempts: u16 = 0; + while connection.is_none() { + if attempts <= NUMBER_OF_CONNECTION_ATTEMPTS { + sleep(Duration::from_secs(1)); + + debug!("Connection to grpc scheduler server failed, retrying..."); + connection = connect_to_scheduler(addr.clone()).await; + + attempts += 1; + } else { + panic!("Error, unable to connect to the Scheduler server."); + } + } + + let mut client = connection.unwrap(); + + info!("Node agent connected to the Scheduler at {}", addr); + + // Registration with the Scheduler + + let certificate = node_id.clone(); + let mut registration = register_to_scheduler(&mut client, certificate.clone()).await; + + // setup node network + + let node_ip = registration.unwrap().into_inner().ip; + let node_ip_addr = Ipv4Addr::from_str(&node_ip).unwrap(); + let node_ip_cidr = Ipv4Inet::new(node_ip_addr, 24).unwrap(); + + let request = SetupNodeRequest::new(node_id.to_string(), node_ip_cidr); + let response = setup_node(request).unwrap(); + + attempts = 0; + while registration.is_none() { + if attempts <= NUMBER_OF_CONNECTION_ATTEMPTS { + sleep(Duration::from_secs(1)); + + debug!("Registration to the Scheduler failed, retrying..."); + registration = register_to_scheduler(&mut client, certificate.clone()).await; + + attempts += 1; + } else { + panic!("Error, unable to register to the Scheduler."); + } + } + + info!("Node agent registered to the Scheduler"); + + // Send Node status to the Scheduler + + let node_system = NodeSystem::new(); + let arc_node_system = Arc::new(Mutex::new(node_system)); + + let mut send_node_resources_to_scheduler = send_node_status_to_scheduler( + &mut client, + Arc::clone(&arc_node_system), + node_id.clone(), + ) + .await; + + attempts = 0; + while send_node_resources_to_scheduler.is_none() { + if attempts <= NUMBER_OF_CONNECTION_ATTEMPTS { + sleep(Duration::from_secs(1)); + + debug!("Sending node status to the Scheduler failed, retrying..."); + send_node_resources_to_scheduler = send_node_status_to_scheduler( + &mut client, + Arc::clone(&arc_node_system), + node_id.clone(), + ) + .await; + + attempts += 1; + } else { + panic!("Error, unable to send node status to the Scheduler."); + } + } + }) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + info!("Starting up node agent"); + + info!("Loading config"); + let mut dir = env::current_exe()?; // get executable path + dir.pop(); // remove executable name + dir.push("agent.conf"); // add config file name + + trace!("Node Agent config at: {:?}", dir); + + // load config from path + let config: NodeAgentConfig = confy::load_path(dir.as_path())?; + debug!("config: {:?}", config); + + // generate node id + let node_id = Uuid::new_v4().to_string(); + + // start grpc server and client + let client_handler = create_grpc_client(config.client, node_id.clone()); + let server_handler = create_grpc_server(config.server, node_id.clone()); + + client_handler.await?; + server_handler.await?; + + info!("Shutting down node agent"); + + Ok(()) } diff --git a/node-agent/workload_manager/Cargo.lock b/node-agent/workload_manager/Cargo.lock index b5c768f1..a3e9d0a6 100644 --- a/node-agent/workload_manager/Cargo.lock +++ b/node-agent/workload_manager/Cargo.lock @@ -3,5 +3,1246 @@ version = 3 [[package]] -name = "workload-manager-lib" +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bollard" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d4b9e55620571c2200f4be87db2a9a69e2a107fc7d206a6accad58c3536cb" +dependencies = [ + "base64", + "bollard-stubs", + "bytes", + "chrono", + "futures-core", + "futures-util", + "hex", + "http", + "hyper", + "hyperlocal", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.42.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4295240332c78d04291f3ac857a281d5534a8e036f3dfcdaa294b22c0d424427" +dependencies = [ + "chrono", + "serde", + "serde_with", +] + +[[package]] +name = "bytes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time", + "winapi", +] + +[[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", +] + +[[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", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +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.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "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", + "tokio-io-timeout", +] + +[[package]] +name = "hyperlocal" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" +dependencies = [ + "futures-util", + "hex", + "hyper", + "pin-project", + "tokio", +] + +[[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.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "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_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[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.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[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", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "workload_manager_lib" version = "0.1.0" +dependencies = [ + "bollard", + "futures", + "tokio", + "tonic", +] diff --git a/node-agent/workload_manager/Cargo.toml b/node-agent/workload_manager/Cargo.toml index 9023980a..d08a2633 100644 --- a/node-agent/workload_manager/Cargo.toml +++ b/node-agent/workload_manager/Cargo.toml @@ -7,10 +7,24 @@ edition = "2021" [dependencies] proto = { path = "../../proto" } +network = { path = "../../network" } tonic = "0.7" bollard = "0.13" futures-util = "0.3" +futures = "0.3" +tokio = { version= "1", features = ["full"]} anyhow = "1.0" +sysinfo = "0.25.3" +cidr = "0.2.1" + +[dependencies.uuid] +version = "1.1.2" +features = [ + "v4", # Lets you generate random UUIDs + "fast-rng", # Use a faster (but still sufficiently random) RNG + "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs +] + [dev-dependencies] tokio-test = "*" \ No newline at end of file diff --git a/node-agent/workload_manager/src/workload_manager/mod.rs b/node-agent/workload_manager/src/workload_manager/mod.rs index 8580f7df..6430fa00 100644 --- a/node-agent/workload_manager/src/workload_manager/mod.rs +++ b/node-agent/workload_manager/src/workload_manager/mod.rs @@ -1 +1,2 @@ pub mod workload; +pub mod workload_listener; diff --git a/node-agent/workload_manager/src/workload_manager/workload/container.rs b/node-agent/workload_manager/src/workload_manager/workload/container.rs index 553beabd..7bf96f4b 100644 --- a/node-agent/workload_manager/src/workload_manager/workload/container.rs +++ b/node-agent/workload_manager/src/workload_manager/workload/container.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; + use bollard::container::{ - Config, KillContainerOptions, RemoveContainerOptions, RenameContainerOptions, + Config, KillContainerOptions, NetworkingConfig, RemoveContainerOptions, RenameContainerOptions, StopContainerOptions, }; use bollard::Docker; @@ -7,8 +9,10 @@ use bollard::Docker; use anyhow::{Context, Error, Result}; use bollard::image::CreateImageOptions; +use bollard::service::EndpointSettings; use futures_util::TryStreamExt; +use super::workload_runner::NetworkSettings; use super::workload_trait::Workload; use proto::agent::Instance; @@ -17,10 +21,11 @@ pub struct Container { } impl Container { - // - // Create a new workload (container) and start it - // - pub async fn new(instance: Instance) -> Result { + /// Create a new workload (container) and start it + pub async fn new( + instance: Instance, + network_settings: &NetworkSettings, + ) -> Result { let docker = Docker::connect_with_socket_defaults().context("Can't connect to docker socket. ")?; @@ -37,27 +42,7 @@ impl Container { .await .context("Can't create image. ")?; - let container_config: Config<&str> = Config { - image: Some(instance.uri.as_str()), - tty: Some(true), - ..Default::default() - }; - - let container_id = docker - .create_container::<&str, &str>(None, container_config) - .await - .context("Can't create container. ")? - .id; - - docker - .rename_container( - container_id.as_str(), - RenameContainerOptions { - name: instance.name, - }, - ) - .await - .ok(); + let container_id = create_container(&docker, instance, network_settings).await?; docker .start_container::(container_id.as_str(), None) @@ -67,9 +52,7 @@ impl Container { Ok(Container { id: container_id }) } - // - // Removes a container - // + /// Removes a container async fn remove(&self) -> Result<(), Error> { let docker = Docker::connect_with_socket_defaults().context("Can't connect to docker socket. ")?; @@ -94,9 +77,7 @@ impl Workload for Container { self.id.to_string() } - // // Gracefully stop a workload - // async fn stop(&self) -> Result<(), Error> { let docker = Docker::connect_with_socket_defaults().context("Can't connect to docker socket. ")?; @@ -116,10 +97,8 @@ impl Workload for Container { Ok(()) } - // // Force a workload to stop // (equivalent to a `kill -9` on linux) - // async fn kill(&self) -> Result<(), Error> { let docker = Docker::connect_with_socket_defaults().context("Can't connect to docker socket. ")?; @@ -138,21 +117,114 @@ impl Workload for Container { } } +/// It creates a container with the given instance's configuration +/// +/// Arguments: +/// +/// * `docker`: &Docker - This is the docker client that we created earlier. +/// * `instance`: Instance +/// +/// Returns: +/// +/// A string that is the container id. +async fn create_container( + docker: &Docker, + instance: Instance, + network_settings: &NetworkSettings, +) -> Result { + let mut ports = HashMap::new(); + let port_list = &instance.ports; + for port in port_list { + ports.insert( + port.destination.to_string(), + Some(vec![bollard::service::PortBinding { + host_port: Some(port.destination.to_string()), + ..Default::default() + }]), + ); + } + + let random_id = uuid::Uuid::new_v4().to_string(); + + let container_config: Config<&str> = Config { + image: Some(instance.uri.as_str()), + tty: Some(true), + host_config: Some(bollard::service::HostConfig { + port_bindings: Some(ports), + nano_cpus: instance + .resource + .clone() + .and_then(|resource| resource.limit.map(|limit| limit.cpu.try_into().unwrap())), + memory: instance + .resource + .clone() + .and_then(|resource| resource.limit.map(|limit| limit.memory.try_into().unwrap())), + + ..Default::default() + }), + networking_config: Some(NetworkingConfig { + endpoints_config: HashMap::from([( + random_id.as_str(), + EndpointSettings { + links: Some(vec![network_settings.bridge_name.clone()]), + ip_address: Some(instance.ip), + ..Default::default() + }, + )]), + }), + ..Default::default() + }; + + let container_id = docker + .create_container::<&str, &str>(None, container_config) + .await + .context("Can't create container. ")? + .id; + + docker + .rename_container( + container_id.as_str(), + RenameContainerOptions { + name: instance.name, + }, + ) + .await + .ok(); + + Ok(container_id) +} + #[cfg(test)] mod tests { - use crate::workload_manager::workload::workload_trait::Workload; + use std::str::FromStr; + + use crate::workload_manager::workload::{ + workload_runner::NetworkSettings, workload_trait::Workload, + }; use super::Container; use anyhow::{Error, Result}; - use bollard::{ - container::{ListContainersOptions, RemoveContainerOptions}, - Docker, + use bollard::{container::ListContainersOptions, Docker}; + use cidr::Ipv4Inet; + use network::node::{ + clean_node, + request::{CleanNodeRequest, SetupNodeRequest}, + setup_node, }; use proto::agent::{Instance, Resource, ResourceSummary, Type}; const IMAGE: &str = "alpine:3"; - async fn create_default_container() -> Result { + /// Creates a container used for test + /// + /// Arguments: + /// + /// * `node_id`: The ID of the node that the container is running on. + /// + /// Returns: + /// + /// A container + async fn create_default_container(node_id: String) -> Result { let resource: Resource = Resource { limit: Some(ResourceSummary { cpu: 0, @@ -166,10 +238,12 @@ mod tests { }), }; + let instance_id = node_id.clone(); + let instance = Instance { - id: "0".to_string(), + id: instance_id, name: "test_container".to_string(), - ip: "".to_string(), + ip: "127.0.0.1/32".to_string(), uri: IMAGE.to_string(), environment: Vec::new(), ports: Vec::new(), @@ -178,11 +252,58 @@ mod tests { r#type: Type::Container.into(), }; - Ok(Container::new(instance).await?) + Ok(Container::new( + instance, + &NetworkSettings { + node_id: node_id.clone(), + bridge_name: node_id, + }, + ) + .await?) } - async fn create_container_test() -> Result<(), Error> { - let container = create_default_container().await?; + /// It sets up a node, runs a test, and cleans up the node + /// + /// Arguments: + /// + /// * `test`: The test function to run. + /// * `node_id`: The name of the node to be created. + fn run_container(test: F, node_id: String) { + let mut nb_retry = 5; + + loop { + match setup_node(SetupNodeRequest::new( + node_id.to_string(), + Ipv4Inet::from_str("127.0.0.1/32").unwrap(), + )) { + Ok(_) => break, + Err(e) => { + if nb_retry <= 0 { + panic!("{:?}", e) + } + clean_node(CleanNodeRequest::new(node_id.to_string())).ok(); + nb_retry -= 1; + std::thread::sleep(core::time::Duration::from_millis(1000)); + } + }; + } + + tokio_test::block_on(test); + + clean_node(CleanNodeRequest::new(node_id.to_string())).ok(); + } + + /// It creates a container, checks that it exists, and then stops it + /// + /// Arguments: + /// + /// * `node_id`: The id of the node to create the container on. + /// + /// Returns: + /// + /// A Result<(), Error> + async fn create_container_test(node_id: String) -> Result<(), Error> { + let container = create_default_container(node_id).await?; let docker = Docker::connect_with_socket_defaults()?; @@ -198,23 +319,24 @@ mod tests { .iter() .any(|cont| cont.id.as_ref().unwrap() == &container.id),); - let _ = &docker - .remove_container( - container.id().as_str(), - Some(RemoveContainerOptions { - force: true, - ..Default::default() - }), - ) - .await?; + container.stop().await.unwrap(); Ok(()) } - async fn stop_container_test() -> Result<(), Error> { + /// It creates a container, stops it, and then checks that it's no longer in the list of containers + /// + /// Arguments: + /// + /// * `node_id`: The id of the node to run the test on. + /// + /// Returns: + /// + /// A Result<(), Error> + async fn stop_container_test(node_id: String) -> Result<(), Error> { let docker = Docker::connect_with_socket_defaults()?; - let container = create_default_container().await?; + let container = create_default_container(node_id).await?; container.stop().await?; @@ -237,11 +359,13 @@ mod tests { #[test] fn test_create_container() { - tokio_test::block_on(create_container_test()).unwrap(); + let name = "test_create"; + run_container(create_container_test(name.to_string()), name.to_string()); } #[test] fn test_stop_container() { - tokio_test::block_on(stop_container_test()).unwrap(); + let name = "test_stop"; + run_container(stop_container_test(name.to_string()), name.to_string()); } } diff --git a/node-agent/workload_manager/src/workload_manager/workload/mod.rs b/node-agent/workload_manager/src/workload_manager/workload/mod.rs index 8262ddd0..4993dce7 100644 --- a/node-agent/workload_manager/src/workload_manager/workload/mod.rs +++ b/node-agent/workload_manager/src/workload_manager/workload/mod.rs @@ -1,12 +1,3 @@ -use anyhow::Result; -use proto::agent::{Instance, Type}; -use workload_trait::Workload; - mod container; +pub mod workload_runner; pub mod workload_trait; - -pub async fn create(instance: Instance) -> Result { - match instance.r#type() { - Type::Container => container::Container::new(instance).await, - } -} diff --git a/node-agent/workload_manager/src/workload_manager/workload/workload_runner.rs b/node-agent/workload_manager/src/workload_manager/workload/workload_runner.rs new file mode 100644 index 00000000..7f0c1f12 --- /dev/null +++ b/node-agent/workload_manager/src/workload_manager/workload/workload_runner.rs @@ -0,0 +1,51 @@ +use super::{container::Container, workload_trait::Workload}; +use anyhow::Result; +use proto::agent::{Instance, Type}; + +pub struct NetworkSettings { + pub node_id: String, + pub bridge_name: String, +} + +pub struct WorkloadRunner { + network_settings: NetworkSettings, +} + +impl WorkloadRunner { + /// Create a new WorkloadRunner and returns it + /// + /// Arguments: + /// + /// * `node_id`: The ID of the node that the instance is running on. + /// * `node_ip`: The IP address of the node that the instance is running on. + /// + /// Returns: + /// + /// A new instance of the WorkloadRunner struct. + pub fn new(node_id: String) -> Self { + WorkloadRunner { + network_settings: NetworkSettings { + node_id: node_id.clone(), + bridge_name: network::utils::bridge_name(node_id), + }, + } + } + + /// run a workload based on the given instance definition + /// + /// The `run` function is the entry point for the `Workload` trait. It is the function that is + /// called when a user wants to run a workload + /// + /// Arguments: + /// + /// * `instance`: Instance - The instance of the workload to run. + /// + /// Returns: + /// + /// A future that resolves to a Workload. + pub async fn run(&self, instance: Instance) -> Result { + match instance.r#type() { + Type::Container => Container::new(instance, &self.network_settings).await, + } + } +} diff --git a/node-agent/workload_manager/src/workload_manager/workload/workload_trait.rs b/node-agent/workload_manager/src/workload_manager/workload/workload_trait.rs index 78d415f0..3436f103 100644 --- a/node-agent/workload_manager/src/workload_manager/workload/workload_trait.rs +++ b/node-agent/workload_manager/src/workload_manager/workload/workload_trait.rs @@ -4,14 +4,10 @@ use anyhow::Result; pub trait Workload { fn id(&self) -> String; - // - // Gracefully stops a workload - // + /// Gracefully stops a workload async fn stop(&self) -> Result<()>; - // - // Force a workload to stop - // (equivalent to a `kill -9` on linux) - // + /// Force a workload to stop + /// (equivalent to a `kill -9` on linux) async fn kill(&self) -> Result<()>; } diff --git a/node-agent/workload_manager/src/workload_manager/workload_listener/containe_listener.rs b/node-agent/workload_manager/src/workload_manager/workload_listener/containe_listener.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/node-agent/workload_manager/src/workload_manager/workload_listener/container_listener.rs b/node-agent/workload_manager/src/workload_manager/workload_listener/container_listener.rs new file mode 100644 index 00000000..bc1d8502 --- /dev/null +++ b/node-agent/workload_manager/src/workload_manager/workload_listener/container_listener.rs @@ -0,0 +1,426 @@ +use crate::workload_manager::workload_listener::workload_listener::WorkloadListener; + +use futures::StreamExt; + +use bollard::container::{CPUStats, StatsOptions}; +use bollard::errors::Error; +use bollard::models::{ContainerStateStatusEnum, HealthStatusEnum}; +use bollard::Docker; + +use tokio::runtime::Runtime; +use tonic::Status; + +use proto::agent::{Instance, InstanceStatus, Resource, ResourceSummary, Status as WorkloadStatus}; + +use sysinfo::{System, SystemExt}; +use tokio::sync::mpsc::Sender; + +fn convert_status(state: Option) -> WorkloadStatus { + match state.unwrap_or(ContainerStateStatusEnum::RUNNING) { + ContainerStateStatusEnum::RUNNING => WorkloadStatus::Running, + ContainerStateStatusEnum::REMOVING => WorkloadStatus::Destroying, + ContainerStateStatusEnum::EXITED => WorkloadStatus::Terminated, + ContainerStateStatusEnum::DEAD => WorkloadStatus::Crashed, + _ => WorkloadStatus::Running, + } +} + +#[derive(Clone)] +pub struct ContainerListener {} + +impl ContainerListener { + /// Fetches all the needed data and return it in an InstanceStatus + /// + /// # Arguments + /// + /// * `container_id` - container's id + /// * `instance` - instance's struct + /// * `docker_connection` - bollard's Docker Struct + pub async fn fetch_instance_status( + container_id: &str, + instance: &Instance, + docker_connection: &Docker, + ) -> Result { + let stats_options = Some(StatsOptions { + stream: true, + one_shot: false, + }); + + let lim = match instance.clone().resource { + Some(resource) => match resource.limit { + Some(limit) => limit, + None => return Err(Status::internal("Could not get limit in Instance struct")), + }, + None => return Err(Status::internal("No ressource in Instance struct")), + }; + + let container_data_result = docker_connection + .inspect_container(container_id, None) + .await; + let container_resources_opt = docker_connection + .stats(container_id, stats_options) + .next() + .await; + + let container_resources = container_resources_opt + .ok_or(|e: Error| e) + .map_err( + //At this point we have a Result, Error> so we use map_er two times + |_e| { + Status::internal(format!( + "Cannot get state of workload {} (container {}) ", + instance.id, container_id + )) + }, + )? + .map_err(|e| Status::internal(e.to_string()))?; + + let container_data = container_data_result.map_err(|e| Status::internal(e.to_string()))?; + + let container_health_opt = container_data.clone().state; + + let mut workload_status = convert_status( + container_data + .state + .ok_or_else(|| { + Status::internal(format!( + "Cannot get state of workload {} (container {}) ", + instance.id, container_id + )) + })? + .status, + ); + + // In case we have a healthcheck in our container, we can override it with WorkloadStatus::Starting + // https://docs.rs/bollard/latest/bollard/models/struct.Health.html + workload_status = container_health_opt.map_or_else( + || workload_status, + |s| { + s.health.map_or_else( + || workload_status, + |h| { + h.status.map_or_else( + || workload_status, + |health_enum| { + if health_enum == HealthStatusEnum::STARTING { + WorkloadStatus::Starting + } else { + workload_status + } + }, + ) + }, + ) + }, + ); + + let cpu_usage_in_milli_cpu = ContainerListener::calculate_cpu_usage( + container_resources.cpu_stats, + container_resources.precpu_stats, + ); + + Ok(InstanceStatus { + id: instance.clone().id, + status: workload_status as i32, + description: "heres a description".to_string(), + resource: Some(Resource { + limit: Some(ResourceSummary { + cpu: lim.clone().cpu, + memory: lim.clone().memory, + disk: lim.clone().disk, + }), + usage: Some(ResourceSummary { + cpu: cpu_usage_in_milli_cpu, + memory: container_resources.memory_stats.usage.unwrap_or(0), + disk: container_resources + .storage_stats + .read_count_normalized + .unwrap_or(0), + }), + }), + }) + } + + /// Calculates and returns the CPU usage of container in millicpu, https://docs.docker.com/engine/api/v1.41/#tag/Container/operation/ContainerStats + /// + /// # Arguments + /// + /// * `cpu` - cpu usage + /// * `pre_cpu` - pre_cpu usage + fn calculate_cpu_usage(cpu: CPUStats, pre_cpu: CPUStats) -> u64 { + let mut sys = System::new_all(); + sys.refresh_all(); + let cpu_cores_number = match sys.physical_core_count() { + None => 0, + Some(n) => n as u64, + }; + let total_mulli_cpu = cpu_cores_number * 1000; + + let cpu_delta = cpu.cpu_usage.total_usage - pre_cpu.cpu_usage.total_usage; + + let mut cpu_delta_system = cpu.system_cpu_usage.unwrap_or(0); + + //to avoid overflow: u64::abs does not exists and u64::abs_diff is deprecated + cpu_delta_system = if cpu_delta_system >= pre_cpu.system_cpu_usage.unwrap_or(0) { + cpu_delta_system - pre_cpu.system_cpu_usage.unwrap_or(0) + } else { + pre_cpu.system_cpu_usage.unwrap_or(0) - cpu_delta_system + }; + + let cpu_percentage = if cpu_delta_system > 0 { + (cpu_delta / cpu_delta_system) * cpu_cores_number + } else { + 0 + }; + + total_mulli_cpu * cpu_percentage + } +} + +impl WorkloadListener for ContainerListener { + fn run(id: String, instance: Instance, sender: Sender>) { + std::thread::spawn(move || -> Result<(), Status> { + #[cfg(unix)] + let docker = Docker::connect_with_socket_defaults().unwrap(); + let rt = Runtime::new().unwrap(); + + loop { + let new_instance_status = rt.block_on(ContainerListener::fetch_instance_status( + id.as_str(), + &instance, + &docker, + )); + match new_instance_status { + Ok(instance_to_send) => { + let status: i32 = instance_to_send.status; + + rt.block_on(sender.send(Ok(instance_to_send))) + .map_err(|e| Status::internal(e.to_string()))?; + + if status == WorkloadStatus::Crashed as i32 + || status == WorkloadStatus::Terminated as i32 + { + break; + } + } + Err(e) => { + rt.block_on(sender.send(Err(e))).unwrap(); + break; + } + }; + } + + Ok(()) + }); + } +} + +#[cfg(test)] +mod tests { + use super::WorkloadListener; + use bollard::service::HealthConfig; + use bollard::{container::Config, image::CreateImageOptions, Docker}; + use futures::TryStreamExt; + use proto::agent::Status as WorkloadStatus; + use proto::agent::{Instance, Port, Resource, ResourceSummary, Status, Type as IType}; + use tokio::sync::mpsc::channel; + + #[tokio::test] + async fn test_fetch() -> Result<(), Status> { + //test setup + #[cfg(unix)] + let docker = Docker::connect_with_socket_defaults().unwrap(); + + docker + .create_image( + Some(CreateImageOptions::<&str> { + from_image: "debian:latest", + ..Default::default() + }), + None, + None, + ) + .try_collect::>() + .await + .unwrap(); + + let health_check = Some(HealthConfig { + test: Some(vec![ + "/bin/echo".to_string(), + "a very well done test".to_string(), + ]), + interval: Some(1000000), + timeout: Some(500000000), + retries: Some(1), + start_period: Some(1000000), + }); + + let cfg = Config { + cmd: Some(vec!["tee"]), + image: Some("debian"), + tty: Some(true), + attach_stdin: Some(false), + attach_stdout: Some(false), + attach_stderr: Some(false), + open_stdin: Some(false), + healthcheck: health_check, + ..Default::default() + }; + + let container = docker + .create_container::<&str, &str>(None, cfg) + .await + .unwrap(); + docker + .start_container::(&container.clone().id, None) + .await + .unwrap(); + + let instance = Instance { + id: "someuuid".to_string(), + name: "somename".to_string(), + r#type: IType::Container as i32, + status: Status::Running as i32, + uri: "http://localhost".to_string(), + environment: vec!["A=0".to_string()], + resource: Some(Resource { + limit: Some(ResourceSummary { + cpu: u64::MAX, + memory: u64::MAX, + disk: u64::MAX, + }), + usage: Some(ResourceSummary { + cpu: 0, + memory: 0, + disk: 0, + }), + }), + ports: vec![Port { + source: 80, + destination: 80, + }], + ip: "127.0.0.1".to_string(), + }; + + let res = super::ContainerListener::fetch_instance_status( + container.id.as_str(), + &instance, + &docker, + ) + .await + .unwrap(); + + docker + .kill_container::(container.id.as_str(), None) + .await + .unwrap(); + docker + .remove_container(container.id.as_str(), None) + .await + .unwrap(); + + assert_eq!(res.id, instance.id); + assert!(res.resource.unwrap().usage.unwrap().cpu < u64::MAX); + + Ok(()) + } + + #[tokio::test] + async fn it_works() -> Result<(), ()> { + //test setup + #[cfg(unix)] + let docker = Docker::connect_with_socket_defaults().unwrap(); + + docker + .create_image( + Some(CreateImageOptions::<&str> { + from_image: "debian:latest", + ..Default::default() + }), + None, + None, + ) + .try_collect::>() + .await + .unwrap(); + + let cfg = Config { + cmd: Some(vec!["/bin/sleep", "5"]), + image: Some("debian"), + tty: Some(true), + attach_stdin: Some(false), + attach_stdout: Some(false), + attach_stderr: Some(false), + open_stdin: Some(false), + ..Default::default() + }; + + let container = docker + .create_container::<&str, &str>(None, cfg) + .await + .unwrap(); + docker + .start_container::(&container.clone().id, None) + .await + .unwrap(); + + let instance = Instance { + id: "someuuid".to_string(), + name: "somename".to_string(), + r#type: IType::Container as i32, + status: Status::Running as i32, + uri: "http://localhost".to_string(), + environment: vec!["A=0".to_string()], + resource: Some(Resource { + limit: Some(ResourceSummary { + cpu: u64::MAX, + memory: u64::MAX, + disk: u64::MAX, + }), + usage: Some(ResourceSummary { + cpu: 0, + memory: 0, + disk: 0, + }), + }), + ports: vec![Port { + source: 80, + destination: 80, + }], + ip: "127.0.0.1".to_string(), + }; + + let (tx, mut rx) = channel(1000); + + //test + super::ContainerListener::run(container.clone().id, instance, tx.clone()); + + loop { + let msg = rx.recv().await; + + let received = msg.unwrap().unwrap(); + let status = received.status as i32; + let received2 = received.clone(); + let received3 = received.clone(); + + println!("{:?}", received); + + assert_eq!(received.resource.unwrap().limit.unwrap().cpu, u64::MAX); + assert!(received2.resource.unwrap().usage.unwrap().cpu < u64::MAX); + assert!(received3.status as i32 <= 8); + + if status == WorkloadStatus::Crashed as i32 + || status == WorkloadStatus::Terminated as i32 + { + break; + } + } + + docker + .remove_container(container.id.as_str(), None) + .await + .unwrap(); + + Ok(()) + } +} diff --git a/node-agent/workload_manager/src/workload_manager/workload_listener/mod.rs b/node-agent/workload_manager/src/workload_manager/workload_listener/mod.rs index e69de29b..1f15f47e 100644 --- a/node-agent/workload_manager/src/workload_manager/workload_listener/mod.rs +++ b/node-agent/workload_manager/src/workload_manager/workload_listener/mod.rs @@ -0,0 +1,16 @@ +pub mod container_listener; +#[allow(clippy::module_inception)] +pub mod workload_listener; + +use proto::agent::{Instance, InstanceStatus, Type}; +use tokio::sync::mpsc::Sender; +use tonic::Status; +use workload_listener::WorkloadListener; + +use self::container_listener::ContainerListener; + +pub fn create(id: String, instance: Instance, sender: Sender>) { + match instance.r#type() { + Type::Container => ContainerListener::run(id, instance, sender), + } +} diff --git a/node-agent/workload_manager/src/workload_manager/workload_listener/vm_listener.rs b/node-agent/workload_manager/src/workload_manager/workload_listener/vm_listener.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/node-agent/workload_manager/src/workload_manager/workload_listener/workload_listener.rs b/node-agent/workload_manager/src/workload_manager/workload_listener/workload_listener.rs new file mode 100644 index 00000000..885ab6bf --- /dev/null +++ b/node-agent/workload_manager/src/workload_manager/workload_listener/workload_listener.rs @@ -0,0 +1,14 @@ +use proto::agent::{Instance, InstanceStatus}; +use tokio::sync::mpsc::Sender; +use tonic::Status; + +pub trait WorkloadListener { + /// Launch a thread that will listen to a workload and send continously an InstanceStatus + /// + /// # Arguments + /// + /// * `id` - A String that is used by the workload's engine to identify it (docker containers' id for instance) + /// * `instance` - An Instance struct, given by the scheduler, + /// * `sender` - A sender given by the WorkloadManager, whose receiver is given to the scheduler + fn run(id: String, instance: Instance, sender: Sender>); +} diff --git a/scheduler/Cargo.toml b/scheduler/Cargo.toml index d00ca89e..13e1459f 100644 --- a/scheduler/Cargo.toml +++ b/scheduler/Cargo.toml @@ -17,3 +17,4 @@ serde_derive = "1.0.142" confy = "0.4.0" anyhow = "1.0.62" thiserror = "1.0.32" +async-stream = "0.3.3" diff --git a/scheduler/src/instance_listener.rs b/scheduler/src/instance/listener.rs similarity index 54% rename from scheduler/src/instance_listener.rs rename to scheduler/src/instance/listener.rs index 9db6fac3..2d4f7f39 100644 --- a/scheduler/src/instance_listener.rs +++ b/scheduler/src/instance/listener.rs @@ -1,11 +1,9 @@ -use log::debug; -use tokio::sync::mpsc; -use tokio_stream::wrappers::ReceiverStream; -use tonic::{Request, Response, Status}; - use proto::scheduler::{ instance_service_server::InstanceService, Instance, InstanceIdentifier, InstanceStatus, }; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response}; use crate::{manager::Manager, Event}; @@ -25,8 +23,8 @@ impl InstanceService for InstanceListener { async fn create( &self, request: Request, - ) -> Result, Status> { - debug!("received request: {:?}", request); + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); let (tx, rx) = Manager::create_mpsc_channel(); match self @@ -38,33 +36,22 @@ impl InstanceService for InstanceListener { return Ok(Response::new(ReceiverStream::new(rx))); } Err(_) => { - return Err(Status::internal("could not send event to manager")); + return Err(tonic::Status::internal("could not send event to manager")); } } } - type CreateStream = ReceiverStream>; + type CreateStream = ReceiverStream>; - async fn start(&self, request: Request) -> Result, Status> { - debug!("received request: {:?}", request); - let (tx, rx) = Manager::create_oneshot_channel(); - - match self - .sender - .send(Event::InstanceStart(request.into_inner().id, tx)) - .await - { - Ok(_) => { - return rx.await.unwrap(); - } - Err(_) => { - return Err(Status::internal("could not send event to manager")); - } - } + async fn start(&self, _: Request) -> Result, tonic::Status> { + Err(tonic::Status::unimplemented("not implemented")) } - async fn stop(&self, request: Request) -> Result, Status> { - debug!("received request: {:?}", request); + async fn stop( + &self, + request: Request, + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); let (tx, rx) = Manager::create_oneshot_channel(); match self @@ -76,13 +63,16 @@ impl InstanceService for InstanceListener { return rx.await.unwrap(); } Err(_) => { - return Err(Status::internal("could not send event to manager")); + return Err(tonic::Status::internal("could not send event to manager")); } } } - async fn destroy(&self, request: Request) -> Result, Status> { - debug!("received request: {:?}", request); + async fn destroy( + &self, + request: Request, + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); let (tx, rx) = Manager::create_oneshot_channel(); match self @@ -94,7 +84,7 @@ impl InstanceService for InstanceListener { return rx.await.unwrap(); } Err(_) => { - return Err(Status::internal("could not send event to manager")); + return Err(tonic::Status::internal("could not send event to manager")); } } } diff --git a/scheduler/src/instance/mod.rs b/scheduler/src/instance/mod.rs new file mode 100644 index 00000000..5301ebcb --- /dev/null +++ b/scheduler/src/instance/mod.rs @@ -0,0 +1,2 @@ +pub mod listener; +pub mod scheduled; diff --git a/scheduler/src/instance/scheduled.rs b/scheduler/src/instance/scheduled.rs new file mode 100644 index 00000000..e71ac8e6 --- /dev/null +++ b/scheduler/src/instance/scheduled.rs @@ -0,0 +1,82 @@ +use proto::scheduler::{Instance, InstanceStatus, Status}; +use tokio::sync::mpsc; + +use crate::{NodeIdentifier, ProxyError}; + +/// InstanceScheduled represents an instance that is scheduled to a node. It is used to send +/// messages to the node and contains the node identifier where it's scheduled. +/// +/// Properties: +/// +/// * `id`: The id of the instance. +/// * `instance`: The instance that is being registered. +/// * `node_id`: The node identifier of the node that the instance is running on. +/// * `tx`: This is the channel that the instance will use to send status updates to the controller. +#[derive(Debug, Clone)] +pub struct InstanceScheduled { + pub id: String, + pub instance: Instance, + pub node_id: Option, + pub tx: mpsc::Sender>, +} + +impl InstanceScheduled { + /// `new` creates a new `InstanceStatus` struct + /// + /// Arguments: + /// + /// * `id`: The id of the instance. + /// * `instance`: The instance that we want to run. + /// * `node_id`: The node identifier of the node that the instance is running on. + /// * `tx`: This is the channel that the instance will use to send status updates to the controller. + /// + /// Returns: + /// + /// A new instance of the struct `InstanceStatus` + pub fn new( + id: String, + instance: Instance, + node_id: Option, + tx: mpsc::Sender>, + ) -> Self { + Self { + id, + instance, + node_id, + tx, + } + } + + /// This function updates the status of the instance and sends the updated status to the controller + /// + /// Arguments: + /// + /// * `status`: The status of the node. + /// * `description`: A string that describes the status of the node. + /// + /// Returns: + /// + /// A Result<(), ProxyError> + pub async fn change_status( + &mut self, + status: Status, + description: Option, + ) -> Result<(), ProxyError> { + self.instance.status = status.into(); + + self.tx + .send(Ok(InstanceStatus { + id: self.id.clone(), + status: status.into(), + status_description: description.unwrap_or_else(|| "".to_string()), + resource: match self.instance.status() { + Status::Running => self.instance.resource.clone(), + _ => None, + }, + })) + .await + .map_err(|_| ProxyError::ChannelSenderError)?; + + Ok(()) + } +} diff --git a/scheduler/src/lib.rs b/scheduler/src/lib.rs index a6a4c225..45a45f8d 100644 --- a/scheduler/src/lib.rs +++ b/scheduler/src/lib.rs @@ -1,3 +1,5 @@ +use std::net::IpAddr; + use proto::scheduler::{ Instance, InstanceStatus, NodeRegisterRequest, NodeRegisterResponse, NodeStatus, NodeUnregisterRequest, NodeUnregisterResponse, @@ -7,9 +9,10 @@ use tokio::sync::{mpsc, oneshot}; use tonic::Response; pub mod config; -pub mod instance_listener; +pub mod instance; pub mod manager; -pub mod node_listener; +pub mod node; +pub mod parser; pub mod storage; #[derive(Error, Debug)] @@ -26,6 +29,20 @@ pub enum SchedulerError { Unknown, } +#[derive(Error, Debug)] +pub enum ProxyError { + #[error("an transport error occurred from tonic: {0}")] + TonicTransportError(#[from] tonic::transport::Error), + #[error("an status error occurred from tonic: {0}")] + TonicStatusError(#[from] tonic::Status), + #[error("the gRPC client was not found")] + GrpcClientNotFound, + #[error("the gRPC stream was not found")] + GrpcStreamNotFound, + #[error("an error occurred while sending a message to the channel")] + ChannelSenderError, +} + #[derive(Debug)] #[allow(dead_code)] pub struct Node { @@ -33,6 +50,7 @@ pub struct Node { } pub type NodeIdentifier = String; +pub type InstanceIdentifier = String; #[derive(Debug)] pub enum Event { @@ -57,6 +75,7 @@ pub enum Event { // Node events NodeRegister( NodeRegisterRequest, + IpAddr, oneshot::Sender, tonic::Status>>, ), NodeUnregister( diff --git a/scheduler/src/manager.rs b/scheduler/src/manager.rs index f58bd960..166d5d97 100644 --- a/scheduler/src/manager.rs +++ b/scheduler/src/manager.rs @@ -10,11 +10,10 @@ use tokio::sync::mpsc; use tokio::{sync::oneshot, task::JoinHandle}; use tonic::{transport::Server, Response}; +use crate::instance::listener::InstanceListener; +use crate::node::listener::NodeListener; use crate::SchedulerError; -use crate::{ - config::Config, instance_listener::InstanceListener, node_listener::NodeListener, - storage::Storage, Event, Node, -}; +use crate::{config::Config, storage::Storage, Event, Node}; #[derive(Debug)] pub struct Manager { @@ -141,7 +140,7 @@ impl Manager { info!("received instance destroy event : {:?}", id); tx.send(Ok(Response::new(()))).unwrap(); } - Event::NodeRegister(request, tx) => { + Event::NodeRegister(request, _, tx) => { info!("received node register event : {:?}", request); tx.send(Ok(Response::new(NodeRegisterResponse::default()))) .unwrap(); diff --git a/scheduler/src/node_listener.rs b/scheduler/src/node/listener.rs similarity index 62% rename from scheduler/src/node_listener.rs rename to scheduler/src/node/listener.rs index b5b505cd..68c2341d 100644 --- a/scheduler/src/node_listener.rs +++ b/scheduler/src/node/listener.rs @@ -1,15 +1,13 @@ -use log::debug; use proto::scheduler::{ node_service_server::NodeService, NodeRegisterRequest, NodeRegisterResponse, NodeStatus, NodeUnregisterRequest, NodeUnregisterResponse, }; use tokio::sync::mpsc; -use tonic::{Request, Response, Status, Streaming}; +use tonic::{Request, Response, Streaming}; use crate::{manager::Manager, Event}; #[derive(Debug)] -#[allow(dead_code)] pub struct NodeListener { sender: mpsc::Sender, } @@ -25,33 +23,34 @@ impl NodeService for NodeListener { async fn status( &self, request: Request>, - ) -> Result, Status> { + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); + let mut stream = request.into_inner(); - let (tx, mut rx) = Manager::create_mpsc_channel(); + // send each status to the manager loop { + let (tx, mut rx) = Manager::create_mpsc_channel(); let message = stream.message().await?; + match message { Some(node_status) => { - debug!("Node status: {:?}", node_status); self.sender .send(Event::NodeStatus(node_status, tx.clone())) .await .unwrap(); + // wait for the manager to respond if let Some(res) = rx.recv().await { match res { - Ok(()) => { - debug!("Node status updated successfully"); - } - Err(err) => { - debug!("Error updating node status: {:?}", err); - return Err(err); - } + Ok(_) => {} + Err(err) => return Err(err), } } } None => { + log::error!("Node status stream closed"); + // todo: emit node crash event (get the node id from the first status) return Ok(Response::new(())); } } @@ -61,20 +60,23 @@ impl NodeService for NodeListener { async fn register( &self, request: Request, - ) -> Result, Status> { - debug!("{:?}", request); + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); + let (tx, rx) = Manager::create_oneshot_channel(); + let remote_addr = request.remote_addr().unwrap().ip(); + log::debug!("Registering a new node from: {:?}", remote_addr); match self .sender - .send(Event::NodeRegister(request.into_inner(), tx)) + .send(Event::NodeRegister(request.into_inner(), remote_addr, tx)) .await { Ok(_) => { return rx.await.unwrap(); } Err(_) => { - return Err(Status::internal("could not send event to manager")); + return Err(tonic::Status::internal("could not send event to manager")); } } } @@ -82,8 +84,9 @@ impl NodeService for NodeListener { async fn unregister( &self, request: Request, - ) -> Result, Status> { - debug!("{:?}", request); + ) -> Result, tonic::Status> { + log::debug!("received gRPC request: {:?}", request); + let (tx, rx) = Manager::create_oneshot_channel(); match self @@ -95,7 +98,7 @@ impl NodeService for NodeListener { return rx.await.unwrap(); } Err(_) => { - return Err(Status::internal("could not send event to manager")); + return Err(tonic::Status::internal("could not send event to manager")); } } } diff --git a/scheduler/src/node/mod.rs b/scheduler/src/node/mod.rs new file mode 100644 index 00000000..952b18e3 --- /dev/null +++ b/scheduler/src/node/mod.rs @@ -0,0 +1,11 @@ +use proto::scheduler::{Resource, Status}; + +pub mod listener; +pub mod registered; + +#[derive(Debug, Clone)] +pub struct Node { + pub id: String, + pub status: Status, + pub resource: Option, +} diff --git a/scheduler/src/node/registered.rs b/scheduler/src/node/registered.rs new file mode 100644 index 00000000..557ba2f5 --- /dev/null +++ b/scheduler/src/node/registered.rs @@ -0,0 +1,279 @@ +use std::{net::IpAddr, sync::Arc}; + +use proto::{ + agent::{instance_service_client::InstanceServiceClient, Signal, SignalInstruction}, + controller::node_service_client::NodeServiceClient, + scheduler::{Instance, Resource, Status}, +}; +use tokio::sync::{mpsc, Mutex}; +use tonic::{Request, Streaming}; + +use crate::{ + manager::Manager, + parser::{instance::InstanceParser, resource::ResourceParser}, + storage::{IStorage, Storage}, + InstanceIdentifier, ProxyError, +}; + +use super::Node; + +/// NodeRegistered represents a node that is registered to the cluster. It is responsible for +/// keeping track of the node's status to the controller, create/stop/destroy instances on the node. +/// +/// Properties: +/// +/// * `id`: The id of the node. +/// * `node`: The node that is being registered. +/// * `address`: The address of the node. +/// * `tx`: This is the channel that the node will use to send status updates to the controller. +/// * `grpc_client`: The grpc client for the node. +/// * `instances`: The instances that are running on the node. +#[derive(Debug)] +pub struct NodeRegistered { + pub id: String, + pub node: Node, + pub address: IpAddr, + pub tx: Option>>, + pub grpc_client: Option>, + pub instances: Storage, +} + +impl NodeRegistered { + /// `new` creates a new `NodeProxied` struct + /// + /// Arguments: + /// + /// * `id`: The id of the node. + /// * `node`: The node that this proxied node is connected to. + /// * `address`: The IP address of the node. + /// + /// Returns: + /// + /// A new instance of the NodeProxied struct. + pub fn new(id: String, node: Node, address: IpAddr) -> Self { + NodeRegistered { + id, + node, + address, + tx: None, + grpc_client: None, + instances: Storage::new(), + } + } + + /// This function connects to the gRPC server and stores the client in the `grpc_client` field of + /// the `Proxy` struct + /// + /// Returns: + /// + /// A Result<(), ProxyError> + pub async fn connect(&mut self) -> Result<(), ProxyError> { + let addr = format!("http://{}:{}", self.address, "50053"); + + let client = InstanceServiceClient::connect(addr) + .await + .map_err(ProxyError::TonicTransportError)?; + + self.grpc_client = Some(client); + Ok(()) + } + + /// It creates the node status stream between the controller and the scheduler. + /// It forwards the node status to the controller. + /// + /// Arguments: + /// + /// * `client`: The client that will be used to send the request to the node. + /// + /// Returns: + /// + /// A Result<(), ProxyError> + pub async fn open_node_status_stream( + &mut self, + client: Arc>>>, + ) -> Result<(), ProxyError> { + if self.tx.is_some() { + return Ok(()); + } + + let (tx, mut rx) = Manager::create_mpsc_channel(); + self.tx = Some(tx); + + let node_status_stream = async_stream::stream! { + loop { + let event = rx.recv().await; + match event { + Some(Ok(node_status)) => { + yield node_status; + } + Some(Err(_)) => { + break; + } + None => { + break; + } + } + } + + log::debug!("Node status stream closed"); + // todo: emit node crash event (get the node id from the first status) + }; + + let request = Self::wrap_request(node_status_stream); + + tokio::spawn(async move { + client + .lock() + .await + .as_mut() + .unwrap() + .update_node_status(request) + .await + }); + + Ok(()) + } + + /// This function updates the status of the node and sends the updated status to the controller + /// + /// Arguments: + /// + /// * `status`: The status of the node. + /// * `description`: A string that describes the status of the node. + /// * `resource`: The resource that the node is currently running. + /// + /// Returns: + /// + /// A Result<(), ProxyError> + pub async fn update_status( + &mut self, + status: Status, + description: Option, + resource: Option, + ) -> Result<(), ProxyError> { + self.node.status = status; + self.node.resource = resource; + + self.tx + .as_mut() + .ok_or(ProxyError::GrpcStreamNotFound)? + .send(Ok(proto::controller::NodeStatus { + id: self.id.clone(), + state: self.node.status.into(), + status_description: description.unwrap_or_else(|| "".to_string()), + resource: match self.node.status { + Status::Running => Some(ResourceParser::to_controller_resource( + self.node.resource.clone().unwrap(), + )), + _ => None, + }, + instances: self + .instances + .get_all() + .values() + .map(|instance| InstanceParser::fake_controller_instance(instance.id.clone())) + .collect(), + })) + .await + .map_err(|_| ProxyError::ChannelSenderError)?; + + Ok(()) + } + + /// Create a new instance to the node and return the InstanceStatus streaming. + /// + /// Arguments: + /// + /// * `instance`: Instance - The instance to create. + /// + /// Returns: + /// + /// Streaming of InstanceStatus - The streaming of the instance status. + pub async fn create_instance( + &mut self, + instance: Instance, + ) -> Result, ProxyError> { + let client = self + .grpc_client + .as_mut() + .ok_or(ProxyError::GrpcClientNotFound)?; + + let request = Self::wrap_request(InstanceParser::to_agent_instance(instance.clone())); + + let response = client + .create(request) + .await + .map_err(ProxyError::TonicStatusError)?; + + Ok(response.into_inner()) + } + + /// Send a stop signal to the node for the given instance. + /// + /// Arguments: + /// + /// * `id`: InstanceIdentifier - The instance identifier of the instance to stop. + /// + /// Returns: + /// + /// A future that resolves to a result of either a unit or a ProxyError. + pub async fn stop_instance(&mut self, id: InstanceIdentifier) -> Result<(), ProxyError> { + let client = self + .grpc_client + .as_mut() + .ok_or(ProxyError::GrpcClientNotFound)?; + + let request = Self::wrap_request(SignalInstruction { + signal: Signal::Stop.into(), + instance: Some(InstanceParser::fake_agent_instance(id)), + }); + + client + .signal(request) + .await + .map_err(ProxyError::TonicStatusError)?; + + Ok(()) + } + + /// Send a kill signal to the node for the given instance. + /// + /// Arguments: + /// + /// * `id`: InstanceIdentifier - The instance identifier of the instance to be killed. + /// + /// Returns: + /// + /// A future that resolves to a result of either a unit or a ProxyError. + pub async fn kill_instance(&mut self, id: InstanceIdentifier) -> Result<(), ProxyError> { + let client = self + .grpc_client + .as_mut() + .ok_or(ProxyError::GrpcClientNotFound)?; + + let request = Self::wrap_request(SignalInstruction { + signal: Signal::Kill.into(), + instance: Some(InstanceParser::fake_agent_instance(id)), + }); + + client + .signal(request) + .await + .map_err(ProxyError::TonicStatusError)?; + + Ok(()) + } + + /// This function takes a request and returns a request wrapped with tonic. + /// + /// Arguments: + /// + /// * `request`: The request object that you want to wrap. + /// + /// Returns: + /// + /// A Request object with the request as the inner value. + pub fn wrap_request(request: T) -> Request { + Request::new(request) + } +} diff --git a/scheduler/src/parser/instance.rs b/scheduler/src/parser/instance.rs new file mode 100644 index 00000000..89d17fd0 --- /dev/null +++ b/scheduler/src/parser/instance.rs @@ -0,0 +1,87 @@ +use proto::scheduler::{Instance, Status}; + +use super::{port::PortParser, resource::ResourceParser}; + +pub struct InstanceParser {} + +impl InstanceParser { + /// It converts a `Instance` struct to a `proto::agent::Instance` struct + /// + /// Arguments: + /// + /// * `instance`: Instance - The instance to convert + pub fn to_agent_instance(instance: Instance) -> proto::agent::Instance { + proto::agent::Instance { + id: instance.id, + name: instance.name, + r#type: instance.r#type, + status: instance.status, + uri: instance.uri, + environment: instance.environnement, + resource: instance.resource.map(ResourceParser::to_agent_resource), + ports: PortParser::to_agent_ports(instance.ports), + ip: instance.ip, + } + } + + /// It converts a `proto::agent::Instance` struct to a `Instance` struct + /// + /// Arguments: + /// + /// * `instance`: proto::agent::Instance + /// + /// Returns: + /// + /// An Instance struct + pub fn from_agent_instance(instance: proto::agent::Instance) -> Instance { + Instance { + id: instance.id, + name: instance.name, + r#type: instance.r#type, + status: instance.status, + uri: instance.uri, + environnement: instance.environment, + resource: instance.resource.map(ResourceParser::from_agent_resource), + ports: PortParser::from_agent_ports(instance.ports), + ip: instance.ip, + } + } + + /// It creates a fake agent instance + /// + /// Arguments: + /// + /// * `id`: The id of the agent instance. + pub fn fake_agent_instance(id: String) -> proto::agent::Instance { + proto::agent::Instance { + id, + name: "".to_string(), + r#type: proto::agent::Type::Container.into(), + status: Status::Stopping.into(), + uri: "".to_string(), + environment: vec![], + resource: None, + ports: vec![], + ip: "".to_string(), + } + } + + /// It creates a fake controller instance + /// + /// Arguments: + /// + /// * `id`: The id of the instance + pub fn fake_controller_instance(id: String) -> proto::controller::Instance { + proto::controller::Instance { + id, + name: "".to_string(), + r#type: proto::agent::Type::Container.into(), + state: Status::Stopping.into(), + uri: "".to_string(), + environnement: vec![], + resource: None, + ports: vec![], + ip: "".to_string(), + } + } +} diff --git a/scheduler/src/parser/mod.rs b/scheduler/src/parser/mod.rs new file mode 100644 index 00000000..672d117f --- /dev/null +++ b/scheduler/src/parser/mod.rs @@ -0,0 +1,3 @@ +pub mod instance; +pub mod port; +pub mod resource; diff --git a/scheduler/src/parser/port.rs b/scheduler/src/parser/port.rs new file mode 100644 index 00000000..62bd4f8b --- /dev/null +++ b/scheduler/src/parser/port.rs @@ -0,0 +1,82 @@ +use proto::scheduler::{InstanceStatus, NodeStatus, Port}; + +use super::resource::ResourceParser; + +pub struct PortParser {} + +impl PortParser { + /// `ports` is a vector of `Port`s, and we want to convert it into a vector of `proto::agent::Port`s + /// + /// Arguments: + /// + /// * `ports`: Vec + /// + /// Returns: + /// + /// A vector of proto::agent::Port + pub fn to_agent_ports(ports: Vec) -> Vec { + ports + .into_iter() + .map(|port| proto::agent::Port { + source: port.source, + destination: port.destination, + }) + .collect() + } + + /// `from_agent_ports` takes a vector of `proto::agent::Port`s and returns a vector of `Port`s + /// + /// Arguments: + /// + /// * `ports`: Vec + /// + /// Returns: + /// + /// A vector of Port structs. + pub fn from_agent_ports(ports: Vec) -> Vec { + ports + .into_iter() + .map(|port| Port { + source: port.source, + destination: port.destination, + }) + .collect() + } +} + +pub struct StatusParser {} + +impl StatusParser { + /// It takes a `proto::agent::InstanceStatus` and returns an `InstanceStatus` + /// + /// Arguments: + /// + /// * `status`: proto::agent::InstanceStatus + /// + /// Returns: + /// + /// A new InstanceStatus struct + pub fn from_agent_instance_status(status: proto::agent::InstanceStatus) -> InstanceStatus { + InstanceStatus { + id: status.id, + status: status.status, + status_description: status.description, + resource: status.resource.map(ResourceParser::from_agent_resource), + } + } + + /// It takes a `NodeStatus` struct and returns a `proto::controller::NodeStatus` struct + /// + /// Arguments: + /// + /// * `status`: NodeStatus - this is the status of the node. + pub fn to_controller_node_status(status: NodeStatus) -> proto::controller::NodeStatus { + proto::controller::NodeStatus { + id: status.id, + state: status.status, + status_description: status.status_description, + resource: status.resource.map(ResourceParser::to_controller_resource), + instances: vec![], // todo; + } + } +} diff --git a/scheduler/src/parser/resource.rs b/scheduler/src/parser/resource.rs new file mode 100644 index 00000000..987e2fac --- /dev/null +++ b/scheduler/src/parser/resource.rs @@ -0,0 +1,106 @@ +use proto::scheduler::{Resource, ResourceSummary}; + +pub struct ResourceParser {} + +impl ResourceParser { + /// It converts a Resource struct to a proto::agent::Resource struct. + /// + /// Arguments: + /// + /// * `resource`: Resource + /// + /// Returns: + /// + /// A proto::agent::Resource struct + pub fn to_agent_resource(resource: Resource) -> proto::agent::Resource { + proto::agent::Resource { + limit: resource.limit.map(Self::to_agent_resourcesummary), + usage: resource.usage.map(Self::to_agent_resourcesummary), + } + } + + /// It converts a Resource struct to a proto::controller::Resource struct. + /// + /// Arguments: + /// + /// * `resource`: Resource + /// + /// Returns: + /// + /// A proto::controller::Resource struct + pub fn to_controller_resource(resource: Resource) -> proto::controller::Resource { + proto::controller::Resource { + limit: resource.limit.map(Self::to_controller_resourcesummary), + usage: resource.usage.map(Self::to_controller_resourcesummary), + } + } + + /// It converts a proto::agent::Resource struct to a Resource struct. + /// + /// Arguments: + /// + /// * `resource`: proto::agent::Resource + /// + /// Returns: + /// + /// A Resource struct + pub fn from_agent_resource(resource: proto::agent::Resource) -> Resource { + Resource { + limit: resource.limit.map(Self::from_agent_resourcesummary), + usage: resource.usage.map(Self::from_agent_resourcesummary), + } + } + + /// It converts a ResourceSummary to a proto::agent::ResourceSummary struct. + /// + /// Arguments: + /// + /// * `resource`: ResourceSummary + /// + /// Returns: + /// + /// A proto::agent::ResourceSummary struct + pub fn to_agent_resourcesummary(resource: ResourceSummary) -> proto::agent::ResourceSummary { + proto::agent::ResourceSummary { + cpu: resource.cpu, + memory: resource.memory, + disk: resource.disk, + } + } + + /// It converts a ResourceSummary to a proto::agent::ResourceSummary struct. + /// + /// Arguments: + /// + /// * `resource`: ResourceSummary + /// + /// Returns: + /// + /// A proto::agent::ResourceSummary struct + pub fn to_controller_resourcesummary( + resource: ResourceSummary, + ) -> proto::controller::ResourceSummary { + proto::controller::ResourceSummary { + cpu: resource.cpu, + memory: resource.memory, + disk: resource.disk, + } + } + + /// It converts a proto::agent::ResourceSummary to a ResourceSummary struct. + /// + /// Arguments: + /// + /// * `resource`: proto::agent::ResourceSummary + /// + /// Returns: + /// + /// A ResourceSummary struct + pub fn from_agent_resourcesummary(resource: proto::agent::ResourceSummary) -> ResourceSummary { + ResourceSummary { + cpu: resource.cpu, + memory: resource.memory, + disk: resource.disk, + } + } +}