diff --git a/Cargo.lock b/Cargo.lock index 014029d..eadbab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,180 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bty" +version = "0.1.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb14eb54d819224dedc18ddf2a1f2fc37f2a6546ddbe18b2e01d0bc27c75dced" +dependencies = [ + "paste", + "serde", + "uuid", +] + +[[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.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "ctl" version = "0.1.0" +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proto" +version = "0.1.0" +dependencies = [ + "bty", + "chrono", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "worker" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index dc6ab6a..39389c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,29 @@ [workspace] -members = ["ctl", "worker"] +members = ["ctl", "proto", "worker"] resolver = "2" [workspace.package] version = "0.1.0" edition = "2021" +[workspace.dependencies] +# Internal deps +ctl.path = "ctl" +proto.path = "proto" +worker.path = "worker" +# External deps (keep alphabetically sorted) +axum = "0.7.4" +bty = { version = "0.1.0-pre.1", features = ["uuid"] } +chrono = { version = "0.4", default-features = false, features = [ + "std", + "serde", +] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["serde", "v4"] } + [workspace.lints.clippy] all = "warn" pedantic = "warn" -wildcard_imports = { level = "allow", priority = 2 } +wildcard_imports = "allow" +module_name_repetitions = "allow" diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 67e37a5..42a4765 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -2,40 +2,44 @@ # Diagrama do sistema + + ```mermaid graph TB - user + user["User"] user -- request --> balancer - sysadmin + sysadmin["SysAdmin"] sysadmin -- (http) configures --> deployer - sysadmin ---> config_mgr - agent_mgr -- alerts --> sysadmin + sysadmin ---> config + sysadmin --> inspector + agent -- alerts --> sysadmin subgraph system-network - subgraph ctrl - deployer - balancer - agent_mgr - config_mgr - discovery + subgraph Controller + deployer["Deployer"] + balancer["Load Balancer"] + agent["Agent Manager"] + config["Config Manager"] + inspector["Inspector"] + discovery["Discovery"] deployer --> discovery discovery --- balancer - agent_mgr --- discovery + agent --- discovery end - balancer -- routes requests --> program + balancer -- routes requests --> service deployer -- (http) deploy service --> runner - monitor -- (http) send metrics and status --> agent_mgr + monitor -- (http) send metrics and status --> agent - subgraph worker - monitor - runner - program + subgraph Worker + monitor["Monitor"] + runner["Runner"] + service["(service)"] - runner --> program + runner --> service end end diff --git a/proto/Cargo.toml b/proto/Cargo.toml new file mode 100644 index 0000000..6b9f23b --- /dev/null +++ b/proto/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "proto" +version.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +bty.workspace = true +chrono.workspace = true +serde.workspace = true +serde_json.workspace = true +uuid.workspace = true diff --git a/proto/all.proto b/proto/all.proto deleted file mode 100644 index 21a27df..0000000 --- a/proto/all.proto +++ /dev/null @@ -1,79 +0,0 @@ -syntax = "proto3"; - -service Controller { - rpc Configure() returns (stream ConfigStatus); -} - -service DeployerService { - rpc Deploy(DeployMessage) returns (stream DeployStatus); - rpc DropDeployment(DropDeployMessage) returns (stream DropDeployStatus); - rpc ServiceStatus(ServiceStatusMessage) returns (WorkerStatus); -} - -service AgentManagerService { - rpc PushStatus(WorkerStatus); -} - -service WorkerRunnerService { - rpc Deploy(DeployMessage) returns (stream DeployStatus); - rpc DropDeployment(DropDeployMessage) returns (stream DropDeployStatus); - rpc Status() returns (WorkerStatus); -} - -message ServiceStatusMessage { - string service_name = 1; -} - -enum ConfigStatusState { - CONFIG_STATUS_STATE_UNKNOWN = 0; - CONFIG_STATUS_STATE_STARTED = 1; - CONFIG_STATUS_STATE_APPLIED = 2; -} - -message ConfigStatus { - ConfigStatusState state = 1; -} - -message WorkerStatus { - double cpu_usage = 1; - double mem_usage = 2; - // XX: More metrics? -} - -message DeployMessage { - string service_name = 1; - NetworkOptions network_options = 2; - string build_script = 3; - string runtime_string = 4; - uint32 concurrency = 5; -} - -message NetworkOptions { - bool open_ingress = 1; - optional uint32 expose_port = 2; -} - -message DropDeployMessage { - string service_name = 1; -} - -enum DeployActionState { - DEPLOY_ACTION_STATE_UNKNOWN = 0; - DEPLOY_ACTION_STATE_STARTED = 1; - DEPLOY_ACTION_STATE_RUNNING = 2; - DEPLOY_ACTION_STATE_FINISHED = 3; -} - -message DeployStatus { - DeployActionState state = 1; -} - -enum DropDeployState { - DROP_DEPLOY_STATE_UNKNOWN = 0; - DROP_DEPLOY_STATE_STARTED = 1; - DROP_DEPLOY_STATE_DROPPED = 2; -} - -message DropDeployStatus { - DropDeployState state = 1; -} diff --git a/proto/src/common/mod.rs b/proto/src/common/mod.rs new file mode 100644 index 0000000..bed5368 --- /dev/null +++ b/proto/src/common/mod.rs @@ -0,0 +1,2 @@ +pub mod node; +pub mod service; diff --git a/proto/src/common/node.rs b/proto/src/common/node.rs new file mode 100644 index 0000000..fd24ce7 --- /dev/null +++ b/proto/src/common/node.rs @@ -0,0 +1,30 @@ +use std::net::SocketAddr; + +use serde::{Deserialize, Serialize}; + +bty::brand!( + pub type NodeName = String; +); + +#[derive(Debug, Serialize, Deserialize)] +pub struct Node { + pub name: NodeName, + pub addr: SocketAddr, + pub kind: NodeKind, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NodeKind { + Ctl, + Worker, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Metrics { + pub cpu_usage: f64, + /// The total memory, in MiB. + pub mem_total_mib: f64, + /// The used memory, in MiB. + pub mem_used_mib: f64, +} diff --git a/proto/src/common/service.rs b/proto/src/common/service.rs new file mode 100644 index 0000000..009d52b --- /dev/null +++ b/proto/src/common/service.rs @@ -0,0 +1,44 @@ +use std::fmt; + +use serde::{Deserialize, Serialize}; + +bty::brand!( + pub type ServiceName = String; +); + +#[derive(Debug, Serialize, Deserialize)] +pub struct NetworkSpec { + /// If `None`, won't expose any port. + pub expose_port: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ServiceSpec { + pub name: ServiceName, + pub network: NetworkSpec, + pub scripts: Scripts, + /// The maximum number of instances that Tucano is allowed to run for this + /// service. + pub concurrency: u32, +} + +#[derive(Serialize, Deserialize)] +pub struct Scripts { + /// The script that is used to build a new instance of this service. + pub build_script: String, + /// The script that is used to run an instance of this service. + pub runtime_script: String, + /// An optional string that is used to remove files associated with this + /// service from a given worker node. + pub teardown_script: Option, +} + +impl fmt::Debug for Scripts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Scripts") + .field("build", &"<...>") + .field("runtime", &"<...>") + .field("teardown", &self.teardown_script.as_ref().map(|_| "<...>")) + .finish_non_exhaustive() + } +} diff --git a/proto/src/ctl/agent.rs b/proto/src/ctl/agent.rs new file mode 100644 index 0000000..d6caffc --- /dev/null +++ b/proto/src/ctl/agent.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::common::{ + node::{Metrics, NodeName}, + service::ServiceName, +}; + +/// Pushes new metrics of a given **worker** node. +/// +/// The server must validate whether the node name corresponds to the +/// appropriate node address. If they don't match, the operation fails. +/// +/// The server *may* ignore older requests that are received out-of-order with +/// respect to the `recorded_at` field. +#[derive(Debug, Serialize, Deserialize)] +pub struct PushWorkerMetricsReq { + pub node_name: NodeName, + pub metrics: Metrics, + /// The number of services that are being executed on the node. + pub services: HashMap, + pub recorded_at: DateTime, +} + +/// Response for [`PushWorkerMetricsReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct PushWorkerMetricsRes {} diff --git a/proto/src/ctl/config.rs b/proto/src/ctl/config.rs new file mode 100644 index 0000000..6ed3617 --- /dev/null +++ b/proto/src/ctl/config.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +bty::brand!( + pub type ConfigVersion = Uuid; +); + +/// Controller configuration. +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + // TODO: Define configuration values. +} + +/// Returns the current configuration. +pub struct GetConfigurationReq {} + +/// Response for [`GetConfigurationReq`]. +pub struct GetConfigurationRes { + pub version: ConfigVersion, + pub config: Config, +} + +/// Defines a new configuration for the controller. +/// +/// Implementors must implement optimistic locking. +#[derive(Debug, Serialize, Deserialize)] +pub struct PutConfigurationReq { + /// The previous configuration version. + pub version: ConfigVersion, + /// The new configuration (will override the current one). + pub config: Config, +} + +/// Response for [`PutConfigurationReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct PutConfigurationRes { + /// The new configuration version. + pub version: ConfigVersion, +} diff --git a/proto/src/ctl/deployer.rs b/proto/src/ctl/deployer.rs new file mode 100644 index 0000000..8d6affa --- /dev/null +++ b/proto/src/ctl/deployer.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +use crate::common::service::{ServiceName, ServiceSpec}; + +/// Starts a new deploy in the system. +#[derive(Debug, Serialize, Deserialize)] +pub struct DeployReq { + pub service_spec: ServiceSpec, +} + +/// Response for [`DeployReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct DeployRes { + // ??? +} + +/// Stops a given service from running in the system. +#[derive(Debug, Serialize, Deserialize)] +pub struct StopReq { + pub service_name: ServiceName, + /// Whether to completely remove the service from the system, calling the + /// teardown script, if any. + pub remove: bool, +} + +/// Response for [`StopReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct StopRes { + /// Whether the service was removed. + /// + /// Only returns `true` if the service has a teardown script and it was + /// successfully executed. + pub removed: bool, +} diff --git a/proto/src/ctl/inspector.rs b/proto/src/ctl/inspector.rs new file mode 100644 index 0000000..733c1ce --- /dev/null +++ b/proto/src/ctl/inspector.rs @@ -0,0 +1,36 @@ +use std::{collections::HashMap, os::unix::net::SocketAddr}; + +use serde::{Deserialize, Serialize}; + +use crate::common::{ + node::{Metrics, Node}, + service::ServiceName, +}; + +/// Returns the current system's topological information (regarding its nodes). +#[derive(Debug, Serialize, Deserialize)] +pub struct InspectTopologyReq {} + +/// Response for [`InspectTopologyReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct InspectTopologyRes { + pub nodes: Vec<(Node, Metrics)>, + // More stuff? +} + +/// Returns information about the **services** that are being executed on the +/// system. +pub struct InspectServicesReq {} + +/// Response for [`InspectServicesReq`]. +pub struct InspectServicesRes { + pub services: HashMap, +} + +pub struct ServiceInfo { + /// The current number of service instances that are running. + pub total: u32, + /// Maps the node address to the number of instances that are executing on + /// it. + pub nodes: HashMap, +} diff --git a/proto/src/ctl/mod.rs b/proto/src/ctl/mod.rs new file mode 100644 index 0000000..876f383 --- /dev/null +++ b/proto/src/ctl/mod.rs @@ -0,0 +1,11 @@ +/// Agent manager. +pub mod agent; + +/// System inspector, used to introspect the system and its nodes. +pub mod inspector; + +/// Configuration manager. +pub mod config; + +/// Deployer. +pub mod deployer; diff --git a/proto/src/etc/error.rs b/proto/src/etc/error.rs new file mode 100644 index 0000000..072a487 --- /dev/null +++ b/proto/src/etc/error.rs @@ -0,0 +1,11 @@ +use std::borrow::Cow; + +use serde::{Deserialize, Serialize}; + +/// Common error definition that is used by all procedures. +#[derive(Debug, Serialize, Deserialize)] +pub struct Error { + pub message: Cow<'static, str>, + // XX: Maybe add some error kind in the future? + // See: +} diff --git a/proto/src/etc/mod.rs b/proto/src/etc/mod.rs new file mode 100644 index 0000000..a91e735 --- /dev/null +++ b/proto/src/etc/mod.rs @@ -0,0 +1 @@ +pub mod error; diff --git a/proto/src/lib.rs b/proto/src/lib.rs new file mode 100644 index 0000000..8e14142 --- /dev/null +++ b/proto/src/lib.rs @@ -0,0 +1,6 @@ +pub mod common; + +pub mod ctl; +pub mod worker; + +pub mod etc; diff --git a/proto/src/worker/mod.rs b/proto/src/worker/mod.rs new file mode 100644 index 0000000..748377a --- /dev/null +++ b/proto/src/worker/mod.rs @@ -0,0 +1 @@ +pub mod runner; diff --git a/proto/src/worker/runner.rs b/proto/src/worker/runner.rs new file mode 100644 index 0000000..a1d85f6 --- /dev/null +++ b/proto/src/worker/runner.rs @@ -0,0 +1,41 @@ +//! Very similar to [`crate::ctl::deployer`], but while the former coordinates a +//! **system deploy**, this module is concerned with the actual deployment of a +//! service on a given worker node. + +use serde::{Deserialize, Serialize}; + +use crate::common::service::{ServiceName, ServiceSpec}; + +/// Starts a **single** deploy of the given service spec. +/// +/// The worker server doesn't follow the concurrency limit for the service as +/// defined in [`ServiceSpec`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct DeployReq { + pub service_spec: ServiceSpec, +} + +/// Response for [`DeployReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct DeployRes { + // ??? +} + +/// Stops a given service from running in the system. +#[derive(Debug, Serialize, Deserialize)] +pub struct StopReq { + pub service_name: ServiceName, + /// Whether to completely remove the service from the node, calling the + /// teardown script, if any. + pub remove: bool, +} + +/// Response for [`StopReq`]. +#[derive(Debug, Serialize, Deserialize)] +pub struct StopRes { + /// Whether the service was removed. + /// + /// Only returns `true` if the service has a teardown script and it was + /// successfully executed. + pub removed: bool, +}