From 35e96b918dcc407b83a5b88de3c288e2da18612d Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 26 Jan 2025 20:18:53 +0100 Subject: [PATCH 01/74] fix: Fix some issues in latest release --- clients/jvm/paper/build.gradle.kts | 2 +- controller/src/application.rs | 8 ++++---- drivers/local/templates/papermc/startup/windows.ps1 | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/jvm/paper/build.gradle.kts b/clients/jvm/paper/build.gradle.kts index d4fd3ece..51a8662c 100644 --- a/clients/jvm/paper/build.gradle.kts +++ b/clients/jvm/paper/build.gradle.kts @@ -63,5 +63,5 @@ tasks { fun getFullVersion(): String { val commit = System.getenv("CURRENT_COMMIT") ?: "unknown" val build = System.getenv("CURRENT_BUILD") ?: "0" - return "${project.properties["client_version"]}-alpha.$commit+build.$build" + return "${project.properties["client_version"]}.$commit+build.$build" } \ No newline at end of file diff --git a/controller/src/application.rs b/controller/src/application.rs index 8655f2d9..d401d831 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -113,10 +113,6 @@ impl Controller { info!("Stopping all units..."); self.units.stop_all_instant(); - // Let the drivers cleanup there messes - info!("Letting the drivers cleanup..."); - self.drivers.cleanup(); - // Stop network stack info!("Stopping network stack..."); network_handle.shutdown(); @@ -127,6 +123,10 @@ impl Controller { .take() .unwrap() .shutdown_timeout(SHUTDOWN_WAIT); + + // Let the drivers cleanup there messes + info!("Letting the drivers cleanup..."); + self.drivers.cleanup(); } pub fn request_stop(&self) { diff --git a/drivers/local/templates/papermc/startup/windows.ps1 b/drivers/local/templates/papermc/startup/windows.ps1 index 119d2f61..500a53a0 100644 --- a/drivers/local/templates/papermc/startup/windows.ps1 +++ b/drivers/local/templates/papermc/startup/windows.ps1 @@ -21,4 +21,4 @@ if (Test-Path $PropertiesFile) { Set-Content $PropertiesFile -Value "server-port=$Port" } -java -Xms128M -XX:MaxRAMPercentage=95.0 -Dterminal.jline=false -Dterminal.ansi=true -jar $env:SERVER_JARFILE nogui \ No newline at end of file +java -Xms128M -XX:MaxRAMPercentage=95.0 -jar $env:SERVER_JARFILE nogui \ No newline at end of file From 29914ef1e58b2ef5361a19766066ca558d76f876 Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:37:52 +0000 Subject: [PATCH 02/74] fix: Fix broken devcontainers on github codespaces --- .devcontainer/devcontainer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 11a30901..369d48a7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ "dockerFile": "Dockerfile", "build": { "args": { - "REMOTE_USER": "${localEnv:USER}", + "REMOTE_USER": "${localEnv:USER:code}", "REMOTE_UID": "${localEnv:REMOTE_UID:1000}", "REMOTE_GID": "${localEnv:REMOTE_GID:1000}" } @@ -31,5 +31,6 @@ "github.vscode-github-actions" ] } - } + }, + "remoteUser": "${localEnv:USER:code}" } \ No newline at end of file From 5ee5250fc55ebdd96c67c970c400ea62fce2590a Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:44:31 +0000 Subject: [PATCH 03/74] fix: Remove unused programs from devcontainers --- .devcontainer/Dockerfile | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 061cb545..bf48af4f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ RUN pacman-key --init RUN pacman -Syu --noconfirm # Install required packages -RUN pacman -S base-devel git openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm +RUN pacman -S base-devel git less openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm # Install gRPC UI RUN aur-install grpcui-bin @@ -24,13 +24,4 @@ ENV HOME=/home/${REMOTE_USER} USER ${REMOTE_USER} # Install Rustup and set default toolchains -RUN rustup default nightly - -# Install binstall -RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - -# Add $HOME/.cargo/bin to PATH -ENV PATH="/root/.cargo/bin:${PATH}" - -# Install wasm-tools -RUN cargo binstall wasm-tools --no-confirm \ No newline at end of file +RUN rustup default nightly \ No newline at end of file From 4313bc1d5ad528c67b7e060f9bd233d81af5fd7e Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:52:35 +0000 Subject: [PATCH 04/74] fix: Add vi to installed programs --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index bf48af4f..6b0d7520 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ RUN pacman-key --init RUN pacman -Syu --noconfirm # Install required packages -RUN pacman -S base-devel git less openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm +RUN pacman -S base-devel git less vi openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm # Install gRPC UI RUN aur-install grpcui-bin From afdc5145042cb7a9baccfb8e1857ab412bf8a42f Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:58:58 +0000 Subject: [PATCH 05/74] fix: Fix some small errors in docs --- docker-compose.yml | 1 + docs/controller/installation/docker.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ba741182..e5eab4eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - "12892:12892" environment: - PTERODACTYL=true + - LOCAL=true volumes: - ./run/logs:/app/logs - ./run/auth:/app/auth diff --git a/docs/controller/installation/docker.md b/docs/controller/installation/docker.md index d83f7961..2b61697e 100644 --- a/docs/controller/installation/docker.md +++ b/docs/controller/installation/docker.md @@ -11,11 +11,12 @@ Next, add the following content to the file: ```yaml services: controller: - image: ghcr.io/httprafa/atomic-cloud:v0.3.0-alpha + image: ghcr.io/httprafa/atomic-cloud:latest ports: - "12892:12892" environment: - PTERODACTYL=true # Enable Pterodactyl driver installation + - LOCAL=true # Enable Local driver installation volumes: - ./logs:/app/logs - ./auth:/app/auth From 598217b21afb784378bfc62be3dc43bda7982462 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:47:39 +0000 Subject: [PATCH 06/74] wip: Start to rewrite the tick loop using tokio --- .devcontainer/Dockerfile | 9 +- .devcontainer/devcontainer.json | 6 +- controller/Cargo.toml | 10 +- controller/src/application.rs | 121 ++++-------- controller/src/application/auth.rs | 175 ++++++++---------- controller/src/application/driver.rs | 49 +++-- controller/src/application/driver/wasm.rs | 163 +++++++--------- .../src/application/driver/wasm/cloudlet.rs | 4 +- .../src/application/driver/wasm/process.rs | 2 +- controller/src/application/user.rs | 2 +- controller/src/config.rs | 125 +++++-------- controller/src/main.rs | 9 +- controller/src/network.rs | 52 ++---- controller/src/network/admin.rs | 7 +- controller/src/network/auth.rs | 6 +- controller/src/network/unit.rs | 1 - protocol/wit/driver.wit | 5 +- 17 files changed, 303 insertions(+), 443 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6b0d7520..94f4df49 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ RUN pacman-key --init RUN pacman -Syu --noconfirm # Install required packages -RUN pacman -S base-devel git less vi openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm +RUN pacman -S base-devel nushell git less nano openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm # Install gRPC UI RUN aur-install grpcui-bin @@ -23,5 +23,12 @@ ENV HOME=/home/${REMOTE_USER} USER ${REMOTE_USER} +# Switch to nano as default editor +ENV EDITOR=nano + +# Disable nu welcome message +RUN mkdir -p ~/.config/nushell/ +RUN echo "\$env.config.show_banner = false" >> ~/.config/nushell/config.nu + # Install Rustup and set default toolchains RUN rustup default nightly \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 369d48a7..a015f7e6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,11 @@ "customizations": { "vscode": { "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "terminal.integrated.profiles.linux": { + "nu": { + "path": "nu" + } + } }, "extensions": [ // Extensions for Rust development diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 4d935b01..ffcd4e5e 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -17,10 +17,10 @@ anyhow = "1.0.95" ctrlc = "3.4.5" # Unit system -uuid = { version = "1.12.0", features = ["v4"] } +uuid = { version = "1.12.1", features = ["v4"] } # Command line arguments -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.27", features = ["derive"] } # Regex parsing regex = "1.11.1" @@ -39,9 +39,9 @@ prost = "0.13.4" tonic = "0.12.3" # Drivers -wasmtime = { version = "28.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } -wasmtime-wasi = { version = "28.0.1", optional = true } -minreq = { version = "2.13.0", features = ["https-rustls"], optional = true } +wasmtime = { version = "29.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } +wasmtime-wasi = { version = "29.0.1", optional = true } +minreq = { version = "2.13.2", features = ["https-rustls"], optional = true } [build-dependencies] toml = "0.8.19" diff --git a/controller/src/application.rs b/controller/src/application.rs index d401d831..bf98b02a 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -29,67 +29,58 @@ static SHUTDOWN_WAIT: Duration = Duration::from_secs(10); const TICK_RATE: u64 = 1; -pub type ControllerHandle = Arc; -pub type WeakControllerHandle = Weak; - pub struct Controller { - handle: WeakControllerHandle, + /* Runtime State */ + running: Arc, - /* Immutable */ - pub(crate) configuration: Config, - pub(crate) drivers: Drivers, + /* Configuration */ + configuration: Config, - /* Runtime State */ - runtime: RwLock>, - running: AtomicBool, + /* Drivers */ + drivers: Drivers, /* Authentication */ auth: Auth, + users: Users, - /* Accessed rarely */ - cloudlets: RwLock, - deployments: RwLock, - - /* Accessed frequently */ + /* Resources */ + cloudlets: Cloudlets, + deployments: Deployments, units: Units, - users: Users, /* Event Bus */ event_bus: EventBus, } impl Controller { - pub fn new(configuration: Config) -> Arc { - Arc::new_cyclic(move |handle| { - let auth = Auth::load_all(); - let drivers = Drivers::load_all(configuration.identifier.as_ref().unwrap()); - let cloudlets = Cloudlets::load_all(handle.clone(), &drivers); - let deployments = Deployments::load_all(handle.clone(), &cloudlets); - let units = Units::new(handle.clone()); - let users = Users::new(handle.clone()); - let event_bus = EventBus::new(/*handle.clone()*/); - Self { - handle: handle.clone(), - configuration, - drivers, - runtime: RwLock::new(Some( - Builder::new_multi_thread() - .enable_all() - .build() - .expect("Failed to create Tokio runtime"), - )), - running: AtomicBool::new(true), - auth, - cloudlets: RwLock::new(cloudlets), - deployments: RwLock::new(deployments), - units, - users, - event_bus, - } - }) + pub async fn new(configuration: Config) -> Self { + let auth = Auth::load_all().await; + let drivers = Drivers::load_all(&configuration.identifier).await; + let cloudlets = Cloudlets::load_all(handle.clone(), &drivers); + let deployments = Deployments::load_all(handle.clone(), &cloudlets); + let units = Units::new(handle.clone()); + let users = Users::new(handle.clone()); + let event_bus = EventBus::new(/*handle.clone()*/); + Self { + configuration, + drivers, + runtime: RwLock::new(Some( + Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to create Tokio runtime"), + )), + running: Arc::new(AtomicBool::new(true)), + auth, + cloudlets: RwLock::new(cloudlets), + deployments: RwLock::new(deployments), + units, + users, + event_bus, + } } - pub fn start(&self) { + pub async fn start(&self) { // Set up signal handlers self.setup_interrupts(); @@ -117,13 +108,6 @@ impl Controller { info!("Stopping network stack..."); network_handle.shutdown(); - // Wait for all tokio task to finish - info!("Stopping async runtime..."); - (*self.runtime.write().unwrap()) - .take() - .unwrap() - .shutdown_timeout(SHUTDOWN_WAIT); - // Let the drivers cleanup there messes info!("Letting the drivers cleanup..."); self.drivers.cleanup(); @@ -134,28 +118,8 @@ impl Controller { self.running.store(false, Ordering::Relaxed); } - pub fn lock_cloudlets(&self) -> RwLockReadGuard { - self.cloudlets - .read() - .expect("Failed to get lock to cloudlets") - } - - pub fn lock_deployments(&self) -> RwLockReadGuard { - self.deployments - .read() - .expect("Failed to get lock to deployments") - } - - pub fn lock_cloudlets_mut(&self) -> RwLockWriteGuard { - self.cloudlets - .write() - .expect("Failed to get lock to cloudlets") - } - - pub fn lock_deployments_mut(&self) -> RwLockWriteGuard { - self.deployments - .write() - .expect("Failed to get lock to deployments") + pub fn get_config(&self) -> &Config { + &self.configuration } pub fn get_drivers(&self) -> &Drivers { @@ -201,14 +165,11 @@ impl Controller { fn setup_interrupts(&self) { // Set up signal handlers - let controller = self.handle.clone(); + let running = self.running.clone(); ctrlc::set_handler(move || { info!("Interrupt signal received. Stopping..."); - if let Some(controller) = controller.upgrade() { - controller.request_stop(); - } - }) - .expect("Failed to set Ctrl+C handler"); + running.store(false, Ordering::Relaxed); + }).expect("Failed to set Ctrl+C handler"); } } diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs index 90396542..2ad8fb4a 100644 --- a/controller/src/application/auth.rs +++ b/controller/src/application/auth.rs @@ -1,47 +1,90 @@ -use std::{ - collections::HashMap, - fs, - sync::{Arc, RwLock}, -}; +use std::{collections::HashMap, fs, sync::Arc}; use common::config::{LoadFromTomlFile, SaveToTomlFile}; use simplelog::{error, info, warn}; use stored::StoredUser; +use tokio::sync::RwLock; use uuid::Uuid; use crate::storage::Storage; -use super::unit::{UnitHandle, WeakUnitHandle}; - const DEFAULT_ADMIN_USERNAME: &str = "admin"; -pub type AuthUserHandle = Arc; -pub type AuthUnitHandle = Arc; +pub type AuthToken = String; -pub struct AuthUser { - pub username: String, - pub token: String, +#[derive(Clone)] +pub enum Authorization { + User(String), // Username + Unit(Uuid), // UUID } -pub struct AuthUnit { - pub unit: WeakUnitHandle, - pub token: String, +pub type AuthValidator = Arc; + +pub struct AuthValidatorInner { + pub tokens: RwLock>, +} + +impl AuthValidatorInner { + pub async fn get_auth(&self, token: &str) -> Option { + self.tokens.read().await.get(token).cloned() + } + + pub async fn register_unit(&self, uuid: Uuid) -> String { + let token = format!( + "sctl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + + self.tokens + .write() + .await + .insert(token.clone(), Authorization::Unit(uuid)); + + token + } + + pub async fn unregister(&self, token: &str) { + self.tokens.write().await.remove(token); + } + + pub async fn register_user(&self, username: &str) -> Option { + let token = format!( + "actl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + let stored_user = StoredUser { + token: token.to_string(), + }; + let user_path = Storage::get_user_file(username); + if stored_user.save_to_file(&user_path, true).is_err() { + error!( + "Failed to save user to file: {}", + &user_path.display() + ); + return None; + } + self.tokens.write().await.insert(token.clone(), Authorization::User(username.to_string())); + + Some(token) + } } pub struct Auth { - pub users: RwLock>, - pub units: RwLock>, + pub validator: AuthValidator, } impl Auth { - pub fn new(users: HashMap) -> Self { + pub fn new(users: HashMap) -> Self { Auth { - users: RwLock::new(users), - units: RwLock::new(HashMap::new()), + validator: Arc::new(AuthValidatorInner { + tokens: RwLock::new(users), + }), } } - pub fn load_all() -> Self { + pub async fn load_all() -> Self { info!("Loading users..."); let users_directory = Storage::get_users_folder(); @@ -77,7 +120,7 @@ impl Auth { continue; } - let name = match path.file_stem() { + let username = match path.file_stem() { Some(name) => name.to_string_lossy().to_string(), None => continue, }; @@ -87,7 +130,7 @@ impl Auth { Err(error) => { error!( "Failed to read user {} from file({:?}): {}", - &name, + &username, &path, &error ); @@ -95,31 +138,31 @@ impl Auth { } }; - let user = AuthUser { - username: name.clone(), - token: user.token, - }; if users .values() - .any(|u| u.username.eq_ignore_ascii_case(&user.username)) + .filter_map(|entry| match entry { + Authorization::User(name) => Some(name), + _ => None, + }) + .any(|name| name.eq_ignore_ascii_case(&username)) { - error!("User with the name {} already exists", &name); + error!("User with the name {} already exists", &username); continue; } - users.insert(user.token.clone(), Arc::new(user)); - info!("Loaded user {}", &name); + info!("Loaded user {}", &username); + users.insert(user.token.clone(), Authorization::User(username)); } let amount = users.len(); let auth = Auth::new(users); if amount == 0 { - let user = auth - .register_user(DEFAULT_ADMIN_USERNAME) + let token = auth.get_validator() + .register_user(DEFAULT_ADMIN_USERNAME).await .expect("Failed to create default admin user"); info!("-----------------------------------"); info!("No users found, created default admin user"); info!("Username: {}", DEFAULT_ADMIN_USERNAME); - info!("Token: {}", &user.token); + info!("Token: {}", &token); info!("-----------------------------------"); info!("Welcome to Atomic Cloud"); info!("-----------------------------------"); @@ -129,68 +172,8 @@ impl Auth { auth } - pub fn get_user(&self, token: &str) -> Option { - self.users.read().unwrap().get(token).cloned() - } - - pub fn get_unit(&self, token: &str) -> Option { - self.units.read().unwrap().get(token).cloned() - } - - pub fn register_unit(&self, unit: WeakUnitHandle) -> AuthUnitHandle { - let token = format!( - "sctl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - - let unit = Arc::new(AuthUnit { - unit, - token: token.clone(), - }); - self.units - .write() - .unwrap() - .insert(token.clone(), unit.clone()); - - unit - } - - pub fn unregister_unit(&self, unit: &UnitHandle) { - self.units.write().unwrap().retain(|_, value| { - if let Some(ref_unit) = value.unit.upgrade() { - !Arc::ptr_eq(&ref_unit, unit) - } else { - true - } - }) - } - - pub fn register_user(&self, username: &str) -> Option { - let token = format!( - "actl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - let stored_user = StoredUser { - token: token.to_string(), - }; - let user_path = Storage::get_user_file(username); - if stored_user.save_to_file(&user_path, true).is_err() { - error!( - "Failed to save user to file: {}", - &user_path.display() - ); - return None; - } - - let user = Arc::new(AuthUser { - username: username.to_string(), - token: token.clone(), - }); - self.users.write().unwrap().insert(token, user.clone()); - - Some(user) + pub fn get_validator(&self) -> AuthValidator { + self.validator.clone() } } diff --git a/controller/src/application/driver.rs b/controller/src/application/driver.rs index c0686457..2ef46396 100644 --- a/controller/src/application/driver.rs +++ b/controller/src/application/driver.rs @@ -24,57 +24,57 @@ pub struct Information { ready: bool, } +pub type BoxedDriver = Box; +pub type BoxedCloudlet = Box; + #[async_trait] pub trait GenericDriver: Send + Sync { - fn name(&self) -> &String; - fn init(&self) -> Result; - fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result; + fn name(&self) -> &str; + async fn init(&self) -> Result; + async fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result; /* Cleanup */ - fn cleanup(&self) -> Result<()>; + async fn cleanup(&self) -> Result<()>; /* Ticking */ - fn tick(&self) -> Result<()>; + async fn tick(&self) -> Result<()>; } #[async_trait] pub trait GenericCloudlet: Send + Sync { /* Ticking */ - fn tick(&self) -> Result<()>; + async fn tick(&self) -> Result<()>; /* Prepare */ - fn allocate_addresses(&self, request: &StartRequestHandle) -> Result>; - fn deallocate_addresses(&self, addresses: Vec) -> Result<()>; + async fn allocate_addresses(&self, request: &StartRequestHandle) -> Result>; + async fn deallocate_addresses(&self, addresses: Vec) -> Result<()>; /* Unitss */ - fn start_unit(&self, unit: &UnitHandle) -> Result<()>; - fn restart_unit(&self, unit: &UnitHandle) -> Result<()>; - fn stop_unit(&self, unit: &UnitHandle) -> Result<()>; + async fn start_unit(&self, unit: &UnitHandle) -> Result<()>; + async fn restart_unit(&self, unit: &UnitHandle) -> Result<()>; + async fn stop_unit(&self, unit: &UnitHandle) -> Result<()>; } -pub type DriverHandle = Arc; -pub type DriverCloudletHandle = Arc; - pub struct Drivers { - drivers: Vec, + drivers: Vec, } impl Drivers { - pub fn load_all(cloud_identifier: &str) -> Self { + pub async fn load_all(cloud_identifier: &str) -> Self { info!("Loading drivers..."); let mut drivers = Vec::new(); #[cfg(feature = "wasm-drivers")] - WasmDriver::load_all(cloud_identifier, &mut drivers); + WasmDriver::load_all(cloud_identifier, &mut drivers).await; info!("Loaded {} driver(s)", drivers.len()); Self { drivers } } - pub fn cleanup(&self) { + pub async fn cleanup(&self) { for driver in &self.drivers { - if let Err(error) = driver.cleanup() { + if let Err(error) = driver.cleanup().await { error!( "Failed to dispose resources of driver {}: {}", driver.name(), @@ -84,9 +84,9 @@ impl Drivers { } } - pub fn tick(&self) { + pub async fn tick(&self) { for driver in &self.drivers { - if let Err(error) = driver.tick() { + if let Err(error) = driver.tick().await { error!( "Failed to tick driver {}: {}", driver.name(), @@ -96,15 +96,14 @@ impl Drivers { } } - pub fn find_by_name(&self, name: &str) -> Option> { + pub fn find_by_name(&self, name: &str) -> Option<&Box> { self.drivers .iter() .find(|driver| driver.name().eq_ignore_ascii_case(name)) - .cloned() } - pub fn get_drivers(&self) -> Vec { - self.drivers.clone() + pub fn get_drivers(&self) -> &Vec> { + &self.drivers } } diff --git a/controller/src/application/driver/wasm.rs b/controller/src/application/driver/wasm.rs index 65f53bc7..c5766380 100644 --- a/controller/src/application/driver/wasm.rs +++ b/controller/src/application/driver/wasm.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::fs; -use std::sync::{Arc, Mutex, RwLock, Weak}; use anyhow::{anyhow, Result}; use cloudlet::WasmCloudlet; @@ -9,13 +8,15 @@ use config::WasmConfig; use generated::exports::cloudlet::driver::bridge; use generated::Driver; use simplelog::{error, info, warn}; -use wasmtime::component::{Component, Linker, ResourceAny}; -use wasmtime::{Config, Engine, Store}; +use tokio::sync::{Mutex, MutexGuard}; +use tonic::async_trait; +use wasmtime::component::{Component, Linker, Resource, ResourceAny}; +use wasmtime::{AsContextMut, Config, Engine, Store}; use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; use super::process::DriverProcess; use super::source::Source; -use super::{DriverCloudletHandle, GenericDriver, Information}; +use super::{BoxedCloudlet, BoxedDriver, GenericCloudlet, GenericDriver, Information}; use crate::application::cloudlet::{Capabilities, Cloudlet, HostAndPort, RemoteController}; use crate::storage::Storage; @@ -34,6 +35,7 @@ pub mod generated { bindgen!({ world: "driver", path: "../protocol/wit/", + async: true, }); } @@ -61,7 +63,12 @@ allow_remove_dir_all = false mounts = []"#; struct WasmDriverState { - handle: Weak, + /* Generic Information */ + + /* Processes */ + processes: HashMap, + + /* Wasmtime */ wasi: WasiCtx, table: ResourceTable, } @@ -76,99 +83,67 @@ impl WasiView for WasmDriverState { } impl generated::cloudlet::driver::types::Host for WasmDriverState {} - -impl generated::cloudlet::driver::api::Host for WasmDriverState { - fn get_name(&mut self) -> String { - self.handle.upgrade().unwrap().name.clone() - } -} - -struct WasmDriverHandle { - store: Store, - resource: ResourceAny, // This is delete when the store is dropped -} - -impl WasmDriverHandle { - fn new(store: Store, resource: ResourceAny) -> Self { - WasmDriverHandle { store, resource } - } - - fn get(&mut self) -> (ResourceAny, &mut Store) { - (self.resource, &mut self.store) - } -} - -pub struct WasmDriverData { - processes: RwLock>, -} +impl generated::cloudlet::driver::api::Host for WasmDriverState {} pub struct WasmDriver { - own: Weak, - name: String, bindings: Driver, - handle: Mutex>, - - data: WasmDriverData, + store: Mutex>, + instance: ResourceAny, // This is delete when the store is dropped } impl WasmDriver { - fn get_resource_and_store( - handle: &mut Option, - ) -> (ResourceAny, &mut Store) { - handle.as_mut().unwrap().get() + async fn get_required(&self) -> (&Driver, ResourceAny, &Mutex>) { + (&self.bindings, self.instance, &self.store) } } +#[async_trait] impl GenericDriver for WasmDriver { - fn name(&self) -> &String { + fn name(&self) -> &str { &self.name } - fn init(&self) -> Result { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings + async fn init(&self) -> Result { + let (bindings, instance, store) = self.get_required().await; + let mut store = store.lock().await; + match bindings .cloudlet_driver_bridge() .generic_driver() - .call_init(store, resource) + .call_init(store.as_context_mut(), instance).await { Ok(information) => Ok(information.into()), Err(error) => Err(error), } } - fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings + async fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result { + let (bindings, instance, store) = self.get_required().await; + let mut store = store.lock().await; + match bindings .cloudlet_driver_bridge() .generic_driver() .call_init_cloudlet( - store, - resource, + store.as_context_mut(), + instance, &cloudlet.name, &(&cloudlet.capabilities).into(), &(&cloudlet.controller).into(), - )? { - Ok(cloudlet) => Ok(Arc::new(WasmCloudlet { - handle: self.own.clone(), - resource: cloudlet, + ).await? { + Ok(cloudlet) => Ok(Box::new(WasmCloudlet { + instance: cloudlet, })), Err(error) => Err(anyhow!(error)), } } - fn cleanup(&self) -> Result<()> { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings + async fn cleanup(&self) -> Result<()> { + let (bindings, instance, store) = self.get_required().await; + let mut store = store.lock().await; + match bindings .cloudlet_driver_bridge() .generic_driver() - .call_cleanup(store, resource) + .call_cleanup(store.as_context_mut(), instance).await { Ok(result) => result.map_err(|errors| { anyhow!(errors @@ -181,14 +156,13 @@ impl GenericDriver for WasmDriver { } } - fn tick(&self) -> Result<()> { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings + async fn tick(&self) -> Result<()> { + let (bindings, instance, store) = self.get_required().await; + let mut store = store.lock().await; + match bindings .cloudlet_driver_bridge() .generic_driver() - .call_tick(store, resource) + .call_tick(store.as_context_mut(), instance).await { Ok(result) => result.map_err(|errors| { anyhow!(errors @@ -203,12 +177,12 @@ impl GenericDriver for WasmDriver { } impl WasmDriver { - fn new( + async fn new( config: &WasmConfig, cloud_identifier: &str, name: &str, source: &Source, - ) -> Result> { + ) -> Result { let config_directory = Storage::get_config_folder_for_driver(name); let data_directory = Storage::get_data_folder_for_driver(name); if !config_directory.exists() { @@ -230,6 +204,7 @@ impl WasmDriver { let mut engine_config = Config::new(); engine_config.wasm_component_model(true); + engine_config.async_support(true); if let Err(error) = engine_config.cache_config_load(Storage::get_configs_folder().join(ENGINE_CONFIG_FILE)) { @@ -243,7 +218,7 @@ impl WasmDriver { let component = Component::from_binary(&engine, &source.code)?; let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker_sync(&mut linker)?; + wasmtime_wasi::add_to_linker_async(&mut linker)?; Driver::add_to_linker(&mut linker, |state: &mut WasmDriverState| state)?; let mut wasi = WasiCtxBuilder::new(); @@ -278,44 +253,30 @@ impl WasmDriver { .build(); let table = ResourceTable::new(); - let mut store = Store::new( &engine, WasmDriverState { - handle: Weak::new(), + processes: HashMap::new(), wasi, table, }, ); - let bindings = Driver::instantiate(&mut store, &component, &linker)?; - let driver = Arc::new_cyclic(|handle| { - store.data_mut().handle = handle.clone(); - WasmDriver { - own: handle.clone(), - name: name.to_string(), - bindings, - handle: Mutex::new(None), - data: WasmDriverData { - processes: RwLock::new(HashMap::new()), - }, - } - }); - let driver_resource = driver - .bindings + let bindings = Driver::instantiate_async(&mut store, &component, &linker).await?; + let instance = bindings .cloudlet_driver_bridge() .generic_driver() - .call_constructor(&mut store, cloud_identifier)?; - driver - .handle - .lock() - .unwrap() - .replace(WasmDriverHandle::new(store, driver_resource)); - Ok(driver) + .call_constructor(&mut store, cloud_identifier).await?; + Ok(WasmDriver { + name: name.to_string(), + bindings, + store: Mutex::new(store), + instance, + }) } - pub fn load_all( + pub async fn load_all( cloud_identifier: &str, - drivers: &mut Vec>, + drivers: &mut Vec, ) -> WasmConfig { // Check if cache configuration exists { @@ -405,9 +366,9 @@ impl WasmDriver { }; info!("Compiling driver {}...", &name); - let driver = WasmDriver::new(&config, cloud_identifier, &name, &source); + let driver = WasmDriver::new(&config, cloud_identifier, &name, &source).await; match driver { - Ok(driver) => match driver.init() { + Ok(driver) => match driver.init().await { Ok(info) => { if info.ready { info!( @@ -415,7 +376,7 @@ impl WasmDriver { &driver.name, &info.version, &info.authors.join(", ") ); - drivers.push(driver); + drivers.push(Box::new(driver)); } else { warn!( "Driver {} marked itself as not ready, skipping...", diff --git a/controller/src/application/driver/wasm/cloudlet.rs b/controller/src/application/driver/wasm/cloudlet.rs index 686f0a13..ec455b51 100644 --- a/controller/src/application/driver/wasm/cloudlet.rs +++ b/controller/src/application/driver/wasm/cloudlet.rs @@ -4,7 +4,6 @@ use anyhow::{anyhow, Result}; use wasmtime::component::ResourceAny; use crate::application::{ - auth::AuthUnit, cloudlet::{Allocation, HostAndPort}, driver::GenericCloudlet, unit::{ @@ -18,8 +17,7 @@ use super::{ }; pub struct WasmCloudlet { - pub handle: Weak, - pub resource: ResourceAny, // This is delete if the handle is dropped + pub instance: ResourceAny, // This is delete if the handle is dropped } impl GenericCloudlet for WasmCloudlet { diff --git a/controller/src/application/driver/wasm/process.rs b/controller/src/application/driver/wasm/process.rs index 6bee2444..042a1cfb 100644 --- a/controller/src/application/driver/wasm/process.rs +++ b/controller/src/application/driver/wasm/process.rs @@ -22,7 +22,7 @@ use super::{ }; impl driver::process::Host for WasmDriverState { - fn spawn_process( + async fn spawn_process( &mut self, command: String, args: Vec, diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs index f68f550d..6c58a637 100644 --- a/controller/src/application/user.rs +++ b/controller/src/application/user.rs @@ -197,4 +197,4 @@ pub struct User { pub name: String, pub uuid: Uuid, pub unit: RwLock, -} +} \ No newline at end of file diff --git a/controller/src/config.rs b/controller/src/config.rs index 5153845a..b02fa0a0 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -17,24 +17,24 @@ const DEFAULT_EMPTY_UNIT_TIMEOUT: Duration = Duration::from_secs(120); const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; const DEFAULT_BIND_PORT: u16 = 12892; -#[derive(Deserialize, Serialize, Default)] +#[derive(Deserialize, Serialize)] pub struct NetworkConfig { - pub bind: Option, + pub bind: SocketAddr, } -#[derive(Deserialize, Serialize, Default)] +#[derive(Deserialize, Serialize)] pub struct Timings { - pub startup: Option, - pub restart: Option, - pub healthbeat: Option, - pub transfer: Option, - pub empty_unit: Option, + pub startup: Duration, + pub restart: Duration, + pub healthbeat: Duration, + pub transfer: Duration, + pub empty_unit: Duration, } -#[derive(Deserialize, Serialize, Default)] +#[derive(Deserialize, Serialize)] pub struct Config { /* Cloud Identification */ - pub identifier: Option, + pub identifier: String, /* Network */ pub network: NetworkConfig, @@ -44,77 +44,52 @@ pub struct Config { } impl Config { - fn load_or_empty() -> Self { - let path = Storage::get_primary_config_file(); - if !path.exists() { - return Self::default(); + fn default() -> Self { + Self { + identifier: Uuid::new_v4().to_string(), + network: NetworkConfig { + bind: SocketAddr::new( + DEFAULT_BIND_ADDRESS.parse().unwrap(), + DEFAULT_BIND_PORT, + ), + }, + timings: Timings { + startup: DEFAULT_EXPECTED_STARTUP_TIME, + restart: DEFAULT_EXPECTED_RESTART_TIME, + healthbeat: DEFAULT_HEALTH_CHECK_TIMEOUT, + transfer: DEFAULT_TRANSFER_TIMEOUT, + empty_unit: DEFAULT_EMPTY_UNIT_TIMEOUT, + }, } - Self::load_from_file(&path).unwrap_or_else(|error| { - warn!( - "Failed to read configuration from file: {}", - error - ); - Self::default() - }) } - pub fn new_filled() -> Self { - let mut config = Self::load_or_empty(); - - let mut save = false; - if config.identifier.is_none() { - config.identifier = Some(Uuid::new_v4().to_string()); - save = true; - } - if config.network.bind.is_none() { - config.network.bind = Some(SocketAddr::new( - DEFAULT_BIND_ADDRESS.parse().unwrap(), - DEFAULT_BIND_PORT, - )); - save = true; - } - if config.timings.startup.is_none() { - config.timings.startup = Some(DEFAULT_EXPECTED_STARTUP_TIME); - save = true; - } - if config.timings.restart.is_none() { - config.timings.restart = Some(DEFAULT_EXPECTED_RESTART_TIME); - save = true; - } - if config.timings.healthbeat.is_none() { - config.timings.healthbeat = Some(DEFAULT_HEALTH_CHECK_TIMEOUT); - save = true; - } - if config.timings.transfer.is_none() { - config.timings.transfer = Some(DEFAULT_TRANSFER_TIMEOUT); - save = true; - } - if config.timings.empty_unit.is_none() { - config.timings.empty_unit = Some(DEFAULT_EMPTY_UNIT_TIMEOUT); - save = true; - } - if save { - if let Err(error) = config.save_to_file(&Storage::get_primary_config_file(), true) { - error!( - "Failed to save generated configuration to file: {}", - &error + pub fn load() -> Self { + let path = Storage::get_primary_config_file(); + let mut config = if path.exists() { + Self::load_from_file(&path).unwrap_or_else(|error| { + warn!( + "Failed to read configuration from file: {}", + error ); - } - } + Self::default() + }) + } else { + Self::default() + }; - // Check config values are overridden by environment variables - if let Ok(identifier) = std::env::var("INSTANCE_IDENTIFIER") { - config.identifier = Some(identifier); - } - if let Ok(address) = std::env::var("BIND_ADDRESS") { - if let Ok(address) = address.parse() { - config.network.bind.replace(address); - } else { - error!("Failed to parse BIND_ADDRESS environment variable"); - } - } + // Check config values are overridden by environment variables + if let Ok(identifier) = std::env::var("INSTANCE_IDENTIFIER") { + config.identifier = identifier; + } + if let Ok(address) = std::env::var("BIND_ADDRESS") { + if let Ok(address) = address.parse() { + config.network.bind = address; + } else { + error!("Failed to parse BIND_ADDRESS environment variable"); + } + } - config + config } } diff --git a/controller/src/main.rs b/controller/src/main.rs index e6963dca..5473fd3d 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -22,7 +22,8 @@ include!(concat!(env!("OUT_DIR"), "/build_info.rs")); pub const AUTHORS: [&str; 1] = ["HttpRafa"]; -fn main() { +#[tokio::main] +async fn main() { let args = Args::parse(); CloudInit::init_logging(args.debug, false, Storage::get_latest_log_file()); CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); @@ -31,8 +32,8 @@ fn main() { info!("Starting cloud version v{}...", VERSION); info!("Loading configuration..."); - let configuration = Config::new_filled(); - let controller = Controller::new(configuration); + let configuration = Config::load(); + let controller = Controller::new(configuration).await; info!("Loaded cloud in {:.2?}", start_time.elapsed()); - controller.start(); + controller.start().await; } diff --git a/controller/src/network.rs b/controller/src/network.rs index f44e187a..764137b5 100644 --- a/controller/src/network.rs +++ b/controller/src/network.rs @@ -1,14 +1,13 @@ use anyhow::Result; use auth::{AdminInterceptor, UnitInterceptor}; use simplelog::{error, info}; -use std::sync::Arc; +use std::{net::SocketAddr, sync::Arc}; use tokio::{ - sync::watch::{self, Receiver, Sender}, - task::JoinHandle, + spawn, sync::watch::{self, Receiver, Sender}, task::JoinHandle }; use tonic::transport::Server; -use crate::application::{Controller, WeakControllerHandle}; +use crate::application::{auth::AuthValidator, Controller,}; use admin::{proto::admin_service_server::AdminServiceServer, AdminServiceImpl}; use unit::{proto::unit_service_server::UnitServiceServer, UnitServiceImpl}; @@ -20,59 +19,47 @@ pub mod unit; pub struct NetworkStack { shutdown: Sender, handle: JoinHandle<()>, - controller: WeakControllerHandle, } impl NetworkStack { - pub fn start(controller: Arc) -> Self { + pub fn start(controller: &Controller) -> Self { info!("Starting networking stack..."); let (sender, receiver) = watch::channel(false); + let bind = controller.get_config().network.bind.clone(); + let validator = controller.get_auth().get_validator(); return NetworkStack { shutdown: sender, - handle: controller - .get_runtime() - .as_ref() - .unwrap() - .spawn(launch_server(controller.clone(), receiver)), - controller: Arc::downgrade(&controller), + handle: spawn(launch_server(bind, validator, receiver)), }; - async fn launch_server(controller: Arc, shutdown: Receiver) { - if let Err(error) = run(controller, shutdown).await { + async fn launch_server(bind: SocketAddr, validator: AuthValidator, shutdown: Receiver) { + if let Err(error) = run(bind, validator, shutdown).await { error!("Failed to start gRPC server: {}", error); } } - async fn run(controller: Arc, mut shutdown: Receiver) -> Result<()> { - let address = controller - .configuration - .network - .bind - .expect("No bind address found in the config"); - + async fn run(bind: SocketAddr, validator: AuthValidator, mut shutdown: Receiver) -> Result<()> { let admin_service = AdminServiceImpl { - controller: Arc::clone(&controller), }; let unit_service = UnitServiceImpl { - controller: Arc::clone(&controller), }; - info!("Controller listening on {}", address); + info!("Controller listening on {}", bind); Server::builder() .add_service(AdminServiceServer::with_interceptor( admin_service, AdminInterceptor { - controller: Arc::clone(&controller), + validator: validator.clone(), }, )) .add_service(UnitServiceServer::with_interceptor( unit_service, - UnitInterceptor { controller }, + UnitInterceptor { validator }, )) - .serve_with_shutdown(address, async { + .serve_with_shutdown(bind, async { shutdown.changed().await.ok(); }) .await?; @@ -81,17 +68,10 @@ impl NetworkStack { } } - pub fn shutdown(self) { + pub async fn shutdown(self) { self.shutdown .send(true) .expect("Failed to send shutdown signal"); - if let Some(controller) = self.controller.upgrade() { - controller - .get_runtime() - .as_ref() - .unwrap() - .block_on(self.handle) - .expect("Failed to shutdown network stack"); - } + self.handle.await; } } diff --git a/controller/src/network/admin.rs b/controller/src/network/admin.rs index 60fbb92b..388d4b92 100644 --- a/controller/src/network/admin.rs +++ b/controller/src/network/admin.rs @@ -6,11 +6,7 @@ use uuid::Uuid; use crate::{ application::{ - cloudlet::{Capabilities, LifecycleStatus, RemoteController}, - deployment::{ScalingPolicy, StartConstraints}, - unit::{FallbackPolicy, KeyValue, Resources, Retention, Spec}, - user::transfer::TransferTarget, - ControllerHandle, CreationResult, + auth::AuthValidator, cloudlet::{Capabilities, LifecycleStatus, RemoteController}, deployment::{ScalingPolicy, StartConstraints}, unit::{FallbackPolicy, KeyValue, Resources, Retention, Spec}, user::transfer::TransferTarget, ControllerHandle, CreationResult }, VERSION, }; @@ -23,7 +19,6 @@ pub mod proto { } pub struct AdminServiceImpl { - pub controller: ControllerHandle, } #[async_trait] diff --git a/controller/src/network/auth.rs b/controller/src/network/auth.rs index 664f035c..76b51cb1 100644 --- a/controller/src/network/auth.rs +++ b/controller/src/network/auth.rs @@ -2,11 +2,11 @@ use std::sync::Arc; use tonic::{service::Interceptor, Request, Status}; -use crate::application::Controller; +use crate::application::{auth::AuthValidator, Controller}; #[derive(Clone)] pub struct AdminInterceptor { - pub controller: Arc, + pub validator: AuthValidator, } impl Interceptor for AdminInterceptor { @@ -30,7 +30,7 @@ impl Interceptor for AdminInterceptor { #[derive(Clone)] pub struct UnitInterceptor { - pub controller: Arc, + pub validator: AuthValidator, } impl Interceptor for UnitInterceptor { diff --git a/controller/src/network/unit.rs b/controller/src/network/unit.rs index 22be76b9..ccb53d17 100644 --- a/controller/src/network/unit.rs +++ b/controller/src/network/unit.rs @@ -27,7 +27,6 @@ pub mod proto { } pub struct UnitServiceImpl { - pub controller: ControllerHandle, } #[async_trait] diff --git a/protocol/wit/driver.wit b/protocol/wit/driver.wit index 9ed84a01..d0e48d25 100644 --- a/protocol/wit/driver.wit +++ b/protocol/wit/driver.wit @@ -126,10 +126,7 @@ interface process { } // Interface for API functionality -interface api { - // Function to get the name of the API - get-name: func() -> string; -} +interface api {} // Interface for bridge functionality interface bridge { From 4c156cc65855203e4de289b2fcebebbcc98b6df9 Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Fri, 31 Jan 2025 07:47:14 +0000 Subject: [PATCH 07/74] Revert "wip: Start to rewrite the tick loop using tokio" This reverts commit 598217b21afb784378bfc62be3dc43bda7982462. --- .devcontainer/Dockerfile | 9 +- .devcontainer/devcontainer.json | 6 +- controller/Cargo.toml | 10 +- controller/src/application.rs | 121 ++++++++---- controller/src/application/auth.rs | 175 ++++++++++-------- controller/src/application/driver.rs | 49 ++--- controller/src/application/driver/wasm.rs | 163 +++++++++------- .../src/application/driver/wasm/cloudlet.rs | 4 +- .../src/application/driver/wasm/process.rs | 2 +- controller/src/application/user.rs | 2 +- controller/src/config.rs | 125 ++++++++----- controller/src/main.rs | 9 +- controller/src/network.rs | 52 ++++-- controller/src/network/admin.rs | 7 +- controller/src/network/auth.rs | 6 +- controller/src/network/unit.rs | 1 + protocol/wit/driver.wit | 5 +- 17 files changed, 443 insertions(+), 303 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 94f4df49..6b0d7520 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -9,7 +9,7 @@ RUN pacman-key --init RUN pacman -Syu --noconfirm # Install required packages -RUN pacman -S base-devel nushell git less nano openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm +RUN pacman -S base-devel git less vi openssh nano protobuf rustup jdk21-openjdk gradle mkdocs-material --noconfirm # Install gRPC UI RUN aur-install grpcui-bin @@ -23,12 +23,5 @@ ENV HOME=/home/${REMOTE_USER} USER ${REMOTE_USER} -# Switch to nano as default editor -ENV EDITOR=nano - -# Disable nu welcome message -RUN mkdir -p ~/.config/nushell/ -RUN echo "\$env.config.show_banner = false" >> ~/.config/nushell/config.nu - # Install Rustup and set default toolchains RUN rustup default nightly \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a015f7e6..369d48a7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,11 +11,7 @@ "customizations": { "vscode": { "settings": { - "terminal.integrated.profiles.linux": { - "nu": { - "path": "nu" - } - } + "terminal.integrated.shell.linux": "/bin/bash" }, "extensions": [ // Extensions for Rust development diff --git a/controller/Cargo.toml b/controller/Cargo.toml index ffcd4e5e..4d935b01 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -17,10 +17,10 @@ anyhow = "1.0.95" ctrlc = "3.4.5" # Unit system -uuid = { version = "1.12.1", features = ["v4"] } +uuid = { version = "1.12.0", features = ["v4"] } # Command line arguments -clap = { version = "4.5.27", features = ["derive"] } +clap = { version = "4.5.26", features = ["derive"] } # Regex parsing regex = "1.11.1" @@ -39,9 +39,9 @@ prost = "0.13.4" tonic = "0.12.3" # Drivers -wasmtime = { version = "29.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } -wasmtime-wasi = { version = "29.0.1", optional = true } -minreq = { version = "2.13.2", features = ["https-rustls"], optional = true } +wasmtime = { version = "28.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } +wasmtime-wasi = { version = "28.0.1", optional = true } +minreq = { version = "2.13.0", features = ["https-rustls"], optional = true } [build-dependencies] toml = "0.8.19" diff --git a/controller/src/application.rs b/controller/src/application.rs index bf98b02a..d401d831 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -29,58 +29,67 @@ static SHUTDOWN_WAIT: Duration = Duration::from_secs(10); const TICK_RATE: u64 = 1; +pub type ControllerHandle = Arc; +pub type WeakControllerHandle = Weak; + pub struct Controller { - /* Runtime State */ - running: Arc, + handle: WeakControllerHandle, - /* Configuration */ - configuration: Config, + /* Immutable */ + pub(crate) configuration: Config, + pub(crate) drivers: Drivers, - /* Drivers */ - drivers: Drivers, + /* Runtime State */ + runtime: RwLock>, + running: AtomicBool, /* Authentication */ auth: Auth, - users: Users, - /* Resources */ - cloudlets: Cloudlets, - deployments: Deployments, + /* Accessed rarely */ + cloudlets: RwLock, + deployments: RwLock, + + /* Accessed frequently */ units: Units, + users: Users, /* Event Bus */ event_bus: EventBus, } impl Controller { - pub async fn new(configuration: Config) -> Self { - let auth = Auth::load_all().await; - let drivers = Drivers::load_all(&configuration.identifier).await; - let cloudlets = Cloudlets::load_all(handle.clone(), &drivers); - let deployments = Deployments::load_all(handle.clone(), &cloudlets); - let units = Units::new(handle.clone()); - let users = Users::new(handle.clone()); - let event_bus = EventBus::new(/*handle.clone()*/); - Self { - configuration, - drivers, - runtime: RwLock::new(Some( - Builder::new_multi_thread() - .enable_all() - .build() - .expect("Failed to create Tokio runtime"), - )), - running: Arc::new(AtomicBool::new(true)), - auth, - cloudlets: RwLock::new(cloudlets), - deployments: RwLock::new(deployments), - units, - users, - event_bus, - } + pub fn new(configuration: Config) -> Arc { + Arc::new_cyclic(move |handle| { + let auth = Auth::load_all(); + let drivers = Drivers::load_all(configuration.identifier.as_ref().unwrap()); + let cloudlets = Cloudlets::load_all(handle.clone(), &drivers); + let deployments = Deployments::load_all(handle.clone(), &cloudlets); + let units = Units::new(handle.clone()); + let users = Users::new(handle.clone()); + let event_bus = EventBus::new(/*handle.clone()*/); + Self { + handle: handle.clone(), + configuration, + drivers, + runtime: RwLock::new(Some( + Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to create Tokio runtime"), + )), + running: AtomicBool::new(true), + auth, + cloudlets: RwLock::new(cloudlets), + deployments: RwLock::new(deployments), + units, + users, + event_bus, + } + }) } - pub async fn start(&self) { + pub fn start(&self) { // Set up signal handlers self.setup_interrupts(); @@ -108,6 +117,13 @@ impl Controller { info!("Stopping network stack..."); network_handle.shutdown(); + // Wait for all tokio task to finish + info!("Stopping async runtime..."); + (*self.runtime.write().unwrap()) + .take() + .unwrap() + .shutdown_timeout(SHUTDOWN_WAIT); + // Let the drivers cleanup there messes info!("Letting the drivers cleanup..."); self.drivers.cleanup(); @@ -118,8 +134,28 @@ impl Controller { self.running.store(false, Ordering::Relaxed); } - pub fn get_config(&self) -> &Config { - &self.configuration + pub fn lock_cloudlets(&self) -> RwLockReadGuard { + self.cloudlets + .read() + .expect("Failed to get lock to cloudlets") + } + + pub fn lock_deployments(&self) -> RwLockReadGuard { + self.deployments + .read() + .expect("Failed to get lock to deployments") + } + + pub fn lock_cloudlets_mut(&self) -> RwLockWriteGuard { + self.cloudlets + .write() + .expect("Failed to get lock to cloudlets") + } + + pub fn lock_deployments_mut(&self) -> RwLockWriteGuard { + self.deployments + .write() + .expect("Failed to get lock to deployments") } pub fn get_drivers(&self) -> &Drivers { @@ -165,11 +201,14 @@ impl Controller { fn setup_interrupts(&self) { // Set up signal handlers - let running = self.running.clone(); + let controller = self.handle.clone(); ctrlc::set_handler(move || { info!("Interrupt signal received. Stopping..."); - running.store(false, Ordering::Relaxed); - }).expect("Failed to set Ctrl+C handler"); + if let Some(controller) = controller.upgrade() { + controller.request_stop(); + } + }) + .expect("Failed to set Ctrl+C handler"); } } diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs index 2ad8fb4a..90396542 100644 --- a/controller/src/application/auth.rs +++ b/controller/src/application/auth.rs @@ -1,90 +1,47 @@ -use std::{collections::HashMap, fs, sync::Arc}; +use std::{ + collections::HashMap, + fs, + sync::{Arc, RwLock}, +}; use common::config::{LoadFromTomlFile, SaveToTomlFile}; use simplelog::{error, info, warn}; use stored::StoredUser; -use tokio::sync::RwLock; use uuid::Uuid; use crate::storage::Storage; -const DEFAULT_ADMIN_USERNAME: &str = "admin"; - -pub type AuthToken = String; +use super::unit::{UnitHandle, WeakUnitHandle}; -#[derive(Clone)] -pub enum Authorization { - User(String), // Username - Unit(Uuid), // UUID -} +const DEFAULT_ADMIN_USERNAME: &str = "admin"; -pub type AuthValidator = Arc; +pub type AuthUserHandle = Arc; +pub type AuthUnitHandle = Arc; -pub struct AuthValidatorInner { - pub tokens: RwLock>, +pub struct AuthUser { + pub username: String, + pub token: String, } -impl AuthValidatorInner { - pub async fn get_auth(&self, token: &str) -> Option { - self.tokens.read().await.get(token).cloned() - } - - pub async fn register_unit(&self, uuid: Uuid) -> String { - let token = format!( - "sctl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - - self.tokens - .write() - .await - .insert(token.clone(), Authorization::Unit(uuid)); - - token - } - - pub async fn unregister(&self, token: &str) { - self.tokens.write().await.remove(token); - } - - pub async fn register_user(&self, username: &str) -> Option { - let token = format!( - "actl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - let stored_user = StoredUser { - token: token.to_string(), - }; - let user_path = Storage::get_user_file(username); - if stored_user.save_to_file(&user_path, true).is_err() { - error!( - "Failed to save user to file: {}", - &user_path.display() - ); - return None; - } - self.tokens.write().await.insert(token.clone(), Authorization::User(username.to_string())); - - Some(token) - } +pub struct AuthUnit { + pub unit: WeakUnitHandle, + pub token: String, } pub struct Auth { - pub validator: AuthValidator, + pub users: RwLock>, + pub units: RwLock>, } impl Auth { - pub fn new(users: HashMap) -> Self { + pub fn new(users: HashMap) -> Self { Auth { - validator: Arc::new(AuthValidatorInner { - tokens: RwLock::new(users), - }), + users: RwLock::new(users), + units: RwLock::new(HashMap::new()), } } - pub async fn load_all() -> Self { + pub fn load_all() -> Self { info!("Loading users..."); let users_directory = Storage::get_users_folder(); @@ -120,7 +77,7 @@ impl Auth { continue; } - let username = match path.file_stem() { + let name = match path.file_stem() { Some(name) => name.to_string_lossy().to_string(), None => continue, }; @@ -130,7 +87,7 @@ impl Auth { Err(error) => { error!( "Failed to read user {} from file({:?}): {}", - &username, + &name, &path, &error ); @@ -138,31 +95,31 @@ impl Auth { } }; + let user = AuthUser { + username: name.clone(), + token: user.token, + }; if users .values() - .filter_map(|entry| match entry { - Authorization::User(name) => Some(name), - _ => None, - }) - .any(|name| name.eq_ignore_ascii_case(&username)) + .any(|u| u.username.eq_ignore_ascii_case(&user.username)) { - error!("User with the name {} already exists", &username); + error!("User with the name {} already exists", &name); continue; } - info!("Loaded user {}", &username); - users.insert(user.token.clone(), Authorization::User(username)); + users.insert(user.token.clone(), Arc::new(user)); + info!("Loaded user {}", &name); } let amount = users.len(); let auth = Auth::new(users); if amount == 0 { - let token = auth.get_validator() - .register_user(DEFAULT_ADMIN_USERNAME).await + let user = auth + .register_user(DEFAULT_ADMIN_USERNAME) .expect("Failed to create default admin user"); info!("-----------------------------------"); info!("No users found, created default admin user"); info!("Username: {}", DEFAULT_ADMIN_USERNAME); - info!("Token: {}", &token); + info!("Token: {}", &user.token); info!("-----------------------------------"); info!("Welcome to Atomic Cloud"); info!("-----------------------------------"); @@ -172,8 +129,68 @@ impl Auth { auth } - pub fn get_validator(&self) -> AuthValidator { - self.validator.clone() + pub fn get_user(&self, token: &str) -> Option { + self.users.read().unwrap().get(token).cloned() + } + + pub fn get_unit(&self, token: &str) -> Option { + self.units.read().unwrap().get(token).cloned() + } + + pub fn register_unit(&self, unit: WeakUnitHandle) -> AuthUnitHandle { + let token = format!( + "sctl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + + let unit = Arc::new(AuthUnit { + unit, + token: token.clone(), + }); + self.units + .write() + .unwrap() + .insert(token.clone(), unit.clone()); + + unit + } + + pub fn unregister_unit(&self, unit: &UnitHandle) { + self.units.write().unwrap().retain(|_, value| { + if let Some(ref_unit) = value.unit.upgrade() { + !Arc::ptr_eq(&ref_unit, unit) + } else { + true + } + }) + } + + pub fn register_user(&self, username: &str) -> Option { + let token = format!( + "actl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + let stored_user = StoredUser { + token: token.to_string(), + }; + let user_path = Storage::get_user_file(username); + if stored_user.save_to_file(&user_path, true).is_err() { + error!( + "Failed to save user to file: {}", + &user_path.display() + ); + return None; + } + + let user = Arc::new(AuthUser { + username: username.to_string(), + token: token.clone(), + }); + self.users.write().unwrap().insert(token, user.clone()); + + Some(user) } } diff --git a/controller/src/application/driver.rs b/controller/src/application/driver.rs index 2ef46396..c0686457 100644 --- a/controller/src/application/driver.rs +++ b/controller/src/application/driver.rs @@ -24,57 +24,57 @@ pub struct Information { ready: bool, } -pub type BoxedDriver = Box; -pub type BoxedCloudlet = Box; - #[async_trait] pub trait GenericDriver: Send + Sync { - fn name(&self) -> &str; - async fn init(&self) -> Result; - async fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result; + fn name(&self) -> &String; + fn init(&self) -> Result; + fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result; /* Cleanup */ - async fn cleanup(&self) -> Result<()>; + fn cleanup(&self) -> Result<()>; /* Ticking */ - async fn tick(&self) -> Result<()>; + fn tick(&self) -> Result<()>; } #[async_trait] pub trait GenericCloudlet: Send + Sync { /* Ticking */ - async fn tick(&self) -> Result<()>; + fn tick(&self) -> Result<()>; /* Prepare */ - async fn allocate_addresses(&self, request: &StartRequestHandle) -> Result>; - async fn deallocate_addresses(&self, addresses: Vec) -> Result<()>; + fn allocate_addresses(&self, request: &StartRequestHandle) -> Result>; + fn deallocate_addresses(&self, addresses: Vec) -> Result<()>; /* Unitss */ - async fn start_unit(&self, unit: &UnitHandle) -> Result<()>; - async fn restart_unit(&self, unit: &UnitHandle) -> Result<()>; - async fn stop_unit(&self, unit: &UnitHandle) -> Result<()>; + fn start_unit(&self, unit: &UnitHandle) -> Result<()>; + fn restart_unit(&self, unit: &UnitHandle) -> Result<()>; + fn stop_unit(&self, unit: &UnitHandle) -> Result<()>; } +pub type DriverHandle = Arc; +pub type DriverCloudletHandle = Arc; + pub struct Drivers { - drivers: Vec, + drivers: Vec, } impl Drivers { - pub async fn load_all(cloud_identifier: &str) -> Self { + pub fn load_all(cloud_identifier: &str) -> Self { info!("Loading drivers..."); let mut drivers = Vec::new(); #[cfg(feature = "wasm-drivers")] - WasmDriver::load_all(cloud_identifier, &mut drivers).await; + WasmDriver::load_all(cloud_identifier, &mut drivers); info!("Loaded {} driver(s)", drivers.len()); Self { drivers } } - pub async fn cleanup(&self) { + pub fn cleanup(&self) { for driver in &self.drivers { - if let Err(error) = driver.cleanup().await { + if let Err(error) = driver.cleanup() { error!( "Failed to dispose resources of driver {}: {}", driver.name(), @@ -84,9 +84,9 @@ impl Drivers { } } - pub async fn tick(&self) { + pub fn tick(&self) { for driver in &self.drivers { - if let Err(error) = driver.tick().await { + if let Err(error) = driver.tick() { error!( "Failed to tick driver {}: {}", driver.name(), @@ -96,14 +96,15 @@ impl Drivers { } } - pub fn find_by_name(&self, name: &str) -> Option<&Box> { + pub fn find_by_name(&self, name: &str) -> Option> { self.drivers .iter() .find(|driver| driver.name().eq_ignore_ascii_case(name)) + .cloned() } - pub fn get_drivers(&self) -> &Vec> { - &self.drivers + pub fn get_drivers(&self) -> Vec { + self.drivers.clone() } } diff --git a/controller/src/application/driver/wasm.rs b/controller/src/application/driver/wasm.rs index c5766380..65f53bc7 100644 --- a/controller/src/application/driver/wasm.rs +++ b/controller/src/application/driver/wasm.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::fs; +use std::sync::{Arc, Mutex, RwLock, Weak}; use anyhow::{anyhow, Result}; use cloudlet::WasmCloudlet; @@ -8,15 +9,13 @@ use config::WasmConfig; use generated::exports::cloudlet::driver::bridge; use generated::Driver; use simplelog::{error, info, warn}; -use tokio::sync::{Mutex, MutexGuard}; -use tonic::async_trait; -use wasmtime::component::{Component, Linker, Resource, ResourceAny}; -use wasmtime::{AsContextMut, Config, Engine, Store}; +use wasmtime::component::{Component, Linker, ResourceAny}; +use wasmtime::{Config, Engine, Store}; use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; use super::process::DriverProcess; use super::source::Source; -use super::{BoxedCloudlet, BoxedDriver, GenericCloudlet, GenericDriver, Information}; +use super::{DriverCloudletHandle, GenericDriver, Information}; use crate::application::cloudlet::{Capabilities, Cloudlet, HostAndPort, RemoteController}; use crate::storage::Storage; @@ -35,7 +34,6 @@ pub mod generated { bindgen!({ world: "driver", path: "../protocol/wit/", - async: true, }); } @@ -63,12 +61,7 @@ allow_remove_dir_all = false mounts = []"#; struct WasmDriverState { - /* Generic Information */ - - /* Processes */ - processes: HashMap, - - /* Wasmtime */ + handle: Weak, wasi: WasiCtx, table: ResourceTable, } @@ -83,67 +76,99 @@ impl WasiView for WasmDriverState { } impl generated::cloudlet::driver::types::Host for WasmDriverState {} -impl generated::cloudlet::driver::api::Host for WasmDriverState {} + +impl generated::cloudlet::driver::api::Host for WasmDriverState { + fn get_name(&mut self) -> String { + self.handle.upgrade().unwrap().name.clone() + } +} + +struct WasmDriverHandle { + store: Store, + resource: ResourceAny, // This is delete when the store is dropped +} + +impl WasmDriverHandle { + fn new(store: Store, resource: ResourceAny) -> Self { + WasmDriverHandle { store, resource } + } + + fn get(&mut self) -> (ResourceAny, &mut Store) { + (self.resource, &mut self.store) + } +} + +pub struct WasmDriverData { + processes: RwLock>, +} pub struct WasmDriver { + own: Weak, + name: String, bindings: Driver, - store: Mutex>, - instance: ResourceAny, // This is delete when the store is dropped + handle: Mutex>, + + data: WasmDriverData, } impl WasmDriver { - async fn get_required(&self) -> (&Driver, ResourceAny, &Mutex>) { - (&self.bindings, self.instance, &self.store) + fn get_resource_and_store( + handle: &mut Option, + ) -> (ResourceAny, &mut Store) { + handle.as_mut().unwrap().get() } } -#[async_trait] impl GenericDriver for WasmDriver { - fn name(&self) -> &str { + fn name(&self) -> &String { &self.name } - async fn init(&self) -> Result { - let (bindings, instance, store) = self.get_required().await; - let mut store = store.lock().await; - match bindings + fn init(&self) -> Result { + let mut handle = self.handle.lock().unwrap(); + let (resource, store) = Self::get_resource_and_store(&mut handle); + match self + .bindings .cloudlet_driver_bridge() .generic_driver() - .call_init(store.as_context_mut(), instance).await + .call_init(store, resource) { Ok(information) => Ok(information.into()), Err(error) => Err(error), } } - async fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result { - let (bindings, instance, store) = self.get_required().await; - let mut store = store.lock().await; - match bindings + fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result { + let mut handle = self.handle.lock().unwrap(); + let (resource, store) = Self::get_resource_and_store(&mut handle); + match self + .bindings .cloudlet_driver_bridge() .generic_driver() .call_init_cloudlet( - store.as_context_mut(), - instance, + store, + resource, &cloudlet.name, &(&cloudlet.capabilities).into(), &(&cloudlet.controller).into(), - ).await? { - Ok(cloudlet) => Ok(Box::new(WasmCloudlet { - instance: cloudlet, + )? { + Ok(cloudlet) => Ok(Arc::new(WasmCloudlet { + handle: self.own.clone(), + resource: cloudlet, })), Err(error) => Err(anyhow!(error)), } } - async fn cleanup(&self) -> Result<()> { - let (bindings, instance, store) = self.get_required().await; - let mut store = store.lock().await; - match bindings + fn cleanup(&self) -> Result<()> { + let mut handle = self.handle.lock().unwrap(); + let (resource, store) = Self::get_resource_and_store(&mut handle); + match self + .bindings .cloudlet_driver_bridge() .generic_driver() - .call_cleanup(store.as_context_mut(), instance).await + .call_cleanup(store, resource) { Ok(result) => result.map_err(|errors| { anyhow!(errors @@ -156,13 +181,14 @@ impl GenericDriver for WasmDriver { } } - async fn tick(&self) -> Result<()> { - let (bindings, instance, store) = self.get_required().await; - let mut store = store.lock().await; - match bindings + fn tick(&self) -> Result<()> { + let mut handle = self.handle.lock().unwrap(); + let (resource, store) = Self::get_resource_and_store(&mut handle); + match self + .bindings .cloudlet_driver_bridge() .generic_driver() - .call_tick(store.as_context_mut(), instance).await + .call_tick(store, resource) { Ok(result) => result.map_err(|errors| { anyhow!(errors @@ -177,12 +203,12 @@ impl GenericDriver for WasmDriver { } impl WasmDriver { - async fn new( + fn new( config: &WasmConfig, cloud_identifier: &str, name: &str, source: &Source, - ) -> Result { + ) -> Result> { let config_directory = Storage::get_config_folder_for_driver(name); let data_directory = Storage::get_data_folder_for_driver(name); if !config_directory.exists() { @@ -204,7 +230,6 @@ impl WasmDriver { let mut engine_config = Config::new(); engine_config.wasm_component_model(true); - engine_config.async_support(true); if let Err(error) = engine_config.cache_config_load(Storage::get_configs_folder().join(ENGINE_CONFIG_FILE)) { @@ -218,7 +243,7 @@ impl WasmDriver { let component = Component::from_binary(&engine, &source.code)?; let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker_async(&mut linker)?; + wasmtime_wasi::add_to_linker_sync(&mut linker)?; Driver::add_to_linker(&mut linker, |state: &mut WasmDriverState| state)?; let mut wasi = WasiCtxBuilder::new(); @@ -253,30 +278,44 @@ impl WasmDriver { .build(); let table = ResourceTable::new(); + let mut store = Store::new( &engine, WasmDriverState { - processes: HashMap::new(), + handle: Weak::new(), wasi, table, }, ); - let bindings = Driver::instantiate_async(&mut store, &component, &linker).await?; - let instance = bindings + let bindings = Driver::instantiate(&mut store, &component, &linker)?; + let driver = Arc::new_cyclic(|handle| { + store.data_mut().handle = handle.clone(); + WasmDriver { + own: handle.clone(), + name: name.to_string(), + bindings, + handle: Mutex::new(None), + data: WasmDriverData { + processes: RwLock::new(HashMap::new()), + }, + } + }); + let driver_resource = driver + .bindings .cloudlet_driver_bridge() .generic_driver() - .call_constructor(&mut store, cloud_identifier).await?; - Ok(WasmDriver { - name: name.to_string(), - bindings, - store: Mutex::new(store), - instance, - }) + .call_constructor(&mut store, cloud_identifier)?; + driver + .handle + .lock() + .unwrap() + .replace(WasmDriverHandle::new(store, driver_resource)); + Ok(driver) } - pub async fn load_all( + pub fn load_all( cloud_identifier: &str, - drivers: &mut Vec, + drivers: &mut Vec>, ) -> WasmConfig { // Check if cache configuration exists { @@ -366,9 +405,9 @@ impl WasmDriver { }; info!("Compiling driver {}...", &name); - let driver = WasmDriver::new(&config, cloud_identifier, &name, &source).await; + let driver = WasmDriver::new(&config, cloud_identifier, &name, &source); match driver { - Ok(driver) => match driver.init().await { + Ok(driver) => match driver.init() { Ok(info) => { if info.ready { info!( @@ -376,7 +415,7 @@ impl WasmDriver { &driver.name, &info.version, &info.authors.join(", ") ); - drivers.push(Box::new(driver)); + drivers.push(driver); } else { warn!( "Driver {} marked itself as not ready, skipping...", diff --git a/controller/src/application/driver/wasm/cloudlet.rs b/controller/src/application/driver/wasm/cloudlet.rs index ec455b51..686f0a13 100644 --- a/controller/src/application/driver/wasm/cloudlet.rs +++ b/controller/src/application/driver/wasm/cloudlet.rs @@ -4,6 +4,7 @@ use anyhow::{anyhow, Result}; use wasmtime::component::ResourceAny; use crate::application::{ + auth::AuthUnit, cloudlet::{Allocation, HostAndPort}, driver::GenericCloudlet, unit::{ @@ -17,7 +18,8 @@ use super::{ }; pub struct WasmCloudlet { - pub instance: ResourceAny, // This is delete if the handle is dropped + pub handle: Weak, + pub resource: ResourceAny, // This is delete if the handle is dropped } impl GenericCloudlet for WasmCloudlet { diff --git a/controller/src/application/driver/wasm/process.rs b/controller/src/application/driver/wasm/process.rs index 042a1cfb..6bee2444 100644 --- a/controller/src/application/driver/wasm/process.rs +++ b/controller/src/application/driver/wasm/process.rs @@ -22,7 +22,7 @@ use super::{ }; impl driver::process::Host for WasmDriverState { - async fn spawn_process( + fn spawn_process( &mut self, command: String, args: Vec, diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs index 6c58a637..f68f550d 100644 --- a/controller/src/application/user.rs +++ b/controller/src/application/user.rs @@ -197,4 +197,4 @@ pub struct User { pub name: String, pub uuid: Uuid, pub unit: RwLock, -} \ No newline at end of file +} diff --git a/controller/src/config.rs b/controller/src/config.rs index b02fa0a0..5153845a 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -17,24 +17,24 @@ const DEFAULT_EMPTY_UNIT_TIMEOUT: Duration = Duration::from_secs(120); const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; const DEFAULT_BIND_PORT: u16 = 12892; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Default)] pub struct NetworkConfig { - pub bind: SocketAddr, + pub bind: Option, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Default)] pub struct Timings { - pub startup: Duration, - pub restart: Duration, - pub healthbeat: Duration, - pub transfer: Duration, - pub empty_unit: Duration, + pub startup: Option, + pub restart: Option, + pub healthbeat: Option, + pub transfer: Option, + pub empty_unit: Option, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Default)] pub struct Config { /* Cloud Identification */ - pub identifier: String, + pub identifier: Option, /* Network */ pub network: NetworkConfig, @@ -44,52 +44,77 @@ pub struct Config { } impl Config { - fn default() -> Self { - Self { - identifier: Uuid::new_v4().to_string(), - network: NetworkConfig { - bind: SocketAddr::new( - DEFAULT_BIND_ADDRESS.parse().unwrap(), - DEFAULT_BIND_PORT, - ), - }, - timings: Timings { - startup: DEFAULT_EXPECTED_STARTUP_TIME, - restart: DEFAULT_EXPECTED_RESTART_TIME, - healthbeat: DEFAULT_HEALTH_CHECK_TIMEOUT, - transfer: DEFAULT_TRANSFER_TIMEOUT, - empty_unit: DEFAULT_EMPTY_UNIT_TIMEOUT, - }, + fn load_or_empty() -> Self { + let path = Storage::get_primary_config_file(); + if !path.exists() { + return Self::default(); } + Self::load_from_file(&path).unwrap_or_else(|error| { + warn!( + "Failed to read configuration from file: {}", + error + ); + Self::default() + }) } - pub fn load() -> Self { - let path = Storage::get_primary_config_file(); - let mut config = if path.exists() { - Self::load_from_file(&path).unwrap_or_else(|error| { - warn!( - "Failed to read configuration from file: {}", - error + pub fn new_filled() -> Self { + let mut config = Self::load_or_empty(); + + let mut save = false; + if config.identifier.is_none() { + config.identifier = Some(Uuid::new_v4().to_string()); + save = true; + } + if config.network.bind.is_none() { + config.network.bind = Some(SocketAddr::new( + DEFAULT_BIND_ADDRESS.parse().unwrap(), + DEFAULT_BIND_PORT, + )); + save = true; + } + if config.timings.startup.is_none() { + config.timings.startup = Some(DEFAULT_EXPECTED_STARTUP_TIME); + save = true; + } + if config.timings.restart.is_none() { + config.timings.restart = Some(DEFAULT_EXPECTED_RESTART_TIME); + save = true; + } + if config.timings.healthbeat.is_none() { + config.timings.healthbeat = Some(DEFAULT_HEALTH_CHECK_TIMEOUT); + save = true; + } + if config.timings.transfer.is_none() { + config.timings.transfer = Some(DEFAULT_TRANSFER_TIMEOUT); + save = true; + } + if config.timings.empty_unit.is_none() { + config.timings.empty_unit = Some(DEFAULT_EMPTY_UNIT_TIMEOUT); + save = true; + } + if save { + if let Err(error) = config.save_to_file(&Storage::get_primary_config_file(), true) { + error!( + "Failed to save generated configuration to file: {}", + &error ); - Self::default() - }) - } else { - Self::default() - }; + } + } - // Check config values are overridden by environment variables - if let Ok(identifier) = std::env::var("INSTANCE_IDENTIFIER") { - config.identifier = identifier; - } - if let Ok(address) = std::env::var("BIND_ADDRESS") { - if let Ok(address) = address.parse() { - config.network.bind = address; - } else { - error!("Failed to parse BIND_ADDRESS environment variable"); - } - } + // Check config values are overridden by environment variables + if let Ok(identifier) = std::env::var("INSTANCE_IDENTIFIER") { + config.identifier = Some(identifier); + } + if let Ok(address) = std::env::var("BIND_ADDRESS") { + if let Ok(address) = address.parse() { + config.network.bind.replace(address); + } else { + error!("Failed to parse BIND_ADDRESS environment variable"); + } + } - config + config } } diff --git a/controller/src/main.rs b/controller/src/main.rs index 5473fd3d..e6963dca 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -22,8 +22,7 @@ include!(concat!(env!("OUT_DIR"), "/build_info.rs")); pub const AUTHORS: [&str; 1] = ["HttpRafa"]; -#[tokio::main] -async fn main() { +fn main() { let args = Args::parse(); CloudInit::init_logging(args.debug, false, Storage::get_latest_log_file()); CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); @@ -32,8 +31,8 @@ async fn main() { info!("Starting cloud version v{}...", VERSION); info!("Loading configuration..."); - let configuration = Config::load(); - let controller = Controller::new(configuration).await; + let configuration = Config::new_filled(); + let controller = Controller::new(configuration); info!("Loaded cloud in {:.2?}", start_time.elapsed()); - controller.start().await; + controller.start(); } diff --git a/controller/src/network.rs b/controller/src/network.rs index 764137b5..f44e187a 100644 --- a/controller/src/network.rs +++ b/controller/src/network.rs @@ -1,13 +1,14 @@ use anyhow::Result; use auth::{AdminInterceptor, UnitInterceptor}; use simplelog::{error, info}; -use std::{net::SocketAddr, sync::Arc}; +use std::sync::Arc; use tokio::{ - spawn, sync::watch::{self, Receiver, Sender}, task::JoinHandle + sync::watch::{self, Receiver, Sender}, + task::JoinHandle, }; use tonic::transport::Server; -use crate::application::{auth::AuthValidator, Controller,}; +use crate::application::{Controller, WeakControllerHandle}; use admin::{proto::admin_service_server::AdminServiceServer, AdminServiceImpl}; use unit::{proto::unit_service_server::UnitServiceServer, UnitServiceImpl}; @@ -19,47 +20,59 @@ pub mod unit; pub struct NetworkStack { shutdown: Sender, handle: JoinHandle<()>, + controller: WeakControllerHandle, } impl NetworkStack { - pub fn start(controller: &Controller) -> Self { + pub fn start(controller: Arc) -> Self { info!("Starting networking stack..."); let (sender, receiver) = watch::channel(false); - let bind = controller.get_config().network.bind.clone(); - let validator = controller.get_auth().get_validator(); return NetworkStack { shutdown: sender, - handle: spawn(launch_server(bind, validator, receiver)), + handle: controller + .get_runtime() + .as_ref() + .unwrap() + .spawn(launch_server(controller.clone(), receiver)), + controller: Arc::downgrade(&controller), }; - async fn launch_server(bind: SocketAddr, validator: AuthValidator, shutdown: Receiver) { - if let Err(error) = run(bind, validator, shutdown).await { + async fn launch_server(controller: Arc, shutdown: Receiver) { + if let Err(error) = run(controller, shutdown).await { error!("Failed to start gRPC server: {}", error); } } - async fn run(bind: SocketAddr, validator: AuthValidator, mut shutdown: Receiver) -> Result<()> { + async fn run(controller: Arc, mut shutdown: Receiver) -> Result<()> { + let address = controller + .configuration + .network + .bind + .expect("No bind address found in the config"); + let admin_service = AdminServiceImpl { + controller: Arc::clone(&controller), }; let unit_service = UnitServiceImpl { + controller: Arc::clone(&controller), }; - info!("Controller listening on {}", bind); + info!("Controller listening on {}", address); Server::builder() .add_service(AdminServiceServer::with_interceptor( admin_service, AdminInterceptor { - validator: validator.clone(), + controller: Arc::clone(&controller), }, )) .add_service(UnitServiceServer::with_interceptor( unit_service, - UnitInterceptor { validator }, + UnitInterceptor { controller }, )) - .serve_with_shutdown(bind, async { + .serve_with_shutdown(address, async { shutdown.changed().await.ok(); }) .await?; @@ -68,10 +81,17 @@ impl NetworkStack { } } - pub async fn shutdown(self) { + pub fn shutdown(self) { self.shutdown .send(true) .expect("Failed to send shutdown signal"); - self.handle.await; + if let Some(controller) = self.controller.upgrade() { + controller + .get_runtime() + .as_ref() + .unwrap() + .block_on(self.handle) + .expect("Failed to shutdown network stack"); + } } } diff --git a/controller/src/network/admin.rs b/controller/src/network/admin.rs index 388d4b92..60fbb92b 100644 --- a/controller/src/network/admin.rs +++ b/controller/src/network/admin.rs @@ -6,7 +6,11 @@ use uuid::Uuid; use crate::{ application::{ - auth::AuthValidator, cloudlet::{Capabilities, LifecycleStatus, RemoteController}, deployment::{ScalingPolicy, StartConstraints}, unit::{FallbackPolicy, KeyValue, Resources, Retention, Spec}, user::transfer::TransferTarget, ControllerHandle, CreationResult + cloudlet::{Capabilities, LifecycleStatus, RemoteController}, + deployment::{ScalingPolicy, StartConstraints}, + unit::{FallbackPolicy, KeyValue, Resources, Retention, Spec}, + user::transfer::TransferTarget, + ControllerHandle, CreationResult, }, VERSION, }; @@ -19,6 +23,7 @@ pub mod proto { } pub struct AdminServiceImpl { + pub controller: ControllerHandle, } #[async_trait] diff --git a/controller/src/network/auth.rs b/controller/src/network/auth.rs index 76b51cb1..664f035c 100644 --- a/controller/src/network/auth.rs +++ b/controller/src/network/auth.rs @@ -2,11 +2,11 @@ use std::sync::Arc; use tonic::{service::Interceptor, Request, Status}; -use crate::application::{auth::AuthValidator, Controller}; +use crate::application::Controller; #[derive(Clone)] pub struct AdminInterceptor { - pub validator: AuthValidator, + pub controller: Arc, } impl Interceptor for AdminInterceptor { @@ -30,7 +30,7 @@ impl Interceptor for AdminInterceptor { #[derive(Clone)] pub struct UnitInterceptor { - pub validator: AuthValidator, + pub controller: Arc, } impl Interceptor for UnitInterceptor { diff --git a/controller/src/network/unit.rs b/controller/src/network/unit.rs index ccb53d17..22be76b9 100644 --- a/controller/src/network/unit.rs +++ b/controller/src/network/unit.rs @@ -27,6 +27,7 @@ pub mod proto { } pub struct UnitServiceImpl { + pub controller: ControllerHandle, } #[async_trait] diff --git a/protocol/wit/driver.wit b/protocol/wit/driver.wit index d0e48d25..9ed84a01 100644 --- a/protocol/wit/driver.wit +++ b/protocol/wit/driver.wit @@ -126,7 +126,10 @@ interface process { } // Interface for API functionality -interface api {} +interface api { + // Function to get the name of the API + get-name: func() -> string; +} // Interface for bridge functionality interface bridge { From 9d32a9b418e9f0c6399d1f7ff6e7a2af4268915c Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:31:56 +0000 Subject: [PATCH 08/74] Remove old controller --- cli/Cargo.toml | 2 +- clients/wrapper/Cargo.toml | 4 +- common/Cargo.toml | 2 +- controller/Cargo.toml | 11 +- controller/src/application.rs | 219 ------ controller/src/application/auth.rs | 208 ------ controller/src/application/cloudlet.rs | 468 ------------- controller/src/application/deployment.rs | 554 ---------------- controller/src/application/driver.rs | 136 ---- controller/src/application/driver/process.rs | 114 ---- controller/src/application/driver/wasm.rs | 481 -------------- .../src/application/driver/wasm/cloudlet.rs | 231 ------- .../src/application/driver/wasm/config.rs | 64 -- .../src/application/driver/wasm/file.rs | 17 - .../src/application/driver/wasm/http.rs | 57 -- controller/src/application/driver/wasm/log.rs | 33 - .../src/application/driver/wasm/platform.rs | 14 - .../src/application/driver/wasm/process.rs | 339 ---------- controller/src/application/event.rs | 151 ----- controller/src/application/event/channel.rs | 10 - controller/src/application/event/transfer.rs | 10 - controller/src/application/unit.rs | 560 ---------------- controller/src/application/user.rs | 200 ------ controller/src/application/user/transfer.rs | 126 ---- controller/src/args.rs | 7 - controller/src/config.rs | 122 ---- controller/src/main.rs | 42 +- controller/src/network.rs | 97 --- controller/src/network/admin.rs | 624 ------------------ controller/src/network/auth.rs | 53 -- controller/src/network/stream.rs | 32 - controller/src/network/unit.rs | 428 ------------ controller/src/storage.rs | 80 --- drivers/local/Cargo.toml | 2 +- drivers/pterodactyl/Cargo.toml | 4 +- 35 files changed, 16 insertions(+), 5486 deletions(-) delete mode 100644 controller/src/application.rs delete mode 100644 controller/src/application/auth.rs delete mode 100644 controller/src/application/cloudlet.rs delete mode 100644 controller/src/application/deployment.rs delete mode 100644 controller/src/application/driver.rs delete mode 100644 controller/src/application/driver/process.rs delete mode 100644 controller/src/application/driver/wasm.rs delete mode 100644 controller/src/application/driver/wasm/cloudlet.rs delete mode 100644 controller/src/application/driver/wasm/config.rs delete mode 100644 controller/src/application/driver/wasm/file.rs delete mode 100644 controller/src/application/driver/wasm/http.rs delete mode 100644 controller/src/application/driver/wasm/log.rs delete mode 100644 controller/src/application/driver/wasm/platform.rs delete mode 100644 controller/src/application/driver/wasm/process.rs delete mode 100644 controller/src/application/event.rs delete mode 100644 controller/src/application/event/channel.rs delete mode 100644 controller/src/application/event/transfer.rs delete mode 100644 controller/src/application/unit.rs delete mode 100644 controller/src/application/user.rs delete mode 100644 controller/src/application/user/transfer.rs delete mode 100644 controller/src/args.rs delete mode 100644 controller/src/config.rs delete mode 100644 controller/src/network.rs delete mode 100644 controller/src/network/admin.rs delete mode 100644 controller/src/network/auth.rs delete mode 100644 controller/src/network/stream.rs delete mode 100644 controller/src/network/unit.rs delete mode 100644 controller/src/storage.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index eeaa5018..7e3314e9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -21,7 +21,7 @@ inquire = "0.7.5" anyhow = "1.0.95" # Command line arguments -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.27", features = ["derive"] } # Configuration serde = { version = "1.0.217", features = ["derive"] } diff --git a/clients/wrapper/Cargo.toml b/clients/wrapper/Cargo.toml index 12fc481c..63829be4 100644 --- a/clients/wrapper/Cargo.toml +++ b/clients/wrapper/Cargo.toml @@ -17,10 +17,10 @@ anyhow = "1.0.95" ctrlc = "3.4.5" # User system -uuid = { version = "1.12.0", features = ["v4"] } +uuid = { version = "1.12.1", features = ["v4"] } # Command line arguments -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.27", features = ["derive"] } # Regex parsing regex = "1.11.1" diff --git a/common/Cargo.toml b/common/Cargo.toml index d85cbefb..89975a4b 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -12,7 +12,7 @@ colored = "3.0.0" anyhow = "1.0.95" # Command line arguments -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.27", features = ["derive"] } # Configuration serde = { version = "1.0.217", features = ["derive"] } diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 4d935b01..1c294877 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -17,10 +17,10 @@ anyhow = "1.0.95" ctrlc = "3.4.5" # Unit system -uuid = { version = "1.12.0", features = ["v4"] } +uuid = { version = "1.12.1", features = ["v4"] } # Command line arguments -clap = { version = "4.5.26", features = ["derive"] } +clap = { version = "4.5.27", features = ["derive"] } # Regex parsing regex = "1.11.1" @@ -31,7 +31,6 @@ toml = "0.8.19" # Async runtime tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "macros"] } -tokio-stream = "0.1.17" # API url = { version = "2.5.4", features = ["serde"] } @@ -39,9 +38,9 @@ prost = "0.13.4" tonic = "0.12.3" # Drivers -wasmtime = { version = "28.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } -wasmtime-wasi = { version = "28.0.1", optional = true } -minreq = { version = "2.13.0", features = ["https-rustls"], optional = true } +wasmtime = { version = "29.0.1", default-features = false, features = ["runtime", "component-model", "cranelift", "parallel-compilation", "cache"], optional = true } +wasmtime-wasi = { version = "29.0.1", optional = true } +minreq = { version = "2.13.2", features = ["https-rustls"], optional = true } [build-dependencies] toml = "0.8.19" diff --git a/controller/src/application.rs b/controller/src/application.rs deleted file mode 100644 index d401d831..00000000 --- a/controller/src/application.rs +++ /dev/null @@ -1,219 +0,0 @@ -use anyhow::Error; -use auth::Auth; -use cloudlet::Cloudlets; -use deployment::Deployments; -use driver::Drivers; -use event::EventBus; -use simplelog::info; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}; -use std::thread; -use std::time::{Duration, Instant}; -use tokio::runtime::{Builder, Runtime}; -use unit::Units; -use user::Users; - -use crate::config::Config; -use crate::network::NetworkStack; - -pub mod auth; -pub mod cloudlet; -pub mod deployment; -pub mod driver; -pub mod event; -pub mod unit; -pub mod user; - -static STARTUP_SLEEP: Duration = Duration::from_secs(1); -static SHUTDOWN_WAIT: Duration = Duration::from_secs(10); - -const TICK_RATE: u64 = 1; - -pub type ControllerHandle = Arc; -pub type WeakControllerHandle = Weak; - -pub struct Controller { - handle: WeakControllerHandle, - - /* Immutable */ - pub(crate) configuration: Config, - pub(crate) drivers: Drivers, - - /* Runtime State */ - runtime: RwLock>, - running: AtomicBool, - - /* Authentication */ - auth: Auth, - - /* Accessed rarely */ - cloudlets: RwLock, - deployments: RwLock, - - /* Accessed frequently */ - units: Units, - users: Users, - - /* Event Bus */ - event_bus: EventBus, -} - -impl Controller { - pub fn new(configuration: Config) -> Arc { - Arc::new_cyclic(move |handle| { - let auth = Auth::load_all(); - let drivers = Drivers::load_all(configuration.identifier.as_ref().unwrap()); - let cloudlets = Cloudlets::load_all(handle.clone(), &drivers); - let deployments = Deployments::load_all(handle.clone(), &cloudlets); - let units = Units::new(handle.clone()); - let users = Users::new(handle.clone()); - let event_bus = EventBus::new(/*handle.clone()*/); - Self { - handle: handle.clone(), - configuration, - drivers, - runtime: RwLock::new(Some( - Builder::new_multi_thread() - .enable_all() - .build() - .expect("Failed to create Tokio runtime"), - )), - running: AtomicBool::new(true), - auth, - cloudlets: RwLock::new(cloudlets), - deployments: RwLock::new(deployments), - units, - users, - event_bus, - } - }) - } - - pub fn start(&self) { - // Set up signal handlers - self.setup_interrupts(); - - let network_handle = NetworkStack::start(self.handle.upgrade().unwrap()); - let tick_duration = Duration::from_millis(1000 / TICK_RATE); - - // Wait for 1 second before starting the tick loop - thread::sleep(STARTUP_SLEEP); - - while self.running.load(Ordering::Relaxed) { - let start_time = Instant::now(); - self.tick(); - - let elapsed_time = start_time.elapsed(); - if elapsed_time < tick_duration { - thread::sleep(tick_duration - elapsed_time); - } - } - - // Stop all units - info!("Stopping all units..."); - self.units.stop_all_instant(); - - // Stop network stack - info!("Stopping network stack..."); - network_handle.shutdown(); - - // Wait for all tokio task to finish - info!("Stopping async runtime..."); - (*self.runtime.write().unwrap()) - .take() - .unwrap() - .shutdown_timeout(SHUTDOWN_WAIT); - - // Let the drivers cleanup there messes - info!("Letting the drivers cleanup..."); - self.drivers.cleanup(); - } - - pub fn request_stop(&self) { - info!("Controller stop requested. Stopping..."); - self.running.store(false, Ordering::Relaxed); - } - - pub fn lock_cloudlets(&self) -> RwLockReadGuard { - self.cloudlets - .read() - .expect("Failed to get lock to cloudlets") - } - - pub fn lock_deployments(&self) -> RwLockReadGuard { - self.deployments - .read() - .expect("Failed to get lock to deployments") - } - - pub fn lock_cloudlets_mut(&self) -> RwLockWriteGuard { - self.cloudlets - .write() - .expect("Failed to get lock to cloudlets") - } - - pub fn lock_deployments_mut(&self) -> RwLockWriteGuard { - self.deployments - .write() - .expect("Failed to get lock to deployments") - } - - pub fn get_drivers(&self) -> &Drivers { - &self.drivers - } - - pub fn get_auth(&self) -> &Auth { - &self.auth - } - - pub fn get_units(&self) -> &Units { - &self.units - } - - pub fn get_users(&self) -> &Users { - &self.users - } - - pub fn get_event_bus(&self) -> &EventBus { - &self.event_bus - } - - pub fn get_runtime(&self) -> RwLockReadGuard> { - self.runtime.read().expect("Failed to get lock to runtime") - } - - fn tick(&self) { - // Tick all drivers - self.drivers.tick(); - - // Tick all driver cloudlets - self.lock_cloudlets().tick(); - - // Check if all deployments have started there units etc.. - self.lock_deployments().tick(&self.units); - - // Check if all units have sent their heartbeats and start requested units if we can - self.units.tick(); - - // Check state of all users - self.users.tick(); - } - - fn setup_interrupts(&self) { - // Set up signal handlers - let controller = self.handle.clone(); - ctrlc::set_handler(move || { - info!("Interrupt signal received. Stopping..."); - if let Some(controller) = controller.upgrade() { - controller.request_stop(); - } - }) - .expect("Failed to set Ctrl+C handler"); - } -} - -pub enum CreationResult { - Created, - AlreadyExists, - Denied(Error), -} diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs deleted file mode 100644 index 90396542..00000000 --- a/controller/src/application/auth.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::{ - collections::HashMap, - fs, - sync::{Arc, RwLock}, -}; - -use common::config::{LoadFromTomlFile, SaveToTomlFile}; -use simplelog::{error, info, warn}; -use stored::StoredUser; -use uuid::Uuid; - -use crate::storage::Storage; - -use super::unit::{UnitHandle, WeakUnitHandle}; - -const DEFAULT_ADMIN_USERNAME: &str = "admin"; - -pub type AuthUserHandle = Arc; -pub type AuthUnitHandle = Arc; - -pub struct AuthUser { - pub username: String, - pub token: String, -} - -pub struct AuthUnit { - pub unit: WeakUnitHandle, - pub token: String, -} - -pub struct Auth { - pub users: RwLock>, - pub units: RwLock>, -} - -impl Auth { - pub fn new(users: HashMap) -> Self { - Auth { - users: RwLock::new(users), - units: RwLock::new(HashMap::new()), - } - } - - pub fn load_all() -> Self { - info!("Loading users..."); - - let users_directory = Storage::get_users_folder(); - if !users_directory.exists() { - if let Err(error) = fs::create_dir_all(&users_directory) { - warn!( - "Failed to create users directory: {}", - &error - ); - } - } - - let mut users = HashMap::new(); - let entries = match fs::read_dir(&users_directory) { - Ok(entries) => entries, - Err(error) => { - error!("Failed to read users directory: {}", &error); - return Auth::new(users); - } - }; - - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - error!("Failed to read user entry: {}", &error); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() { - continue; - } - - let name = match path.file_stem() { - Some(name) => name.to_string_lossy().to_string(), - None => continue, - }; - - let user = match StoredUser::load_from_file(&path) { - Ok(user) => user, - Err(error) => { - error!( - "Failed to read user {} from file({:?}): {}", - &name, - &path, - &error - ); - continue; - } - }; - - let user = AuthUser { - username: name.clone(), - token: user.token, - }; - if users - .values() - .any(|u| u.username.eq_ignore_ascii_case(&user.username)) - { - error!("User with the name {} already exists", &name); - continue; - } - users.insert(user.token.clone(), Arc::new(user)); - info!("Loaded user {}", &name); - } - - let amount = users.len(); - let auth = Auth::new(users); - if amount == 0 { - let user = auth - .register_user(DEFAULT_ADMIN_USERNAME) - .expect("Failed to create default admin user"); - info!("-----------------------------------"); - info!("No users found, created default admin user"); - info!("Username: {}", DEFAULT_ADMIN_USERNAME); - info!("Token: {}", &user.token); - info!("-----------------------------------"); - info!("Welcome to Atomic Cloud"); - info!("-----------------------------------"); - } - - info!("Loaded {} user(s)", amount); - auth - } - - pub fn get_user(&self, token: &str) -> Option { - self.users.read().unwrap().get(token).cloned() - } - - pub fn get_unit(&self, token: &str) -> Option { - self.units.read().unwrap().get(token).cloned() - } - - pub fn register_unit(&self, unit: WeakUnitHandle) -> AuthUnitHandle { - let token = format!( - "sctl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - - let unit = Arc::new(AuthUnit { - unit, - token: token.clone(), - }); - self.units - .write() - .unwrap() - .insert(token.clone(), unit.clone()); - - unit - } - - pub fn unregister_unit(&self, unit: &UnitHandle) { - self.units.write().unwrap().retain(|_, value| { - if let Some(ref_unit) = value.unit.upgrade() { - !Arc::ptr_eq(&ref_unit, unit) - } else { - true - } - }) - } - - pub fn register_user(&self, username: &str) -> Option { - let token = format!( - "actl_{}{}", - Uuid::new_v4().as_simple(), - Uuid::new_v4().as_simple() - ); - let stored_user = StoredUser { - token: token.to_string(), - }; - let user_path = Storage::get_user_file(username); - if stored_user.save_to_file(&user_path, true).is_err() { - error!( - "Failed to save user to file: {}", - &user_path.display() - ); - return None; - } - - let user = Arc::new(AuthUser { - username: username.to_string(), - token: token.clone(), - }); - self.users.write().unwrap().insert(token, user.clone()); - - Some(user) - } -} - -mod stored { - use common::config::{LoadFromTomlFile, SaveToTomlFile}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - pub struct StoredUser { - pub token: String, - } - - impl LoadFromTomlFile for StoredUser {} - impl SaveToTomlFile for StoredUser {} -} diff --git a/controller/src/application/cloudlet.rs b/controller/src/application/cloudlet.rs deleted file mode 100644 index 83328334..00000000 --- a/controller/src/application/cloudlet.rs +++ /dev/null @@ -1,468 +0,0 @@ -use std::{ - collections::HashMap, - fmt::Display, - fs, - sync::{Arc, RwLock, Weak}, -}; - -use anyhow::{anyhow, Result}; -use common::config::{LoadFromTomlFile, SaveToTomlFile}; -use serde::{Deserialize, Serialize}; -use simplelog::{error, info, warn}; -use stored::StoredCloudlet; -use url::Url; - -use super::{ - driver::{DriverCloudletHandle, DriverHandle, Drivers, GenericDriver}, - unit::{Resources, Spec, StartRequestHandle}, - CreationResult, WeakControllerHandle, -}; -use crate::storage::Storage; - -pub type CloudletHandle = Arc; -pub type WeakCloudletHandle = Weak; - -pub struct Cloudlets { - controller: WeakControllerHandle, - - cloudlets: HashMap, -} - -impl Cloudlets { - pub fn new(controller: WeakControllerHandle) -> Self { - Self { - controller, - cloudlets: HashMap::new(), - } - } - - /// This will try to load all the cloudletss stored as toml files from the cloudlets directory - /// - /// Any compilcations will be logged and the cloudlet will be skipped - pub fn load_all(controller: WeakControllerHandle, drivers: &Drivers) -> Self { - info!("Loading cloudlets..."); - - let mut cloudlets = Self::new(controller); - let cloudlets_directory = Storage::get_cloudlets_folder(); - if !cloudlets_directory.exists() { - if let Err(error) = fs::create_dir_all(&cloudlets_directory) { - warn!( - "Failed to create cloudlets directory: {}", - &error - ); - return cloudlets; - } - } - - let entries = match fs::read_dir(&cloudlets_directory) { - Ok(entries) => entries, - Err(error) => { - error!( - "Failed to read cloudlets directory: {}", - &error - ); - return cloudlets; - } - }; - - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - error!("Failed to read cloudlet entry: {}", &error); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() { - continue; - } - - let name = match path.file_stem() { - Some(name) => name.to_string_lossy().to_string(), - None => continue, - }; - - let cloudlet = match StoredCloudlet::load_from_file(&path) { - Ok(cloudlet) => cloudlet, - Err(error) => { - error!( - "Failed to read cloudlet {} from file({:?}): {}", - &name, - &path, - &error - ); - continue; - } - }; - - info!("Loading cloudlet {}", &name); - let cloudlet = match Cloudlet::try_from(&name, &cloudlet, drivers) { - Some(cloudlet) => cloudlet, - None => continue, - }; - - if let Err(error) = cloudlets.add_cloudlet(cloudlet) { - warn!( - "Failed to load cloudlet {} because it was denied by the driver", - &name - ); - warn!(" -> {}", &error); - } - } - - info!("Loaded {} cloudlet(s)", cloudlets.cloudlets.len()); - cloudlets - } - - pub fn tick(&self) { - for cloudlet in self.cloudlets.values() { - if let Err(error) = cloudlet.get_inner().tick() { - error!( - "Failed to tick cloudlet {}: {}", - cloudlet.name, error - ); - } - } - } - - pub fn get_amount(&self) -> usize { - self.cloudlets.len() - } - - pub fn get_cloudlets(&self) -> Vec { - self.cloudlets.values().cloned().collect() - } - - pub fn find_by_name(&self, name: &str) -> Option { - self.cloudlets.get(name).cloned() - } - - /// This can be used to retire or activate a cloudlet - /// - /// Retiring a cloudlet will remove it from the deployments that use it and stop all units on it - pub fn set_cloudlet_status( - &mut self, - cloudlet: &CloudletHandle, - status: LifecycleStatus, - ) -> Result<()> { - match status { - LifecycleStatus::Inactive => { - self.retire_cloudlet(cloudlet); - info!("Inactive cloudlet {}", cloudlet.name); - } - LifecycleStatus::Active => { - self.activate_cloudlet(cloudlet); - info!("Activated cloudlet {}", cloudlet.name); - } - } - *cloudlet.status.write().unwrap() = status; - cloudlet.mark_dirty()?; - Ok(()) - } - - /// This should only be called from set_cloudlet_status and delete_cloudlet - fn retire_cloudlet(&mut self, cloudlet: &CloudletHandle) { - let controller = self - .controller - .upgrade() - .expect("The controller is dead while still running code that requires it"); - { - controller - .lock_deployments() - .search_and_remove_cloudlet(cloudlet); - controller.get_units().stop_all_on_cloudlet(cloudlet); - } - } - - /// This should only be called from set_cloudlet_status - fn activate_cloudlet(&mut self, _cloudlet: &CloudletHandle) {} - - pub fn delete_cloudlet(&mut self, cloudlet: &CloudletHandle) -> Result<()> { - if *cloudlet - .status - .read() - .expect("Failed to lock status of cloudlet") - != LifecycleStatus::Inactive - { - return Err(anyhow!("Cloudlet is not inactive")); - } - self.retire_cloudlet(cloudlet); // Just to be sure - cloudlet.delete_file()?; - self.remove_cloudlet(cloudlet); - - let ref_count = Arc::strong_count(cloudlet); - if ref_count > 1 { - warn!( - "Cloudlet {} still has strong references[{}] this chould indicate a memory leak!", - cloudlet.name, - ref_count - ); - } - - info!("Deleted cloudlet {}", cloudlet.name); - Ok(()) - } - - pub fn create_cloudlet( - &mut self, - name: &str, - driver: Arc, - capabilities: Capabilities, - controller: RemoteController, - ) -> Result { - if self.cloudlets.contains_key(name) { - return Ok(CreationResult::AlreadyExists); - } - - let stored_cloudlet = StoredCloudlet { - driver: driver.name().to_string(), - capabilities, - status: LifecycleStatus::Inactive, - controller, - }; - let cloudlet = Cloudlet::from(name, &stored_cloudlet, driver); - - match self.add_cloudlet(cloudlet) { - Ok(_) => { - stored_cloudlet.save_to_file(&Storage::get_cloudlet_file(name), true)?; - info!("Created cloudlet {}", name); - Ok(CreationResult::Created) - } - Err(error) => Ok(CreationResult::Denied(error)), - } - } - - fn add_cloudlet(&mut self, mut cloudlet: Cloudlet) -> Result<()> { - match cloudlet.init() { - Ok(_) => { - self.cloudlets - .insert(cloudlet.name.clone(), Arc::new(cloudlet)); - Ok(()) - } - Err(error) => Err(error), - } - } - - fn remove_cloudlet(&mut self, cloudlet: &CloudletHandle) { - self.cloudlets.remove(&cloudlet.name); - } -} - -pub type AllocationHandle = Arc; - -pub struct Allocation { - pub addresses: Vec, - pub resources: Resources, - pub spec: Spec, -} - -impl Allocation { - pub fn primary_address(&self) -> &HostAndPort { - &self.addresses[0] - } -} - -pub struct Cloudlet { - /* Settings */ - pub name: String, - pub capabilities: Capabilities, - pub status: RwLock, - - /* Controller */ - pub controller: RemoteController, - - /* Driver handles */ - pub driver: DriverHandle, - inner: Option, - - /* Allocations made on this cloudlet */ - pub allocations: RwLock>, -} - -impl Cloudlet { - fn from(name: &str, stored_cloudlet: &StoredCloudlet, driver: Arc) -> Self { - Self { - name: name.to_string(), - capabilities: stored_cloudlet.capabilities.clone(), - status: RwLock::new(stored_cloudlet.status.clone()), - controller: stored_cloudlet.controller.clone(), - driver, - inner: None, - allocations: RwLock::new(Vec::new()), - } - } - - fn try_from(name: &str, stored_cloudlet: &StoredCloudlet, drivers: &Drivers) -> Option { - drivers - .find_by_name(&stored_cloudlet.driver) - .map(|driver| Self::from(name, stored_cloudlet, driver)) - .or_else(|| { - error!( - "Failed to load cloudlet {} because there is no loaded driver with the name {}", - &name, - &stored_cloudlet.driver - ); - None - }) - } - - pub fn init(&mut self) -> Result<()> { - match self.driver.init_cloudlet(self) { - Ok(value) => { - self.inner = Some(value); - Ok(()) - } - Err(error) => Err(error), - } - } - - pub fn allocate(&self, request: &StartRequestHandle) -> Result { - if *self.status.read().unwrap() == LifecycleStatus::Inactive { - warn!( - "Attempted to allocate resources on inactive cloudlet {}", - self.name - ); - return Err(anyhow!("Can not allocate resources on inactive cloudlet")); - } - - let mut allocations = self - .allocations - .write() - .expect("Failed to lock allocations"); - - if let Some(max_memory) = self.capabilities.memory { - let used_memory: u32 = allocations - .iter() - .map(|allocation| allocation.resources.memory) - .sum(); - if used_memory > max_memory { - return Err(anyhow!("Cloudlet has reached the memory limit")); - } - } - - if let Some(max_allocations) = self.capabilities.max_allocations { - if allocations.len() + 1 > max_allocations as usize { - return Err(anyhow!( - "Cloudlet has reached the maximum amount of allocations" - )); - } - } - - let addresses = self.inner.as_ref().unwrap().allocate_addresses(request)?; - if addresses.len() < request.resources.addresses as usize { - return Err(anyhow!( - "Cloudlet did not allocate the required amount of addresses" - )); - } - - let allocation = Arc::new(Allocation { - addresses, - resources: request.resources.clone(), - spec: request.spec.clone(), - }); - allocations.push(allocation.clone()); - Ok(allocation) - } - - pub fn deallocate(&self, allocation: &AllocationHandle) { - if let Err(error) = self - .inner - .as_ref() - .unwrap() - .deallocate_addresses(allocation.addresses.clone()) - { - error!("Failed to deallocate addresses: {}", &error); - } - self.allocations - .write() - .expect("Failed to lock allocations") - .retain(|alloc| !Arc::ptr_eq(alloc, allocation)); - } - - pub fn get_inner(&self) -> &DriverCloudletHandle { - self.inner.as_ref().unwrap() - } - - pub fn mark_dirty(&self) -> Result<()> { - self.save_to_file() - } - - fn delete_file(&self) -> Result<()> { - let file_path = Storage::get_cloudlet_file(&self.name); - if file_path.exists() { - fs::remove_file(file_path)?; - } - Ok(()) - } - - fn save_to_file(&self) -> Result<()> { - let stored_cloudlet = StoredCloudlet { - driver: self.driver.name().to_string(), - capabilities: self.capabilities.clone(), - status: self.status.read().unwrap().clone(), - controller: self.controller.clone(), - }; - stored_cloudlet.save_to_file(&Storage::get_cloudlet_file(&self.name), true) - } -} - -#[derive(Serialize, Deserialize, Clone, Default)] -pub struct Capabilities { - pub memory: Option, - pub max_allocations: Option, - pub child: Option, -} - -#[derive(Serialize, Deserialize, Clone, Default, PartialEq)] -pub enum LifecycleStatus { - #[serde(rename = "inactive")] - #[default] - Inactive, - #[serde(rename = "active")] - Active, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct RemoteController { - pub address: Url, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct HostAndPort { - pub host: S, - pub port: u16, -} - -impl HostAndPort { - pub fn new(host: String, port: u16) -> Self { - Self { host, port } - } -} - -impl Display for HostAndPort { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "{}:{}", self.host, self.port) - } -} - -mod stored { - use super::{Capabilities, LifecycleStatus, RemoteController}; - use common::config::{LoadFromTomlFile, SaveToTomlFile}; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - pub struct StoredCloudlet { - /* Settings */ - pub driver: String, - pub capabilities: Capabilities, - pub status: LifecycleStatus, - - /* Controller */ - pub controller: RemoteController, - } - - impl LoadFromTomlFile for StoredCloudlet {} - impl SaveToTomlFile for StoredCloudlet {} -} diff --git a/controller/src/application/deployment.rs b/controller/src/application/deployment.rs deleted file mode 100644 index a8984825..00000000 --- a/controller/src/application/deployment.rs +++ /dev/null @@ -1,554 +0,0 @@ -use std::{ - collections::HashMap, - fs, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, RwLock, Weak, - }, - time::Instant, -}; - -use anyhow::{anyhow, Result}; -use common::{ - allocator::NumberAllocator, - config::{LoadFromTomlFile, SaveToTomlFile}, -}; -use serde::{Deserialize, Serialize}; -use shared::StoredDeployment; -use simplelog::{debug, error, info, warn}; - -use crate::storage::Storage; - -use super::{ - cloudlet::{CloudletHandle, Cloudlets, LifecycleStatus, WeakCloudletHandle}, - unit::{DeploymentRef, Resources, Spec, StartRequest, StartRequestHandle, UnitHandle, Units}, - CreationResult, WeakControllerHandle, -}; - -pub type DeploymentHandle = Arc; -pub type WeakDeploymentHandle = Weak; - -pub struct Deployments { - controller: WeakControllerHandle, - - deployments: HashMap, -} - -impl Deployments { - pub fn new(controller: WeakControllerHandle) -> Self { - Self { - controller, - deployments: HashMap::new(), - } - } - - pub fn load_all(controller: WeakControllerHandle, cloudlets: &Cloudlets) -> Self { - info!("Loading deployments..."); - - let mut deployments = Self::new(controller); - let deployments_directory = Storage::get_deployments_folder(); - if !deployments_directory.exists() { - if let Err(error) = fs::create_dir_all(&deployments_directory) { - warn!( - "Failed to create deployments directory: {}", - &error - ); - return deployments; - } - } - - let entries = match fs::read_dir(&deployments_directory) { - Ok(entries) => entries, - Err(error) => { - error!( - "Failed to read deployments directory: {}", - &error - ); - return deployments; - } - }; - - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - error!( - "Failed to read deployment entry: {}", - &error - ); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() { - continue; - } - - let name = match path.file_stem() { - Some(name) => name.to_string_lossy().to_string(), - None => continue, - }; - - let deployment = match StoredDeployment::load_from_file(&path) { - Ok(deployment) => deployment, - Err(error) => { - error!( - "Failed to read deployment {} from file({:?}): {}", - &name, - &path, - &error - ); - continue; - } - }; - - let deployment = match Deployment::try_from(&name, &deployment, cloudlets) { - Some(deployment) => deployment, - None => continue, - }; - - deployments.add_deployment(deployment); - info!("Loaded deployment {}", &name); - } - - info!( - "Loaded {} deployment(s)", - deployments.deployments.len() - ); - deployments - } - - pub fn get_amount(&self) -> usize { - self.deployments.len() - } - - pub fn get_deployments(&self) -> &HashMap { - &self.deployments - } - - pub fn tick(&self, units: &Units) { - for deployment in self.deployments.values() { - deployment.tick(&self.controller, units); - } - } - - pub fn find_by_name(&self, name: &str) -> Option { - self.deployments.get(name).cloned() - } - - pub fn set_deployment_status( - &mut self, - deployment: &DeploymentHandle, - status: LifecycleStatus, - ) -> Result<()> { - match status { - LifecycleStatus::Inactive => { - self.retire_deployment(deployment); - info!( - "Inactive deployment {}", - deployment.name - ); - } - LifecycleStatus::Active => { - self.activate_deployment(deployment); - info!( - "Activated deployment {}", - deployment.name - ); - } - } - *deployment.status.write().unwrap() = status; - deployment.mark_dirty()?; - Ok(()) - } - - fn retire_deployment(&mut self, deployment: &DeploymentHandle) { - let controller = self - .controller - .upgrade() - .expect("The controller is dead while still running code that requires it"); - { - let unit_manager = controller.get_units(); - let mut units = deployment.units.write().unwrap(); - for unit in units.iter() { - if let AssociatedUnit::Active(unit) = unit { - unit_manager.checked_unit_stop(unit); - } else if let AssociatedUnit::Queueing(request) = unit { - request.canceled.store(true, Ordering::Relaxed); - } - } - units.clear(); - } - } - - fn activate_deployment(&mut self, _deployment: &DeploymentHandle) {} - - pub fn delete_deployment(&mut self, deployment: &DeploymentHandle) -> Result<()> { - if *deployment - .status - .read() - .expect("Failed to lock status of deployment") - != LifecycleStatus::Inactive - { - return Err(anyhow!("Deployment is not inactive")); - } - self.retire_deployment(deployment); // Make sure all units are stopped - deployment.delete_file()?; - self.remove_deployment(deployment); - - let ref_count = Arc::strong_count(deployment); - if ref_count > 1 { - warn!( - "Deployment {} still has strong references[{}] this chould indicate a memory leak!", - deployment.name, - ref_count - ); - } - - info!("Deleted deployment {}", deployment.name); - Ok(()) - } - - pub fn create_deployment( - &mut self, - name: &str, - cloudlet_handles: Vec, - constraints: StartConstraints, - scaling: ScalingPolicy, - resources: Resources, - spec: Spec, - ) -> Result { - if cloudlet_handles.is_empty() { - return Ok(CreationResult::Denied(anyhow!("No cloudlet provided"))); - } - - if self.deployments.contains_key(name) { - return Ok(CreationResult::AlreadyExists); - } - - let cloudlets: Vec = cloudlet_handles - .iter() - .map(|cloudlet| cloudlet.name.clone()) - .collect(); - - let stored_deployment = StoredDeployment { - status: LifecycleStatus::Inactive, - cloudlets, - constraints, - scaling, - resources, - spec, - }; - let deployment = Deployment::from( - name, - &stored_deployment, - cloudlet_handles.iter().map(Arc::downgrade).collect(), - ); - - self.add_deployment(deployment); - stored_deployment.save_to_file(&Storage::get_deployment_file(name), true)?; - info!("Created deployment {}", name); - Ok(CreationResult::Created) - } - - pub fn search_and_remove_cloudlet(&self, cloudlet: &CloudletHandle) { - for deployment in self.deployments.values() { - deployment - .cloudlets - .write() - .expect("Failed to lock cloudlets list of deployment") - .retain(|handle| { - if let Some(strong_cloudlet) = handle.upgrade() { - return !Arc::ptr_eq(&strong_cloudlet, cloudlet); - } - false - }); - deployment - .mark_dirty() - .expect("Failed to mark deployment as dirty"); - } - } - - fn add_deployment(&mut self, deployment: DeploymentHandle) { - self.deployments - .insert(deployment.name.to_string(), deployment); - } - - fn remove_deployment(&mut self, deployment: &DeploymentHandle) { - self.deployments.remove(&deployment.name); - } -} - -#[derive(Serialize, Deserialize, Clone, Copy, Default)] -pub struct StartConstraints { - pub minimum: u32, - pub maximum: u32, - pub priority: i32, -} - -#[derive(Serialize, Deserialize, Clone, Copy, Default)] -pub struct ScalingPolicy { - pub enabled: bool, - pub start_threshold: f32, - pub stop_empty_units: bool, -} - -pub enum AssociatedUnit { - Queueing(StartRequestHandle), - Active(UnitHandle), -} - -pub struct Deployment { - handle: WeakDeploymentHandle, - - /* Settings */ - pub name: String, - pub status: RwLock, - - /* Where? */ - pub cloudlets: RwLock>, - pub constraints: StartConstraints, - pub scaling: ScalingPolicy, - - /* How? */ - pub resources: Resources, - pub spec: Spec, - - /* What do i need to know? */ - id_allocator: RwLock>, - units: RwLock>, -} - -impl Deployment { - fn from( - name: &str, - stored_deployment: &StoredDeployment, - cloudlets: Vec, - ) -> DeploymentHandle { - Arc::new_cyclic(|handle| Self { - handle: handle.clone(), - name: name.to_string(), - status: RwLock::new(stored_deployment.status.clone()), - cloudlets: RwLock::new(cloudlets), - constraints: stored_deployment.constraints, - scaling: stored_deployment.scaling, - resources: stored_deployment.resources.clone(), - spec: stored_deployment.spec.clone(), - id_allocator: RwLock::new(NumberAllocator::new(1..usize::MAX)), - units: RwLock::new(Vec::new()), - }) - } - - fn try_from( - name: &str, - stored_deployment: &StoredDeployment, - cloudlets: &Cloudlets, - ) -> Option { - let cloudlet_handles: Vec = stored_deployment - .cloudlets - .iter() - .filter_map(|name| { - cloudlets - .find_by_name(name) - .map(|handle| Arc::downgrade(&handle)) - }) - .collect(); - if cloudlet_handles.is_empty() { - return None; - } - Some(Self::from(name, stored_deployment, cloudlet_handles)) - } - - fn tick(&self, controller: &WeakControllerHandle, units: &Units) { - if *self.status.read().unwrap() == LifecycleStatus::Inactive { - // Do not tick this deployment because it is inactive - return; - } - - let mut deployment_units = self.units.write().expect("Failed to lock units"); - let mut id_allocator = self - .id_allocator - .write() - .expect("Failed to lock id allocator"); - let mut target_unit_count = self.constraints.minimum; - - // Apply scaling policy - if self.scaling.enabled { - for unit in deployment_units.iter() { - if let AssociatedUnit::Active(unit) = unit { - let player_ratio = unit.get_user_count() as f32 / self.spec.max_players as f32; - if player_ratio >= self.scaling.start_threshold { - target_unit_count += 1; // Unit has reached the threshold, start a new one - } - } - } - - if self.scaling.stop_empty_units && deployment_units.len() as u32 > target_unit_count { - let mut amount_to_stop = deployment_units.len() as u32 - target_unit_count; - - // We have more units than needed - // Check if a unit is empty and stop it after the configured timeout - if let Some(controller) = controller.upgrade() { - for unit in deployment_units.iter() { - if let AssociatedUnit::Active(unit) = unit { - let mut stop_flag = - unit.flags.stop.write().expect("Failed to lock stop flag"); - if unit.get_user_count() == 0 { - if let Some(stop_time) = stop_flag.as_ref() { - if &Instant::now() > stop_time && amount_to_stop > 0 { - debug!( - "Unit {} is empty and reached the timeout, stopping it...", - unit.name - ); - controller.get_units().checked_unit_stop(unit); - amount_to_stop -= 1; - } - } else { - debug!( - "Unit {} is empty, starting stop timer...", - unit.name - ); - stop_flag.replace( - Instant::now() - + controller.configuration.timings.empty_unit.unwrap(), - ); - } - } else if stop_flag.is_some() { - debug!( - "Unit {} is no longer empty, clearing stop timer...", - unit.name - ); - stop_flag.take(); - } - } - } - } - } - } - - // Check if we need to start more units - for requested in 0..(target_unit_count as usize).saturating_sub(deployment_units.len()) { - if (deployment_units.len() + requested) >= target_unit_count as usize { - break; - } - - let unit_id = id_allocator - .allocate() - .expect("We reached the maximum unit count. Wow this is a lot of units"); - let request = units.queue_unit(StartRequest { - canceled: AtomicBool::new(false), - when: None, - name: format!("{}-{}", self.name, unit_id), - cloudlets: self.cloudlets.read().unwrap().clone(), - deployment: Some(DeploymentRef { - unit_id, - deployment: self.handle.clone(), - }), - resources: self.resources.clone(), - spec: self.spec.clone(), - priority: self.constraints.priority, - }); - - // Add queueing unit to deployment - deployment_units.push(AssociatedUnit::Queueing(request)); - } - } - - pub fn set_unit_active(&self, unit: UnitHandle, request: &StartRequestHandle) { - let mut units = self.units.write().expect("Failed to lock units"); - units.retain(|queued_unit| { - if let AssociatedUnit::Queueing(start_request) = queued_unit { - return !Arc::ptr_eq(start_request, request); - } - true - }); - units.push(AssociatedUnit::Active(unit)); - } - - pub fn remove_unit(&self, unit: &UnitHandle) { - self.units - .write() - .expect("Failed to lock units") - .retain(|handle| { - if let AssociatedUnit::Active(s) = handle { - return !Arc::ptr_eq(s, unit); - } - true - }); - self.id_allocator - .write() - .expect("Failed to lock id allocator") - .release(unit.deployment.as_ref().unwrap().unit_id); - } - - pub fn get_free_unit(&self) -> Option { - let units = self.units.read().expect("Failed to lock units"); - for unit in units.iter() { - if let AssociatedUnit::Active(unit) = unit { - return Some(unit.clone()); - } - } - None - } - - pub fn mark_dirty(&self) -> Result<()> { - self.save_to_file() - } - - fn delete_file(&self) -> Result<()> { - let file_path = Storage::get_deployment_file(&self.name); - if file_path.exists() { - fs::remove_file(file_path)?; - } - Ok(()) - } - - fn save_to_file(&self) -> Result<()> { - let stored_deployment = StoredDeployment { - status: self.status.read().unwrap().clone(), - cloudlets: self - .cloudlets - .read() - .unwrap() - .iter() - .map(|cloudlet| cloudlet.upgrade().unwrap().name.clone()) - .collect(), - constraints: self.constraints, - scaling: self.scaling, - resources: self.resources.clone(), - spec: self.spec.clone(), - }; - stored_deployment.save_to_file(&Storage::get_deployment_file(&self.name), true) - } -} - -mod shared { - use common::config::{LoadFromTomlFile, SaveToTomlFile}; - use serde::{Deserialize, Serialize}; - - use crate::application::{ - cloudlet::LifecycleStatus, - unit::{Resources, Spec}, - }; - - use super::{ScalingPolicy, StartConstraints}; - - #[derive(Serialize, Deserialize)] - pub struct StoredDeployment { - /* Settings */ - pub status: LifecycleStatus, - - /* Where? */ - pub cloudlets: Vec, - pub constraints: StartConstraints, - pub scaling: ScalingPolicy, - - /* How? */ - pub resources: Resources, - pub spec: Spec, - } - - impl LoadFromTomlFile for StoredDeployment {} - impl SaveToTomlFile for StoredDeployment {} -} diff --git a/controller/src/application/driver.rs b/controller/src/application/driver.rs deleted file mode 100644 index c0686457..00000000 --- a/controller/src/application/driver.rs +++ /dev/null @@ -1,136 +0,0 @@ -use anyhow::Result; -use simplelog::error; -use simplelog::info; -use std::sync::Arc; -use tonic::async_trait; - -use crate::application::cloudlet::Cloudlet; -use crate::application::unit::StartRequestHandle; -use crate::application::unit::UnitHandle; - -#[cfg(feature = "wasm-drivers")] -use crate::application::driver::wasm::WasmDriver; - -use super::cloudlet::HostAndPort; - -mod process; - -#[cfg(feature = "wasm-drivers")] -mod wasm; - -pub struct Information { - authors: Vec, - version: String, - ready: bool, -} - -#[async_trait] -pub trait GenericDriver: Send + Sync { - fn name(&self) -> &String; - fn init(&self) -> Result; - fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result; - - /* Cleanup */ - fn cleanup(&self) -> Result<()>; - - /* Ticking */ - fn tick(&self) -> Result<()>; -} - -#[async_trait] -pub trait GenericCloudlet: Send + Sync { - /* Ticking */ - fn tick(&self) -> Result<()>; - - /* Prepare */ - fn allocate_addresses(&self, request: &StartRequestHandle) -> Result>; - fn deallocate_addresses(&self, addresses: Vec) -> Result<()>; - - /* Unitss */ - fn start_unit(&self, unit: &UnitHandle) -> Result<()>; - fn restart_unit(&self, unit: &UnitHandle) -> Result<()>; - fn stop_unit(&self, unit: &UnitHandle) -> Result<()>; -} - -pub type DriverHandle = Arc; -pub type DriverCloudletHandle = Arc; - -pub struct Drivers { - drivers: Vec, -} - -impl Drivers { - pub fn load_all(cloud_identifier: &str) -> Self { - info!("Loading drivers..."); - - let mut drivers = Vec::new(); - - #[cfg(feature = "wasm-drivers")] - WasmDriver::load_all(cloud_identifier, &mut drivers); - - info!("Loaded {} driver(s)", drivers.len()); - Self { drivers } - } - - pub fn cleanup(&self) { - for driver in &self.drivers { - if let Err(error) = driver.cleanup() { - error!( - "Failed to dispose resources of driver {}: {}", - driver.name(), - error - ); - } - } - } - - pub fn tick(&self) { - for driver in &self.drivers { - if let Err(error) = driver.tick() { - error!( - "Failed to tick driver {}: {}", - driver.name(), - error - ); - } - } - } - - pub fn find_by_name(&self, name: &str) -> Option> { - self.drivers - .iter() - .find(|driver| driver.name().eq_ignore_ascii_case(name)) - .cloned() - } - - pub fn get_drivers(&self) -> Vec { - self.drivers.clone() - } -} - -#[cfg(feature = "wasm-drivers")] -mod source { - use anyhow::Result; - use std::fmt::{Display, Formatter}; - use std::fs; - use std::path::{Path, PathBuf}; - - pub struct Source { - pub path: PathBuf, - pub code: Vec, - } - - impl Display for Source { - fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { - write!(formatter, "{}", self.path.display()) - } - } - - impl Source { - pub fn from_file(path: &Path) -> Result { - let path = path.to_owned(); - let code = fs::read(&path)?; - Ok(Source { path, code }) - } - } -} diff --git a/controller/src/application/driver/process.rs b/controller/src/application/driver/process.rs deleted file mode 100644 index 8634ddf2..00000000 --- a/controller/src/application/driver/process.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::sync::mpsc::{self, Receiver}; -use std::thread; -use std::{ - io::{BufRead, BufReader, BufWriter, Read}, - marker::PhantomData, - process::{Child, ChildStderr, ChildStdin, ChildStdout}, - sync::Mutex, -}; - -use anyhow::{anyhow, Result}; - -pub struct DriverProcess { - /* Process */ - process: Child, - - /* Std Readers */ - stdout: ProcessStream, - stderr: ProcessStream, - - /* StdIn Writer */ - stdin: BufWriter, -} - -impl DriverProcess { - pub fn new(mut process: Child, direct: bool) -> Result { - let stdout = BufReader::new( - process - .stdout - .take() - .ok_or(anyhow!("Failed to take stdout from child process"))?, - ); - let stderr = BufReader::new( - process - .stderr - .take() - .ok_or(anyhow!("Failed to take stderr from child process"))?, - ); - let stdin = BufWriter::new( - process - .stdin - .take() - .ok_or(anyhow!("Failed to take stdin from child process"))?, - ); - - let (stdout, stderr) = if direct { - (ProcessStream::Direct(stdout), ProcessStream::Direct(stderr)) - } else { - ( - ProcessStream::Async(AsyncBufReader::new(stdout)), - ProcessStream::Async(AsyncBufReader::new(stderr)), - ) - }; - - Ok(Self { - process, - stdout, - stderr, - stdin, - }) - } - - pub fn get_process(&mut self) -> &mut Child { - &mut self.process - } - - pub fn get_stdout(&mut self) -> &mut ProcessStream { - &mut self.stdout - } - - pub fn get_stderr(&mut self) -> &mut ProcessStream { - &mut self.stderr - } - - pub fn get_stdin(&mut self) -> &mut BufWriter { - &mut self.stdin - } -} - -pub enum ProcessStream { - Direct(BufReader), - Async(AsyncBufReader), -} - -pub struct AsyncBufReader { - receiver: Mutex>, - phantom: PhantomData, -} - -impl AsyncBufReader { - pub fn new(mut reader: BufReader) -> Self { - let (sender, receiver) = mpsc::channel(); - - thread::spawn(move || { - let mut buffer = String::new(); - while reader.read_line(&mut buffer).unwrap_or(0) > 0 { - sender.send(buffer.clone()).unwrap(); - buffer.clear(); - } - }); - - AsyncBufReader { - receiver: Mutex::new(receiver), - phantom: PhantomData, - } - } - - pub fn try_recv(&self) -> Option { - self.receiver - .lock() - .expect("Failed to lock reader for readline") - .try_recv() - .ok() - } -} diff --git a/controller/src/application/driver/wasm.rs b/controller/src/application/driver/wasm.rs deleted file mode 100644 index 65f53bc7..00000000 --- a/controller/src/application/driver/wasm.rs +++ /dev/null @@ -1,481 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::sync::{Arc, Mutex, RwLock, Weak}; - -use anyhow::{anyhow, Result}; -use cloudlet::WasmCloudlet; -use common::config::LoadFromTomlFile; -use config::WasmConfig; -use generated::exports::cloudlet::driver::bridge; -use generated::Driver; -use simplelog::{error, info, warn}; -use wasmtime::component::{Component, Linker, ResourceAny}; -use wasmtime::{Config, Engine, Store}; -use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; - -use super::process::DriverProcess; -use super::source::Source; -use super::{DriverCloudletHandle, GenericDriver, Information}; -use crate::application::cloudlet::{Capabilities, Cloudlet, HostAndPort, RemoteController}; -use crate::storage::Storage; - -mod config; - -mod cloudlet; -mod file; -mod http; -mod log; -mod platform; -mod process; - -pub mod generated { - use wasmtime::component::bindgen; - - bindgen!({ - world: "driver", - path: "../protocol/wit/", - }); -} - -/* Caching of compiled wasm artifacts and other configuration */ -const CONFIG_FILE: &str = "wasm.toml"; -const ENGINE_CONFIG_FILE: &str = "wasm-engine.toml"; -const DEFAULT_ENGINE_CONFIG: &str = r#"# For more settings, please refer to the documentation: -# https://bytecodealliance.github.io/wasmtime/cli-cache.html - -[cache] -enabled = true"#; -const DEFAULT_CONFIG: &str = r#"# This configuration is crucial for granting the drivers their required permissions -# https://httprafa.github.io/atomic-cloud/controller/drivers/wasm/permissions/ - -[[drivers]] -name = "pterodactyl" -inherit_stdio = false -inherit_args = false -inherit_env = false -inherit_network = true -allow_ip_name_lookup = true -allow_http = true -allow_process = false -allow_remove_dir_all = false -mounts = []"#; - -struct WasmDriverState { - handle: Weak, - wasi: WasiCtx, - table: ResourceTable, -} - -impl WasiView for WasmDriverState { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi - } - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} - -impl generated::cloudlet::driver::types::Host for WasmDriverState {} - -impl generated::cloudlet::driver::api::Host for WasmDriverState { - fn get_name(&mut self) -> String { - self.handle.upgrade().unwrap().name.clone() - } -} - -struct WasmDriverHandle { - store: Store, - resource: ResourceAny, // This is delete when the store is dropped -} - -impl WasmDriverHandle { - fn new(store: Store, resource: ResourceAny) -> Self { - WasmDriverHandle { store, resource } - } - - fn get(&mut self) -> (ResourceAny, &mut Store) { - (self.resource, &mut self.store) - } -} - -pub struct WasmDriverData { - processes: RwLock>, -} - -pub struct WasmDriver { - own: Weak, - - name: String, - bindings: Driver, - handle: Mutex>, - - data: WasmDriverData, -} - -impl WasmDriver { - fn get_resource_and_store( - handle: &mut Option, - ) -> (ResourceAny, &mut Store) { - handle.as_mut().unwrap().get() - } -} - -impl GenericDriver for WasmDriver { - fn name(&self) -> &String { - &self.name - } - - fn init(&self) -> Result { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings - .cloudlet_driver_bridge() - .generic_driver() - .call_init(store, resource) - { - Ok(information) => Ok(information.into()), - Err(error) => Err(error), - } - } - - fn init_cloudlet(&self, cloudlet: &Cloudlet) -> Result { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings - .cloudlet_driver_bridge() - .generic_driver() - .call_init_cloudlet( - store, - resource, - &cloudlet.name, - &(&cloudlet.capabilities).into(), - &(&cloudlet.controller).into(), - )? { - Ok(cloudlet) => Ok(Arc::new(WasmCloudlet { - handle: self.own.clone(), - resource: cloudlet, - })), - Err(error) => Err(anyhow!(error)), - } - } - - fn cleanup(&self) -> Result<()> { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings - .cloudlet_driver_bridge() - .generic_driver() - .call_cleanup(store, resource) - { - Ok(result) => result.map_err(|errors| { - anyhow!(errors - .iter() - .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) - .collect::>() - .join("\n")) - }), - Err(error) => Err(error), - } - } - - fn tick(&self) -> Result<()> { - let mut handle = self.handle.lock().unwrap(); - let (resource, store) = Self::get_resource_and_store(&mut handle); - match self - .bindings - .cloudlet_driver_bridge() - .generic_driver() - .call_tick(store, resource) - { - Ok(result) => result.map_err(|errors| { - anyhow!(errors - .iter() - .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) - .collect::>() - .join("\n")) - }), - Err(error) => Err(error), - } - } -} - -impl WasmDriver { - fn new( - config: &WasmConfig, - cloud_identifier: &str, - name: &str, - source: &Source, - ) -> Result> { - let config_directory = Storage::get_config_folder_for_driver(name); - let data_directory = Storage::get_data_folder_for_driver(name); - if !config_directory.exists() { - fs::create_dir_all(&config_directory).unwrap_or_else(|error| { - warn!( - "Failed to create configs directory for driver {}: {}", - &name, &error - ) - }); - } - if !data_directory.exists() { - fs::create_dir_all(&data_directory).unwrap_or_else(|error| { - warn!( - "Failed to create data directory for driver {}: {}", - &name, &error - ) - }); - } - - let mut engine_config = Config::new(); - engine_config.wasm_component_model(true); - if let Err(error) = - engine_config.cache_config_load(Storage::get_configs_folder().join(ENGINE_CONFIG_FILE)) - { - warn!( - "Failed to enable caching for wasmtime engine: {}", - &error - ); - } - - let engine = Engine::new(&engine_config)?; - let component = Component::from_binary(&engine, &source.code)?; - - let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker_sync(&mut linker)?; - Driver::add_to_linker(&mut linker, |state: &mut WasmDriverState| state)?; - - let mut wasi = WasiCtxBuilder::new(); - if let Some(config) = config.get_config(name) { - if config.inherit_stdio { - wasi.inherit_stdio(); - } - if config.inherit_args { - wasi.inherit_args(); - } - if config.inherit_env { - wasi.inherit_env(); - } - if config.inherit_network { - wasi.inherit_network(); - } - if config.allow_ip_name_lookup { - wasi.allow_ip_name_lookup(true); - } - for mount in &config.mounts { - wasi.preopened_dir(&mount.host, &mount.guest, DirPerms::all(), FilePerms::all())?; - } - } - let wasi = wasi - .preopened_dir( - &config_directory, - "/configs/", - DirPerms::all(), - FilePerms::all(), - )? - .preopened_dir(&data_directory, "/data/", DirPerms::all(), FilePerms::all())? - .build(); - - let table = ResourceTable::new(); - - let mut store = Store::new( - &engine, - WasmDriverState { - handle: Weak::new(), - wasi, - table, - }, - ); - let bindings = Driver::instantiate(&mut store, &component, &linker)?; - let driver = Arc::new_cyclic(|handle| { - store.data_mut().handle = handle.clone(); - WasmDriver { - own: handle.clone(), - name: name.to_string(), - bindings, - handle: Mutex::new(None), - data: WasmDriverData { - processes: RwLock::new(HashMap::new()), - }, - } - }); - let driver_resource = driver - .bindings - .cloudlet_driver_bridge() - .generic_driver() - .call_constructor(&mut store, cloud_identifier)?; - driver - .handle - .lock() - .unwrap() - .replace(WasmDriverHandle::new(store, driver_resource)); - Ok(driver) - } - - pub fn load_all( - cloud_identifier: &str, - drivers: &mut Vec>, - ) -> WasmConfig { - // Check if cache configuration exists - { - let engine_config_file = Storage::get_configs_folder().join(ENGINE_CONFIG_FILE); - if !engine_config_file.exists() { - fs::write(&engine_config_file, DEFAULT_ENGINE_CONFIG).unwrap_or_else(|error| { - warn!( - "Failed to create default wasmtime configuration file: {}", - &error - ) - }); - } - } - let config_file = Storage::get_configs_folder().join(CONFIG_FILE); - if !config_file.exists() { - fs::write(&config_file, DEFAULT_CONFIG).unwrap_or_else(|error| { - warn!( - "Failed to create default wasm configuration file: {}", - &error - ) - }); - } - - let config = WasmConfig::load_from_file(&config_file).unwrap_or_else(|error| { - warn!( - "Failed to load wasm configuration file: {}", - &error - ); - WasmConfig::default() - }); - - let old_loaded = drivers.len(); - - let drivers_directory = Storage::get_drivers_folder(); - if !drivers_directory.exists() { - fs::create_dir_all(&drivers_directory).unwrap_or_else(|error| { - warn!( - "Failed to create drivers directory: {}", - &error - ) - }); - } - - let entries = match fs::read_dir(&drivers_directory) { - Ok(entries) => entries, - Err(error) => { - error!( - "Failed to read driver directory: {}", - &error - ); - return config; - } - }; - - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - error!("Failed to read driver entry: {}", &error); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() - || !path - .file_name() - .unwrap() - .to_string_lossy() - .ends_with(".wasm") - { - continue; - } - - let name = path.file_stem().unwrap().to_string_lossy().to_string(); - let source = match Source::from_file(&path) { - Ok(source) => source, - Err(error) => { - error!( - "Failed to read source code for driver {} from file({:?}): {}", - &name, - &path, - &error - ); - continue; - } - }; - - info!("Compiling driver {}...", &name); - let driver = WasmDriver::new(&config, cloud_identifier, &name, &source); - match driver { - Ok(driver) => match driver.init() { - Ok(info) => { - if info.ready { - info!( - "Loaded driver {} v{} by {}", - &driver.name, &info.version, - &info.authors.join(", ") - ); - drivers.push(driver); - } else { - warn!( - "Driver {} marked itself as not ready, skipping...", - &driver.name - ); - } - } - Err(error) => error!("Failed to load driver {}: {}", &name, &error), - }, - Err(error) => error!( - "Failed to compile driver {} at location {}: {}", - &name, - &source, - &error - ), - } - } - - if old_loaded == drivers.len() { - warn!( - "The Wasm driver feature is enabled, but no Wasm drivers were loaded." - ); - } - config - } -} - -impl From for Information { - fn from(val: bridge::Information) -> Self { - Information { - authors: val.authors, - version: val.version, - ready: val.ready, - } - } -} - -impl From<&Capabilities> for bridge::Capabilities { - fn from(val: &Capabilities) -> Self { - bridge::Capabilities { - memory: val.memory, - max_allocations: val.max_allocations, - child: val.child.clone(), - } - } -} - -impl From<&RemoteController> for bridge::RemoteController { - fn from(val: &RemoteController) -> Self { - bridge::RemoteController { - address: val.address.to_string(), - } - } -} - -impl From<&HostAndPort> for bridge::Address { - fn from(val: &HostAndPort) -> Self { - bridge::Address { - host: val.host.clone(), - port: val.port, - } - } -} diff --git a/controller/src/application/driver/wasm/cloudlet.rs b/controller/src/application/driver/wasm/cloudlet.rs deleted file mode 100644 index 686f0a13..00000000 --- a/controller/src/application/driver/wasm/cloudlet.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::sync::{Arc, Weak}; - -use anyhow::{anyhow, Result}; -use wasmtime::component::ResourceAny; - -use crate::application::{ - auth::AuthUnit, - cloudlet::{Allocation, HostAndPort}, - driver::GenericCloudlet, - unit::{ - KeyValue, Resources, Retention, Spec, StartRequest, StartRequestHandle, Unit, UnitHandle, - }, -}; - -use super::{ - generated::exports::cloudlet::driver::bridge::{self, Address}, - WasmDriver, -}; - -pub struct WasmCloudlet { - pub handle: Weak, - pub resource: ResourceAny, // This is delete if the handle is dropped -} - -impl GenericCloudlet for WasmCloudlet { - fn tick(&self) -> Result<()> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - match driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_tick(store, self.resource) - { - Ok(result) => result.map_err(|errors| { - anyhow!(errors - .iter() - .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) - .collect::>() - .join("\n")) - }), - Err(error) => Err(error), - } - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } - - fn allocate_addresses(&self, request: &StartRequestHandle) -> Result> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - match driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_allocate_addresses(store, self.resource, &(request.into())) - { - Ok(Ok(addresses)) => addresses - .into_iter() - .map(|address| Ok(HostAndPort::new(address.host, address.port))) - .collect::>>(), - Ok(Err(error)) => Err(anyhow!(error)), - Err(error) => Err(error), - } - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } - - fn deallocate_addresses(&self, addresses: Vec) -> Result<()> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_deallocate_addresses( - store, - self.resource, - &addresses - .iter() - .map(|address| address.into()) - .collect::>(), - ) - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } - - fn start_unit(&self, unit: &UnitHandle) -> Result<()> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_start_unit(store, self.resource, &unit.into()) - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } - - fn restart_unit(&self, unit: &UnitHandle) -> Result<()> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_restart_unit(store, self.resource, &unit.into()) - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } - - fn stop_unit(&self, unit: &UnitHandle) -> Result<()> { - if let Some(driver) = self.handle.upgrade() { - let mut handle = driver.handle.lock().unwrap(); - let (_, store) = WasmDriver::get_resource_and_store(&mut handle); - driver - .bindings - .cloudlet_driver_bridge() - .generic_cloudlet() - .call_stop_unit(store, self.resource, &unit.into()) - } else { - Err(anyhow!("Failed to get handle to wasm driver")) - } - } -} - -impl From<&KeyValue> for bridge::KeyValue { - fn from(val: &KeyValue) -> Self { - bridge::KeyValue { - key: val.key.clone(), - value: val.value.clone(), - } - } -} - -impl From<&Retention> for bridge::Retention { - fn from(val: &Retention) -> Self { - match val { - Retention::Permanent => bridge::Retention::Permanent, - Retention::Temporary => bridge::Retention::Temporary, - } - } -} - -impl From<&Spec> for bridge::Spec { - fn from(val: &Spec) -> Self { - bridge::Spec { - settings: val.settings.iter().map(|setting| setting.into()).collect(), - environment: val.environment.iter().map(|env| env.into()).collect(), - disk_retention: (&val.disk_retention).into(), - image: val.image.clone(), - } - } -} - -impl From for bridge::Resources { - fn from(val: Resources) -> Self { - bridge::Resources { - memory: val.memory, - swap: val.swap, - cpu: val.cpu, - io: val.io, - disk: val.disk, - addresses: val.addresses, - } - } -} - -impl From> for bridge::Allocation { - fn from(val: Arc) -> Self { - bridge::Allocation { - addresses: val.addresses.iter().map(|address| address.into()).collect(), - resources: val.resources.clone().into(), - spec: (&val.spec).into(), - } - } -} - -impl From> for bridge::Auth { - fn from(val: Arc) -> Self { - bridge::Auth { - token: val.token.clone(), - } - } -} - -impl From<&Arc> for bridge::Unit { - fn from(val: &Arc) -> Self { - bridge::Unit { - name: val.name.clone(), - uuid: val.uuid.to_string(), - deployment: val.deployment.as_ref().map(|deployment| { - deployment - .deployment - .upgrade() - .expect("Deployment dropped while units of the deployment are still active") - .name - .clone() - }), - allocation: val.allocation.clone().into(), - auth: val.auth.clone().into(), - } - } -} - -impl From<&Arc> for bridge::UnitProposal { - fn from(val: &Arc) -> Self { - bridge::UnitProposal { - name: val.name.clone(), - deployment: val.deployment.as_ref().map(|deployment| { - deployment - .deployment - .upgrade() - .expect("Deployment dropped while units of the deployment are still active") - .name - .clone() - }), - resources: val.resources.clone().into(), - spec: (&val.spec).into(), - } - } -} diff --git a/controller/src/application/driver/wasm/config.rs b/controller/src/application/driver/wasm/config.rs deleted file mode 100644 index e833614f..00000000 --- a/controller/src/application/driver/wasm/config.rs +++ /dev/null @@ -1,64 +0,0 @@ -use common::config::{LoadFromTomlFile, SaveToTomlFile}; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use simplelog::warn; - -#[derive(Serialize, Deserialize, Default)] -pub struct WasmConfig { - pub drivers: Vec, -} - -impl LoadFromTomlFile for WasmConfig {} -impl SaveToTomlFile for WasmConfig {} - -impl WasmConfig { - pub fn get_config(&self, name: &str) -> Option<&DriverConfig> { - self.drivers - .iter() - .find(|driver| match Regex::new(&driver.name) { - Ok(regex) => regex.is_match(name), - Err(error) => { - warn!("Failed to compile driver name regex: {}", error); - false - } - }) - } -} - -#[derive(Serialize, Deserialize)] -pub struct DriverConfig { - pub name: String, - pub inherit_stdio: bool, - pub inherit_args: bool, - pub inherit_env: bool, - pub inherit_network: bool, - pub allow_ip_name_lookup: bool, - pub allow_http: bool, - pub allow_process: bool, - pub allow_remove_dir_all: bool, - - pub mounts: Vec, -} - -impl Default for DriverConfig { - fn default() -> Self { - Self { - name: String::new(), - inherit_stdio: true, - inherit_args: true, - inherit_env: true, - inherit_network: true, - allow_ip_name_lookup: true, - allow_http: true, - allow_process: true, - allow_remove_dir_all: true, - mounts: Vec::new(), - } - } -} - -#[derive(Serialize, Deserialize)] -pub struct MountConfig { - pub host: String, - pub guest: String, -} diff --git a/controller/src/application/driver/wasm/file.rs b/controller/src/application/driver/wasm/file.rs deleted file mode 100644 index c548d9c1..00000000 --- a/controller/src/application/driver/wasm/file.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::fs; - -use super::{ - generated::cloudlet::driver::{ - self, - types::{Directory, ErrorMessage}, - }, - WasmDriverState, -}; - -impl driver::file::Host for WasmDriverState { - fn remove_dir_all(&mut self, directory: Directory) -> Result<(), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let path = self.get_directory(&driver.name, &directory)?; - fs::remove_dir_all(path).map_err(|error| format!("Failed to remove directory: {}", error)) - } -} diff --git a/controller/src/application/driver/wasm/http.rs b/controller/src/application/driver/wasm/http.rs deleted file mode 100644 index b1a866a9..00000000 --- a/controller/src/application/driver/wasm/http.rs +++ /dev/null @@ -1,57 +0,0 @@ -use simplelog::warn; - -use super::{ - generated::cloudlet::driver::{ - self, - http::{Header, Method, Response}, - }, - WasmDriverState, -}; - -impl driver::http::Host for WasmDriverState { - fn send_http_request( - &mut self, - method: Method, - url: String, - headers: Vec
, - body: Option>, - ) -> Option { - let driver = self.handle.upgrade().unwrap(); - let mut request = match method { - Method::Get => minreq::get(url), - Method::Patch => minreq::patch(url), - Method::Post => minreq::post(url), - Method::Put => minreq::put(url), - Method::Delete => minreq::delete(url), - }; - if let Some(body) = body { - request = request.with_body(body); - } - for header in headers { - request = request.with_header(&header.key, &header.value); - } - let response = match request.send() { - Ok(response) => response, - Err(error) => { - warn!( - "Failed to send HTTP request for driver {}: {}", - &driver.name, error - ); - return None; - } - }; - Some(Response { - status_code: response.status_code as u32, - reason_phrase: response.reason_phrase.clone(), - headers: response - .headers - .iter() - .map(|header| Header { - key: header.0.clone(), - value: header.1.clone(), - }) - .collect(), - bytes: response.into_bytes(), - }) - } -} diff --git a/controller/src/application/driver/wasm/log.rs b/controller/src/application/driver/wasm/log.rs deleted file mode 100644 index b52782b5..00000000 --- a/controller/src/application/driver/wasm/log.rs +++ /dev/null @@ -1,33 +0,0 @@ -use simplelog::{debug, error, info, warn}; - -use super::{ - generated::cloudlet::driver::{self, log::Level}, - WasmDriverState, -}; - -impl driver::log::Host for WasmDriverState { - fn log_string(&mut self, level: Level, message: String) { - match level { - Level::Info => info!( - "[{}] {}", - &self.handle.upgrade().unwrap().name.to_uppercase(), - message - ), - Level::Warn => warn!( - "[{}] {}", - &self.handle.upgrade().unwrap().name.to_uppercase(), - message - ), - Level::Error => error!( - "[{}] {}", - &self.handle.upgrade().unwrap().name.to_uppercase(), - message - ), - Level::Debug => debug!( - "[{}] {}", - &self.handle.upgrade().unwrap().name.to_uppercase(), - message - ), - } - } -} diff --git a/controller/src/application/driver/wasm/platform.rs b/controller/src/application/driver/wasm/platform.rs deleted file mode 100644 index 0b328f31..00000000 --- a/controller/src/application/driver/wasm/platform.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::{ - generated::cloudlet::driver::{self, platform::Os}, - WasmDriverState, -}; - -impl driver::platform::Host for WasmDriverState { - fn get_os(&mut self) -> Os { - if cfg!(target_os = "windows") { - Os::Windows - } else { - Os::Unix - } - } -} diff --git a/controller/src/application/driver/wasm/process.rs b/controller/src/application/driver/wasm/process.rs deleted file mode 100644 index 6bee2444..00000000 --- a/controller/src/application/driver/wasm/process.rs +++ /dev/null @@ -1,339 +0,0 @@ -use std::{ - collections::HashMap, - io::{BufRead, Read, Write}, - path::PathBuf, - process::{Command, Stdio}, -}; - -use simplelog::debug; - -use crate::{ - application::driver::process::{DriverProcess, ProcessStream}, - storage::Storage, -}; - -use super::{ - generated::cloudlet::driver::{ - self, - process::{Directory, KeyValue, ReaderMode, StdReader}, - types::{ErrorMessage, Reference}, - }, - WasmDriverState, -}; - -impl driver::process::Host for WasmDriverState { - fn spawn_process( - &mut self, - command: String, - args: Vec, - environment: Vec, - directory: Directory, - mode: ReaderMode, - ) -> Result { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let process_dir = self.get_directory(&driver.name, &directory)?; - let environment: HashMap<_, _> = environment - .into_iter() - .map(|kv| (kv.key, kv.value)) - .collect(); - - debug!("Spawning process: {} {:?}", command, args); - let mut command = Command::new(command); - command - .args(args) - .current_dir(process_dir) - .envs(environment) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - let process = command - .spawn() - .map_err(|e| format!("Failed to spawn process: {}", e))?; - let pid = process.id(); - - driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")? - .insert( - pid, - DriverProcess::new( - process, - match mode { - ReaderMode::Direct => true, - ReaderMode::Async => false, - }, - ) - .map_err(|error| error.to_string())?, - ); - - Ok(pid) - } - - fn kill_process(&mut self, pid: u32) -> Result<(), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - debug!("Killing process: {}", pid); - if let Some(mut process) = processes.remove(&pid) { - process - .get_process() - .kill() - .map_err(|e| format!("Failed to kill process: {}", e)) - } else { - Ok(()) - } - } - - fn drop_process(&mut self, pid: u32) -> Result { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - debug!("Dropping process: {}", pid); - Ok(processes.remove(&pid).is_some()) - } - - fn try_wait(&mut self, pid: u32) -> Result, ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - process - .get_process() - .try_wait() - .map_err(|e| format!("Failed to wait for process: {}", e)) - .map(|status| status.and_then(|s| s.code())) - } else { - Ok(None) - } - } - - fn read_line_direct( - &mut self, - pid: u32, - std: StdReader, - ) -> Result<(u32, String), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - let mut buffer = String::new(); - let bytes = match std { - StdReader::Stdout => match process.get_stdout() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read_line(&mut buffer), - StdReader::Stderr => match process.get_stderr() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read_line(&mut buffer), - } - .map_err(|error| format!("Failed to read from process: {}", error))?; - Ok((bytes as u32, buffer)) - } else { - Err("Process does not exist".to_string()) - } - } - - fn has_data_left_direct(&mut self, pid: u32, std: StdReader) -> Result { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - let has_data = match std { - StdReader::Stdout => match process.get_stdout() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .has_data_left(), - StdReader::Stderr => match process.get_stderr() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .has_data_left(), - } - .map_err(|error| format!("Failed to check buffer of process: {}", error))?; - Ok(has_data) - } else { - Err("Process does not exist".to_string()) - } - } - - fn read_direct( - &mut self, - pid: u32, - buf_size: u32, - std: StdReader, - ) -> Result<(u32, Vec), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - let mut buffer = Vec::with_capacity(buf_size as usize); - let bytes = match std { - StdReader::Stdout => match process.get_stdout() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read(&mut buffer), - StdReader::Stderr => match process.get_stderr() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read(&mut buffer), - } - .map_err(|e| format!("Failed to read from process: {}", e))?; - Ok((bytes as u32, buffer)) - } else { - Err("Process does not exist".to_string()) - } - } - - fn read_to_end_direct( - &mut self, - pid: u32, - std: StdReader, - ) -> Result<(u32, Vec), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - let mut buffer = Vec::new(); - let bytes = match std { - StdReader::Stdout => match process.get_stdout() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read_to_end(&mut buffer), - StdReader::Stderr => match process.get_stderr() { - ProcessStream::Direct(stream) => stream, - ProcessStream::Async(_) => { - return Err("Cannot read from stream that is handeled async".to_string()) - } - } - .read_to_end(&mut buffer), - } - .map_err(|e| format!("Failed to read from process: {}", e))?; - Ok((bytes as u32, buffer)) - } else { - Err("Process does not exist".to_string()) - } - } - - fn read_line_async( - &mut self, - pid: u32, - std: StdReader, - ) -> Result, ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - Ok(match std { - StdReader::Stdout => match process.get_stdout() { - ProcessStream::Direct(_) => { - return Err("Cannot read from stream that is handeled directly".to_string()) - } - ProcessStream::Async(stream) => stream, - } - .try_recv(), - StdReader::Stderr => match process.get_stderr() { - ProcessStream::Direct(_) => { - return Err("Cannot read from stream that is handeled directly".to_string()) - } - ProcessStream::Async(stream) => stream, - } - .try_recv(), - }) - } else { - Err("Process does not exist".to_string()) - } - } - - fn write_stdin(&mut self, pid: u32, data: Vec) -> Result<(), ErrorMessage> { - let driver = self.handle.upgrade().ok_or("Failed to upgrade handle")?; - let mut processes = driver - .data - .processes - .write() - .map_err(|_| "Failed to acquire write lock on processes")?; - - if let Some(process) = processes.get_mut(&pid) { - process - .get_stdin() - .write_all(&data) - .map_err(|e| format!("Failed to write to stdin of process: {}", e))?; - Ok(()) - } else { - Err("Process does not exist".to_string()) - } - } -} - -impl WasmDriverState { - pub fn get_directory( - &self, - driver_name: &str, - directory: &Directory, - ) -> Result { - match &directory.reference { - Reference::Controller => Ok(PathBuf::from(".").join(&directory.path)), - Reference::Data => { - Ok(Storage::get_data_folder_for_driver(driver_name).join(&directory.path)) - } - Reference::Configs => { - Ok(Storage::get_config_folder_for_driver(driver_name).join(&directory.path)) - } - } - } -} diff --git a/controller/src/application/event.rs b/controller/src/application/event.rs deleted file mode 100644 index d5acd787..00000000 --- a/controller/src/application/event.rs +++ /dev/null @@ -1,151 +0,0 @@ -use simplelog::debug; -use uuid::Uuid; - -use super::unit::{UnitHandle, WeakUnitHandle}; - -use std::{ - any::Any, - collections::HashMap, - fmt::Debug, - hash::Hash, - sync::{Arc, RwLock}, -}; - -pub mod channel; -pub mod transfer; - -#[derive(Eq, PartialEq)] -pub enum EventKey { - Channel(String), - Transfer(Uuid), - //Custom(TypeId), -} - -pub trait Event: Any + Send + Sync + Debug {} - -pub type EventListener = Box; - -struct RegisteredListener { - unit: Option, - listener: Box, -} - -pub struct EventBus { - listeners: RwLock>>, -} - -impl EventBus { - pub fn new() -> Self { - Self { - listeners: RwLock::new(HashMap::new()), - } - } - - /*pub fn register_listener(&self, key: EventKey, listener: EventListener) { - let registered_listener = RegisteredListener { - unit: None, - listener: Box::new(listener), - }; - self.listeners - .write() - .unwrap() - .entry(key) - .or_default() - .push(registered_listener); - }*/ - - pub fn register_listener_under_unit( - &self, - key: EventKey, - unit: WeakUnitHandle, - listener: EventListener, - ) { - let registered_listener = RegisteredListener { - unit: Some(unit), - listener: Box::new(listener), - }; - self.listeners - .write() - .unwrap() - .entry(key) - .or_default() - .push(registered_listener); - } - - pub fn unregister_listener(&self, key: EventKey, unit: &UnitHandle) { - let mut listeners = self.listeners.write().unwrap(); - if let Some(registered_listeners) = listeners.get_mut(&key) { - registered_listeners.retain(|registered_listener| { - if let Some(weak_unit) = ®istered_listener.unit { - if let Some(strong_unit) = weak_unit.upgrade() { - if Arc::ptr_eq(unit, &strong_unit) { - return false; - } - } else { - return false; // Unit is dead - } - } - true - }); - } - } - - pub fn cleanup_unit(&self, unit: &UnitHandle) { - let mut listeners = self.listeners.write().unwrap(); - for (_, registered_listeners) in listeners.iter_mut() { - registered_listeners.retain(|registered_listener| { - if let Some(weak_unit) = ®istered_listener.unit { - if let Some(strong_unit) = weak_unit.upgrade() { - if Arc::ptr_eq(unit, &strong_unit) { - return false; - } - } else { - return false; // Unit is dead - } - } - true - }); - } - } - - pub fn dispatch(&self, key: &EventKey, event: &E) -> u32 { - debug!("[EVENTS] Dispatching event: {:?}", event); - - let mut count = 0; - let listeners = self.listeners.read().unwrap(); - if let Some(registered_listeners) = listeners.get(key) { - for registered_listener in registered_listeners { - if let Some(listener) = registered_listener - .listener - .downcast_ref::>() - { - listener(event); - count += 1; - } - } - } - count - } - - /*pub fn dispatch_custom(&self, event: &E) -> u32 { - self.dispatch(&EventKey::Custom(TypeId::of::()), event) - }*/ -} - -impl Hash for EventKey { - fn hash(&self, state: &mut H) { - match self { - EventKey::Channel(channel) => { - state.write_u8(0); - channel.hash(state); - } - EventKey::Transfer(unit) => { - state.write_u8(1); - unit.hash(state); - } /*EventKey::Custom(type_id) => { - state.write_u8(2); - type_id.hash(state); - }*/ - } - } -} diff --git a/controller/src/application/event/channel.rs b/controller/src/application/event/channel.rs deleted file mode 100644 index 3d1dcced..00000000 --- a/controller/src/application/event/channel.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::network::unit::proto::channel_management::ChannelMessageValue; - -use super::Event; - -#[derive(Debug)] -pub struct ChannelMessageSended { - pub message: ChannelMessageValue, -} - -impl Event for ChannelMessageSended {} diff --git a/controller/src/application/event/transfer.rs b/controller/src/application/event/transfer.rs deleted file mode 100644 index 2e319e6a..00000000 --- a/controller/src/application/event/transfer.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::application::user::transfer::Transfer; - -use super::Event; - -#[derive(Debug)] -pub struct UserTransferRequested { - pub transfer: Transfer, -} - -impl Event for UserTransferRequested {} diff --git a/controller/src/application/unit.rs b/controller/src/application/unit.rs deleted file mode 100644 index 96b7bb95..00000000 --- a/controller/src/application/unit.rs +++ /dev/null @@ -1,560 +0,0 @@ -use std::{ - collections::{HashMap, VecDeque}, - sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, - Arc, RwLock, RwLockReadGuard, Weak, - }, - time::{Duration, Instant}, -}; - -use serde::{Deserialize, Serialize}; -use simplelog::{debug, error, info, warn}; -use uuid::Uuid; - -use super::{ - auth::AuthUnitHandle, - cloudlet::{AllocationHandle, CloudletHandle, WeakCloudletHandle}, - deployment::WeakDeploymentHandle, - ControllerHandle, WeakControllerHandle, -}; - -pub type UnitHandle = Arc; -pub type WeakUnitHandle = Weak; -pub type StartRequestHandle = Arc; - -pub struct Units { - controller: WeakControllerHandle, - - /* Units started by this atomic cloud instance */ - units: RwLock>, - - /* Units that should be started/stopped next controller tick */ - start_requests: RwLock>, - stop_requests: RwLock>, -} - -impl Units { - pub fn new(controller: WeakControllerHandle) -> Self { - Self { - controller, - units: RwLock::new(HashMap::new()), - start_requests: RwLock::new(VecDeque::new()), - stop_requests: RwLock::new(VecDeque::new()), - } - } - - pub fn tick(&self) { - // Get Controller handle - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller"); - - // Check health of units - { - let dead_units = self.units.read().unwrap().values().filter(|unit| { - let health = unit.health.read().unwrap(); - if health.is_dead() { - match *unit.state.read().unwrap() { - State::Starting | State::Restarting => { - warn!("Unit {} failed to establish online status within the expected startup time of {:.2?}.", unit.name, controller.configuration.timings.restart.unwrap()); - } - _ => { - warn!("Unit {} has not checked in for {:.2?}, indicating a potential error.", unit.name, health.timeout); - } - } - true - } else { - false - } - }).cloned().collect::>(); - for unit in dead_units { - self.restart_unit(&unit); - } - } - - // Stop all units that have to be stopped - { - let mut requests = self.stop_requests.write().unwrap(); - requests.retain(|request| { - if let Some(when) = request.when { - if when > Instant::now() { - return true; - } - } - - self.stop_unit_nolock(request, &mut self.units.write().unwrap()); - false - }); - } - - // Sort requests by priority and process them - { - let mut requests = self.start_requests.write().unwrap(); - { - let contiguous = requests.make_contiguous(); - contiguous.sort_unstable_by_key(|req| req.priority); - contiguous.reverse(); - } - requests.retain(|request| { - if request.canceled.load(Ordering::Relaxed) { - debug!( - "Canceled start of unit {}", - request.name - ); - return false; - } - - if let Some(when) = request.when { - if when > Instant::now() { - return true; - } - } - - if request.cloudlets.is_empty() { - warn!( - "Failed to allocate resources for unit {} because no cloudlets were specified", - request.name - ); - return true; - } - - // Collect and sort cloudlets by the number of allocations - for cloudlet in &request.cloudlets { - let cloudlet = cloudlet.upgrade().unwrap(); - // Try to allocate resources on cloudlets - if let Ok(allocation) = cloudlet.allocate(request) { - // Start unit on the cloudlet - self.start_unit(request, allocation, &cloudlet); - return false; - } - } - warn!( - "Failed to allocate resources for unit {}", - request.name - ); - true - }); - } - } - - pub fn queue_unit(&self, request: StartRequest) -> StartRequestHandle { - let arc = Arc::new(request); - self.start_requests.write().unwrap().push_back(arc.clone()); - arc - } - - pub fn stop_all_instant(&self) { - self.units.write().unwrap().drain().for_each(|(_, unit)| { - self.stop_unit_internal(&StopRequest { when: None, unit }); - }); - } - - pub fn stop_all_on_cloudlet(&self, cloudlet: &CloudletHandle) { - self.units - .read() - .unwrap() - .values() - .filter(|unit| Arc::ptr_eq(&unit.cloudlet.upgrade().unwrap(), cloudlet)) - .for_each(|unit| { - self.stop_unit_now(unit.clone()); - }); - } - - fn stop_unit_nolock(&self, request: &StopRequest, units: &mut HashMap) { - self.stop_unit_internal(request); - units.remove(&request.unit.uuid); - } - - fn stop_unit_internal(&self, request: &StopRequest) { - let unit = &request.unit; - info!("Stopping unit {}", unit.name); - - // Remove resources allocated by unit from cloudlet - if let Some(cloudlet) = unit.cloudlet.upgrade() { - cloudlet.deallocate(&unit.allocation); - } - - // Send start request to cloudlet - // We do this async because the driver chould be running blocking code like network requests - let controller = self - .controller - .upgrade() - .expect("The controller is dead while still running code that requires it"); - { - let unit = unit.clone(); - controller - .get_runtime() - .as_ref() - .unwrap() - .spawn_blocking(move || stop_thread(unit)); - } - - // Remove unit from deployment and units list - if let Some(deployment) = &unit.deployment { - deployment.remove_unit(unit); - } - if let Some(controller) = self.controller.upgrade() { - controller.get_auth().unregister_unit(unit); - } - - // Remove users connected to the unit - controller.get_users().cleanup_users(unit); - // Remove subscribers from channels - controller.get_event_bus().cleanup_unit(unit); - - fn stop_thread(unit: UnitHandle) { - if let Some(cloudlet) = unit.cloudlet.upgrade() { - if let Err(error) = cloudlet.get_inner().stop_unit(&unit) { - error!( - "Failed to stop unit {}: {}", - unit.name, error - ); - } - } - } - } - - pub fn stop_unit_now(&self, unit: UnitHandle) { - self.stop_requests - .write() - .unwrap() - .push_back(StopRequest { when: None, unit }); - } - - pub fn _stop_unit(&self, when: Instant, unit: UnitHandle) { - self.stop_requests.write().unwrap().push_back(StopRequest { - when: Some(when), - unit, - }); - } - - pub fn restart_unit(&self, unit: &UnitHandle) { - info!("Restarting unit {}", unit.name); - - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller"); - - *unit.state.write().unwrap() = State::Restarting; - *unit.health.write().unwrap() = Health::new( - controller.configuration.timings.restart.unwrap(), - controller.configuration.timings.healthbeat.unwrap(), - ); - - // Send restart request to cloudlet - // We do this async because the driver chould be running blocking code like network requests - if let Some(controller) = self.controller.upgrade() { - let unit = unit.clone(); - let copy = controller.clone(); - controller - .get_runtime() - .as_ref() - .unwrap() - .spawn_blocking(move || restart_thread(copy, unit)); - } - - fn restart_thread(controller: ControllerHandle, unit: UnitHandle) { - if let Some(cloudlet) = unit.cloudlet.upgrade() { - if let Err(error) = &cloudlet.get_inner().restart_unit(&unit) { - error!( - "Failed to restart unit {}: {}", - unit.name, error - ); - controller.get_units().stop_unit_now(unit); - } - } - } - } - - pub fn handle_heart_beat(&self, unit: &UnitHandle) { - debug!("Received heartbeat from unit {}", &unit.name); - - // Reset health - unit.health.write().unwrap().reset(); - - // Check were the unit is in the state machine - let mut state = unit.state.write().unwrap(); - if *state == State::Starting || *state == State::Restarting { - *state = State::Preparing; - info!("The unit {} is now loading", unit.name); - } - } - - pub fn mark_ready(&self, unit: &UnitHandle) { - if !unit.rediness.load(Ordering::Relaxed) { - debug!("The unit {} is ready", unit.name); - unit.rediness.store(true, Ordering::Relaxed); - } - } - - pub fn mark_not_ready(&self, unit: &UnitHandle) { - if unit.rediness.load(Ordering::Relaxed) { - debug!("The unit {} is no longer ready", unit.name); - unit.rediness.store(false, Ordering::Relaxed); - } - } - - pub fn mark_running(&self, unit: &UnitHandle) { - let mut state = unit.state.write().unwrap(); - if *state == State::Preparing { - info!("The unit {} is now running", unit.name); - *state = State::Running; - } - } - - pub fn checked_unit_stop(&self, unit: &UnitHandle) { - let mut state = unit.state.write().unwrap(); - if *state != State::Stopping { - self.mark_not_ready(unit); - *state = State::Stopping; - self.stop_unit_now(unit.clone()); - } - } - - pub fn find_fallback_unit(&self, excluded: &UnitHandle) -> Option { - // TODO: Also check if the unit have free slots - self.units - .read() - .unwrap() - .values() - .filter(|unit| { - !Arc::ptr_eq(unit, excluded) - && unit.allocation.spec.fallback.enabled - && unit.rediness.load(Ordering::Relaxed) - && *unit.state.read().unwrap() == State::Running - }) - .max_by_key(|unit| unit.allocation.spec.fallback.priority) - .cloned() - } - - pub fn get_unit(&self, uuid: Uuid) -> Option { - self.units.read().unwrap().get(&uuid).cloned() - } - - pub fn get_units(&self) -> RwLockReadGuard> { - self.units.read().expect("Failed to lock units") - } - - fn start_unit( - &self, - request: &StartRequestHandle, - allocation: AllocationHandle, - cloudlet: &CloudletHandle, - ) { - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller"); - - info!( - "Spinning up unit {} on cloudlet {} listening on port {}", - request.name, - cloudlet.name, - allocation.primary_address().to_string() - ); - let unit = Arc::new_cyclic(|handle| { - // Create a token for the unit - let auth = self - .controller - .upgrade() - .expect("WAIT. We are creating a unit while the controller is dead?") - .get_auth() - .register_unit(handle.clone()); - - Unit { - name: request.name.clone(), - uuid: Uuid::new_v4(), - deployment: request.deployment.clone(), - cloudlet: Arc::downgrade(cloudlet), - allocation, - connected_users: AtomicU32::new(0), - auth, - health: RwLock::new(Health::new( - controller.configuration.timings.startup.unwrap(), - controller.configuration.timings.healthbeat.unwrap(), - )), - state: RwLock::new(State::Starting), - rediness: AtomicBool::new(false), - flags: Flags { - stop: RwLock::new(None), - }, - } - }); - - if let Some(deployment) = &request.deployment { - deployment.set_active(unit.clone(), request); - } - self.units.write().unwrap().insert(unit.uuid, unit.clone()); - - // Print unit information to the console for debugging - debug!("-----------------------------------"); - debug!("New unit added to controller"); - debug!("Name: {}", unit.name); - debug!("UUID: {}", unit.uuid.to_string()); - debug!("Token: {}", unit.auth.token); - debug!("-----------------------------------"); - - // Send start request to cloudlet - // We do this async because the driver chould be running blocking code like network requests - if let Some(controller) = self.controller.upgrade() { - let copy = controller.clone(); - controller - .get_runtime() - .as_ref() - .unwrap() - .spawn_blocking(move || start_thread(copy, unit)); - } - - fn start_thread(controller: ControllerHandle, unit: UnitHandle) { - if let Some(cloudlet) = unit.cloudlet.upgrade() { - if let Err(error) = cloudlet.get_inner().start_unit(&unit) { - error!( - "Failed to start unit {}: {}", - unit.name, error - ); - controller.get_units().stop_unit_now(unit); - } - } - } - } -} - -pub struct Unit { - pub name: String, - pub uuid: Uuid, - pub deployment: Option, - pub cloudlet: WeakCloudletHandle, - pub allocation: AllocationHandle, - - /* Users */ - pub connected_users: AtomicU32, - - /* Auth */ - pub auth: AuthUnitHandle, - - /* Health and State of the unit */ - pub health: RwLock, - pub state: RwLock, - pub flags: Flags, - pub rediness: AtomicBool, -} - -impl Unit { - pub fn get_user_count(&self) -> u32 { - self.connected_users.load(Ordering::Relaxed) - } -} - -pub struct Health { - pub next_checkin: Instant, - pub timeout: Duration, -} - -impl Health { - pub fn new(startup_time: Duration, timeout: Duration) -> Self { - Self { - next_checkin: Instant::now() + startup_time, - timeout, - } - } - pub fn reset(&mut self) { - self.next_checkin = Instant::now() + self.timeout; - } - pub fn is_dead(&self) -> bool { - Instant::now() > self.next_checkin - } -} - -pub struct StartRequest { - pub canceled: AtomicBool, - pub when: Option, - pub name: String, - pub deployment: Option, - pub cloudlets: Vec, - pub resources: Resources, - pub spec: Spec, - pub priority: i32, -} - -pub struct StopRequest { - pub when: Option, - pub unit: UnitHandle, -} - -#[derive(PartialEq, Clone)] -pub enum State { - Starting, - Preparing, - Restarting, - Running, - Stopping, -} - -pub struct Flags { - /* Required for the deployment system */ - pub stop: RwLock>, -} - -#[derive(Clone)] -pub struct DeploymentRef { - pub unit_id: usize, - pub deployment: WeakDeploymentHandle, -} - -impl DeploymentRef { - pub fn remove_unit(&self, unit: &UnitHandle) { - if let Some(deployment) = self.deployment.upgrade() { - deployment.remove_unit(unit); - } - } - - pub fn set_active(&self, unit: UnitHandle, request: &StartRequestHandle) { - if let Some(deployment) = self.deployment.upgrade() { - deployment.set_unit_active(unit, request); - } - } -} - -#[derive(Serialize, Deserialize, Default, Clone)] -pub struct Resources { - pub memory: u32, - pub swap: u32, - pub cpu: u32, - pub io: u32, - pub disk: u32, - pub addresses: u32, -} - -#[derive(Serialize, Deserialize, Clone, Default)] -pub enum Retention { - #[serde(rename = "temporary")] - #[default] - Temporary, - #[serde(rename = "permanent")] - Permanent, -} - -#[derive(Serialize, Deserialize, Clone, Default)] -pub struct FallbackPolicy { - pub enabled: bool, - pub priority: i32, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct KeyValue { - pub key: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Clone, Default)] -pub struct Spec { - pub settings: Vec, - pub environment: Vec, - pub disk_retention: Retention, - pub image: String, - - pub max_players: u32, - pub fallback: FallbackPolicy, -} diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs deleted file mode 100644 index f68f550d..00000000 --- a/controller/src/application/user.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::{ - collections::HashMap, - ops::Deref, - sync::{atomic::Ordering, Arc, RwLock, Weak}, - time::Instant, -}; - -use simplelog::{debug, info, warn}; -use transfer::Transfer; -use uuid::Uuid; - -use super::{ - unit::{UnitHandle, WeakUnitHandle}, - WeakControllerHandle, -}; - -pub mod transfer; - -pub type UserHandle = Arc; -pub type WeakUserHandle = Weak; - -pub struct Users { - controller: WeakControllerHandle, - - /* Users that joined some started unit */ - users: RwLock>, -} - -impl Users { - pub fn new(controller: WeakControllerHandle) -> Self { - Self { - controller, - users: RwLock::new(HashMap::new()), - } - } - - pub fn tick(&self) { - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller"); - - let mut users = self.users.write().unwrap(); - users.retain(|_, user| { - if let CurrentUnit::Transfering(transfer) = user.unit.read().unwrap().deref() { - if Instant::now().duration_since(transfer.timestamp) - > controller.configuration.timings.transfer.unwrap() - { - if let Some(to) = transfer.to.upgrade() { - warn!( - "User {}[{}] failed to transfer to unit {} in time", - user.name, - user.uuid.to_string(), - to.name - ); - } - return false; - } - } - true - }); - } - - pub fn handle_user_connected(&self, unit: UnitHandle, name: String, uuid: Uuid) { - // Update unit that the user is connected to - unit.connected_users.fetch_add(1, Ordering::Relaxed); - - // Update internal user list - let mut users = self.users.write().unwrap(); - if let Some(user) = users.get(&uuid) { - let mut current_unit = user.unit.write().unwrap(); - match current_unit.deref() { - CurrentUnit::Connected(_) => { - *current_unit = CurrentUnit::Connected(Arc::downgrade(&unit)); - warn!( - "User {}[{}] was never flagged as transferring but switched to unit {}", - name, - uuid.to_string(), - unit.name - ); - } - CurrentUnit::Transfering(_) => { - *current_unit = CurrentUnit::Connected(Arc::downgrade(&unit)); - info!( - "User {}[{}] successfully transferred to unit {}", - name, - uuid.to_string(), - unit.name - ); - } - } - } else { - info!( - "User {}[{}] connected to unit {}", - name, - uuid.to_string(), - unit.name - ); - users.insert(uuid, self.create_user(name, uuid, &unit)); - } - } - - pub fn handle_user_disconnected(&self, unit: UnitHandle, uuid: Uuid) { - // Update unit that the user was connected to - unit.connected_users.fetch_sub(1, Ordering::Relaxed); - - // Update internal user list - let mut users = self.users.write().unwrap(); - if let Some(user) = users.get(&uuid).cloned() { - if let CurrentUnit::Connected(weak_unit) = user.unit.read().unwrap().deref() { - if let Some(strong_unit) = weak_unit.upgrade() { - // Verify if the user is connected to the unit that is saying he is disconnecting - if Arc::ptr_eq(&strong_unit, &unit) { - info!( - "User {}[{}] disconnected from unit {}", - user.name, - user.uuid.to_string(), - strong_unit.name, - ); - users.remove(&user.uuid); - } - } - } - } - } - - pub fn cleanup_users(&self, dead_unit: &UnitHandle) -> u32 { - let mut amount = 0; - self.users.write().unwrap().retain(|_, user| { - if let CurrentUnit::Connected(weak_unit) = user.unit.read().unwrap().deref() { - if let Some(unit) = weak_unit.upgrade() { - if Arc::ptr_eq(&unit, dead_unit) { - info!( - "User {}[{}] disconnected from unit {}", - user.name, - user.uuid.to_string(), - unit.name, - ); - amount += 1; - return false; - } - } else { - debug!( - "User {}[{}] is connected to a dead unit removing him", - user.name, - user.uuid.to_string() - ); - amount += 1; - return false; - } - } - true - }); - amount - } - - pub fn get_users(&self) -> Vec { - self.users.read().unwrap().values().cloned().collect() - } - - pub fn _get_users_on_unit(&self, unit: &UnitHandle) -> Vec { - self.users - .read() - .unwrap() - .values() - .filter(|user| { - if let CurrentUnit::Connected(weak_unit) = user.unit.read().unwrap().deref() { - if let Some(strong_unit) = weak_unit.upgrade() { - return Arc::ptr_eq(&strong_unit, unit); - } - } - false - }) - .cloned() - .collect() - } - - pub fn get_user(&self, uuid: Uuid) -> Option { - self.users.read().unwrap().get(&uuid).cloned() - } - - fn create_user(&self, name: String, uuid: Uuid, unit: &UnitHandle) -> UserHandle { - Arc::new(User { - name, - uuid, - unit: RwLock::new(CurrentUnit::Connected(Arc::downgrade(unit))), - }) - } -} - -pub enum CurrentUnit { - Connected(WeakUnitHandle), - Transfering(Transfer), -} - -pub struct User { - pub name: String, - pub uuid: Uuid, - pub unit: RwLock, -} diff --git a/controller/src/application/user/transfer.rs b/controller/src/application/user/transfer.rs deleted file mode 100644 index 7b36cc27..00000000 --- a/controller/src/application/user/transfer.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::time::Instant; -use std::{ops::Deref, sync::Arc}; - -use simplelog::{error, info, warn}; - -use crate::application::deployment::DeploymentHandle; -use crate::application::{ - event::{transfer::UserTransferRequested, EventKey}, - unit::{UnitHandle, WeakUnitHandle}, -}; - -use super::{CurrentUnit, UserHandle, Users, WeakUserHandle}; - -impl Users { - pub fn resolve_transfer(&self, user: &UserHandle, target: &TransferTarget) -> Option { - let from = { - let unit = user.unit.read().unwrap(); - if let CurrentUnit::Connected(from) = unit.deref() { - from.clone() - } else { - return None; - } - }; - - match target { - TransferTarget::Unit(to) => { - return Some(Transfer::new( - Arc::downgrade(user), - from.clone(), - Arc::downgrade(to), - )); - } - TransferTarget::Deployment(deployment) => { - if let Some(to) = deployment.get_free_unit() { - return Some(Transfer::new( - Arc::downgrade(user), - from.clone(), - Arc::downgrade(&to), - )); - } else { - warn!("Failed to find free unit in deployment {} while resolving transfer of user {}", deployment.name, user.name); - } - } - TransferTarget::Fallback => { - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller. This should never happen"); - if let Some(fallback) = controller - .get_units() - .find_fallback_unit( - &from - .upgrade() - .expect("Failed to upgrade unit. This should never happen"), - ) - .map(TransferTarget::Unit) - { - return self.resolve_transfer(user, &fallback); - } else { - warn!("Failed to find fallback unit while resolving transfer of user {}", user.name); - } - } - } - - None - } - - pub fn transfer_user(&self, transfer: Transfer) -> bool { - if let Some((user, from, to)) = transfer.get_strong() { - info!( - "Transfering user {} from {} to unit {}", - user.name, from.name, to.name - ); - - let controller = self - .controller - .upgrade() - .expect("Failed to upgrade controller. This should never happen"); - controller.get_event_bus().dispatch( - &EventKey::Transfer(from.uuid), - &UserTransferRequested { - transfer: transfer.clone(), - }, - ); - - *user.unit.write().unwrap() = CurrentUnit::Transfering(transfer); - return true; - } else { - error!("Failed to transfer user because some required information is missing",); - } - - false - } -} - -pub enum TransferTarget { - Unit(UnitHandle), - Deployment(DeploymentHandle), - Fallback, -} - -#[derive(Clone, Debug)] -pub struct Transfer { - pub timestamp: Instant, - pub user: WeakUserHandle, - pub from: WeakUnitHandle, - pub to: WeakUnitHandle, -} - -impl Transfer { - pub fn new(user: WeakUserHandle, from: WeakUnitHandle, to: WeakUnitHandle) -> Self { - Self { - timestamp: Instant::now(), - user, - from, - to, - } - } - - pub fn get_strong(&self) -> Option<(UserHandle, UnitHandle, UnitHandle)> { - let user = self.user.upgrade()?; - let from = self.from.upgrade()?; - let to = self.to.upgrade()?; - Some((user, from, to)) - } -} diff --git a/controller/src/args.rs b/controller/src/args.rs deleted file mode 100644 index b141bad2..00000000 --- a/controller/src/args.rs +++ /dev/null @@ -1,7 +0,0 @@ -use clap::{ArgAction, Parser}; - -#[derive(Parser)] -pub struct Args { - #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] - pub debug: bool, -} diff --git a/controller/src/config.rs b/controller/src/config.rs deleted file mode 100644 index 5153845a..00000000 --- a/controller/src/config.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::net::SocketAddr; -use std::time::Duration; - -use common::config::{LoadFromTomlFile, SaveToTomlFile}; -use serde::{Deserialize, Serialize}; -use simplelog::{error, warn}; -use uuid::Uuid; - -use crate::storage::Storage; - -const DEFAULT_EXPECTED_STARTUP_TIME: Duration = Duration::from_secs(130); -const DEFAULT_EXPECTED_RESTART_TIME: Duration = Duration::from_secs(120); -const DEFAULT_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(15); -const DEFAULT_TRANSFER_TIMEOUT: Duration = Duration::from_secs(10); -const DEFAULT_EMPTY_UNIT_TIMEOUT: Duration = Duration::from_secs(120); - -const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; -const DEFAULT_BIND_PORT: u16 = 12892; - -#[derive(Deserialize, Serialize, Default)] -pub struct NetworkConfig { - pub bind: Option, -} - -#[derive(Deserialize, Serialize, Default)] -pub struct Timings { - pub startup: Option, - pub restart: Option, - pub healthbeat: Option, - pub transfer: Option, - pub empty_unit: Option, -} - -#[derive(Deserialize, Serialize, Default)] -pub struct Config { - /* Cloud Identification */ - pub identifier: Option, - - /* Network */ - pub network: NetworkConfig, - - /* Timings */ - pub timings: Timings, -} - -impl Config { - fn load_or_empty() -> Self { - let path = Storage::get_primary_config_file(); - if !path.exists() { - return Self::default(); - } - Self::load_from_file(&path).unwrap_or_else(|error| { - warn!( - "Failed to read configuration from file: {}", - error - ); - Self::default() - }) - } - - pub fn new_filled() -> Self { - let mut config = Self::load_or_empty(); - - let mut save = false; - if config.identifier.is_none() { - config.identifier = Some(Uuid::new_v4().to_string()); - save = true; - } - if config.network.bind.is_none() { - config.network.bind = Some(SocketAddr::new( - DEFAULT_BIND_ADDRESS.parse().unwrap(), - DEFAULT_BIND_PORT, - )); - save = true; - } - if config.timings.startup.is_none() { - config.timings.startup = Some(DEFAULT_EXPECTED_STARTUP_TIME); - save = true; - } - if config.timings.restart.is_none() { - config.timings.restart = Some(DEFAULT_EXPECTED_RESTART_TIME); - save = true; - } - if config.timings.healthbeat.is_none() { - config.timings.healthbeat = Some(DEFAULT_HEALTH_CHECK_TIMEOUT); - save = true; - } - if config.timings.transfer.is_none() { - config.timings.transfer = Some(DEFAULT_TRANSFER_TIMEOUT); - save = true; - } - if config.timings.empty_unit.is_none() { - config.timings.empty_unit = Some(DEFAULT_EMPTY_UNIT_TIMEOUT); - save = true; - } - if save { - if let Err(error) = config.save_to_file(&Storage::get_primary_config_file(), true) { - error!( - "Failed to save generated configuration to file: {}", - &error - ); - } - } - - // Check config values are overridden by environment variables - if let Ok(identifier) = std::env::var("INSTANCE_IDENTIFIER") { - config.identifier = Some(identifier); - } - if let Ok(address) = std::env::var("BIND_ADDRESS") { - if let Ok(address) = address.parse() { - config.network.bind.replace(address); - } else { - error!("Failed to parse BIND_ADDRESS environment variable"); - } - } - - config - } -} - -impl SaveToTomlFile for Config {} -impl LoadFromTomlFile for Config {} diff --git a/controller/src/main.rs b/controller/src/main.rs index e6963dca..5a2b998a 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,38 +1,4 @@ -#![feature(buf_read_has_data_left)] - -use std::time::Instant; - -use args::Args; -use clap::Parser; -use common::init::CloudInit; -use simplelog::info; -use storage::Storage; - -use crate::application::Controller; -use crate::config::Config; - -mod application; -mod args; -mod config; -mod network; -mod storage; - -// Include the build information generated by build.rs -include!(concat!(env!("OUT_DIR"), "/build_info.rs")); - -pub const AUTHORS: [&str; 1] = ["HttpRafa"]; - -fn main() { - let args = Args::parse(); - CloudInit::init_logging(args.debug, false, Storage::get_latest_log_file()); - CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); - - let start_time = Instant::now(); - info!("Starting cloud version v{}...", VERSION); - info!("Loading configuration..."); - - let configuration = Config::new_filled(); - let controller = Controller::new(configuration); - info!("Loaded cloud in {:.2?}", start_time.elapsed()); - controller.start(); -} +#[tokio::main] +async fn main() { + +} \ No newline at end of file diff --git a/controller/src/network.rs b/controller/src/network.rs deleted file mode 100644 index f44e187a..00000000 --- a/controller/src/network.rs +++ /dev/null @@ -1,97 +0,0 @@ -use anyhow::Result; -use auth::{AdminInterceptor, UnitInterceptor}; -use simplelog::{error, info}; -use std::sync::Arc; -use tokio::{ - sync::watch::{self, Receiver, Sender}, - task::JoinHandle, -}; -use tonic::transport::Server; - -use crate::application::{Controller, WeakControllerHandle}; -use admin::{proto::admin_service_server::AdminServiceServer, AdminServiceImpl}; -use unit::{proto::unit_service_server::UnitServiceServer, UnitServiceImpl}; - -mod admin; -mod auth; -mod stream; -pub mod unit; - -pub struct NetworkStack { - shutdown: Sender, - handle: JoinHandle<()>, - controller: WeakControllerHandle, -} - -impl NetworkStack { - pub fn start(controller: Arc) -> Self { - info!("Starting networking stack..."); - - let (sender, receiver) = watch::channel(false); - - return NetworkStack { - shutdown: sender, - handle: controller - .get_runtime() - .as_ref() - .unwrap() - .spawn(launch_server(controller.clone(), receiver)), - controller: Arc::downgrade(&controller), - }; - - async fn launch_server(controller: Arc, shutdown: Receiver) { - if let Err(error) = run(controller, shutdown).await { - error!("Failed to start gRPC server: {}", error); - } - } - - async fn run(controller: Arc, mut shutdown: Receiver) -> Result<()> { - let address = controller - .configuration - .network - .bind - .expect("No bind address found in the config"); - - let admin_service = AdminServiceImpl { - controller: Arc::clone(&controller), - }; - let unit_service = UnitServiceImpl { - controller: Arc::clone(&controller), - }; - - info!("Controller listening on {}", address); - - Server::builder() - .add_service(AdminServiceServer::with_interceptor( - admin_service, - AdminInterceptor { - controller: Arc::clone(&controller), - }, - )) - .add_service(UnitServiceServer::with_interceptor( - unit_service, - UnitInterceptor { controller }, - )) - .serve_with_shutdown(address, async { - shutdown.changed().await.ok(); - }) - .await?; - - Ok(()) - } - } - - pub fn shutdown(self) { - self.shutdown - .send(true) - .expect("Failed to send shutdown signal"); - if let Some(controller) = self.controller.upgrade() { - controller - .get_runtime() - .as_ref() - .unwrap() - .block_on(self.handle) - .expect("Failed to shutdown network stack"); - } - } -} diff --git a/controller/src/network/admin.rs b/controller/src/network/admin.rs deleted file mode 100644 index 60fbb92b..00000000 --- a/controller/src/network/admin.rs +++ /dev/null @@ -1,624 +0,0 @@ -use std::{str::FromStr, sync::atomic::Ordering}; - -use proto::{admin_service_server::AdminService, user_management::UserValue}; -use tonic::{async_trait, Request, Response, Status}; -use uuid::Uuid; - -use crate::{ - application::{ - cloudlet::{Capabilities, LifecycleStatus, RemoteController}, - deployment::{ScalingPolicy, StartConstraints}, - unit::{FallbackPolicy, KeyValue, Resources, Retention, Spec}, - user::transfer::TransferTarget, - ControllerHandle, CreationResult, - }, - VERSION, -}; - -#[allow(clippy::all)] -pub mod proto { - use tonic::include_proto; - - include_proto!("admin"); -} - -pub struct AdminServiceImpl { - pub controller: ControllerHandle, -} - -#[async_trait] -impl AdminService for AdminServiceImpl { - async fn request_stop(&self, _request: Request<()>) -> Result, Status> { - self.controller.request_stop(); - Ok(Response::new(())) - } - - async fn set_resource_status( - &self, - request: Request, - ) -> Result, Status> { - let resource = request.into_inner(); - let status = match proto::resource_management::ResourceStatus::try_from(resource.status) { - Ok(proto::resource_management::ResourceStatus::Active) => LifecycleStatus::Active, - Ok(proto::resource_management::ResourceStatus::Inactive) => LifecycleStatus::Inactive, - _ => return Err(Status::invalid_argument("Invalid resource status")), - }; - match proto::resource_management::ResourceCategory::try_from(resource.category) { - Ok(proto::resource_management::ResourceCategory::Cloudlet) => { - let mut handle = self.controller.lock_cloudlets_mut(); - let cloudlet = handle - .find_by_name(&resource.id) - .ok_or(Status::not_found("Cloudlet not found"))?; - match handle.set_cloudlet_status(&cloudlet, status) { - Ok(()) => Ok(Response::new(())), - Err(error) => Err(Status::internal(error.to_string())), - } - } - Ok(proto::resource_management::ResourceCategory::Deployment) => { - let mut handle = self.controller.lock_deployments_mut(); - let deployment: std::sync::Arc = handle - .find_by_name(&resource.id) - .ok_or(Status::not_found("Deployment not found"))?; - match handle.set_deployment_status(&deployment, status) { - Ok(()) => Ok(Response::new(())), - Err(error) => Err(Status::internal(error.to_string())), - } - } - Err(_) => Err(Status::not_found("Invalid resource category")), - _ => Err(Status::not_found( - "This action is not possible with this resource category", - )), - } - } - - async fn delete_resource( - &self, - request: Request, - ) -> Result, Status> { - let resource = request.into_inner(); - match proto::resource_management::ResourceCategory::try_from(resource.category) { - Ok(proto::resource_management::ResourceCategory::Cloudlet) => { - let mut handle = self.controller.lock_cloudlets_mut(); - let cloudlet = handle - .find_by_name(&resource.id) - .ok_or(Status::not_found("Cloudlet not found"))?; - match handle.delete_cloudlet(&cloudlet) { - Ok(()) => Ok(Response::new(())), - Err(error) => Err(Status::internal(error.to_string())), - } - } - Ok(proto::resource_management::ResourceCategory::Deployment) => { - let mut handle = self.controller.lock_deployments_mut(); - let deployment: std::sync::Arc = handle - .find_by_name(&resource.id) - .ok_or(Status::not_found("Deployment not found"))?; - match handle.delete_deployment(&deployment) { - Ok(()) => Ok(Response::new(())), - Err(error) => Err(Status::internal(error.to_string())), - } - } - Ok(proto::resource_management::ResourceCategory::Unit) => { - let uuid = Uuid::from_str(&resource.id).map_err(|error| { - Status::invalid_argument(format!("Failed to parse UUID of the unit: {}", error)) - })?; - let units = self.controller.get_units(); - let unit = units - .get_unit(uuid) - .ok_or(Status::not_found("Unit not found"))?; - units.checked_unit_stop(&unit); - Ok(Response::new(())) - } - Err(_) => Err(Status::not_found("Invalid resource category")), - } - } - - async fn get_drivers( - &self, - _request: Request<()>, - ) -> Result, Status> { - let drivers = self - .controller - .get_drivers() - .get_drivers() - .iter() - .map(|driver| driver.name().clone()) - .collect(); - - Ok(Response::new( - proto::driver_management::DriverListResponse { drivers }, - )) - } - - async fn create_cloudlet( - &self, - request: Request, - ) -> Result, Status> { - let cloudlet = request.into_inner(); - let name = &cloudlet.name; - let driver = &cloudlet.driver; - - let capabilities = Capabilities { - memory: cloudlet.memory, - max_allocations: cloudlet.max_allocations, - child: cloudlet.child, - }; - - let controller = RemoteController { - address: cloudlet.controller_address.parse().map_err(|_| { - Status::invalid_argument("The controller address is not a valid URL") - })?, - }; - - let driver = match self.controller.drivers.find_by_name(driver) { - Some(driver) => driver, - None => return Err(Status::invalid_argument("The driver does not exist")), - }; - - let mut cloudlets = self.controller.lock_cloudlets_mut(); - match cloudlets.create_cloudlet(name, driver, capabilities, controller) { - Ok(result) => match result { - CreationResult::Created => Ok(Response::new(())), - CreationResult::AlreadyExists => { - Err(Status::already_exists("Cloudlet already exists")) - } - CreationResult::Denied(error) => { - Err(Status::failed_precondition(error.to_string())) - } - }, - Err(error) => Err(Status::internal(error.to_string())), - } - } - - async fn get_cloudlet( - &self, - request: Request, - ) -> Result, Status> { - let handle = self.controller.lock_cloudlets(); - let cloudlet = handle - .find_by_name(&request.into_inner()) - .ok_or(Status::not_found("Cloudlet not found"))?; - - Ok(Response::new(proto::cloudlet_management::CloudletValue { - name: cloudlet.name.to_owned(), - driver: cloudlet.driver.name().to_owned(), - memory: cloudlet.capabilities.memory, - max_allocations: cloudlet.capabilities.max_allocations, - child: cloudlet.capabilities.child.clone(), - controller_address: cloudlet.controller.address.to_string(), - })) - } - - async fn get_cloudlets( - &self, - _request: Request<()>, - ) -> Result, Status> { - let handle = self.controller.lock_cloudlets(); - let mut cloudlets = Vec::with_capacity(handle.get_amount()); - for cloudlet in handle.get_cloudlets() { - cloudlets.push(cloudlet.name.clone()); - } - - Ok(Response::new( - proto::cloudlet_management::CloudletListResponse { cloudlets }, - )) - } - - async fn create_deployment( - &self, - request: Request, - ) -> Result, Status> { - let deployment = request.into_inner(); - let name = &deployment.name; - - /* Constraints */ - let constraints = match &deployment.constraints { - Some(constraints) => StartConstraints { - minimum: constraints.minimum, - maximum: constraints.maximum, - priority: constraints.priority, - }, - None => StartConstraints::default(), - }; - - /* Scaling */ - let scaling = match &deployment.scaling { - Some(scaling) => ScalingPolicy { - enabled: true, - start_threshold: scaling.start_threshold, - stop_empty_units: scaling.stop_empty_units, - }, - None => ScalingPolicy::default(), - }; - - /* Resources */ - let resources = match &deployment.resources { - Some(resources) => Resources { - memory: resources.memory, - swap: resources.swap, - cpu: resources.cpu, - io: resources.io, - disk: resources.disk, - addresses: resources.addresses, - }, - None => Resources::default(), - }; - - /* Spec */ - let mut spec = Spec::default(); - if let Some(value) = deployment.spec { - spec.image.clone_from(&value.image); - spec.max_players = value.max_players; - spec.settings = value - .settings - .iter() - .map(|setting| KeyValue { - key: setting.key.clone(), - value: setting.value.clone(), - }) - .collect(); - spec.environment = value - .environment - .iter() - .map(|setting| KeyValue { - key: setting.key.clone(), - value: setting.value.clone(), - }) - .collect(); - if let Some(value) = value.disk_retention { - spec.disk_retention = - match proto::unit_management::unit_spec::Retention::try_from(value) { - Ok(proto::unit_management::unit_spec::Retention::Permanent) => { - Retention::Permanent - } - _ => Retention::Temporary, - }; - } - if let Some(value) = value.fallback { - spec.fallback = FallbackPolicy { - enabled: value.enabled, - priority: value.priority, - }; - } - } - - /* Cloudlets */ - let mut cloudlet_handles = Vec::with_capacity(deployment.cloudlets.len()); - for cloudlet in &deployment.cloudlets { - let cloudlet = match self.controller.lock_cloudlets().find_by_name(cloudlet) { - Some(cloudlet) => cloudlet, - None => { - return Err(Status::invalid_argument(format!( - "Cloudlet {} does not exist", - cloudlet - ))) - } - }; - cloudlet_handles.push(cloudlet); - } - - let mut deployments = self.controller.lock_deployments_mut(); - match deployments.create_deployment( - name, - cloudlet_handles, - constraints, - scaling, - resources, - spec, - ) { - Ok(result) => match result { - CreationResult::Created => Ok(Response::new(())), - CreationResult::AlreadyExists => { - Err(Status::already_exists("Deployment already exists")) - } - CreationResult::Denied(error) => { - Err(Status::failed_precondition(error.to_string())) - } - }, - Err(error) => Err(Status::internal(error.to_string())), - } - } - - async fn get_deployment( - &self, - request: Request, - ) -> Result, Status> { - let handle = self.controller.lock_deployments(); - let deployment = handle - .find_by_name(&request.into_inner()) - .ok_or(Status::not_found("Deployment not found"))?; - let cloudlets = deployment - .cloudlets - .read() - .unwrap() - .iter() - .filter_map(|cloudlet| cloudlet.upgrade().map(|cloudlet| cloudlet.name.clone())) - .collect(); - - Ok(Response::new( - proto::deployment_management::DeploymentValue { - name: deployment.name.to_owned(), - cloudlets, - constraints: Some( - proto::deployment_management::deployment_value::Constraints { - minimum: deployment.constraints.minimum, - maximum: deployment.constraints.maximum, - priority: deployment.constraints.priority, - }, - ), - scaling: Some(proto::deployment_management::deployment_value::Scaling { - start_threshold: deployment.scaling.start_threshold, - stop_empty_units: deployment.scaling.stop_empty_units, - }), - resources: Some(proto::unit_management::UnitResources { - memory: deployment.resources.memory, - swap: deployment.resources.swap, - cpu: deployment.resources.cpu, - io: deployment.resources.io, - disk: deployment.resources.disk, - addresses: deployment.resources.addresses, - }), - spec: Some(proto::unit_management::UnitSpec { - image: deployment.spec.image.clone(), - max_players: deployment.spec.max_players, - settings: deployment - .spec - .settings - .iter() - .map(|setting| proto::common::KeyValue { - key: setting.key.clone(), - value: setting.value.clone(), - }) - .collect(), - environment: deployment - .spec - .environment - .iter() - .map(|setting| proto::common::KeyValue { - key: setting.key.clone(), - value: setting.value.clone(), - }) - .collect(), - disk_retention: Some(deployment.spec.disk_retention.clone() as i32), - fallback: Some(proto::unit_management::unit_spec::Fallback { - enabled: deployment.spec.fallback.enabled, - priority: deployment.spec.fallback.priority, - }), - }), - }, - )) - } - - async fn get_deployments( - &self, - _request: Request<()>, - ) -> Result, Status> { - let handle = self.controller.lock_deployments(); - let mut deployments = Vec::with_capacity(handle.get_amount()); - for name in handle.get_deployments().keys() { - deployments.push(name.clone()); - } - - Ok(Response::new( - proto::deployment_management::DeploymentListResponse { deployments }, - )) - } - - async fn get_unit( - &self, - request: Request, - ) -> Result, Status> { - let unit_uuid = Uuid::from_str(&request.into_inner()) - .map_err(|e| Status::invalid_argument(format!("Invalid unit UUID: {}", e)))?; - - let unit = self - .controller - .get_units() - .get_unit(unit_uuid) - .ok_or_else(|| Status::not_found("Unit not found"))?; - - let cloudlet = unit - .cloudlet - .upgrade() - .ok_or_else(|| Status::internal("Cloudlet is no longer usable"))?; - - let state = (unit - .state - .read() - .map_err(|_| Status::internal("Failed to lock unit state"))?) - .clone() as i32; - - Ok(Response::new(proto::unit_management::UnitValue { - name: unit.name.clone(), - uuid: unit.uuid.to_string(), - deployment: unit - .deployment - .as_ref() - .and_then(|g| g.deployment.upgrade().map(|grp| grp.name.clone())), - cloudlet: cloudlet.name.clone(), - connected_users: unit.connected_users.load(Ordering::Relaxed), - rediness: unit.rediness.load(Ordering::Relaxed), - auth_token: unit.auth.token.clone(), - allocation: Some(proto::unit_management::UnitAllocation { - addresses: unit - .allocation - .addresses - .iter() - .map(|addr| proto::common::Address { - host: addr.host.clone(), - port: addr.port as u32, - }) - .collect(), - resources: Some(proto::unit_management::UnitResources { - memory: unit.allocation.resources.memory, - swap: unit.allocation.resources.swap, - cpu: unit.allocation.resources.cpu, - io: unit.allocation.resources.io, - disk: unit.allocation.resources.disk, - addresses: unit.allocation.resources.addresses, - }), - spec: Some(proto::unit_management::UnitSpec { - image: unit.allocation.spec.image.clone(), - max_players: unit.allocation.spec.max_players, - settings: unit - .allocation - .spec - .settings - .iter() - .map(|kv| proto::common::KeyValue { - key: kv.key.clone(), - value: kv.value.clone(), - }) - .collect(), - environment: unit - .allocation - .spec - .environment - .iter() - .map(|kv| proto::common::KeyValue { - key: kv.key.clone(), - value: kv.value.clone(), - }) - .collect(), - disk_retention: Some(unit.allocation.spec.disk_retention.clone() as i32), - fallback: Some(proto::unit_management::unit_spec::Fallback { - enabled: unit.allocation.spec.fallback.enabled, - priority: unit.allocation.spec.fallback.priority, - }), - }), - }), - state, - })) - } - - async fn get_units( - &self, - _request: Request<()>, - ) -> Result, Status> { - let units = self - .controller - .get_units() - .get_units() - .values() - .filter_map(|unit| { - unit.cloudlet - .upgrade() - .map(|cloudlet| proto::unit_management::SimpleUnitValue { - name: unit.name.to_string(), - uuid: unit.uuid.to_string(), - deployment: unit - .deployment - .as_ref() - .and_then(|d| d.deployment.upgrade().map(|d| d.name.to_string())), - cloudlet: cloudlet.name.to_string(), - }) - }) - .collect(); - - Ok(Response::new(proto::unit_management::UnitListResponse { - units, - })) - } - - async fn get_users( - &self, - _request: Request<()>, - ) -> Result, Status> { - let users = self - .controller - .get_users() - .get_users() - .iter() - .map(|user| UserValue { - name: user.name.to_string(), - uuid: user.uuid.to_string(), - }) - .collect(); - - Ok(Response::new(proto::user_management::UserListResponse { - users, - })) - } - - async fn transfer_users( - &self, - request: Request, - ) -> Result, Status> { - let transfer = request.into_inner(); - let target = transfer - .target - .ok_or_else(|| Status::invalid_argument("Target must be provided"))?; - - let target = - match proto::transfer_management::transfer_target_value::TargetType::try_from( - target.target_type, - ) { - Ok(proto::transfer_management::transfer_target_value::TargetType::Deployment) => { - TransferTarget::Deployment( - self.controller - .lock_deployments() - .find_by_name(&target.target.ok_or_else(|| { - Status::invalid_argument("Target must be provided") - })?) - .ok_or_else(|| Status::not_found("Deployment does not exist"))?, - ) - } - Ok(proto::transfer_management::transfer_target_value::TargetType::Unit) => { - TransferTarget::Unit( - self.controller - .get_units() - .get_unit( - Uuid::from_str(&target.target.ok_or_else(|| { - Status::invalid_argument("Target must be provided") - })?) - .map_err(|error| { - Status::invalid_argument(format!( - "Failed to parse target UUID: {}", - error - )) - })?, - ) - .ok_or_else(|| Status::not_found("Unit does not exist"))?, - ) - } - Ok(proto::transfer_management::transfer_target_value::TargetType::Fallback) => { - TransferTarget::Fallback - } - Err(error) => return Err(Status::invalid_argument(error.to_string())), - }; - - let mut count = 0; - for user_uuid in &transfer.user_uuids { - let user_uuid = Uuid::from_str(user_uuid).map_err(|error| { - Status::invalid_argument(format!("Failed to parse user UUID: {}", error)) - })?; - - let user = self - .controller - .get_users() - .get_user(user_uuid) - .ok_or_else(|| Status::not_found("User is not connected to this controller"))?; - - let transfer = self - .controller - .get_users() - .resolve_transfer(&user, &target) - .ok_or_else(|| Status::not_found("Failed to resolve transfer"))?; - - if self.controller.get_users().transfer_user(transfer) { - count += 1; - } - } - Ok(Response::new(count)) - } - - async fn get_protocol_version(&self, _request: Request<()>) -> Result, Status> { - Ok(Response::new(VERSION.protocol)) - } - - async fn get_controller_version( - &self, - _request: Request<()>, - ) -> Result, Status> { - Ok(Response::new(VERSION.to_string())) - } -} diff --git a/controller/src/network/auth.rs b/controller/src/network/auth.rs deleted file mode 100644 index 664f035c..00000000 --- a/controller/src/network/auth.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; - -use tonic::{service::Interceptor, Request, Status}; - -use crate::application::Controller; - -#[derive(Clone)] -pub struct AdminInterceptor { - pub controller: Arc, -} - -impl Interceptor for AdminInterceptor { - fn call(&mut self, mut request: Request<()>) -> Result, Status> { - let metadata = request.metadata(); - let token = metadata.get("authorization").and_then(|t| t.to_str().ok()); - match token { - Some(token) => { - let user = self.controller.get_auth().get_user(token); - if let Some(user) = user { - request.extensions_mut().insert(user); - Ok(request) - } else { - Err(Status::unauthenticated("Invalid user token")) - } - } - None => Err(Status::unauthenticated("No user token provided")), - } - } -} - -#[derive(Clone)] -pub struct UnitInterceptor { - pub controller: Arc, -} - -impl Interceptor for UnitInterceptor { - fn call(&mut self, mut request: Request<()>) -> Result, Status> { - let metadata = request.metadata(); - let token = metadata.get("authorization").and_then(|t| t.to_str().ok()); - match token { - Some(token) => { - let unit = self.controller.get_auth().get_unit(token); - if let Some(unit) = unit { - request.extensions_mut().insert(unit); - Ok(request) - } else { - Err(Status::unauthenticated("Invalid unit token")) - } - } - None => Err(Status::unauthenticated("No unit token provided")), - } - } -} diff --git a/controller/src/network/stream.rs b/controller/src/network/stream.rs deleted file mode 100644 index f56eae40..00000000 --- a/controller/src/network/stream.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::{ - pin::Pin, - sync::mpsc::{Receiver, TryRecvError}, - task::{Context, Poll}, -}; - -use tokio_stream::Stream; - -pub struct StdReceiverStream { - receiver: Receiver, -} - -impl Stream for StdReceiverStream { - type Item = T; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.receiver.try_recv() { - Ok(item) => Poll::Ready(Some(item)), - Err(TryRecvError::Empty) => { - cx.waker().wake_by_ref(); - Poll::Pending - } - Err(TryRecvError::Disconnected) => Poll::Ready(None), - } - } -} - -impl StdReceiverStream { - pub fn new(receiver: Receiver) -> Self { - Self { receiver } - } -} diff --git a/controller/src/network/unit.rs b/controller/src/network/unit.rs deleted file mode 100644 index 22be76b9..00000000 --- a/controller/src/network/unit.rs +++ /dev/null @@ -1,428 +0,0 @@ -use crate::{ - application::{ - auth::AuthUnitHandle, - event::{channel::ChannelMessageSended, transfer::UserTransferRequested, EventKey}, - user::{transfer::TransferTarget, CurrentUnit}, - ControllerHandle, - }, - VERSION, -}; - -use super::stream::StdReceiverStream; -use proto::unit_service_server::UnitService; -use tonic::{async_trait, Request, Response, Status}; -use uuid::Uuid; - -use std::{ - ops::Deref, - str::FromStr, - sync::{mpsc::channel, Arc}, -}; - -#[allow(clippy::all)] -pub mod proto { - use tonic::include_proto; - - include_proto!("unit"); -} - -pub struct UnitServiceImpl { - pub controller: ControllerHandle, -} - -#[async_trait] -impl UnitService for UnitServiceImpl { - async fn beat_heart(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller - .get_units() - .handle_heart_beat(&requesting_unit); - Ok(Response::new(())) - } - - async fn mark_ready(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller.get_units().mark_ready(&requesting_unit); - Ok(Response::new(())) - } - - async fn mark_not_ready(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller.get_units().mark_not_ready(&requesting_unit); - Ok(Response::new(())) - } - - async fn mark_running(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller.get_units().mark_running(&requesting_unit); - Ok(Response::new(())) - } - - async fn request_stop(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller - .get_units() - .checked_unit_stop(&requesting_unit); - Ok(Response::new(())) - } - - async fn user_connected( - &self, - request: Request, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let user = request.into_inner(); - self.controller.get_users().handle_user_connected( - requesting_unit, - user.name, - Uuid::from_str(&user.uuid).map_err(|error| { - Status::invalid_argument(format!("Failed to parse UUID: {}", error)) - })?, - ); - Ok(Response::new(())) - } - - async fn user_disconnected( - &self, - request: Request, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let user = request.into_inner(); - self.controller.get_users().handle_user_disconnected( - requesting_unit, - Uuid::from_str(&user.uuid).map_err(|error| { - Status::invalid_argument(format!("Failed to parse UUID: {}", error)) - })?, - ); - Ok(Response::new(())) - } - - type SubscribeToTransfersStream = - StdReceiverStream>; - async fn subscribe_to_transfers( - &self, - request: Request<()>, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let (sender, receiver) = channel(); - self.controller - .get_event_bus() - .register_listener_under_unit( - EventKey::Transfer(requesting_unit.uuid), - Arc::downgrade(&requesting_unit), - Box::new(move |event: &UserTransferRequested| { - let transfer = &event.transfer; - if let Some((user, _, to)) = transfer.get_strong() { - let address = to.allocation.primary_address(); - - let transfer = proto::transfer_management::ResolvedTransferResponse { - user_uuid: user.uuid.to_string(), - host: address.host.clone(), - port: address.port as u32, - }; - sender - .send(Ok(transfer)) - .expect("Failed to send message to transfer stream"); - } - }), - ); - - Ok(Response::new(StdReceiverStream::new(receiver))) - } - - async fn transfer_users( - &self, - request: Request, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let transfer = request.into_inner(); - let target = transfer - .target - .ok_or_else(|| Status::invalid_argument("Target must be provided"))?; - - let target = - match proto::transfer_management::transfer_target_value::TargetType::try_from( - target.target_type, - ) { - Ok(proto::transfer_management::transfer_target_value::TargetType::Deployment) => { - TransferTarget::Deployment( - self.controller - .lock_deployments() - .find_by_name(&target.target.ok_or_else(|| { - Status::invalid_argument("Target must be provided") - })?) - .ok_or_else(|| Status::not_found("Deployment does not exist"))?, - ) - } - Ok(proto::transfer_management::transfer_target_value::TargetType::Unit) => { - TransferTarget::Unit( - self.controller - .get_units() - .get_unit( - Uuid::from_str(&target.target.ok_or_else(|| { - Status::invalid_argument("Target must be provided") - })?) - .map_err(|error| { - Status::invalid_argument(format!( - "Failed to parse target UUID: {}", - error - )) - })?, - ) - .ok_or_else(|| Status::not_found("Unit does not exist"))?, - ) - } - Ok(proto::transfer_management::transfer_target_value::TargetType::Fallback) => { - TransferTarget::Fallback - } - Err(error) => return Err(Status::invalid_argument(error.to_string())), - }; - - let mut count = 0; - for user_uuid in &transfer.user_uuids { - let user_uuid = Uuid::from_str(user_uuid).map_err(|error| { - Status::invalid_argument(format!("Failed to parse user UUID: {}", error)) - })?; - - let user = self - .controller - .get_users() - .get_user(user_uuid) - .ok_or_else(|| { - Status::not_found(format!( - "User {} is not connected to this controller", - user_uuid - )) - })?; - - // Check if the user is connected to the unit that requested the transfer - if let CurrentUnit::Connected(unit) = user.unit.read().unwrap().deref() { - if let Some(unit) = unit.upgrade() { - if !Arc::ptr_eq(&unit, &requesting_unit) { - return Err(Status::permission_denied(format!( - "User {} is not connected to the requesting unit", - user_uuid - ))); - } - } - } else { - return Err(Status::permission_denied(format!( - "User {} is not connected to the requesting unit", - user_uuid - ))); - } - - let transfer = self - .controller - .get_users() - .resolve_transfer(&user, &target) - .ok_or_else(|| Status::not_found("Failed to resolve transfer"))?; - - if self.controller.get_users().transfer_user(transfer) { - count += 1; - } - } - - Ok(Response::new(count)) - } - - async fn send_message_to_channel( - &self, - request: Request, - ) -> Result, Status> { - let _requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let message = request.into_inner(); - let count = self.controller.get_event_bus().dispatch( - &EventKey::Channel(message.channel.clone()), - &ChannelMessageSended { message }, - ); - Ok(Response::new(count)) - } - - async fn unsubscribe_from_channel( - &self, - request: Request, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller - .get_event_bus() - .unregister_listener(EventKey::Channel(request.into_inner()), &requesting_unit); - - Ok(Response::new(())) - } - - type SubscribeToChannelStream = - StdReceiverStream>; - async fn subscribe_to_channel( - &self, - request: Request, - ) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - let channel_name = &request.into_inner(); - - let (sender, receiver) = channel(); - self.controller - .get_event_bus() - .register_listener_under_unit( - EventKey::Channel(channel_name.clone()), - Arc::downgrade(&requesting_unit), - Box::new(move |event: &ChannelMessageSended| { - sender - .send(Ok(event.message.clone())) - .expect("Failed to send message to channel stream"); - }), - ); - - Ok(Response::new(StdReceiverStream::new(receiver))) - } - - async fn get_units( - &self, - _request: Request<()>, - ) -> Result, Status> { - let units = self - .controller - .get_units() - .get_units() - .values() - .map(|unit| proto::unit_information::SimpleUnitValue { - name: unit.name.clone(), - uuid: unit.uuid.to_string(), - deployment: unit - .deployment - .as_ref() - .and_then(|d| d.deployment.upgrade().map(|d| d.name.clone())), - }) - .collect(); - - Ok(Response::new(proto::unit_information::UnitListResponse { - units, - })) - } - - async fn get_deployments( - &self, - _request: Request<()>, - ) -> Result, Status> { - let handle = self.controller.lock_deployments(); - let mut deployments = Vec::with_capacity(handle.get_amount()); - for name in handle.get_deployments().keys() { - deployments.push(name.clone()); - } - - Ok(Response::new( - proto::deployment_information::DeploymentListResponse { deployments }, - )) - } - - async fn reset(&self, request: Request<()>) -> Result, Status> { - let requesting_unit = request - .extensions() - .get::() - .expect("Failed to get unit from extensions. Is tonic broken?") - .unit - .upgrade() - .ok_or_else(|| Status::not_found("The authenticated unit does not exist"))?; - - self.controller - .get_event_bus() - .cleanup_unit(&requesting_unit); - - Ok(Response::new(())) - } - - async fn get_protocol_version(&self, _request: Request<()>) -> Result, Status> { - Ok(Response::new(VERSION.protocol)) - } - - async fn get_controller_version( - &self, - _request: Request<()>, - ) -> Result, Status> { - Ok(Response::new(VERSION.to_string())) - } -} diff --git a/controller/src/storage.rs b/controller/src/storage.rs deleted file mode 100644 index 13523a49..00000000 --- a/controller/src/storage.rs +++ /dev/null @@ -1,80 +0,0 @@ -/* -All the storage related functions are implemented here. -This makes it easier to change them in the future -*/ - -use std::path::PathBuf; - -/* Logs */ -const LOGS_DIRECTORY: &str = "logs"; -const LATEST_LOG_FILE: &str = "latest.log"; - -/* Cloudlets */ -const CLOUDLETS_DIRECTORY: &str = "cloudlets"; - -/* Deployments */ -const DEPLOYMENTS_DIRECTORY: &str = "deployments"; - -/* Auth */ -const AUTH_DIRECTORY: &str = "auth"; -const USERS_DIRECTORY: &str = "users"; - -/* Configs */ -const CONFIG_DIRECTORY: &str = "configs"; -const PRIMARY_CONFIG_FILE: &str = "config.toml"; - -/* Drivers */ -const DRIVERS_DIRECTORY: &str = "drivers"; -const DATA_DIRECTORY: &str = "data"; - -pub struct Storage; - -impl Storage { - /* Logs */ - pub fn get_latest_log_file() -> PathBuf { - PathBuf::from(LOGS_DIRECTORY).join(LATEST_LOG_FILE) - } - - /* Cloudlets */ - pub fn get_cloudlets_folder() -> PathBuf { - PathBuf::from(CLOUDLETS_DIRECTORY) - } - pub fn get_cloudlet_file(name: &str) -> PathBuf { - Storage::get_cloudlets_folder().join(format!("{}.toml", name)) - } - - /* Deployments */ - pub fn get_deployments_folder() -> PathBuf { - PathBuf::from(DEPLOYMENTS_DIRECTORY) - } - pub fn get_deployment_file(name: &str) -> PathBuf { - Storage::get_deployments_folder().join(format!("{}.toml", name)) - } - - /* Auth */ - pub fn get_users_folder() -> PathBuf { - PathBuf::from(AUTH_DIRECTORY).join(USERS_DIRECTORY) - } - pub fn get_user_file(name: &str) -> PathBuf { - Storage::get_users_folder().join(format!("{}.toml", name)) - } - - /* Configs */ - pub fn get_configs_folder() -> PathBuf { - PathBuf::from(CONFIG_DIRECTORY) - } - pub fn get_primary_config_file() -> PathBuf { - Storage::get_configs_folder().join(PRIMARY_CONFIG_FILE) - } - - /* Drivers */ - pub fn get_drivers_folder() -> PathBuf { - PathBuf::from(DRIVERS_DIRECTORY) - } - pub fn get_data_folder_for_driver(name: &str) -> PathBuf { - PathBuf::from(DATA_DIRECTORY).join(name) - } - pub fn get_config_folder_for_driver(name: &str) -> PathBuf { - Storage::get_configs_folder().join(name) - } -} diff --git a/drivers/local/Cargo.toml b/drivers/local/Cargo.toml index 5b11864b..2ea080c9 100644 --- a/drivers/local/Cargo.toml +++ b/drivers/local/Cargo.toml @@ -11,7 +11,7 @@ forced-target = "wasm32-wasip2" common = { path = "../../common" } # Wasm plugin -wit-bindgen = "0.37.0" +wit-bindgen = "0.38.0" # Error handling anyhow = "1.0.95" diff --git a/drivers/pterodactyl/Cargo.toml b/drivers/pterodactyl/Cargo.toml index 39b6542a..c4824736 100644 --- a/drivers/pterodactyl/Cargo.toml +++ b/drivers/pterodactyl/Cargo.toml @@ -11,7 +11,7 @@ forced-target = "wasm32-wasip2" common = { path = "../../common" } # Wasm plugin -wit-bindgen = "0.37.0" +wit-bindgen = "0.38.0" # Error handling anyhow = "1.0.95" @@ -22,7 +22,7 @@ toml = "0.8.19" # Pelican API url = { version = "2.5.4", features = ["serde"] } -serde_json = "1.0.135" +serde_json = "1.0.138" [build-dependencies] toml = "0.8.19" \ No newline at end of file From 2008b4a1f5a0eda9d0fb066ec21d23322533cc8a Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Fri, 31 Jan 2025 20:52:04 +0100 Subject: [PATCH 09/74] More work --- cli/src/application/profile.rs | 4 +- common/src/config.rs | 4 +- controller/src/application.rs | 15 ++++++ controller/src/config.rs | 98 ++++++++++++++++++++++++++++++++++ controller/src/main.rs | 37 ++++++++++++- controller/src/storage.rs | 80 +++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 controller/src/application.rs create mode 100644 controller/src/config.rs create mode 100644 controller/src/storage.rs diff --git a/cli/src/application/profile.rs b/cli/src/application/profile.rs index 8de8ffb3..c4e18f6d 100644 --- a/cli/src/application/profile.rs +++ b/cli/src/application/profile.rs @@ -68,7 +68,7 @@ impl Profiles { None => continue, }; - let profile = match StoredProfile::load_from_file(&path) { + let profile = match StoredProfile::from_file(&path) { Ok(profile) => profile, Err(error) => { progress.warn(format!( @@ -177,7 +177,7 @@ impl Profile { authorization: self.authorization.clone(), url: self.url.clone(), }; - stored_profile.save_to_file(&Storage::get_profile_file(&self.id), true) + stored_profile.write(&Storage::get_profile_file(&self.id), true) } pub fn compute_id(name: &str) -> String { diff --git a/common/src/config.rs b/common/src/config.rs index 689084ca..f80a7833 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -4,7 +4,7 @@ use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; pub trait SaveToTomlFile: Serialize { - fn save_to_file(&self, path: &Path, create_parent: bool) -> Result<()> { + fn write(&self, path: &Path, create_parent: bool) -> Result<()> { if create_parent { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; @@ -16,7 +16,7 @@ pub trait SaveToTomlFile: Serialize { } pub trait LoadFromTomlFile: DeserializeOwned { - fn load_from_file(path: &Path) -> Result { + fn from_file(path: &Path) -> Result { let data = fs::read_to_string(path)?; let config = toml::from_str(&data)?; Ok(config) diff --git a/controller/src/application.rs b/controller/src/application.rs new file mode 100644 index 00000000..30f140eb --- /dev/null +++ b/controller/src/application.rs @@ -0,0 +1,15 @@ +use crate::config::Config; + +pub struct Controller { + +} + +impl Controller { + pub fn init(config: Config) -> Self { + Controller {} + } + + pub async fn run(&mut self) { + + } +} \ No newline at end of file diff --git a/controller/src/config.rs b/controller/src/config.rs new file mode 100644 index 00000000..2000b366 --- /dev/null +++ b/controller/src/config.rs @@ -0,0 +1,98 @@ +use std::{net::SocketAddr, str::FromStr, time::Duration}; + +use common::config::{LoadFromTomlFile, SaveToTomlFile}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::storage::Storage; + +const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(150); +const DEFAULT_RESTART_TIMEOUT: Duration = Duration::from_secs(120); +const DEFAULT_HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(15); +const DEFAULT_TRANSFER_TIMEOUT: Duration = Duration::from_secs(10); +const DEFAULT_EMPTY_INSTANCE_TIMEOUT: Duration = Duration::from_secs(60); + +const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0:8080"; + +#[derive(Deserialize, Serialize)] +struct Network { + bind: SocketAddr, +} + +#[derive(Deserialize, Serialize)] +struct Timeouts { + startup: Duration, + restart: Duration, + heartbeat: Duration, + transfer: Duration, + empty_instance: Duration, +} + +#[derive(Deserialize, Serialize)] +pub struct Config { + identifier: String, + network: Network, + timeouts: Timeouts, +} + +impl Config { + pub fn parse() -> Self { + let path = Storage::get_primary_config_file(); + if path.exists() { + Self::from_file(&path).expect("Failed to load configuration file") + } else { + let default = Self::default(); + default.write(&path, true).expect("Failed to write default configuration file"); + default + } + } + + pub fn get_identifier(&self) -> &str { + &self.identifier + } + + pub fn get_network_bind(&self) -> &SocketAddr { + &self.network.bind + } + + pub fn get_startup_timeout(&self) -> &Duration { + &self.timeouts.startup + } + + pub fn get_restart_timeout(&self) -> &Duration { + &self.timeouts.restart + } + + pub fn get_heartbeat_timeout(&self) -> &Duration { + &self.timeouts.heartbeat + } + + pub fn get_transfer_timeout(&self) -> &Duration { + &self.timeouts.transfer + } + + pub fn get_empty_instance_timeout(&self) -> &Duration { + &self.timeouts.empty_instance + } +} + +impl Default for Config { + fn default() -> Self { + Self { + identifier: Uuid::new_v4().to_string(), + network: Network { + bind: SocketAddr::from_str(DEFAULT_BIND_ADDRESS).expect("Invalid default bind address"), + }, + timeouts: Timeouts { + startup: DEFAULT_STARTUP_TIMEOUT, + restart: DEFAULT_RESTART_TIMEOUT, + heartbeat: DEFAULT_HEARTBEAT_TIMEOUT, + transfer: DEFAULT_TRANSFER_TIMEOUT, + empty_instance: DEFAULT_EMPTY_INSTANCE_TIMEOUT, + }, + } + } +} + +impl LoadFromTomlFile for Config {} +impl SaveToTomlFile for Config {} \ No newline at end of file diff --git a/controller/src/main.rs b/controller/src/main.rs index 5a2b998a..7d71be93 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,4 +1,39 @@ +#![feature(buf_read_has_data_left)] + +use application::Controller; +use clap::{ArgAction, Parser}; +use common::init::CloudInit; +use config::Config; +use simplelog::info; +use storage::Storage; +use tokio::time::Instant; + +mod storage; +mod config; +mod application; + +// Include the build information generated by build.rs +include!(concat!(env!("OUT_DIR"), "/build_info.rs")); + +pub const AUTHORS: [&str; 1] = ["HttpRafa"]; + #[tokio::main] async fn main() { - + let arguments = Arguments::parse(); + CloudInit::init_logging(arguments.debug, false, Storage::get_latest_log_file()); + CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); + + let beginning = Instant::now(); + info!("Starting cloud version v{}...", VERSION); + info!("Initializing controller..."); + + let mut controller = Controller::init(Config::parse()); + info!("Loaded cloud in {:.2?}", beginning.elapsed()); + controller.run().await; +} + +#[derive(Parser)] +pub struct Arguments { + #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] + pub debug: bool, } \ No newline at end of file diff --git a/controller/src/storage.rs b/controller/src/storage.rs new file mode 100644 index 00000000..60f64d19 --- /dev/null +++ b/controller/src/storage.rs @@ -0,0 +1,80 @@ +/* +All the storage related functions are implemented here. +This makes it easier to change them in the future +*/ + +use std::path::PathBuf; + +/* Logs */ +const LOGS_DIRECTORY: &str = "logs"; +const LATEST_LOG_FILE: &str = "latest.log"; + +/* Cloudlets */ +const CLOUDLETS_DIRECTORY: &str = "cloudlets"; + +/* Deployments */ +const DEPLOYMENTS_DIRECTORY: &str = "deployments"; + +/* Auth */ +const AUTH_DIRECTORY: &str = "auth"; +const USERS_DIRECTORY: &str = "users"; + +/* Configs */ +const CONFIG_DIRECTORY: &str = "configs"; +const PRIMARY_CONFIG_FILE: &str = "config.toml"; + +/* Drivers */ +const DRIVERS_DIRECTORY: &str = "drivers"; +const DATA_DIRECTORY: &str = "data"; + +pub struct Storage; + +impl Storage { + /* Logs */ + pub fn get_latest_log_file() -> PathBuf { + PathBuf::from(LOGS_DIRECTORY).join(LATEST_LOG_FILE) + } + + /* Cloudlets */ + pub fn get_cloudlets_folder() -> PathBuf { + PathBuf::from(CLOUDLETS_DIRECTORY) + } + pub fn get_cloudlet_file(name: &str) -> PathBuf { + Storage::get_cloudlets_folder().join(format!("{}.toml", name)) + } + + /* Deployments */ + pub fn get_deployments_folder() -> PathBuf { + PathBuf::from(DEPLOYMENTS_DIRECTORY) + } + pub fn get_deployment_file(name: &str) -> PathBuf { + Storage::get_deployments_folder().join(format!("{}.toml", name)) + } + + /* Auth */ + pub fn get_users_folder() -> PathBuf { + PathBuf::from(AUTH_DIRECTORY).join(USERS_DIRECTORY) + } + pub fn get_user_file(name: &str) -> PathBuf { + Storage::get_users_folder().join(format!("{}.toml", name)) + } + + /* Configs */ + pub fn get_configs_folder() -> PathBuf { + PathBuf::from(CONFIG_DIRECTORY) + } + pub fn get_primary_config_file() -> PathBuf { + Storage::get_configs_folder().join(PRIMARY_CONFIG_FILE) + } + + /* Drivers */ + pub fn get_drivers_folder() -> PathBuf { + PathBuf::from(DRIVERS_DIRECTORY) + } + pub fn get_data_folder_for_driver(name: &str) -> PathBuf { + PathBuf::from(DATA_DIRECTORY).join(name) + } + pub fn get_config_folder_for_driver(name: &str) -> PathBuf { + Storage::get_configs_folder().join(name) + } +} \ No newline at end of file From 3d34801ec07a162478bc099344ca34b8fecdb2ec Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Fri, 31 Jan 2025 23:43:45 +0100 Subject: [PATCH 10/74] Plugin things --- Cargo.toml | 4 +- Dockerfile | 4 +- controller/Cargo.toml | 2 +- controller/src/application.rs | 56 ++++++++++++++++++- controller/src/application/plugin.rs | 14 +++++ controller/src/application/plugin/manager.rs | 7 +++ controller/src/application/plugin/runtime.rs | 2 + .../src/application/plugin/runtime/wasm.rs | 0 {drivers => plugins}/local/Cargo.toml | 0 {drivers => plugins}/local/build.rs | 0 {drivers => plugins}/local/src/driver.rs | 0 .../local/src/driver/cloudlet.rs | 0 .../local/src/driver/cloudlet/unit.rs | 0 .../local/src/driver/config.rs | 4 +- .../local/src/driver/template.rs | 2 +- {drivers => plugins}/local/src/log.rs | 0 {drivers => plugins}/local/src/main.rs | 0 {drivers => plugins}/local/src/storage.rs | 0 .../local/templates/papermc/prepare/unix.sh | 0 .../templates/papermc/prepare/windows.ps1 | 0 .../local/templates/papermc/startup/unix.sh | 0 .../templates/papermc/startup/windows.ps1 | 0 .../local/templates/papermc/template.toml | 0 {drivers => plugins}/pterodactyl/Cargo.toml | 0 {drivers => plugins}/pterodactyl/build.rs | 0 .../pterodactyl/eggs/egg-paper.json | 0 .../pterodactyl/src/driver.rs | 0 .../pterodactyl/src/driver/backend.rs | 4 +- .../src/driver/backend/allocation.rs | 0 .../pterodactyl/src/driver/backend/common.rs | 0 .../pterodactyl/src/driver/backend/node.rs | 0 .../pterodactyl/src/driver/backend/server.rs | 0 .../pterodactyl/src/driver/backend/user.rs | 0 .../pterodactyl/src/driver/cloudlet.rs | 0 .../pterodactyl/src/driver/cloudlet/unit.rs | 0 {drivers => plugins}/pterodactyl/src/log.rs | 0 {drivers => plugins}/pterodactyl/src/main.rs | 0 .../pterodactyl/src/storage.rs | 0 protocol/wit/{driver.wit => plugin.wit} | 2 +- 39 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 controller/src/application/plugin.rs create mode 100644 controller/src/application/plugin/manager.rs create mode 100644 controller/src/application/plugin/runtime.rs create mode 100644 controller/src/application/plugin/runtime/wasm.rs rename {drivers => plugins}/local/Cargo.toml (100%) rename {drivers => plugins}/local/build.rs (100%) rename {drivers => plugins}/local/src/driver.rs (100%) rename {drivers => plugins}/local/src/driver/cloudlet.rs (100%) rename {drivers => plugins}/local/src/driver/cloudlet/unit.rs (100%) rename {drivers => plugins}/local/src/driver/config.rs (89%) rename {drivers => plugins}/local/src/driver/template.rs (99%) rename {drivers => plugins}/local/src/log.rs (100%) rename {drivers => plugins}/local/src/main.rs (100%) rename {drivers => plugins}/local/src/storage.rs (100%) rename {drivers => plugins}/local/templates/papermc/prepare/unix.sh (100%) rename {drivers => plugins}/local/templates/papermc/prepare/windows.ps1 (100%) rename {drivers => plugins}/local/templates/papermc/startup/unix.sh (100%) rename {drivers => plugins}/local/templates/papermc/startup/windows.ps1 (100%) rename {drivers => plugins}/local/templates/papermc/template.toml (100%) rename {drivers => plugins}/pterodactyl/Cargo.toml (100%) rename {drivers => plugins}/pterodactyl/build.rs (100%) rename {drivers => plugins}/pterodactyl/eggs/egg-paper.json (100%) rename {drivers => plugins}/pterodactyl/src/driver.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/backend.rs (99%) rename {drivers => plugins}/pterodactyl/src/driver/backend/allocation.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/backend/common.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/backend/node.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/backend/server.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/backend/user.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/cloudlet.rs (100%) rename {drivers => plugins}/pterodactyl/src/driver/cloudlet/unit.rs (100%) rename {drivers => plugins}/pterodactyl/src/log.rs (100%) rename {drivers => plugins}/pterodactyl/src/main.rs (100%) rename {drivers => plugins}/pterodactyl/src/storage.rs (100%) rename protocol/wit/{driver.wit => plugin.wit} (99%) diff --git a/Cargo.toml b/Cargo.toml index 61c41295..4913a8dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ members = [ "controller", # Drivers - "drivers/pterodactyl", - "drivers/local", + "plugins/pterodactyl", + "plugins/local", # Clients "cli", diff --git a/Dockerfile b/Dockerfile index f865a46d..64139c18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,8 @@ COPY . . # Install necessary build dependencies, including protobuf compiler RUN apk add --no-cache musl-dev openssl-dev protobuf-dev -# Compile the controller with the wasm-drivers feature -RUN cargo build -p controller --features wasm-drivers --release +# Compile the controller with the wasm-plugins feature +RUN cargo build -p controller --features wasm-plugins --release # Runtime stage: Use a minimal Alpine image FROM alpine:latest diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 1c294877..f14b6664 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -47,4 +47,4 @@ toml = "0.8.19" tonic-build = "0.12.3" [features] -wasm-drivers = ["dep:wasmtime", "dep:wasmtime-wasi", "dep:minreq"] +wasm-plugins = ["dep:wasmtime", "dep:wasmtime-wasi", "dep:minreq"] \ No newline at end of file diff --git a/controller/src/application.rs b/controller/src/application.rs index 30f140eb..5392e25d 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -1,15 +1,69 @@ +use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, time::Duration}; + +use plugin::manager::PluginManager; +use simplelog::info; +use tokio::time::interval; + use crate::config::Config; +mod plugin; + +const TICK_RATE: u64 = 20; + pub struct Controller { + /* State */ + running: Arc, + /* Config */ + config: Config, + + /* Components */ + plugin: PluginManager, } impl Controller { pub fn init(config: Config) -> Self { - Controller {} + Self { + running: Arc::new(AtomicBool::new(true)), + config, + plugin: PluginManager::init(), + } } pub async fn run(&mut self) { + // Setup signal handlers + self.setup_handlers(); + + // Main loop + let mut interval = interval(Duration::from_millis(1000 / TICK_RATE)); + while self.running.load(Ordering::Relaxed) { + interval.tick().await; + self.tick().await; + } + + // Shutdown + self.shutdown().await; + } + + async fn tick(&mut self) { + + } + + async fn shutdown(&mut self) { + info!("Starting shutdown sequence..."); + info!("Shutdown complete. Bye :)"); + } + + fn setup_handlers(&self) { + let flag = self.running.clone(); + ctrlc::set_handler(move || { + info!("Received SIGINT, shutting down..."); + flag.store(false, Ordering::Relaxed); + }).expect("Failed to set Ctrl+C handler"); + } + /* Configuration */ + pub fn get_config(&self) -> &Config { + &self.config } } \ No newline at end of file diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs new file mode 100644 index 00000000..225ee78e --- /dev/null +++ b/controller/src/application/plugin.rs @@ -0,0 +1,14 @@ +use anyhow::Result; + +pub mod manager; +mod runtime; + +pub trait GenericPlugin { + fn init(&self) -> Result; +} + +pub struct Information { + authors: Vec, + version: String, + ready: bool, +} \ No newline at end of file diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs new file mode 100644 index 00000000..e8136188 --- /dev/null +++ b/controller/src/application/plugin/manager.rs @@ -0,0 +1,7 @@ +pub struct PluginManager {} + +impl PluginManager { + pub fn init() -> Self { + Self {} + } +} \ No newline at end of file diff --git a/controller/src/application/plugin/runtime.rs b/controller/src/application/plugin/runtime.rs new file mode 100644 index 00000000..88cc4095 --- /dev/null +++ b/controller/src/application/plugin/runtime.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "wasm-plugins")] +mod wasm; \ No newline at end of file diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs new file mode 100644 index 00000000..e69de29b diff --git a/drivers/local/Cargo.toml b/plugins/local/Cargo.toml similarity index 100% rename from drivers/local/Cargo.toml rename to plugins/local/Cargo.toml diff --git a/drivers/local/build.rs b/plugins/local/build.rs similarity index 100% rename from drivers/local/build.rs rename to plugins/local/build.rs diff --git a/drivers/local/src/driver.rs b/plugins/local/src/driver.rs similarity index 100% rename from drivers/local/src/driver.rs rename to plugins/local/src/driver.rs diff --git a/drivers/local/src/driver/cloudlet.rs b/plugins/local/src/driver/cloudlet.rs similarity index 100% rename from drivers/local/src/driver/cloudlet.rs rename to plugins/local/src/driver/cloudlet.rs diff --git a/drivers/local/src/driver/cloudlet/unit.rs b/plugins/local/src/driver/cloudlet/unit.rs similarity index 100% rename from drivers/local/src/driver/cloudlet/unit.rs rename to plugins/local/src/driver/cloudlet/unit.rs diff --git a/drivers/local/src/driver/config.rs b/plugins/local/src/driver/config.rs similarity index 89% rename from drivers/local/src/driver/config.rs rename to plugins/local/src/driver/config.rs index 95d5f459..01b9865a 100644 --- a/drivers/local/src/driver/config.rs +++ b/plugins/local/src/driver/config.rs @@ -27,13 +27,13 @@ impl Config { pub fn new_filled() -> Self { let path = Storage::get_primary_config_file(); if path.exists() { - Self::load_from_file(&path).unwrap_or_else(|err| { + Self::from_file(&path).unwrap_or_else(|err| { warn!("Failed to read configuration from file: {}", err); Self::new_empty() }) } else { let config = Self::new_empty(); - if let Err(error) = config.save_to_file(&path, false) { + if let Err(error) = config.write(&path, false) { error!("Failed to save default configuration to file: {}", &error); } config diff --git a/drivers/local/src/driver/template.rs b/plugins/local/src/driver/template.rs similarity index 99% rename from drivers/local/src/driver/template.rs rename to plugins/local/src/driver/template.rs index 98290ec6..f22a432d 100644 --- a/drivers/local/src/driver/template.rs +++ b/plugins/local/src/driver/template.rs @@ -82,7 +82,7 @@ impl Templates { }; let data_file = Storage::get_template_data_file(&name); - let template = match StoredTemplate::load_from_file(&data_file) { + let template = match StoredTemplate::from_file(&data_file) { Ok(profile) => profile, Err(error) => { warn!( diff --git a/drivers/local/src/log.rs b/plugins/local/src/log.rs similarity index 100% rename from drivers/local/src/log.rs rename to plugins/local/src/log.rs diff --git a/drivers/local/src/main.rs b/plugins/local/src/main.rs similarity index 100% rename from drivers/local/src/main.rs rename to plugins/local/src/main.rs diff --git a/drivers/local/src/storage.rs b/plugins/local/src/storage.rs similarity index 100% rename from drivers/local/src/storage.rs rename to plugins/local/src/storage.rs diff --git a/drivers/local/templates/papermc/prepare/unix.sh b/plugins/local/templates/papermc/prepare/unix.sh similarity index 100% rename from drivers/local/templates/papermc/prepare/unix.sh rename to plugins/local/templates/papermc/prepare/unix.sh diff --git a/drivers/local/templates/papermc/prepare/windows.ps1 b/plugins/local/templates/papermc/prepare/windows.ps1 similarity index 100% rename from drivers/local/templates/papermc/prepare/windows.ps1 rename to plugins/local/templates/papermc/prepare/windows.ps1 diff --git a/drivers/local/templates/papermc/startup/unix.sh b/plugins/local/templates/papermc/startup/unix.sh similarity index 100% rename from drivers/local/templates/papermc/startup/unix.sh rename to plugins/local/templates/papermc/startup/unix.sh diff --git a/drivers/local/templates/papermc/startup/windows.ps1 b/plugins/local/templates/papermc/startup/windows.ps1 similarity index 100% rename from drivers/local/templates/papermc/startup/windows.ps1 rename to plugins/local/templates/papermc/startup/windows.ps1 diff --git a/drivers/local/templates/papermc/template.toml b/plugins/local/templates/papermc/template.toml similarity index 100% rename from drivers/local/templates/papermc/template.toml rename to plugins/local/templates/papermc/template.toml diff --git a/drivers/pterodactyl/Cargo.toml b/plugins/pterodactyl/Cargo.toml similarity index 100% rename from drivers/pterodactyl/Cargo.toml rename to plugins/pterodactyl/Cargo.toml diff --git a/drivers/pterodactyl/build.rs b/plugins/pterodactyl/build.rs similarity index 100% rename from drivers/pterodactyl/build.rs rename to plugins/pterodactyl/build.rs diff --git a/drivers/pterodactyl/eggs/egg-paper.json b/plugins/pterodactyl/eggs/egg-paper.json similarity index 100% rename from drivers/pterodactyl/eggs/egg-paper.json rename to plugins/pterodactyl/eggs/egg-paper.json diff --git a/drivers/pterodactyl/src/driver.rs b/plugins/pterodactyl/src/driver.rs similarity index 100% rename from drivers/pterodactyl/src/driver.rs rename to plugins/pterodactyl/src/driver.rs diff --git a/drivers/pterodactyl/src/driver/backend.rs b/plugins/pterodactyl/src/driver/backend.rs similarity index 99% rename from drivers/pterodactyl/src/driver/backend.rs rename to plugins/pterodactyl/src/driver/backend.rs index 6cc07780..e05e3319 100644 --- a/drivers/pterodactyl/src/driver/backend.rs +++ b/plugins/pterodactyl/src/driver/backend.rs @@ -102,13 +102,13 @@ impl Backend { fn load_or_empty() -> Self { let path = Storage::get_backend_config_file(); if path.exists() { - Self::load_from_file(&path).unwrap_or_else(|err| { + Self::from_file(&path).unwrap_or_else(|err| { warn!("Failed to read backend configuration from file: {}", err); Self::new_empty() }) } else { let backend = Self::new_empty(); - if let Err(error) = backend.save_to_file(&path, false) { + if let Err(error) = backend.write(&path, false) { error!( "Failed to save default backend configuration to file: {}", &error diff --git a/drivers/pterodactyl/src/driver/backend/allocation.rs b/plugins/pterodactyl/src/driver/backend/allocation.rs similarity index 100% rename from drivers/pterodactyl/src/driver/backend/allocation.rs rename to plugins/pterodactyl/src/driver/backend/allocation.rs diff --git a/drivers/pterodactyl/src/driver/backend/common.rs b/plugins/pterodactyl/src/driver/backend/common.rs similarity index 100% rename from drivers/pterodactyl/src/driver/backend/common.rs rename to plugins/pterodactyl/src/driver/backend/common.rs diff --git a/drivers/pterodactyl/src/driver/backend/node.rs b/plugins/pterodactyl/src/driver/backend/node.rs similarity index 100% rename from drivers/pterodactyl/src/driver/backend/node.rs rename to plugins/pterodactyl/src/driver/backend/node.rs diff --git a/drivers/pterodactyl/src/driver/backend/server.rs b/plugins/pterodactyl/src/driver/backend/server.rs similarity index 100% rename from drivers/pterodactyl/src/driver/backend/server.rs rename to plugins/pterodactyl/src/driver/backend/server.rs diff --git a/drivers/pterodactyl/src/driver/backend/user.rs b/plugins/pterodactyl/src/driver/backend/user.rs similarity index 100% rename from drivers/pterodactyl/src/driver/backend/user.rs rename to plugins/pterodactyl/src/driver/backend/user.rs diff --git a/drivers/pterodactyl/src/driver/cloudlet.rs b/plugins/pterodactyl/src/driver/cloudlet.rs similarity index 100% rename from drivers/pterodactyl/src/driver/cloudlet.rs rename to plugins/pterodactyl/src/driver/cloudlet.rs diff --git a/drivers/pterodactyl/src/driver/cloudlet/unit.rs b/plugins/pterodactyl/src/driver/cloudlet/unit.rs similarity index 100% rename from drivers/pterodactyl/src/driver/cloudlet/unit.rs rename to plugins/pterodactyl/src/driver/cloudlet/unit.rs diff --git a/drivers/pterodactyl/src/log.rs b/plugins/pterodactyl/src/log.rs similarity index 100% rename from drivers/pterodactyl/src/log.rs rename to plugins/pterodactyl/src/log.rs diff --git a/drivers/pterodactyl/src/main.rs b/plugins/pterodactyl/src/main.rs similarity index 100% rename from drivers/pterodactyl/src/main.rs rename to plugins/pterodactyl/src/main.rs diff --git a/drivers/pterodactyl/src/storage.rs b/plugins/pterodactyl/src/storage.rs similarity index 100% rename from drivers/pterodactyl/src/storage.rs rename to plugins/pterodactyl/src/storage.rs diff --git a/protocol/wit/driver.wit b/protocol/wit/plugin.wit similarity index 99% rename from protocol/wit/driver.wit rename to protocol/wit/plugin.wit index 9ed84a01..213fcd5e 100644 --- a/protocol/wit/driver.wit +++ b/protocol/wit/plugin.wit @@ -1,4 +1,4 @@ -package cloudlet:driver; +package cloudlet:plugin; // Types required for this protocol interface types { From 29848d96940e4c6732d9710589003686ff4701d0 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:08:35 +0100 Subject: [PATCH 11/74] More plugins --- common/src/config.rs | 2 +- controller/configs/config.toml | 37 +++ controller/configs/wasm-engine.toml | 5 + controller/configs/wasm-plugins.toml | 26 ++ controller/src/application.rs | 84 ++++-- controller/src/application/plugin.rs | 17 +- controller/src/application/plugin/manager.rs | 43 ++- controller/src/application/plugin/runtime.rs | 41 ++- .../src/application/plugin/runtime/wasm.rs | 108 ++++++++ .../application/plugin/runtime/wasm/config.rs | 97 +++++++ .../application/plugin/runtime/wasm/init.rs | 91 ++++++ controller/src/config.rs | 44 +-- controller/src/main.rs | 16 +- controller/src/storage.rs | 46 ++-- controller/src/task.rs | 11 + protocol/wit/plugin.wit | 259 +++++++----------- 16 files changed, 676 insertions(+), 251 deletions(-) create mode 100644 controller/configs/config.toml create mode 100644 controller/configs/wasm-engine.toml create mode 100644 controller/configs/wasm-plugins.toml create mode 100644 controller/src/application/plugin/runtime/wasm/config.rs create mode 100644 controller/src/application/plugin/runtime/wasm/init.rs create mode 100644 controller/src/task.rs diff --git a/common/src/config.rs b/common/src/config.rs index f80a7833..5aabd777 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -4,7 +4,7 @@ use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; pub trait SaveToTomlFile: Serialize { - fn write(&self, path: &Path, create_parent: bool) -> Result<()> { + fn save(&self, path: &Path, create_parent: bool) -> Result<()> { if create_parent { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; diff --git a/controller/configs/config.toml b/controller/configs/config.toml new file mode 100644 index 00000000..f236f1f7 --- /dev/null +++ b/controller/configs/config.toml @@ -0,0 +1,37 @@ +# This identifier is used for the controller. +# It is useful when you have centralized storage for all controllers. +identifier = "%RANDOM%" + +[network] +# The controller will listen on this address. +bind = "0.0.0.0:8080" + +# The maximum time the controller will wait for the instance to start up. +# If this timeout is reached, the startup will be considered as failed. +[timeouts.startup] +secs = 150 +nanos = 0 + +# The maximum time the controller will wait for the instance to restart. +# If this timeout is reached, the restart will be considered as failed. +[timeouts.restart] +secs = 120 +nanos = 0 + +# The interval between heartbeats sent by the controller. +# NOTE: If you change this value, make sure to update the instance's heartbeat timeout as well. +[timeouts.heartbeat] +secs = 15 +nanos = 0 + +# The maximum time the controller will wait for a user to transfer to a different instance. +# If this timeout is reached, the transfer will be considered as failed. +[timeouts.transfer] +secs = 10 +nanos = 0 + +# The maximum time the controller will wait for an empty instance to be filled. +# If this timeout is reached, the instance will be stopped. +[timeouts.empty_instance] +secs = 60 +nanos = 0 \ No newline at end of file diff --git a/controller/configs/wasm-engine.toml b/controller/configs/wasm-engine.toml new file mode 100644 index 00000000..5ac420d2 --- /dev/null +++ b/controller/configs/wasm-engine.toml @@ -0,0 +1,5 @@ +# For more settings, please refer to the documentation: +# https://bytecodealliance.github.io/wasmtime/cli-cache.html + +[cache] +enabled = true \ No newline at end of file diff --git a/controller/configs/wasm-plugins.toml b/controller/configs/wasm-plugins.toml new file mode 100644 index 00000000..52f04d66 --- /dev/null +++ b/controller/configs/wasm-plugins.toml @@ -0,0 +1,26 @@ +# This configuration is crucial for granting the plugins their required permissions +# https://httprafa.github.io/atomic-cloud/controller/plugins/wasm/permissions/ + +[[plugins]] +name = "local" +inherit_stdio = false +inherit_args = false +inherit_env = false +inherit_network = false +allow_ip_name_lookup = false +allow_http = false +allow_process = true +allow_remove_dir_all = true +mounts = [] + +[[plugins]] +name = "pelican" +inherit_stdio = false +inherit_args = false +inherit_env = false +inherit_network = true +allow_ip_name_lookup = true +allow_http = true +allow_process = false +allow_remove_dir_all = false +mounts = [] \ No newline at end of file diff --git a/controller/src/application.rs b/controller/src/application.rs index 5392e25d..468646f2 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -1,69 +1,105 @@ -use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, time::Duration}; - +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; + +use anyhow::Result; use plugin::manager::PluginManager; use simplelog::info; -use tokio::time::interval; +use tokio::{ + select, + sync::mpsc::{channel, Receiver, Sender}, + time::interval, +}; -use crate::config::Config; +use crate::{ + config::Config, + task::{Task, WrappedTask}, +}; mod plugin; const TICK_RATE: u64 = 20; +const TASK_BUFFER: usize = 128; + +pub type TaskSender = Sender; pub struct Controller { /* State */ running: Arc, - /* Config */ - config: Config, + /* Tasks */ + tasks: (TaskSender, Receiver), /* Components */ plugin: PluginManager, + + /* Config */ + config: Config, } impl Controller { - pub fn init(config: Config) -> Self { - Self { + pub async fn init(config: Config) -> Result { + Ok(Self { running: Arc::new(AtomicBool::new(true)), + tasks: channel(TASK_BUFFER), + plugin: PluginManager::init(&config).await?, config, - plugin: PluginManager::init(), - } + }) } - pub async fn run(&mut self) { + pub async fn run(&mut self) -> Result<()> { // Setup signal handlers - self.setup_handlers(); + self.setup_handlers()?; // Main loop let mut interval = interval(Duration::from_millis(1000 / TICK_RATE)); while self.running.load(Ordering::Relaxed) { - interval.tick().await; - self.tick().await; + select! { + _ = interval.tick() => self.tick().await?, + task = self.tasks.1.recv() => if let Some(mut task) = task { + task.run(self).await?; + } + } } // Shutdown - self.shutdown().await; + self.shutdown().await?; + + Ok(()) } - async fn tick(&mut self) { + async fn tick(&mut self) -> Result<()> { + // Tick plugin manager + self.plugin.tick().await?; + Ok(()) } - async fn shutdown(&mut self) { + async fn shutdown(&mut self) -> Result<()> { info!("Starting shutdown sequence..."); + + // Shutdown plugin manager + self.plugin.shutdown().await?; + info!("Shutdown complete. Bye :)"); + Ok(()) } - fn setup_handlers(&self) { + fn setup_handlers(&self) -> Result<()> { let flag = self.running.clone(); ctrlc::set_handler(move || { info!("Received SIGINT, shutting down..."); flag.store(false, Ordering::Relaxed); - }).expect("Failed to set Ctrl+C handler"); + }) + .map_err(|error| error.into()) } +} - /* Configuration */ - pub fn get_config(&self) -> &Config { - &self.config - } -} \ No newline at end of file +pub trait TickService { + async fn tick(&mut self) -> Result<()>; + async fn shutdown(&mut self) -> Result<()>; +} diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index 225ee78e..b1065301 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -1,14 +1,27 @@ use anyhow::Result; +use tokio::task::JoinHandle; pub mod manager; mod runtime; +pub type WrappedPlugin = Box; +pub type WrappedNode = Box; + pub trait GenericPlugin { - fn init(&self) -> Result; + fn name(&self) -> &str; + fn init(&self) -> JoinHandle>; + + /* Ticking */ + fn tick(&self) -> JoinHandle>; +} + +pub trait GenericNode { + /* Ticking */ + fn tick(&self) -> JoinHandle>; } pub struct Information { authors: Vec, version: String, ready: bool, -} \ No newline at end of file +} diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs index e8136188..ac1ff181 100644 --- a/controller/src/application/plugin/manager.rs +++ b/controller/src/application/plugin/manager.rs @@ -1,7 +1,42 @@ -pub struct PluginManager {} +use std::collections::HashMap; + +use anyhow::Result; +use simplelog::info; + +use crate::{application::TickService, config::Config}; + +use super::WrappedPlugin; + +#[cfg(feature = "wasm-plugins")] +use crate::application::plugin::runtime::wasm::init::init_wasm_plugins; + +pub struct PluginManager { + plugins: HashMap, +} impl PluginManager { - pub fn init() -> Self { - Self {} + pub async fn init(config: &Config) -> Result { + info!("Initializing plugin system..."); + + let mut plugins = HashMap::new(); + + #[cfg(feature = "wasm-plugins")] + init_wasm_plugins(config, &mut plugins).await?; + + info!("Loaded {} plugin(s)", plugins.len()); + Ok(Self { plugins }) + } +} + +impl TickService for PluginManager { + async fn tick(&mut self) -> Result<()> { + for plugin in self.plugins.values() { + plugin.tick(); + } + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + Ok(()) } -} \ No newline at end of file +} diff --git a/controller/src/application/plugin/runtime.rs b/controller/src/application/plugin/runtime.rs index 88cc4095..fbfa92ee 100644 --- a/controller/src/application/plugin/runtime.rs +++ b/controller/src/application/plugin/runtime.rs @@ -1,2 +1,41 @@ #[cfg(feature = "wasm-plugins")] -mod wasm; \ No newline at end of file +pub(crate) mod wasm; + +#[cfg(feature = "wasm-plugins")] +pub(crate) mod source { + use std::{ + fmt::{self, Display, Formatter}, + fs, + path::PathBuf, + }; + + use anyhow::Result; + + pub struct Source { + path: PathBuf, + source: Vec, + } + + impl Display for Source { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + write!(formatter, "{}", self.path.display()) + } + } + + impl Source { + pub fn from_file(path: &PathBuf) -> Result { + Ok(Source { + path: path.to_owned(), + source: fs::read(&path)?, + }) + } + + pub fn source(&self) -> &[u8] { + &self.source + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + } +} diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index e69de29b..efc682c8 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -0,0 +1,108 @@ +use std::sync::Arc; + +use anyhow::{anyhow, Result}; +use generated::exports::plugin::system::bridge; +use tokio::{spawn, sync::Mutex, task::JoinHandle}; +use wasmtime::{component::ResourceAny, AsContextMut, Store}; +use wasmtime_wasi::{ResourceTable, WasiCtx, WasiView}; + +use crate::application::plugin::{GenericPlugin, Information}; + +pub(crate) mod config; +pub mod init; + +pub mod generated { + use wasmtime::component::bindgen; + + bindgen!({ + world: "plugin", + path: "../protocol/wit/", + async: true, + }); +} + +pub(crate) struct PluginState { + /* Wasmtime */ + wasi: WasiCtx, + resources: ResourceTable, +} + +pub(crate) struct Plugin { + name: String, + bindings: Arc, + store: Arc>>, + instance: ResourceAny, +} + +impl GenericPlugin for Plugin { + fn name(&self) -> &str { + &self.name + } + + fn init(&self) -> JoinHandle> { + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_init(store.lock().await.as_context_mut(), instance) + .await + { + Ok(information) => Ok(information.into()), + Err(error) => Err(error), + } + }) + } + + fn tick(&self) -> JoinHandle> { + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_tick(store.lock().await.as_context_mut(), instance) + .await + { + Ok(result) => result.map_err(|errors| { + anyhow!(errors + .iter() + .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) + .collect::>() + .join("\n")) + }), + Err(error) => Err(error), + } + }) + } +} + +impl Plugin { + fn get( + &self, + ) -> ( + Arc, + Arc>>, + ResourceAny, + ) { + (self.bindings.clone(), self.store.clone(), self.instance) + } +} + +impl WasiView for PluginState { + fn ctx(&mut self) -> &mut WasiCtx { + &mut self.wasi + } + fn table(&mut self) -> &mut ResourceTable { + &mut self.resources + } +} + +impl From for Information { + fn from(val: bridge::Information) -> Self { + Information { + authors: val.authors, + version: val.version, + ready: val.ready, + } + } +} \ No newline at end of file diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs new file mode 100644 index 00000000..b13ee41a --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -0,0 +1,97 @@ +use std::{fs, path::PathBuf}; + +use anyhow::Result; +use common::config::{LoadFromTomlFile}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use simplelog::warn; + +use crate::storage::Storage; + +const DEFAULT_PLUGINS_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/wasm-plugins.toml")); +const DEFAULT_ENGINE_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/wasm-engine.toml")); + +#[derive(Serialize, Deserialize)] +pub struct PluginsConfig { + plugins: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct PluginConfig { + name: String, + inherit_stdio: bool, + inherit_args: bool, + inherit_env: bool, + inherit_network: bool, + allow_ip_name_lookup: bool, + allow_http: bool, + allow_process: bool, + allow_remove_dir_all: bool, + + mounts: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Mount { + host: String, + guest: String, +} + +impl PluginsConfig { + pub fn parse() -> Result { + let path = Storage::get_wasm_plugins_config_file(); + if path.exists() { + Self::from_file(&path) + } else { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&path, DEFAULT_PLUGINS_CONFIG)?; + Self::from_file(&path) + } + } + + pub fn find_config(&self, name: &str) -> Option<&PluginConfig> { + self.plugins + .iter() + .find(|plugin| match Regex::new(&plugin.name) { + Ok(regex) => regex.is_match(name), + Err(error) => { + warn!("Failed to compile driver name regex: {}", error); + false + } + }) + } +} + +pub fn verify_engine_config() -> Result { + let path = Storage::get_wasm_engine_config_file(); + if path.exists() { + Ok(path) + } else { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&path, DEFAULT_ENGINE_CONFIG)?; + Ok(path) + } +} + +impl Default for PluginConfig { + fn default() -> Self { + Self { + name: String::new(), + inherit_stdio: true, + inherit_args: true, + inherit_env: true, + inherit_network: true, + allow_ip_name_lookup: true, + allow_http: true, + allow_process: true, + allow_remove_dir_all: true, + mounts: Vec::new(), + } + } +} + +impl LoadFromTomlFile for PluginsConfig {} \ No newline at end of file diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs new file mode 100644 index 00000000..a75803cb --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -0,0 +1,91 @@ +use std::{collections::HashMap, fs::{self, DirBuilder}}; + +use anyhow::{anyhow, Result}; +use simplelog::{error, info, warn}; + +use crate::{application::plugin::{runtime::source::Source, WrappedPlugin}, config::{self, Config}, storage::Storage}; + +use super::{config::{verify_engine_config, PluginConfig, PluginsConfig}, Plugin}; + +pub async fn init_wasm_plugins(global_config: &Config, plugins: &mut HashMap) -> Result<()> { + // Verify and load required configuration files + verify_engine_config()?; + let plugins_config = PluginsConfig::parse()?; + + let directory = Storage::get_plugins_directory(); + if !directory.exists() { + fs::create_dir_all(&directory)?; + } + + for entry in fs::read_dir(directory)? { + let entry = match entry { + Ok(entry) => entry, + Err(error) => { + error!("Failed to read plugin entry: {}", error); + continue; + } + }; + + let path = entry.path(); + if path.is_dir() + || !path + .file_name() + .unwrap() + .to_string_lossy() + .ends_with(".wasm") + { + continue; + } + + let name = path.file_stem().unwrap().to_string_lossy().to_string(); + let source = match Source::from_file(&path) { + Ok(source) => source, + Err(error) => { + error!( + "Failed to read source code for plugin {} from file({:?}): {}", + name, + path, + error + ); + continue; + } + }; + + info!("Compiling plugin '{}'...", name); + let plugin = Plugin::new(global_config, &plugins_config).await; + match plugin { + Ok(plugin) => match plugin.init().await { + Ok(info) => { + if info.ready { + info!( + "Loaded plugin {} v{} by {}", + plugin.name, info.version, + info.authors.join(", ") + ); + plugins.insert(plugin.name, Box::new(plugin)); + } else { + warn!( + "Plugin {} marked itself as not ready, skipping...", + plugin.name + ); + } + } + Err(error) => error!("Failed to load plugin {}: {}", name, error), + }, + Err(error) => error!( + "Failed to compile plugin {} at location {}: {}", + name, + source, + error + ), + } + } + + Ok(()) +} + +impl Plugin { + async fn new(global_config: &Config, plugins_config: &PluginsConfig) -> Result { + + } +} \ No newline at end of file diff --git a/controller/src/config.rs b/controller/src/config.rs index 2000b366..6faf3be9 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -1,18 +1,13 @@ -use std::{net::SocketAddr, str::FromStr, time::Duration}; +use std::{fs, net::SocketAddr, str::FromStr, time::Duration}; +use anyhow::Result; use common::config::{LoadFromTomlFile, SaveToTomlFile}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::storage::Storage; -const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(150); -const DEFAULT_RESTART_TIMEOUT: Duration = Duration::from_secs(120); -const DEFAULT_HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(15); -const DEFAULT_TRANSFER_TIMEOUT: Duration = Duration::from_secs(10); -const DEFAULT_EMPTY_INSTANCE_TIMEOUT: Duration = Duration::from_secs(60); - -const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0:8080"; +const DEFAULT_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/config.toml")); #[derive(Deserialize, Serialize)] struct Network { @@ -36,14 +31,16 @@ pub struct Config { } impl Config { - pub fn parse() -> Self { + pub fn parse() -> Result { let path = Storage::get_primary_config_file(); if path.exists() { - Self::from_file(&path).expect("Failed to load configuration file") + Self::from_file(&path) } else { - let default = Self::default(); - default.write(&path, true).expect("Failed to write default configuration file"); - default + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&path, DEFAULT_CONFIG)?; + Self::from_file(&path) } } @@ -76,23 +73,4 @@ impl Config { } } -impl Default for Config { - fn default() -> Self { - Self { - identifier: Uuid::new_v4().to_string(), - network: Network { - bind: SocketAddr::from_str(DEFAULT_BIND_ADDRESS).expect("Invalid default bind address"), - }, - timeouts: Timeouts { - startup: DEFAULT_STARTUP_TIMEOUT, - restart: DEFAULT_RESTART_TIMEOUT, - heartbeat: DEFAULT_HEARTBEAT_TIMEOUT, - transfer: DEFAULT_TRANSFER_TIMEOUT, - empty_instance: DEFAULT_EMPTY_INSTANCE_TIMEOUT, - }, - } - } -} - -impl LoadFromTomlFile for Config {} -impl SaveToTomlFile for Config {} \ No newline at end of file +impl LoadFromTomlFile for Config {} \ No newline at end of file diff --git a/controller/src/main.rs b/controller/src/main.rs index 7d71be93..0617d8b4 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,5 +1,6 @@ #![feature(buf_read_has_data_left)] +use anyhow::Result; use application::Controller; use clap::{ArgAction, Parser}; use common::init::CloudInit; @@ -8,9 +9,10 @@ use simplelog::info; use storage::Storage; use tokio::time::Instant; -mod storage; -mod config; mod application; +mod config; +mod storage; +mod task; // Include the build information generated by build.rs include!(concat!(env!("OUT_DIR"), "/build_info.rs")); @@ -18,7 +20,7 @@ include!(concat!(env!("OUT_DIR"), "/build_info.rs")); pub const AUTHORS: [&str; 1] = ["HttpRafa"]; #[tokio::main] -async fn main() { +async fn main() -> Result<()> { let arguments = Arguments::parse(); CloudInit::init_logging(arguments.debug, false, Storage::get_latest_log_file()); CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); @@ -27,13 +29,15 @@ async fn main() { info!("Starting cloud version v{}...", VERSION); info!("Initializing controller..."); - let mut controller = Controller::init(Config::parse()); + let mut controller = Controller::init(Config::parse()?).await?; info!("Loaded cloud in {:.2?}", beginning.elapsed()); - controller.run().await; + controller.run().await?; + + Ok(()) } #[derive(Parser)] pub struct Arguments { #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] pub debug: bool, -} \ No newline at end of file +} diff --git a/controller/src/storage.rs b/controller/src/storage.rs index 60f64d19..3cfb6992 100644 --- a/controller/src/storage.rs +++ b/controller/src/storage.rs @@ -23,8 +23,12 @@ const USERS_DIRECTORY: &str = "users"; const CONFIG_DIRECTORY: &str = "configs"; const PRIMARY_CONFIG_FILE: &str = "config.toml"; -/* Drivers */ -const DRIVERS_DIRECTORY: &str = "drivers"; +/* Wasm Configs */ +const WASM_PLUGINS_CONFIG_FILE: &str = "wasm-plugins.toml"; +const WASM_ENGINE_CONFIG_FILE: &str = "wasm-engine.toml"; + +/* Plugins */ +const PLUGINS_DIRECTORY: &str = "plugins"; const DATA_DIRECTORY: &str = "data"; pub struct Storage; @@ -36,45 +40,53 @@ impl Storage { } /* Cloudlets */ - pub fn get_cloudlets_folder() -> PathBuf { + pub fn get_cloudlets_directory() -> PathBuf { PathBuf::from(CLOUDLETS_DIRECTORY) } pub fn get_cloudlet_file(name: &str) -> PathBuf { - Storage::get_cloudlets_folder().join(format!("{}.toml", name)) + Storage::get_cloudlets_directory().join(format!("{}.toml", name)) } /* Deployments */ - pub fn get_deployments_folder() -> PathBuf { + pub fn get_deployments_directory() -> PathBuf { PathBuf::from(DEPLOYMENTS_DIRECTORY) } pub fn get_deployment_file(name: &str) -> PathBuf { - Storage::get_deployments_folder().join(format!("{}.toml", name)) + Storage::get_deployments_directory().join(format!("{}.toml", name)) } /* Auth */ - pub fn get_users_folder() -> PathBuf { + pub fn get_users_directory() -> PathBuf { PathBuf::from(AUTH_DIRECTORY).join(USERS_DIRECTORY) } pub fn get_user_file(name: &str) -> PathBuf { - Storage::get_users_folder().join(format!("{}.toml", name)) + Storage::get_users_directory().join(format!("{}.toml", name)) } /* Configs */ - pub fn get_configs_folder() -> PathBuf { + pub fn get_configs_directory() -> PathBuf { PathBuf::from(CONFIG_DIRECTORY) } pub fn get_primary_config_file() -> PathBuf { - Storage::get_configs_folder().join(PRIMARY_CONFIG_FILE) + Storage::get_configs_directory().join(PRIMARY_CONFIG_FILE) + } + + /* Wasm Configs */ + pub fn get_wasm_plugins_config_file() -> PathBuf { + Storage::get_configs_directory().join(WASM_PLUGINS_CONFIG_FILE) + } + pub fn get_wasm_engine_config_file() -> PathBuf { + Storage::get_configs_directory().join(WASM_ENGINE_CONFIG_FILE) } - /* Drivers */ - pub fn get_drivers_folder() -> PathBuf { - PathBuf::from(DRIVERS_DIRECTORY) + /* Plugins */ + pub fn get_plugins_directory() -> PathBuf { + PathBuf::from(PLUGINS_DIRECTORY) } - pub fn get_data_folder_for_driver(name: &str) -> PathBuf { + pub fn get_data_directory_for_plugin(name: &str) -> PathBuf { PathBuf::from(DATA_DIRECTORY).join(name) } - pub fn get_config_folder_for_driver(name: &str) -> PathBuf { - Storage::get_configs_folder().join(name) + pub fn get_config_directory_for_plugin(name: &str) -> PathBuf { + Storage::get_configs_directory().join(name) } -} \ No newline at end of file +} diff --git a/controller/src/task.rs b/controller/src/task.rs new file mode 100644 index 00000000..874b324c --- /dev/null +++ b/controller/src/task.rs @@ -0,0 +1,11 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::application::Controller; + +pub type WrappedTask = Box; + +#[async_trait] +pub trait Task { + async fn run(&mut self, controller: &mut Controller) -> Result<()>; +} diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 213fcd5e..4bb3665c 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -1,276 +1,209 @@ -package cloudlet:plugin; +package plugin:system; -// Types required for this protocol interface types { - type error-message = string; // Type alias for error - type scoped-errors = list; // Type alias for scoped errors + type error-message = string; + type scoped-errors = list; - // Result type for error handling record scoped-error { - scope: string, // Scope of the error - message: error-message, // Error message + scope: string, + message: error-message, } - // Enum for references variant reference { - controller, // Controller reference - configs, // Configurations reference - data, // Data reference + controller, + configs, + data, } - // Record for directory + record directory { - path: string, // Directory path - reference: reference, // Directory reference + path: string, + reference: reference, } - // Record for key-value pair + record key-value { - key: string, // Key - value: string, // Value + key: string, + value: string, } } -// Interface for logging functionality interface log { - // Enum for log levels variant level { - debug, // Debug level - info, // Info level - warn, // Warning level - error, // Error level + debug, + info, + warn, + error, } - // Function to log a message with a specific log level log-string: func(level: level, message: string); } -// Interface for platform-specific functionality interface platform { - // Enum for operating systems variant os { - unix, // Unix-based OS - windows, // Windows OS + unix, + windows, } - // Function to get the current operating system get-os: func() -> os; } -// Interface for file functionality interface file { - use types.{error-message, directory}; // Use directory type from types interface - // Function to delete directories recursively + use types.{error-message, directory}; remove-dir-all: func(directory: directory) -> result<_, error-message>; } -// Interface for HTTP functionality interface http { - // Enum for HTTP methods variant method { - get, // HTTP GET method - patch, // HTTP PATCH method - post, // HTTP POST method - put, // HTTP PUT method - delete, // HTTP DELETE method + get, + patch, + post, + put, + delete, } - // Record for HTTP headers + record header { - key: string, // Header key - value: string, // Header value + key: string, + value: string, } - // Record for HTTP response + record response { - status-code: u32, // HTTP status code - reason-phrase: string, // HTTP reason phrase - headers: list
, // List of HTTP headers - bytes: list, // Response body as bytes + status-code: u32, + reason-phrase: string, + headers: list
, + bytes: list, } - // Function to send an HTTP request + send-http-request: func(method: method, url: string, headers: list
, body: option>) -> option; } -// Interface for process management functionality interface process { - use types.{error-message, key-value, directory}; // Use directory type from types interface + use types.{error-message, key-value, directory}; - // Enum for reader modes variant reader-mode { - direct, // Allows you to directly controll the read process - async, // Reads data asynchronously + direct, + async, } - // Enum for standard readers + variant std-reader { - stdout, // Standard output - stderr, // Standard error + stdout, + stderr, } - // Function to spawn a new process spawn-process: func(command: string, args: list, environment: list, directory: directory, mode: reader-mode) -> result; - // Function to kill a process kill-process: func(pid: u32) -> result<_, error-message>; - // Function to drop a process drop-process: func(pid: u32) -> result; - - // Function to try waiting for a process to finish try-wait: func(pid: u32) -> result, error-message>; - - // Function to read a specified number of bytes from a process's standard output or error read-direct: func(pid: u32, buf-size: u32, std: std-reader) -> result>, error-message>; - // Function to read all remaining bytes from a process's standard output or error read-to-end-direct: func(pid: u32, std: std-reader) -> result>, error-message>; - // Function to read a line from a process's standard output or error read-line-direct: func(pid: u32, std: std-reader) -> result, error-message>; has-data-left-direct: func(pid: u32, std: std-reader) -> result; - // Function read line using async mode read-line-async: func(pid: u32, std: std-reader) -> result, error-message>; - - // Function to write data to a process's standard input write-stdin: func(pid: u32, data: list) -> result<_, error-message>; } -// Interface for API functionality interface api { - // Function to get the name of the API get-name: func() -> string; } -// Interface for bridge functionality interface bridge { - use types.{error-message, scoped-errors, key-value}; // Use key-value type from types interface + use types.{error-message, scoped-errors, key-value}; - // Type alias for UUID type uuid = string; - /* Init */ - // Record for bridge information record information { - authors: list, // List of authors - version: string, // Version of the bridge - ready: bool, // Ready status + authors: list, + version: string, + ready: bool, } - /* Cloudlet | Start */ - // Record for cloudlet capabilities record capabilities { - memory: option, // Optional memory capability - max-allocations: option, // Optional maximum allocations - child: option, // Optional child capability + memory: option, + max-allocations: option, + child: option, } - // Record for remote controller record remote-controller { - address: string, // Controller address + address: string, } - /* Cloudlet | End */ - /* Allocation */ - // Record for address record address { - host: string, // Host address - port: u16, // Port number + host: string, + port: u16, } - /* Unit | Start */ - // Record for unit resources record resources { - memory: u32, // Memory resource - swap: u32, // Swap resource - cpu: u32, // CPU resource - io: u32, // IO resource - disk: u32, // Disk resource - addresses: u32, // Addresses resource + memory: u32, + swap: u32, + cpu: u32, + io: u32, + disk: u32, + addresses: u32, } - // Record for unit setting record setting { - key: string, // Setting key - value: string, // Setting value + key: string, + value: string, } - // Enum for disk retention policy variant retention { - permanent, // Permanent retention - temporary, // Temporary retention + permanent, + temporary, } - // Record for unit specification record spec { - settings: list, // List of settings - environment: list, // List of environment variables - disk-retention: retention, // Disk retention policy - image: string, // Image + settings: list, + environment: list, + disk-retention: retention, + image: string, } - // Record for unit allocation record allocation { - addresses: list
, // List of addresses - resources: resources, // Resources - spec: spec, // Specification + addresses: list
, + resources: resources, + spec: spec, } - // Record for authentication record auth { - token: string, // Authentication token + token: string, } - // Record for unit proposal - record unit-proposal { - name: string, // Unit name - deployment: option, // Optional deployment - resources: resources, // Resources - spec: spec, // Specification + record instance-proposal { + name: string, + deployment: option, + resources: resources, + spec: spec, } - // Record for unit - record unit { - name: string, // Unit name - uuid: uuid, // Unit UUID - deployment: option, // Optional deployment - allocation: allocation, // Allocation - auth: auth, // Authentication + record instance { + name: string, + uuid: uuid, + deployment: option, + allocation: allocation, + auth: auth, } - /* Unit | End */ - // Resource for generic cloudlet - resource generic-cloudlet { - // Constructor for generic cloudlet + resource generic-node { constructor(cloud-identifier: string, name: string, id: option, capabilities: capabilities, controller: remote-controller); - // Function to handle cloudlet tick tick: func() -> result<_, scoped-errors>; - - // Function to allocate addresses for a unit - allocate-addresses: func(unit: unit-proposal) -> result, error-message>; - // Function to deallocate addresses + allocate-addresses: func(instance: instance-proposal) -> result, error-message>; deallocate-addresses: func(addresses: list
); - - // Function to start a unit - start-unit: func(unit: unit); - // Function to restart a unit - restart-unit: func(unit: unit); - // Function to stop a unit - stop-unit: func(unit: unit); + start-instance: func(instance: instance); + restart-instance: func(instance: instance); + stop-instance: func(instance: instance); } - // Resource for generic driver - resource generic-driver { - // Constructor for generic driver + resource generic-plugin { constructor(cloud-identifier: string); - // Function to initialize the driver init: func() -> information; - // Function to initialize a cloudlet - init-cloudlet: func(name: string, capabilities: capabilities, controller: remote-controller) -> result; - // Function that is called when the driver is stopped - cleanup: func() -> result<_, scoped-errors>; - - // Function to handle driver tick + init-node: func(name: string, capabilities: capabilities, controller: remote-controller) -> result; tick: func() -> result<_, scoped-errors>; + shutdown: func() -> result<_, scoped-errors>; } } -// World definition for the driver -world driver { - export bridge; // Export bridge interface - import api; // Import API interface - import log; // Import log interface - import platform; // Import platform interface - import file; // Import file interface - import http; // Import HTTP interface - import process; // Import process interface +world plugin { + export bridge; + import api; + import log; + import platform; + import file; + import http; + import process; } \ No newline at end of file From c810954a826b8b701cda355b023a608d600adde1 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sat, 1 Feb 2025 15:53:26 +0100 Subject: [PATCH 12/74] More plugin work --- cli/src/application/profile.rs | 2 +- controller/src/application/plugin/runtime.rs | 6 +- .../src/application/plugin/runtime/wasm.rs | 6 +- .../application/plugin/runtime/wasm/config.rs | 70 ++++--- .../application/plugin/runtime/wasm/ext.rs | 32 ++++ .../plugin/runtime/wasm/ext/file.rs | 16 ++ .../plugin/runtime/wasm/ext/http.rs | 57 ++++++ .../plugin/runtime/wasm/ext/log.rs | 17 ++ .../plugin/runtime/wasm/ext/platform.rs | 14 ++ .../application/plugin/runtime/wasm/init.rs | 176 +++++++++++++++--- controller/src/config.rs | 10 +- controller/src/main.rs | 2 - protocol/wit/plugin.wit | 8 +- 13 files changed, 355 insertions(+), 61 deletions(-) create mode 100644 controller/src/application/plugin/runtime/wasm/ext.rs create mode 100644 controller/src/application/plugin/runtime/wasm/ext/file.rs create mode 100644 controller/src/application/plugin/runtime/wasm/ext/http.rs create mode 100644 controller/src/application/plugin/runtime/wasm/ext/log.rs create mode 100644 controller/src/application/plugin/runtime/wasm/ext/platform.rs diff --git a/cli/src/application/profile.rs b/cli/src/application/profile.rs index c4e18f6d..1c013187 100644 --- a/cli/src/application/profile.rs +++ b/cli/src/application/profile.rs @@ -177,7 +177,7 @@ impl Profile { authorization: self.authorization.clone(), url: self.url.clone(), }; - stored_profile.write(&Storage::get_profile_file(&self.id), true) + stored_profile.save(&Storage::get_profile_file(&self.id), true) } pub fn compute_id(name: &str) -> String { diff --git a/controller/src/application/plugin/runtime.rs b/controller/src/application/plugin/runtime.rs index fbfa92ee..f88d20fe 100644 --- a/controller/src/application/plugin/runtime.rs +++ b/controller/src/application/plugin/runtime.rs @@ -26,15 +26,15 @@ pub(crate) mod source { pub fn from_file(path: &PathBuf) -> Result { Ok(Source { path: path.to_owned(), - source: fs::read(&path)?, + source: fs::read(path)?, }) } - pub fn source(&self) -> &[u8] { + pub fn get_source(&self) -> &[u8] { &self.source } - pub fn path(&self) -> &PathBuf { + pub fn get_path(&self) -> &PathBuf { &self.path } } diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index efc682c8..35e5615b 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -9,6 +9,7 @@ use wasmtime_wasi::{ResourceTable, WasiCtx, WasiView}; use crate::application::plugin::{GenericPlugin, Information}; pub(crate) mod config; +mod ext; pub mod init; pub mod generated { @@ -22,6 +23,9 @@ pub mod generated { } pub(crate) struct PluginState { + /* Plugin */ + name: String, + /* Wasmtime */ wasi: WasiCtx, resources: ResourceTable, @@ -105,4 +109,4 @@ impl From for Information { ready: val.ready, } } -} \ No newline at end of file +} diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs index b13ee41a..ded82777 100644 --- a/controller/src/application/plugin/runtime/wasm/config.rs +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -1,15 +1,21 @@ use std::{fs, path::PathBuf}; use anyhow::Result; -use common::config::{LoadFromTomlFile}; +use common::config::LoadFromTomlFile; use regex::Regex; use serde::{Deserialize, Serialize}; use simplelog::warn; use crate::storage::Storage; -const DEFAULT_PLUGINS_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/wasm-plugins.toml")); -const DEFAULT_ENGINE_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/wasm-engine.toml")); +const DEFAULT_PLUGINS_CONFIG: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/configs/wasm-plugins.toml" +)); +const DEFAULT_ENGINE_CONFIG: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/configs/wasm-engine.toml" +)); #[derive(Serialize, Deserialize)] pub struct PluginsConfig { @@ -31,12 +37,51 @@ pub struct PluginConfig { mounts: Vec, } +impl PluginConfig { + pub fn has_inherit_stdio(&self) -> bool { + self.inherit_stdio + } + pub fn has_inherit_args(&self) -> bool { + self.inherit_args + } + pub fn has_inherit_env(&self) -> bool { + self.inherit_env + } + pub fn has_inherit_network(&self) -> bool { + self.inherit_network + } + pub fn has_allow_ip_name_lookup(&self) -> bool { + self.allow_ip_name_lookup + } + pub fn has_allow_http(&self) -> bool { + self.allow_http + } + pub fn has_allow_process(&self) -> bool { + self.allow_process + } + pub fn has_allow_remove_dir_all(&self) -> bool { + self.allow_remove_dir_all + } + pub fn get_mounts(&self) -> &[Mount] { + &self.mounts + } +} + #[derive(Serialize, Deserialize)] pub struct Mount { host: String, guest: String, } +impl Mount { + pub fn get_host(&self) -> &str { + &self.host + } + pub fn get_guest(&self) -> &str { + &self.guest + } +} + impl PluginsConfig { pub fn parse() -> Result { let path = Storage::get_wasm_plugins_config_file(); @@ -77,21 +122,4 @@ pub fn verify_engine_config() -> Result { } } -impl Default for PluginConfig { - fn default() -> Self { - Self { - name: String::new(), - inherit_stdio: true, - inherit_args: true, - inherit_env: true, - inherit_network: true, - allow_ip_name_lookup: true, - allow_http: true, - allow_process: true, - allow_remove_dir_all: true, - mounts: Vec::new(), - } - } -} - -impl LoadFromTomlFile for PluginsConfig {} \ No newline at end of file +impl LoadFromTomlFile for PluginsConfig {} diff --git a/controller/src/application/plugin/runtime/wasm/ext.rs b/controller/src/application/plugin/runtime/wasm/ext.rs new file mode 100644 index 00000000..c7498f80 --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/ext.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use crate::storage::Storage; + +use super::{ + generated::plugin::system::{ + self, + types::{Directory, Reference}, + }, + PluginState, +}; + +mod file; +mod http; +mod log; +mod platform; + +impl system::types::Host for PluginState {} + +impl PluginState { + pub fn get_directory(&self, name: &str, directory: &Directory) -> Result { + match &directory.reference { + Reference::Controller => Ok(PathBuf::from(".").join(&directory.path)), + Reference::Data => { + Ok(Storage::get_data_directory_for_plugin(name).join(&directory.path)) + } + Reference::Configs => { + Ok(Storage::get_config_directory_for_plugin(name).join(&directory.path)) + } + } + } +} diff --git a/controller/src/application/plugin/runtime/wasm/ext/file.rs b/controller/src/application/plugin/runtime/wasm/ext/file.rs new file mode 100644 index 00000000..350e0c14 --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/ext/file.rs @@ -0,0 +1,16 @@ +use std::fs; + +use crate::application::plugin::runtime::wasm::{ + generated::plugin::system::{ + self, + types::{Directory, ErrorMessage}, + }, + PluginState, +}; + +impl system::file::Host for PluginState { + async fn remove_dir_all(&mut self, directory: Directory) -> Result<(), ErrorMessage> { + fs::remove_dir_all(self.get_directory(&self.name, &directory)?) + .map_err(|error| format!("Failed to remove directory: {}", error)) + } +} diff --git a/controller/src/application/plugin/runtime/wasm/ext/http.rs b/controller/src/application/plugin/runtime/wasm/ext/http.rs new file mode 100644 index 00000000..25fc4f5b --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/ext/http.rs @@ -0,0 +1,57 @@ +use simplelog::warn; + +use crate::application::plugin::runtime::wasm::{ + generated::plugin::system::{ + self, + http::{Header, Method, Response}, + }, + PluginState, +}; + +impl system::http::Host for PluginState { + // TODO: Rewrite this function to use the reqwest crate instead of minreq + async fn send_http_request( + &mut self, + method: Method, + url: String, + headers: Vec
, + body: Option>, + ) -> Option { + let mut request = match method { + Method::Get => minreq::get(url), + Method::Patch => minreq::patch(url), + Method::Post => minreq::post(url), + Method::Put => minreq::put(url), + Method::Delete => minreq::delete(url), + }; + if let Some(body) = body { + request = request.with_body(body); + } + for header in headers { + request = request.with_header(&header.key, &header.value); + } + let response = match request.send() { + Ok(response) => response, + Err(error) => { + warn!( + "Failed to send HTTP request for plugin {}: {}", + self.name, error + ); + return None; + } + }; + Some(Response { + status_code: response.status_code as u32, + reason_phrase: response.reason_phrase.clone(), + headers: response + .headers + .iter() + .map(|header| Header { + key: header.0.clone(), + value: header.1.clone(), + }) + .collect(), + bytes: response.into_bytes(), + }) + } +} diff --git a/controller/src/application/plugin/runtime/wasm/ext/log.rs b/controller/src/application/plugin/runtime/wasm/ext/log.rs new file mode 100644 index 00000000..daa538c6 --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/ext/log.rs @@ -0,0 +1,17 @@ +use simplelog::{debug, error, info, warn}; + +use crate::application::plugin::runtime::wasm::{ + generated::plugin::system::{self, log::Level}, + PluginState, +}; + +impl system::log::Host for PluginState { + async fn log_string(&mut self, level: Level, message: String) { + match level { + Level::Info => info!("[{}] {}", self.name.to_uppercase(), message), + Level::Warn => warn!("[{}] {}", self.name.to_uppercase(), message), + Level::Error => error!("[{}] {}", self.name.to_uppercase(), message), + Level::Debug => debug!("[{}] {}", self.name.to_uppercase(), message), + } + } +} diff --git a/controller/src/application/plugin/runtime/wasm/ext/platform.rs b/controller/src/application/plugin/runtime/wasm/ext/platform.rs new file mode 100644 index 00000000..f3c3b21d --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/ext/platform.rs @@ -0,0 +1,14 @@ +use crate::application::plugin::runtime::wasm::{ + generated::plugin::system::{self, platform::Os}, + PluginState, +}; + +impl system::platform::Host for PluginState { + async fn get_os(&mut self) -> Os { + if cfg!(target_os = "windows") { + Os::Windows + } else { + Os::Unix + } + } +} diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index a75803cb..9861ae6f 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -1,13 +1,34 @@ -use std::{collections::HashMap, fs::{self, DirBuilder}}; +use std::{ + collections::HashMap, + fs::{self}, + path::Path, + sync::Arc, +}; -use anyhow::{anyhow, Result}; +use anyhow::Result; use simplelog::{error, info, warn}; +use tokio::sync::Mutex; +use wasmtime::{ + component::{Component, Linker}, + Engine, Store, +}; +use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtxBuilder}; -use crate::{application::plugin::{runtime::source::Source, WrappedPlugin}, config::{self, Config}, storage::Storage}; +use crate::{ + application::plugin::{runtime::source::Source, GenericPlugin, WrappedPlugin}, + config::Config, + storage::Storage, +}; -use super::{config::{verify_engine_config, PluginConfig, PluginsConfig}, Plugin}; +use super::{ + config::{verify_engine_config, PluginsConfig}, + generated, Plugin, PluginState, +}; -pub async fn init_wasm_plugins(global_config: &Config, plugins: &mut HashMap) -> Result<()> { +pub async fn init_wasm_plugins( + global_config: &Config, + plugins: &mut HashMap, +) -> Result<()> { // Verify and load required configuration files verify_engine_config()?; let plugins_config = PluginsConfig::parse()?; @@ -17,11 +38,12 @@ pub async fn init_wasm_plugins(global_config: &Config, plugins: &mut HashMap entry, Err(error) => { - error!("Failed to read plugin entry: {}", error); + error!("Failed to read plugin entry: {}", error); continue; } }; @@ -43,26 +65,52 @@ pub async fn init_wasm_plugins(global_config: &Config, plugins: &mut HashMap { error!( "Failed to read source code for plugin {} from file({:?}): {}", - name, - path, - error + name, path, error ); continue; } }; + let config_directory = Storage::get_config_directory_for_plugin(&name); + let data_directory = Storage::get_data_directory_for_plugin(&name); + if !config_directory.exists() { + fs::create_dir_all(&config_directory).unwrap_or_else(|error| { + warn!( + "Failed to create configs directory for driver {}: {}", + name, error + ) + }); + } + if !data_directory.exists() { + fs::create_dir_all(&data_directory).unwrap_or_else(|error| { + warn!( + "Failed to create data directory for driver {}: {}", + name, error + ) + }); + } + info!("Compiling plugin '{}'...", name); - let plugin = Plugin::new(global_config, &plugins_config).await; + let plugin = Plugin::new( + &name, + &source, + global_config, + &plugins_config, + &data_directory, + &config_directory, + ) + .await; match plugin { Ok(plugin) => match plugin.init().await { - Ok(info) => { - if info.ready { + Ok(Ok(information)) => { + if information.ready { info!( "Loaded plugin {} v{} by {}", - plugin.name, info.version, - info.authors.join(", ") + plugin.name, + information.version, + information.authors.join(", ") ); - plugins.insert(plugin.name, Box::new(plugin)); + plugins.insert(plugin.name.clone(), Box::new(plugin)); } else { warn!( "Plugin {} marked itself as not ready, skipping...", @@ -70,22 +118,108 @@ pub async fn init_wasm_plugins(global_config: &Config, plugins: &mut HashMap error!("Failed to load plugin {}: {}", name, error), + Ok(Err(error)) => error!("Failed to initialize plugin {}: {}", name, error), + Err(error) => error!( + "Failed to join plugin initialization task {}: {}", + name, error + ), }, Err(error) => error!( "Failed to compile plugin {} at location {}: {}", - name, - source, - error + name, source, error ), } } + if amount == plugins.len() { + warn!("The Wasm plugins feature is enabled, but no Wasm plugins were loaded."); + } + Ok(()) } impl Plugin { - async fn new(global_config: &Config, plugins_config: &PluginsConfig) -> Result { + async fn new( + name: &str, + source: &Source, + global_config: &Config, + plugins_config: &PluginsConfig, + data_directory: &Path, + config_directory: &Path, + ) -> Result { + let mut engine_config = wasmtime::Config::new(); + engine_config.wasm_component_model(true).async_support(true); + if let Err(error) = engine_config.cache_config_load(Storage::get_wasm_engine_config_file()) + { + warn!("Failed to enable caching for wasmtime engine: {}", error); + } + + let engine = Engine::new(&engine_config)?; + let component = Component::from_binary(&engine, source.get_source())?; + + let mut linker = Linker::new(&engine); + wasmtime_wasi::add_to_linker_async(&mut linker)?; + generated::Plugin::add_to_linker(&mut linker, |state: &mut PluginState| state)?; + let mut wasi = WasiCtxBuilder::new(); + if let Some(config) = plugins_config.find_config(name) { + if config.has_inherit_stdio() { + wasi.inherit_stdio(); + } + if config.has_inherit_args() { + wasi.inherit_args(); + } + if config.has_inherit_env() { + wasi.inherit_env(); + } + if config.has_inherit_network() { + wasi.inherit_network(); + } + if config.has_allow_ip_name_lookup() { + wasi.allow_ip_name_lookup(true); + } + for mount in config.get_mounts() { + wasi.preopened_dir( + mount.get_host(), + mount.get_guest(), + DirPerms::all(), + FilePerms::all(), + )?; + } + } + let wasi = wasi + .preopened_dir( + config_directory, + "/configs/", + DirPerms::all(), + FilePerms::all(), + )? + .preopened_dir(data_directory, "/data/", DirPerms::all(), FilePerms::all())? + .build(); + + let resources = ResourceTable::new(); + let mut store = Store::new( + &engine, + PluginState { + name: name.to_string(), + wasi, + resources, + }, + ); + + let bindings = + generated::Plugin::instantiate_async(&mut store, &component, &linker).await?; + let instance = bindings + .plugin_system_bridge() + .generic_plugin() + .call_constructor(&mut store, global_config.get_identifier()) + .await?; + + Ok(Plugin { + name: name.to_string(), + bindings: Arc::new(bindings), + store: Arc::new(Mutex::new(store)), + instance, + }) } -} \ No newline at end of file +} diff --git a/controller/src/config.rs b/controller/src/config.rs index 6faf3be9..6bd04493 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -1,13 +1,13 @@ -use std::{fs, net::SocketAddr, str::FromStr, time::Duration}; +use std::{fs, net::SocketAddr, time::Duration}; use anyhow::Result; -use common::config::{LoadFromTomlFile, SaveToTomlFile}; +use common::config::LoadFromTomlFile; use serde::{Deserialize, Serialize}; -use uuid::Uuid; use crate::storage::Storage; -const DEFAULT_CONFIG: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/config.toml")); +const DEFAULT_CONFIG: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/config.toml")); #[derive(Deserialize, Serialize)] struct Network { @@ -73,4 +73,4 @@ impl Config { } } -impl LoadFromTomlFile for Config {} \ No newline at end of file +impl LoadFromTomlFile for Config {} diff --git a/controller/src/main.rs b/controller/src/main.rs index 0617d8b4..d424c771 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,5 +1,3 @@ -#![feature(buf_read_has_data_left)] - use anyhow::Result; use application::Controller; use clap::{ArgAction, Parser}; diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 4bb3665c..88ff410c 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -93,15 +93,10 @@ interface process { read-direct: func(pid: u32, buf-size: u32, std: std-reader) -> result>, error-message>; read-to-end-direct: func(pid: u32, std: std-reader) -> result>, error-message>; read-line-direct: func(pid: u32, std: std-reader) -> result, error-message>; - has-data-left-direct: func(pid: u32, std: std-reader) -> result; read-line-async: func(pid: u32, std: std-reader) -> result, error-message>; write-stdin: func(pid: u32, data: list) -> result<_, error-message>; } -interface api { - get-name: func() -> string; -} - interface bridge { use types.{error-message, scoped-errors, key-value}; @@ -200,10 +195,9 @@ interface bridge { world plugin { export bridge; - import api; import log; import platform; import file; import http; - import process; + //import process; } \ No newline at end of file From 1323665c0209565070f506b2951c6c49227ae7aa Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sat, 1 Feb 2025 16:25:20 +0100 Subject: [PATCH 13/74] Add empty structs --- Cargo.toml | 2 +- controller/src/application.rs | 40 ++++++++++++++++--- controller/src/application/group.rs | 4 ++ controller/src/application/group/manager.rs | 29 ++++++++++++++ controller/src/application/node.rs | 7 ++++ controller/src/application/node/manager.rs | 29 ++++++++++++++ controller/src/application/plugin.rs | 1 - controller/src/application/plugin/runtime.rs | 4 -- .../src/application/plugin/runtime/wasm.rs | 5 --- .../application/plugin/runtime/wasm/init.rs | 7 ++-- controller/src/application/server.rs | 4 ++ controller/src/application/server/manager.rs | 30 ++++++++++++++ controller/src/storage.rs | 28 ++++++------- protocol/wit/plugin.wit | 12 +++--- 14 files changed, 162 insertions(+), 40 deletions(-) create mode 100644 controller/src/application/group.rs create mode 100644 controller/src/application/group/manager.rs create mode 100644 controller/src/application/node.rs create mode 100644 controller/src/application/node/manager.rs create mode 100644 controller/src/application/server.rs create mode 100644 controller/src/application/server/manager.rs diff --git a/Cargo.toml b/Cargo.toml index 4913a8dc..d23f2ab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ ] [workspace.metadata] -protocol-version = 5 +protocol-version = 6 [profile.release] lto = true diff --git a/controller/src/application.rs b/controller/src/application.rs index 468646f2..e86d4b1e 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -7,7 +7,10 @@ use std::{ }; use anyhow::Result; +use group::manager::GroupManager; +use node::manager::NodeManager; use plugin::manager::PluginManager; +use server::manager::ServerManager; use simplelog::info; use tokio::{ select, @@ -17,10 +20,13 @@ use tokio::{ use crate::{ config::Config, - task::{Task, WrappedTask}, + task::WrappedTask, }; mod plugin; +mod node; +mod group; +mod server; const TICK_RATE: u64 = 20; const TASK_BUFFER: usize = 128; @@ -35,7 +41,10 @@ pub struct Controller { tasks: (TaskSender, Receiver), /* Components */ - plugin: PluginManager, + plugins: PluginManager, + nodes: NodeManager, + groups: GroupManager, + servers: ServerManager, /* Config */ config: Config, @@ -46,7 +55,10 @@ impl Controller { Ok(Self { running: Arc::new(AtomicBool::new(true)), tasks: channel(TASK_BUFFER), - plugin: PluginManager::init(&config).await?, + plugins: PluginManager::init(&config).await?, + nodes: NodeManager::init().await?, + groups: GroupManager::init().await?, + servers: ServerManager::init().await?, config, }) } @@ -74,7 +86,16 @@ impl Controller { async fn tick(&mut self) -> Result<()> { // Tick plugin manager - self.plugin.tick().await?; + self.plugins.tick().await?; + + // Tick node manager + self.nodes.tick().await?; + + // Tick group manager + self.groups.tick().await?; + + // Tick server manager + self.servers.tick().await?; Ok(()) } @@ -82,8 +103,17 @@ impl Controller { async fn shutdown(&mut self) -> Result<()> { info!("Starting shutdown sequence..."); + // Shutdown server manager + self.servers.shutdown().await?; + + // Shutdown group manager + self.groups.shutdown().await?; + + // Shutdown node manager + self.nodes.shutdown().await?; + // Shutdown plugin manager - self.plugin.shutdown().await?; + self.plugins.shutdown().await?; info!("Shutdown complete. Bye :)"); Ok(()) diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs new file mode 100644 index 00000000..b53fea82 --- /dev/null +++ b/controller/src/application/group.rs @@ -0,0 +1,4 @@ +pub mod manager; + +pub struct Group { +} \ No newline at end of file diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs new file mode 100644 index 00000000..eeb7efa2 --- /dev/null +++ b/controller/src/application/group/manager.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; + +use anyhow::Result; + +use crate::application::TickService; + +use super::Group; + +pub struct GroupManager { + groups: HashMap, +} + +impl GroupManager { + pub async fn init() -> Result { + Ok(Self { + groups: HashMap::new(), + }) + } +} + +impl TickService for GroupManager { + async fn tick(&mut self) -> Result<()> { + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs new file mode 100644 index 00000000..39c1f43b --- /dev/null +++ b/controller/src/application/node.rs @@ -0,0 +1,7 @@ +use super::plugin::WrappedNode; + +pub mod manager; + +pub struct Node { + inner: WrappedNode, +} \ No newline at end of file diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs new file mode 100644 index 00000000..fe9d9dd7 --- /dev/null +++ b/controller/src/application/node/manager.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; + +use anyhow::{Result}; + +use crate::application::TickService; + +use super::Node; + +pub struct NodeManager { + nodes: HashMap, +} + +impl NodeManager { + pub async fn init() -> Result { + Ok(Self { + nodes: HashMap::new(), + }) + } +} + +impl TickService for NodeManager { + async fn tick(&mut self) -> Result<()> { + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index b1065301..770b21c1 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -8,7 +8,6 @@ pub type WrappedPlugin = Box; pub type WrappedNode = Box; pub trait GenericPlugin { - fn name(&self) -> &str; fn init(&self) -> JoinHandle>; /* Ticking */ diff --git a/controller/src/application/plugin/runtime.rs b/controller/src/application/plugin/runtime.rs index f88d20fe..1b855cc8 100644 --- a/controller/src/application/plugin/runtime.rs +++ b/controller/src/application/plugin/runtime.rs @@ -33,9 +33,5 @@ pub(crate) mod source { pub fn get_source(&self) -> &[u8] { &self.source } - - pub fn get_path(&self) -> &PathBuf { - &self.path - } } } diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index 35e5615b..0f3d06d0 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -32,17 +32,12 @@ pub(crate) struct PluginState { } pub(crate) struct Plugin { - name: String, bindings: Arc, store: Arc>>, instance: ResourceAny, } impl GenericPlugin for Plugin { - fn name(&self) -> &str { - &self.name - } - fn init(&self) -> JoinHandle> { let (bindings, store, instance) = self.get(); spawn(async move { diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index 9861ae6f..369194cf 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -106,15 +106,15 @@ pub async fn init_wasm_plugins( if information.ready { info!( "Loaded plugin {} v{} by {}", - plugin.name, + name, information.version, information.authors.join(", ") ); - plugins.insert(plugin.name.clone(), Box::new(plugin)); + plugins.insert(name, Box::new(plugin)); } else { warn!( "Plugin {} marked itself as not ready, skipping...", - plugin.name + name ); } } @@ -216,7 +216,6 @@ impl Plugin { .await?; Ok(Plugin { - name: name.to_string(), bindings: Arc::new(bindings), store: Arc::new(Mutex::new(store)), instance, diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs new file mode 100644 index 00000000..5fec2890 --- /dev/null +++ b/controller/src/application/server.rs @@ -0,0 +1,4 @@ +pub mod manager; + +pub struct Server { +} \ No newline at end of file diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs new file mode 100644 index 00000000..61e766e5 --- /dev/null +++ b/controller/src/application/server/manager.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use anyhow::Result; +use uuid::Uuid; + +use crate::application::TickService; + +use super::Server; + +pub struct ServerManager { + servers: HashMap, +} + +impl ServerManager { + pub async fn init() -> Result { + Ok(Self { + servers: HashMap::new(), + }) + } +} + +impl TickService for ServerManager { + async fn tick(&mut self) -> Result<()> { + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/controller/src/storage.rs b/controller/src/storage.rs index 3cfb6992..e118eb3c 100644 --- a/controller/src/storage.rs +++ b/controller/src/storage.rs @@ -9,11 +9,11 @@ use std::path::PathBuf; const LOGS_DIRECTORY: &str = "logs"; const LATEST_LOG_FILE: &str = "latest.log"; -/* Cloudlets */ -const CLOUDLETS_DIRECTORY: &str = "cloudlets"; +/* Nodes */ +const NODES_DIRECTORY: &str = "nodes"; -/* Deployments */ -const DEPLOYMENTS_DIRECTORY: &str = "deployments"; +/* Groups */ +const GROUPS_DIRECTORY: &str = "groups"; /* Auth */ const AUTH_DIRECTORY: &str = "auth"; @@ -39,20 +39,20 @@ impl Storage { PathBuf::from(LOGS_DIRECTORY).join(LATEST_LOG_FILE) } - /* Cloudlets */ - pub fn get_cloudlets_directory() -> PathBuf { - PathBuf::from(CLOUDLETS_DIRECTORY) + /* Nodes */ + pub fn get_nodes_directory() -> PathBuf { + PathBuf::from(NODES_DIRECTORY) } - pub fn get_cloudlet_file(name: &str) -> PathBuf { - Storage::get_cloudlets_directory().join(format!("{}.toml", name)) + pub fn get_node_file(name: &str) -> PathBuf { + Storage::get_nodes_directory().join(format!("{}.toml", name)) } - /* Deployments */ - pub fn get_deployments_directory() -> PathBuf { - PathBuf::from(DEPLOYMENTS_DIRECTORY) + /* Groups */ + pub fn get_groups_directory() -> PathBuf { + PathBuf::from(GROUPS_DIRECTORY) } - pub fn get_deployment_file(name: &str) -> PathBuf { - Storage::get_deployments_directory().join(format!("{}.toml", name)) + pub fn get_group_file(name: &str) -> PathBuf { + Storage::get_groups_directory().join(format!("{}.toml", name)) } /* Auth */ diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 88ff410c..24593496 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -159,14 +159,14 @@ interface bridge { token: string, } - record instance-proposal { + record server-proposal { name: string, deployment: option, resources: resources, spec: spec, } - record instance { + record server { name: string, uuid: uuid, deployment: option, @@ -177,11 +177,11 @@ interface bridge { resource generic-node { constructor(cloud-identifier: string, name: string, id: option, capabilities: capabilities, controller: remote-controller); tick: func() -> result<_, scoped-errors>; - allocate-addresses: func(instance: instance-proposal) -> result, error-message>; + allocate-addresses: func(server: server-proposal) -> result, error-message>; deallocate-addresses: func(addresses: list
); - start-instance: func(instance: instance); - restart-instance: func(instance: instance); - stop-instance: func(instance: instance); + start-server: func(server: server); + restart-server: func(server: server); + stop-server: func(server: server); } resource generic-plugin { From c84579d8cc1a722e23e0231d70b3aad5533deb12 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:58:03 +0100 Subject: [PATCH 14/74] Nodes --- common/src/file.rs | 67 +++++++++++ common/src/lib.rs | 2 + common/src/network.rs | 21 ++++ controller/src/application.rs | 22 ++-- controller/src/application/group.rs | 3 +- controller/src/application/group/manager.rs | 2 +- controller/src/application/node.rs | 64 +++++++++- controller/src/application/node/manager.rs | 113 ++++++++++++++++-- controller/src/application/plugin.rs | 12 +- controller/src/application/plugin/manager.rs | 6 +- controller/src/application/plugin/runtime.rs | 4 +- .../src/application/plugin/runtime/wasm.rs | 74 ++++++++++-- .../application/plugin/runtime/wasm/init.rs | 34 +----- .../application/plugin/runtime/wasm/node.rs | 62 ++++++++++ controller/src/application/server.rs | 3 +- controller/src/application/server/manager.rs | 2 +- 16 files changed, 420 insertions(+), 71 deletions(-) create mode 100644 common/src/file.rs create mode 100644 common/src/network.rs create mode 100644 controller/src/application/plugin/runtime/wasm/node.rs diff --git a/common/src/file.rs b/common/src/file.rs new file mode 100644 index 00000000..d264874e --- /dev/null +++ b/common/src/file.rs @@ -0,0 +1,67 @@ +use std::{ + fs::{self}, + path::{Path, PathBuf}, +}; + +use anyhow::Result; +use simplelog::warn; + +use crate::config::LoadFromTomlFile; + +pub fn for_each_content(path: &Path) -> Result> { + Ok(fs::read_dir(path)? + .filter_map(|entry| { + entry + .ok() + .filter(|entry| !entry.path().is_dir()) + .and_then(|entry| { + let path = entry.path(); + match (path.file_name(), path.file_stem()) { + (Some(name), Some(stem)) => Some(( + path.to_owned(), + name.to_string_lossy().to_string(), + stem.to_string_lossy().to_string(), + )), + _ => { + warn!("Failed to read file names: {:?}", path); + None + } + } + }) + }) + .collect()) +} + +pub fn for_each_content_toml( + path: &Path, + error_message: &str, +) -> Result> { + Ok(fs::read_dir(path)? + .filter_map(|entry| { + entry + .ok() + .filter(|entry| !entry.path().is_dir()) + .and_then(|entry| match T::from_file(&entry.path()) { + Ok(value) => { + let path = entry.path(); + match (path.file_name(), path.file_stem()) { + (Some(name), Some(stem)) => Some(( + path.to_owned(), + name.to_string_lossy().to_string(), + stem.to_string_lossy().to_string(), + value, + )), + _ => { + warn!("Failed to read file names: {:?}", path); + None + } + } + } + Err(error) => { + warn!("{}@{:?}: {:?}", error_message, entry.path(), error); + None + } + }) + }) + .collect()) +} diff --git a/common/src/lib.rs b/common/src/lib.rs index c93acd08..124b989a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,8 @@ pub mod allocator; pub mod config; +pub mod file; pub mod init; pub mod name; +pub mod network; pub mod tick; pub mod version; diff --git a/common/src/network.rs b/common/src/network.rs new file mode 100644 index 00000000..03a3f46c --- /dev/null +++ b/common/src/network.rs @@ -0,0 +1,21 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct HostAndPort { + pub host: S, + pub port: u16, +} + +impl HostAndPort { + pub fn new(host: String, port: u16) -> Self { + Self { host, port } + } +} + +impl Display for HostAndPort { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "{}:{}", self.host, self.port) + } +} diff --git a/controller/src/application.rs b/controller/src/application.rs index e86d4b1e..d7e0de91 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -18,14 +18,11 @@ use tokio::{ time::interval, }; -use crate::{ - config::Config, - task::WrappedTask, -}; +use crate::{config::Config, task::WrappedTask}; -mod plugin; -mod node; mod group; +mod node; +mod plugin; mod server; const TICK_RATE: u64 = 20; @@ -52,13 +49,18 @@ pub struct Controller { impl Controller { pub async fn init(config: Config) -> Result { + let plugins = PluginManager::init(&config).await?; + let nodes = NodeManager::init(&plugins).await?; + let groups = GroupManager::init().await?; + let servers = ServerManager::init().await?; + Ok(Self { running: Arc::new(AtomicBool::new(true)), tasks: channel(TASK_BUFFER), - plugins: PluginManager::init(&config).await?, - nodes: NodeManager::init().await?, - groups: GroupManager::init().await?, - servers: ServerManager::init().await?, + plugins, + nodes, + groups, + servers, config, }) } diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index b53fea82..8d6b5d48 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -1,4 +1,3 @@ pub mod manager; -pub struct Group { -} \ No newline at end of file +pub struct Group {} diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs index eeb7efa2..d17fcdd8 100644 --- a/controller/src/application/group/manager.rs +++ b/controller/src/application/group/manager.rs @@ -26,4 +26,4 @@ impl TickService for GroupManager { async fn shutdown(&mut self) -> Result<()> { Ok(()) } -} \ No newline at end of file +} diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index 39c1f43b..07dd743f 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -1,7 +1,67 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use tokio::task::JoinHandle; +use url::Url; + use super::plugin::WrappedNode; pub mod manager; pub struct Node { - inner: WrappedNode, -} \ No newline at end of file + /* Plugin */ + plugin: String, + instance: WrappedNode, + + /* Settings */ + name: String, + capabilities: Capabilities, + status: LifecycleStatus, + + /* Controller */ + controller: RemoteController, +} + +impl Node { + pub fn tick(&self) -> JoinHandle> { + self.instance.tick() + } +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Capabilities { + memory: Option, + max_allocations: Option, + child: Option, +} + +#[derive(Serialize, Deserialize, Clone, Default, PartialEq)] +pub enum LifecycleStatus { + #[serde(rename = "inactive")] + #[default] + Inactive, + #[serde(rename = "active")] + Active, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct RemoteController { + address: Url, +} + +impl Capabilities { + pub fn get_memory(&self) -> Option { + self.memory + } + pub fn get_max_allocations(&self) -> Option { + self.max_allocations + } + pub fn get_child(&self) -> Option<&str> { + self.child.as_deref() + } +} + +impl RemoteController { + pub fn get_address(&self) -> &Url { + &self.address + } +} diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs index fe9d9dd7..4a197a55 100644 --- a/controller/src/application/node/manager.rs +++ b/controller/src/application/node/manager.rs @@ -1,8 +1,17 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fs}; -use anyhow::{Result}; +use anyhow::Result; +use common::file::for_each_content_toml; +use simplelog::{error, info, warn}; +use stored::StoredNode; -use crate::application::TickService; +use crate::{ + application::{ + plugin::{manager::PluginManager, WrappedNode}, + TickService, + }, + storage::Storage, +}; use super::Node; @@ -11,19 +20,107 @@ pub struct NodeManager { } impl NodeManager { - pub async fn init() -> Result { - Ok(Self { - nodes: HashMap::new(), - }) + pub async fn init(plugins: &PluginManager) -> Result { + info!("Loading nodes..."); + let mut nodes = HashMap::new(); + + let directory = Storage::get_nodes_directory(); + if !directory.exists() { + fs::create_dir_all(&directory)?; + } + + for (_, _, name, value) in + for_each_content_toml::(&directory, "Failed to read node from file")? + { + info!("Loading node {}", name); + + let plugin = match plugins.get_plugin(value.get_plugin()) { + Some(plugin) => plugin, + None => { + warn!( + "Plugin {} is not loaded, skipping node {}", + value.get_plugin(), + name + ); + continue; + } + }; + + match plugin + .init_node(&name, value.get_capabilities(), value.get_controller()) + .await + { + Ok(instance) => { + info!("Loaded node {}", name); + nodes.insert(name.clone(), Node::init(&name, value, instance)); + } + Err(error) => error!("Failed to initialize node {}: {}", name, error), + } + } + + info!("Loaded {} node(s)", nodes.len()); + Ok(Self { nodes }) + } +} + +impl Node { + pub fn init(name: &str, node: StoredNode, instance: WrappedNode) -> Self { + Self { + plugin: node.get_plugin().to_string(), + instance, + name: name.to_owned(), + capabilities: node.get_capabilities().clone(), + status: node.get_status().clone(), + controller: node.get_controller().clone(), + } } } impl TickService for NodeManager { async fn tick(&mut self) -> Result<()> { + for node in self.nodes.values() { + node.tick(); + } Ok(()) } async fn shutdown(&mut self) -> Result<()> { Ok(()) } -} \ No newline at end of file +} + +mod stored { + use common::config::{LoadFromTomlFile, SaveToTomlFile}; + use serde::{Deserialize, Serialize}; + + use crate::application::node::{Capabilities, LifecycleStatus, RemoteController}; + + #[derive(Serialize, Deserialize)] + pub struct StoredNode { + /* Settings */ + plugin: String, + capabilities: Capabilities, + status: LifecycleStatus, + + /* Controller */ + controller: RemoteController, + } + + impl StoredNode { + pub fn get_plugin(&self) -> &str { + &self.plugin + } + pub fn get_capabilities(&self) -> &Capabilities { + &self.capabilities + } + pub fn get_status(&self) -> &LifecycleStatus { + &self.status + } + pub fn get_controller(&self) -> &RemoteController { + &self.controller + } + } + + impl LoadFromTomlFile for StoredNode {} + impl SaveToTomlFile for StoredNode {} +} diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index 770b21c1..f27a8f56 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -1,5 +1,8 @@ use anyhow::Result; use tokio::task::JoinHandle; +use tonic::async_trait; + +use super::node::{Capabilities, RemoteController}; pub mod manager; mod runtime; @@ -7,8 +10,15 @@ mod runtime; pub type WrappedPlugin = Box; pub type WrappedNode = Box; +#[async_trait] pub trait GenericPlugin { - fn init(&self) -> JoinHandle>; + async fn init(&self) -> Result; + async fn init_node( + &self, + name: &str, + capabilities: &Capabilities, + remote: &RemoteController, + ) -> Result; /* Ticking */ fn tick(&self) -> JoinHandle>; diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs index ac1ff181..08fa67e9 100644 --- a/controller/src/application/plugin/manager.rs +++ b/controller/src/application/plugin/manager.rs @@ -16,7 +16,7 @@ pub struct PluginManager { impl PluginManager { pub async fn init(config: &Config) -> Result { - info!("Initializing plugin system..."); + info!("Loading plugins..."); let mut plugins = HashMap::new(); @@ -26,6 +26,10 @@ impl PluginManager { info!("Loaded {} plugin(s)", plugins.len()); Ok(Self { plugins }) } + + pub fn get_plugin(&self, name: &str) -> Option<&WrappedPlugin> { + self.plugins.get(name) + } } impl TickService for PluginManager { diff --git a/controller/src/application/plugin/runtime.rs b/controller/src/application/plugin/runtime.rs index 1b855cc8..02d5effb 100644 --- a/controller/src/application/plugin/runtime.rs +++ b/controller/src/application/plugin/runtime.rs @@ -6,7 +6,7 @@ pub(crate) mod source { use std::{ fmt::{self, Display, Formatter}, fs, - path::PathBuf, + path::{Path, PathBuf}, }; use anyhow::Result; @@ -23,7 +23,7 @@ pub(crate) mod source { } impl Source { - pub fn from_file(path: &PathBuf) -> Result { + pub fn from_file(path: &Path) -> Result { Ok(Source { path: path.to_owned(), source: fs::read(path)?, diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index 0f3d06d0..5858cd99 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -2,15 +2,21 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; use generated::exports::plugin::system::bridge; +use node::PluginNode; use tokio::{spawn, sync::Mutex, task::JoinHandle}; +use tonic::async_trait; use wasmtime::{component::ResourceAny, AsContextMut, Store}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiView}; -use crate::application::plugin::{GenericPlugin, Information}; +use crate::application::{ + node::{Capabilities, RemoteController}, + plugin::{GenericPlugin, Information, WrappedNode}, +}; pub(crate) mod config; mod ext; pub mod init; +mod node; pub mod generated { use wasmtime::component::bindgen; @@ -37,20 +43,44 @@ pub(crate) struct Plugin { instance: ResourceAny, } +#[async_trait] impl GenericPlugin for Plugin { - fn init(&self) -> JoinHandle> { + async fn init(&self) -> Result { let (bindings, store, instance) = self.get(); - spawn(async move { - match bindings - .plugin_system_bridge() - .generic_plugin() - .call_init(store.lock().await.as_context_mut(), instance) - .await - { - Ok(information) => Ok(information.into()), - Err(error) => Err(error), - } - }) + let mut store = store.lock().await; + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_init(store.as_context_mut(), instance) + .await + { + Ok(information) => Ok(information.into()), + Err(error) => Err(error), + } + } + + async fn init_node( + &self, + name: &str, + capabilities: &Capabilities, + remote: &RemoteController, + ) -> Result { + let (bindings, store, instance) = self.get(); + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_init_node( + store.clone().lock().await.as_context_mut(), + instance, + name, + &capabilities.into(), + &remote.into(), + ) + .await? + { + Ok(instance) => Ok(Box::new(PluginNode::new(bindings, store, instance))), + Err(error) => Err(anyhow!(error)), + } } fn tick(&self) -> JoinHandle> { @@ -105,3 +135,21 @@ impl From for Information { } } } + +impl From<&Capabilities> for bridge::Capabilities { + fn from(val: &Capabilities) -> Self { + bridge::Capabilities { + memory: val.get_memory(), + max_allocations: val.get_max_allocations(), + child: val.get_child().map(|value| value.to_string()), + } + } +} + +impl From<&RemoteController> for bridge::RemoteController { + fn from(val: &RemoteController) -> Self { + bridge::RemoteController { + address: val.get_address().to_string(), + } + } +} diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index 369194cf..edc64b5f 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Result; +use common::file::for_each_content; use simplelog::{error, info, warn}; use tokio::sync::Mutex; use wasmtime::{ @@ -39,27 +40,11 @@ pub async fn init_wasm_plugins( } let amount = plugins.len(); - for entry in fs::read_dir(directory)? { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - error!("Failed to read plugin entry: {}", error); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() - || !path - .file_name() - .unwrap() - .to_string_lossy() - .ends_with(".wasm") - { + for (path, file_name, name) in for_each_content(&directory)? { + if !file_name.ends_with(".wasm") { continue; } - let name = path.file_stem().unwrap().to_string_lossy().to_string(); let source = match Source::from_file(&path) { Ok(source) => source, Err(error) => { @@ -102,7 +87,7 @@ pub async fn init_wasm_plugins( .await; match plugin { Ok(plugin) => match plugin.init().await { - Ok(Ok(information)) => { + Ok(information) => { if information.ready { info!( "Loaded plugin {} v{} by {}", @@ -112,17 +97,10 @@ pub async fn init_wasm_plugins( ); plugins.insert(name, Box::new(plugin)); } else { - warn!( - "Plugin {} marked itself as not ready, skipping...", - name - ); + warn!("Plugin {} marked itself as not ready, skipping...", name); } } - Ok(Err(error)) => error!("Failed to initialize plugin {}: {}", name, error), - Err(error) => error!( - "Failed to join plugin initialization task {}: {}", - name, error - ), + Err(error) => error!("Failed to initialize plugin {}: {}", name, error), }, Err(error) => error!( "Failed to compile plugin {} at location {}: {}", diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs new file mode 100644 index 00000000..8ea3992b --- /dev/null +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use anyhow::{anyhow, Result}; +use tokio::{spawn, sync::Mutex, task::JoinHandle}; +use wasmtime::{component::ResourceAny, AsContextMut, Store}; + +use crate::application::plugin::GenericNode; + +use super::{generated, PluginState}; + +pub struct PluginNode { + bindings: Arc, + store: Arc>>, + instance: ResourceAny, +} + +impl PluginNode { + pub fn new( + bindings: Arc, + store: Arc>>, + instance: ResourceAny, + ) -> Self { + Self { + bindings, + store, + instance, + } + } + + fn get( + &self, + ) -> ( + Arc, + Arc>>, + ResourceAny, + ) { + (self.bindings.clone(), self.store.clone(), self.instance) + } +} + +impl GenericNode for PluginNode { + fn tick(&self) -> JoinHandle> { + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_tick(store.lock().await.as_context_mut(), instance) + .await + { + Ok(result) => result.map_err(|errors| { + anyhow!(errors + .iter() + .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) + .collect::>() + .join("\n")) + }), + Err(error) => Err(error), + } + }) + } +} diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 5fec2890..6cc32e81 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -1,4 +1,3 @@ pub mod manager; -pub struct Server { -} \ No newline at end of file +pub struct Server {} diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 61e766e5..f7b0c861 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -27,4 +27,4 @@ impl TickService for ServerManager { async fn shutdown(&mut self) -> Result<()> { Ok(()) } -} \ No newline at end of file +} From a94541315ed6ec6efd77f4b4247bef6af0f7c16f Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 00:10:00 +0100 Subject: [PATCH 15/74] Groups --- controller/src/application.rs | 9 +- controller/src/application/group.rs | 74 +++++++++++- controller/src/application/group/manager.rs | 121 +++++++++++++++++-- controller/src/application/node.rs | 10 +- controller/src/application/node/manager.rs | 19 +-- controller/src/application/plugin/manager.rs | 9 +- controller/src/application/server.rs | 112 ++++++++++++++++- controller/src/application/server/manager.rs | 13 +- 8 files changed, 331 insertions(+), 36 deletions(-) diff --git a/controller/src/application.rs b/controller/src/application.rs index d7e0de91..fee91ef9 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -51,7 +51,7 @@ impl Controller { pub async fn init(config: Config) -> Result { let plugins = PluginManager::init(&config).await?; let nodes = NodeManager::init(&plugins).await?; - let groups = GroupManager::init().await?; + let groups = GroupManager::init(&nodes).await?; let servers = ServerManager::init().await?; Ok(Self { @@ -94,7 +94,7 @@ impl Controller { self.nodes.tick().await?; // Tick group manager - self.groups.tick().await?; + self.groups.tick(&self.servers).await?; // Tick server manager self.servers.tick().await?; @@ -130,8 +130,3 @@ impl Controller { .map_err(|error| error.into()) } } - -pub trait TickService { - async fn tick(&mut self) -> Result<()>; - async fn shutdown(&mut self) -> Result<()>; -} diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index 8d6b5d48..fe905ca9 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -1,3 +1,75 @@ +use anyhow::Result; +use common::allocator::NumberAllocator; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::{ + node::LifecycleStatus, + server::{manager::ServerManager, Resources, Spec}, +}; + pub mod manager; -pub struct Group {} +pub struct Group { + /* Settings */ + name: String, + status: LifecycleStatus, + + /* Where? */ + nodes: Vec, + constraints: StartConstraints, + scaling: ScalingPolicy, + + /* How? */ + resources: Resources, + spec: Spec, + + /* What do i need to know? */ + id_allocator: NumberAllocator, + servers: Vec, +} + +impl Group { + pub fn tick(&mut self, servers: &ServerManager) -> Result<()> { + if self.status == LifecycleStatus::Inactive { + // Do not tick this group because it is inactive + return Ok(()); + } + + let mut target_count = self.constraints.minimum; + + // Apply scling policy + if self.scaling.enabled { + self.servers.retain(|server| match server { + AssociatedUnit::Active(server) => servers.get_server(server).map_or(false, |server| { + if server.get_connected_users() as f32 / self.spec.get_max_players() as f32 >= self.scaling.start_threshold { + target_count += 1; + } + true + }), + _ => true, + }); + } + + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct StartConstraints { + minimum: u32, + maximum: u32, + priority: i32, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct ScalingPolicy { + enabled: bool, + start_threshold: f32, + stop_empty_units: bool, +} + +pub enum AssociatedUnit { + Queueing(Uuid), + Active(Uuid), +} diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs index d17fcdd8..68e7d214 100644 --- a/controller/src/application/group/manager.rs +++ b/controller/src/application/group/manager.rs @@ -1,8 +1,14 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fs, vec}; use anyhow::Result; +use common::{allocator::NumberAllocator, file::for_each_content_toml}; +use simplelog::{info, warn}; +use stored::StoredGroup; -use crate::application::TickService; +use crate::{ + application::{node::manager::NodeManager, server::manager::ServerManager}, + storage::Storage, +}; use super::Group; @@ -11,19 +17,116 @@ pub struct GroupManager { } impl GroupManager { - pub async fn init() -> Result { - Ok(Self { - groups: HashMap::new(), - }) + pub async fn init(nodes: &NodeManager) -> Result { + info!("Loading groups..."); + let mut groups = HashMap::new(); + + let directory = Storage::get_groups_directory(); + if !directory.exists() { + fs::create_dir_all(&directory)?; + } + + for (_, _, name, mut value) in + for_each_content_toml::(&directory, "Failed to read group from file")? + { + info!("Loading group {}", name); + + value.get_nodes_mut().retain(|node| { + if !nodes.has_node(node) { + warn!("Node {} is not loaded, skipping node {}", node, name); + return false; + } + true + }); + + info!("Loaded group {}", name); + groups.insert(name.clone(), Group::new(&name, value)); + } + + info!("Loaded {} group(s)", groups.len()); + Ok(Self { groups }) } } -impl TickService for GroupManager { - async fn tick(&mut self) -> Result<()> { +impl Group { + pub fn new(name: &str, group: StoredGroup) -> Self { + Self { + name: name.to_string(), + status: group.get_status().clone(), + nodes: group.get_nodes().clone(), + constraints: group.get_constraints().clone(), + scaling: group.get_scaling().clone(), + resources: group.get_resources().clone(), + spec: group.get_spec().clone(), + id_allocator: NumberAllocator::new(1..usize::MAX), + servers: vec![], + } + } +} + +// Ticking +impl GroupManager { + pub async fn tick(&mut self, servers: &ServerManager) -> Result<()> { + for group in self.groups.values_mut() { + group.tick(servers)?; + } Ok(()) } - async fn shutdown(&mut self) -> Result<()> { + pub async fn shutdown(&mut self) -> Result<()> { Ok(()) } } + +mod stored { + use common::config::{LoadFromTomlFile, SaveToTomlFile}; + use serde::{Deserialize, Serialize}; + + use crate::application::{ + group::{ScalingPolicy, StartConstraints}, + node::LifecycleStatus, + server::{Resources, Spec}, + }; + + #[derive(Serialize, Deserialize)] + pub struct StoredGroup { + /* Settings */ + status: LifecycleStatus, + + /* Where? */ + nodes: Vec, + constraints: StartConstraints, + scaling: ScalingPolicy, + + /* How? */ + resources: Resources, + spec: Spec, + } + + impl StoredGroup { + pub fn get_status(&self) -> &LifecycleStatus { + &self.status + } + pub fn get_nodes(&self) -> &Vec { + &self.nodes + } + pub fn get_nodes_mut(&mut self) -> &mut Vec { + &mut self.nodes + } + pub fn get_constraints(&self) -> &StartConstraints { + &self.constraints + } + pub fn get_scaling(&self) -> &ScalingPolicy { + &self.scaling + } + pub fn get_resources(&self) -> &Resources { + &self.resources + } + pub fn get_spec(&self) -> &Spec { + &self.spec + } + } + + impl LoadFromTomlFile for StoredGroup {} + impl SaveToTomlFile for StoredGroup {} +} diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index 07dd743f..27e9bab7 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -22,8 +22,14 @@ pub struct Node { } impl Node { - pub fn tick(&self) -> JoinHandle> { - self.instance.tick() + pub fn tick(&self) -> Result<()> { + if self.status == LifecycleStatus::Inactive { + // Do not tick this node because it is inactive + return Ok(()); + } + + self.instance.tick(); + Ok(()) } } diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs index 4a197a55..2f8323f4 100644 --- a/controller/src/application/node/manager.rs +++ b/controller/src/application/node/manager.rs @@ -8,7 +8,7 @@ use stored::StoredNode; use crate::{ application::{ plugin::{manager::PluginManager, WrappedNode}, - TickService, + }, storage::Storage, }; @@ -52,7 +52,7 @@ impl NodeManager { { Ok(instance) => { info!("Loaded node {}", name); - nodes.insert(name.clone(), Node::init(&name, value, instance)); + nodes.insert(name.clone(), Node::new(&name, value, instance)); } Err(error) => error!("Failed to initialize node {}: {}", name, error), } @@ -61,10 +61,14 @@ impl NodeManager { info!("Loaded {} node(s)", nodes.len()); Ok(Self { nodes }) } + + pub fn has_node(&self, name: &str) -> bool { + self.nodes.contains_key(name) + } } impl Node { - pub fn init(name: &str, node: StoredNode, instance: WrappedNode) -> Self { + pub fn new(name: &str, node: StoredNode, instance: WrappedNode) -> Self { Self { plugin: node.get_plugin().to_string(), instance, @@ -76,15 +80,16 @@ impl Node { } } -impl TickService for NodeManager { - async fn tick(&mut self) -> Result<()> { +// Ticking +impl NodeManager { + pub async fn tick(&mut self) -> Result<()> { for node in self.nodes.values() { - node.tick(); + node.tick()?; } Ok(()) } - async fn shutdown(&mut self) -> Result<()> { + pub async fn shutdown(&mut self) -> Result<()> { Ok(()) } } diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs index 08fa67e9..28b7eccf 100644 --- a/controller/src/application/plugin/manager.rs +++ b/controller/src/application/plugin/manager.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use simplelog::info; -use crate::{application::TickService, config::Config}; +use crate::{config::Config}; use super::WrappedPlugin; @@ -32,15 +32,16 @@ impl PluginManager { } } -impl TickService for PluginManager { - async fn tick(&mut self) -> Result<()> { +// Ticking +impl PluginManager { + pub async fn tick(&mut self) -> Result<()> { for plugin in self.plugins.values() { plugin.tick(); } Ok(()) } - async fn shutdown(&mut self) -> Result<()> { + pub async fn shutdown(&mut self) -> Result<()> { Ok(()) } } diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 6cc32e81..0060cdca 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -1,3 +1,113 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + pub mod manager; -pub struct Server {} +pub struct Server { + /* Settings */ + name: String, + uuid: Uuid, + group: Option, + node: String, + + /* Users */ + connected_users: u32, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Resources { + memory: u32, + swap: u32, + cpu: u32, + io: u32, + disk: u32, + ports: u32, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub enum DiskRetention { + #[serde(rename = "temporary")] + #[default] + Temporary, + #[serde(rename = "permanent")] + Permanent, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct FallbackPolicy { + enabled: bool, + priority: i32, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Spec { + settings: HashMap, + environment: HashMap, + disk_retention: DiskRetention, + image: String, + + max_players: u32, + fallback: FallbackPolicy, +} + +impl Server { + pub fn get_name(&self) -> &String { + &self.name + } + pub fn get_uuid(&self) -> &Uuid { + &self.uuid + } + pub fn get_group(&self) -> &Option { + &self.group + } + pub fn get_node(&self) -> &String { + &self.node + } + pub fn get_connected_users(&self) -> u32 { + self.connected_users + } +} + +impl Resources { + pub fn get_memory(&self) -> u32 { + self.memory + } + pub fn get_swap(&self) -> u32 { + self.swap + } + pub fn get_cpu(&self) -> u32 { + self.cpu + } + pub fn get_io(&self) -> u32 { + self.io + } + pub fn get_disk(&self) -> u32 { + self.disk + } + pub fn get_ports(&self) -> u32 { + self.ports + } +} + +impl Spec { + pub fn get_settings(&self) -> &HashMap { + &self.settings + } + pub fn get_environment(&self) -> &HashMap { + &self.environment + } + pub fn get_disk_retention(&self) -> &DiskRetention { + &self.disk_retention + } + pub fn get_image(&self) -> &String { + &self.image + } + pub fn get_max_players(&self) -> u32 { + self.max_players + } + pub fn get_fallback(&self) -> &FallbackPolicy { + &self.fallback + } +} diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index f7b0c861..d181dfe4 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -3,8 +3,6 @@ use std::collections::HashMap; use anyhow::Result; use uuid::Uuid; -use crate::application::TickService; - use super::Server; pub struct ServerManager { @@ -17,14 +15,19 @@ impl ServerManager { servers: HashMap::new(), }) } + + pub fn get_server(&self, uuid: &Uuid) -> Option<&Server> { + self.servers.get(uuid) + } } -impl TickService for ServerManager { - async fn tick(&mut self) -> Result<()> { +// Ticking +impl ServerManager { + pub async fn tick(&mut self) -> Result<()> { Ok(()) } - async fn shutdown(&mut self) -> Result<()> { + pub async fn shutdown(&mut self) -> Result<()> { Ok(()) } } From 35a1cb31d55ecf8b25a7a5a44e25d9901b157c9a Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:04:33 +0100 Subject: [PATCH 16/74] Group ticking --- controller/Cargo.toml | 3 + controller/configs/config.toml | 6 +- controller/src/application.rs | 9 +- controller/src/application/group.rs | 98 +++++++++++++-- controller/src/application/group/manager.rs | 7 +- controller/src/application/node.rs | 1 - controller/src/application/node/manager.rs | 7 +- controller/src/application/plugin/manager.rs | 2 +- .../application/plugin/runtime/wasm/config.rs | 4 +- .../application/plugin/runtime/wasm/ext.rs | 6 +- .../application/plugin/runtime/wasm/init.rs | 11 +- controller/src/application/server.rs | 114 ++++++++++-------- controller/src/application/server/manager.rs | 83 ++++++++++++- controller/src/config.rs | 20 +-- controller/src/main.rs | 6 +- controller/src/storage.rs | 42 +++---- 16 files changed, 293 insertions(+), 126 deletions(-) diff --git a/controller/Cargo.toml b/controller/Cargo.toml index f14b6664..00787de8 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -13,6 +13,9 @@ simplelog = { version = "0.12.2", features = ["paris"] } # Error handling anyhow = "1.0.95" +# Getters and setters +getset = "0.1.4" + # Signal handling ctrlc = "3.4.5" diff --git a/controller/configs/config.toml b/controller/configs/config.toml index f236f1f7..ee26e37e 100644 --- a/controller/configs/config.toml +++ b/controller/configs/config.toml @@ -30,8 +30,8 @@ nanos = 0 secs = 10 nanos = 0 -# The maximum time the controller will wait for an empty instance to be filled. -# If this timeout is reached, the instance will be stopped. -[timeouts.empty_instance] +# The maximum time the controller will wait for an empty server to be filled. +# If this timeout is reached, the server will be stopped. +[timeouts.empty_server] secs = 60 nanos = 0 \ No newline at end of file diff --git a/controller/src/application.rs b/controller/src/application.rs index fee91ef9..af94b699 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -7,6 +7,7 @@ use std::{ }; use anyhow::Result; +use getset::{Getters, MutGetters}; use group::manager::GroupManager; use node::manager::NodeManager; use plugin::manager::PluginManager; @@ -30,6 +31,7 @@ const TASK_BUFFER: usize = 128; pub type TaskSender = Sender; +#[derive(Getters, MutGetters)] pub struct Controller { /* State */ running: Arc, @@ -38,12 +40,17 @@ pub struct Controller { tasks: (TaskSender, Receiver), /* Components */ + #[getset(get = "pub", get_mut = "pub")] plugins: PluginManager, + #[getset(get = "pub", get_mut = "pub")] nodes: NodeManager, + #[getset(get = "pub", get_mut = "pub")] groups: GroupManager, + #[getset(get = "pub", get_mut = "pub")] servers: ServerManager, /* Config */ + #[getset(get = "pub")] config: Config, } @@ -94,7 +101,7 @@ impl Controller { self.nodes.tick().await?; // Tick group manager - self.groups.tick(&self.servers).await?; + self.groups.tick(&self.config, &mut self.servers).await?; // Tick server manager self.servers.tick().await?; diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index fe905ca9..25773b9e 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -1,11 +1,17 @@ use anyhow::Result; use common::allocator::NumberAllocator; use serde::{Deserialize, Serialize}; +use simplelog::debug; use uuid::Uuid; +use crate::{application::server::manager::StopRequest, config::Config}; + use super::{ node::LifecycleStatus, - server::{manager::ServerManager, Resources, Spec}, + server::{ + manager::{ServerManager, StartRequest}, + Resources, Spec, + }, }; pub mod manager; @@ -26,11 +32,11 @@ pub struct Group { /* What do i need to know? */ id_allocator: NumberAllocator, - servers: Vec, + servers: Vec, } impl Group { - pub fn tick(&mut self, servers: &ServerManager) -> Result<()> { + pub fn tick(&mut self, config: &Config, servers: &mut ServerManager) -> Result<()> { if self.status == LifecycleStatus::Inactive { // Do not tick this group because it is inactive return Ok(()); @@ -41,14 +47,84 @@ impl Group { // Apply scling policy if self.scaling.enabled { self.servers.retain(|server| match server { - AssociatedUnit::Active(server) => servers.get_server(server).map_or(false, |server| { - if server.get_connected_users() as f32 / self.spec.get_max_players() as f32 >= self.scaling.start_threshold { - target_count += 1; - } - true - }), + AssociatedServer::Active(server) => { + servers.get_server(server).is_some_and(|server| { + if *server.connected_users() as f32 / *self.spec.max_players() as f32 + >= self.scaling.start_threshold + { + target_count += 1; + } + true + }) + } _ => true, }); + + if self.scaling.stop_empty_servers && self.servers.len() as u32 > target_count { + let mut to_stop = self.servers.len() as u32 - target_count; + let mut requests = vec![]; + self.servers.retain(|server| match server { + AssociatedServer::Active(server) => { + servers.get_server_mut(server).is_some_and(|server| { + if server.connected_users() == &0 { + if server.flags().should_stop() && to_stop > 0 { + debug!( + "Server {} is empty and reached the timeout, stopping it...", + server.name() + ); + requests.push(StopRequest::new(None, server.uuid())); + to_stop -= 1; + } else { + debug!( + "Server {} is empty, starting stop timer...", + server.name() + ); + server + .flags_mut() + .replace_stop(*config.empty_server_timeout()); + } + } else if server.flags().is_stop_set() { + debug!( + "Server {} is no longer empty, clearing stop timer...", + server.name() + ); + server.flags_mut().clear_stop(); + } + true + }) + } + _ => true, + }); + servers.schedule_stops(requests); + } + } + + for requested in 0..(target_count as usize).saturating_sub(self.servers.len()) { + if (self.servers.len() + requested) >= target_count as usize { + break; + } + + let id = self + .id_allocator + .allocate() + .expect("We reached the maximum unit count. Wow this is a lot of units"); + let request = StartRequest::new( + None, + self.constraints.priority, + format!("{}-{}", self.name, id), + Some(self.name.clone()), + &self.nodes, + &self.resources, + &self.spec, + ); + self.servers + .push(AssociatedServer::Queueing(*request.uuid())); + debug!( + "Scheduled server({}) start for group {}", + request.name(), + self.name + ); + servers.schedule_start(request); } Ok(()) @@ -66,10 +142,10 @@ pub struct StartConstraints { pub struct ScalingPolicy { enabled: bool, start_threshold: f32, - stop_empty_units: bool, + stop_empty_servers: bool, } -pub enum AssociatedUnit { +pub enum AssociatedServer { Queueing(Uuid), Active(Uuid), } diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs index 68e7d214..f7bc1efb 100644 --- a/controller/src/application/group/manager.rs +++ b/controller/src/application/group/manager.rs @@ -7,6 +7,7 @@ use stored::StoredGroup; use crate::{ application::{node::manager::NodeManager, server::manager::ServerManager}, + config::Config, storage::Storage, }; @@ -21,7 +22,7 @@ impl GroupManager { info!("Loading groups..."); let mut groups = HashMap::new(); - let directory = Storage::get_groups_directory(); + let directory = Storage::groups_directory(); if !directory.exists() { fs::create_dir_all(&directory)?; } @@ -66,9 +67,9 @@ impl Group { // Ticking impl GroupManager { - pub async fn tick(&mut self, servers: &ServerManager) -> Result<()> { + pub async fn tick(&mut self, config: &Config, servers: &mut ServerManager) -> Result<()> { for group in self.groups.values_mut() { - group.tick(servers)?; + group.tick(config, servers)?; } Ok(()) } diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index 27e9bab7..318cf122 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -1,6 +1,5 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use tokio::task::JoinHandle; use url::Url; use super::plugin::WrappedNode; diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs index 2f8323f4..5158c6d9 100644 --- a/controller/src/application/node/manager.rs +++ b/controller/src/application/node/manager.rs @@ -6,10 +6,7 @@ use simplelog::{error, info, warn}; use stored::StoredNode; use crate::{ - application::{ - plugin::{manager::PluginManager, WrappedNode}, - - }, + application::plugin::{manager::PluginManager, WrappedNode}, storage::Storage, }; @@ -24,7 +21,7 @@ impl NodeManager { info!("Loading nodes..."); let mut nodes = HashMap::new(); - let directory = Storage::get_nodes_directory(); + let directory = Storage::nodes_directory(); if !directory.exists() { fs::create_dir_all(&directory)?; } diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs index 28b7eccf..85b9f1e0 100644 --- a/controller/src/application/plugin/manager.rs +++ b/controller/src/application/plugin/manager.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use simplelog::info; -use crate::{config::Config}; +use crate::config::Config; use super::WrappedPlugin; diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs index ded82777..cdab85a7 100644 --- a/controller/src/application/plugin/runtime/wasm/config.rs +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -84,7 +84,7 @@ impl Mount { impl PluginsConfig { pub fn parse() -> Result { - let path = Storage::get_wasm_plugins_config_file(); + let path = Storage::wasm_plugins_config_file(); if path.exists() { Self::from_file(&path) } else { @@ -110,7 +110,7 @@ impl PluginsConfig { } pub fn verify_engine_config() -> Result { - let path = Storage::get_wasm_engine_config_file(); + let path = Storage::wasm_engine_config_file(); if path.exists() { Ok(path) } else { diff --git a/controller/src/application/plugin/runtime/wasm/ext.rs b/controller/src/application/plugin/runtime/wasm/ext.rs index c7498f80..3d3e229f 100644 --- a/controller/src/application/plugin/runtime/wasm/ext.rs +++ b/controller/src/application/plugin/runtime/wasm/ext.rs @@ -21,11 +21,9 @@ impl PluginState { pub fn get_directory(&self, name: &str, directory: &Directory) -> Result { match &directory.reference { Reference::Controller => Ok(PathBuf::from(".").join(&directory.path)), - Reference::Data => { - Ok(Storage::get_data_directory_for_plugin(name).join(&directory.path)) - } + Reference::Data => Ok(Storage::data_directory_for_plugin(name).join(&directory.path)), Reference::Configs => { - Ok(Storage::get_config_directory_for_plugin(name).join(&directory.path)) + Ok(Storage::config_directory_for_plugin(name).join(&directory.path)) } } } diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index edc64b5f..efd82242 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -34,7 +34,7 @@ pub async fn init_wasm_plugins( verify_engine_config()?; let plugins_config = PluginsConfig::parse()?; - let directory = Storage::get_plugins_directory(); + let directory = Storage::plugins_directory(); if !directory.exists() { fs::create_dir_all(&directory)?; } @@ -56,8 +56,8 @@ pub async fn init_wasm_plugins( } }; - let config_directory = Storage::get_config_directory_for_plugin(&name); - let data_directory = Storage::get_data_directory_for_plugin(&name); + let config_directory = Storage::config_directory_for_plugin(&name); + let data_directory = Storage::data_directory_for_plugin(&name); if !config_directory.exists() { fs::create_dir_all(&config_directory).unwrap_or_else(|error| { warn!( @@ -127,8 +127,7 @@ impl Plugin { ) -> Result { let mut engine_config = wasmtime::Config::new(); engine_config.wasm_component_model(true).async_support(true); - if let Err(error) = engine_config.cache_config_load(Storage::get_wasm_engine_config_file()) - { + if let Err(error) = engine_config.cache_config_load(Storage::wasm_engine_config_file()) { warn!("Failed to enable caching for wasmtime engine: {}", error); } @@ -190,7 +189,7 @@ impl Plugin { let instance = bindings .plugin_system_bridge() .generic_plugin() - .call_constructor(&mut store, global_config.get_identifier()) + .call_constructor(&mut store, global_config.identifier()) .await?; Ok(Plugin { diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 0060cdca..4b1bfab7 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -1,28 +1,48 @@ -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; +use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; +use tokio::time::Instant; use uuid::Uuid; pub mod manager; +#[derive(Getters, MutGetters)] pub struct Server { /* Settings */ + #[getset(get = "pub")] name: String, + #[getset(get = "pub")] uuid: Uuid, + #[getset(get = "pub")] group: Option, + #[getset(get = "pub")] node: String, /* Users */ + #[getset(get = "pub")] connected_users: u32, + + /* States */ + #[getset(get = "pub", get_mut = "pub")] + health: Health, + #[getset(get = "pub", get_mut = "pub")] + flags: Flags, } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct Resources { + #[getset(get = "pub")] memory: u32, + #[getset(get = "pub")] swap: u32, + #[getset(get = "pub")] cpu: u32, + #[getset(get = "pub")] io: u32, + #[getset(get = "pub")] disk: u32, + #[getset(get = "pub")] ports: u32, } @@ -35,79 +55,67 @@ pub enum DiskRetention { Permanent, } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct FallbackPolicy { + #[getset(get = "pub")] enabled: bool, + #[getset(get = "pub")] priority: i32, } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct Spec { + #[getset(get = "pub")] settings: HashMap, + #[getset(get = "pub")] environment: HashMap, + #[getset(get = "pub")] disk_retention: DiskRetention, + #[getset(get = "pub")] image: String, + #[getset(get = "pub")] max_players: u32, + #[getset(get = "pub")] fallback: FallbackPolicy, } -impl Server { - pub fn get_name(&self) -> &String { - &self.name - } - pub fn get_uuid(&self) -> &Uuid { - &self.uuid - } - pub fn get_group(&self) -> &Option { - &self.group - } - pub fn get_node(&self) -> &String { - &self.node - } - pub fn get_connected_users(&self) -> u32 { - self.connected_users - } +pub struct Health { + next_check: Instant, + timeout: Duration, } -impl Resources { - pub fn get_memory(&self) -> u32 { - self.memory - } - pub fn get_swap(&self) -> u32 { - self.swap - } - pub fn get_cpu(&self) -> u32 { - self.cpu - } - pub fn get_io(&self) -> u32 { - self.io - } - pub fn get_disk(&self) -> u32 { - self.disk - } - pub fn get_ports(&self) -> u32 { - self.ports - } +pub struct Flags { + /* Required for the deployment system */ + pub stop: Option, } -impl Spec { - pub fn get_settings(&self) -> &HashMap { - &self.settings - } - pub fn get_environment(&self) -> &HashMap { - &self.environment +impl Flags { + pub fn is_stop_set(&self) -> bool { + self.stop.is_some() } - pub fn get_disk_retention(&self) -> &DiskRetention { - &self.disk_retention + pub fn should_stop(&self) -> bool { + self.stop.is_some_and(|stop| stop < Instant::now()) } - pub fn get_image(&self) -> &String { - &self.image + pub fn replace_stop(&mut self, timeout: Duration) { + self.stop = Some(Instant::now() + timeout); } - pub fn get_max_players(&self) -> u32 { - self.max_players + pub fn clear_stop(&mut self) { + self.stop = None; } - pub fn get_fallback(&self) -> &FallbackPolicy { - &self.fallback +} + +impl Health { + pub fn new(startup_time: Duration, timeout: Duration) -> Self { + Self { + next_check: Instant::now() + startup_time, + timeout, + } + } + pub fn reset(&mut self) { + self.next_check = Instant::now() + self.timeout; + } + pub fn is_dead(&self) -> bool { + Instant::now() > self.next_check } } diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index d181dfe4..1c4734bb 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -1,24 +1,49 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use anyhow::Result; +use getset::Getters; +use tokio::time::Instant; use uuid::Uuid; -use super::Server; + +use super::{Resources, Server, Spec}; pub struct ServerManager { + /* Servers */ servers: HashMap, + + /* Requests */ + start_requests: VecDeque, + stop_requests: VecDeque, } impl ServerManager { pub async fn init() -> Result { Ok(Self { servers: HashMap::new(), + start_requests: VecDeque::new(), + stop_requests: VecDeque::new(), }) } pub fn get_server(&self, uuid: &Uuid) -> Option<&Server> { self.servers.get(uuid) } + pub fn get_server_mut(&mut self, uuid: &Uuid) -> Option<&mut Server> { + self.servers.get_mut(uuid) + } + + pub fn schedule_start(&mut self, request: StartRequest) { + self.start_requests.push_back(request); + } + pub fn schedule_stop(&mut self, request: StopRequest) { + self.stop_requests.push_back(request); + } + pub fn schedule_stops(&mut self, requests: Vec) { + for request in requests.into_iter() { + self.stop_requests.push_back(request); + } + } } // Ticking @@ -31,3 +56,57 @@ impl ServerManager { Ok(()) } } + +#[derive(Getters)] +pub struct StartRequest { + /* Request */ + #[getset(get = "pub")] + uuid: Uuid, + when: Option, + + /* Server */ + #[getset(get = "pub")] + name: String, + group: Option, + nodes: Vec, + resources: Resources, + spec: Spec, + priority: i32, +} + +pub struct StopRequest { + when: Option, + server: Uuid, +} + +impl StartRequest { + pub fn new( + when: Option, + priority: i32, + name: String, + group: Option, + nodes: &[String], + resources: &Resources, + spec: &Spec, + ) -> Self { + Self { + uuid: Uuid::new_v4(), + when, + priority, + name, + group, + nodes: nodes.to_vec(), + resources: resources.clone(), + spec: spec.clone(), + } + } +} + +impl StopRequest { + pub fn new(when: Option, server: &Uuid) -> Self { + Self { + when, + server: *server, + } + } +} diff --git a/controller/src/config.rs b/controller/src/config.rs index 6bd04493..cd069fbb 100644 --- a/controller/src/config.rs +++ b/controller/src/config.rs @@ -20,7 +20,7 @@ struct Timeouts { restart: Duration, heartbeat: Duration, transfer: Duration, - empty_instance: Duration, + empty_server: Duration, } #[derive(Deserialize, Serialize)] @@ -32,7 +32,7 @@ pub struct Config { impl Config { pub fn parse() -> Result { - let path = Storage::get_primary_config_file(); + let path = Storage::primary_config_file(); if path.exists() { Self::from_file(&path) } else { @@ -44,32 +44,32 @@ impl Config { } } - pub fn get_identifier(&self) -> &str { + pub fn identifier(&self) -> &str { &self.identifier } - pub fn get_network_bind(&self) -> &SocketAddr { + pub fn network_bind(&self) -> &SocketAddr { &self.network.bind } - pub fn get_startup_timeout(&self) -> &Duration { + pub fn startup_timeout(&self) -> &Duration { &self.timeouts.startup } - pub fn get_restart_timeout(&self) -> &Duration { + pub fn restart_timeout(&self) -> &Duration { &self.timeouts.restart } - pub fn get_heartbeat_timeout(&self) -> &Duration { + pub fn heartbeat_timeout(&self) -> &Duration { &self.timeouts.heartbeat } - pub fn get_transfer_timeout(&self) -> &Duration { + pub fn transfer_timeout(&self) -> &Duration { &self.timeouts.transfer } - pub fn get_empty_instance_timeout(&self) -> &Duration { - &self.timeouts.empty_instance + pub fn empty_server_timeout(&self) -> &Duration { + &self.timeouts.empty_server } } diff --git a/controller/src/main.rs b/controller/src/main.rs index d424c771..cbd64eff 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -20,7 +20,7 @@ pub const AUTHORS: [&str; 1] = ["HttpRafa"]; #[tokio::main] async fn main() -> Result<()> { let arguments = Arguments::parse(); - CloudInit::init_logging(arguments.debug, false, Storage::get_latest_log_file()); + CloudInit::init_logging(arguments.debug, false, Storage::latest_log_file()); CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); let beginning = Instant::now(); @@ -35,7 +35,7 @@ async fn main() -> Result<()> { } #[derive(Parser)] -pub struct Arguments { +struct Arguments { #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] - pub debug: bool, + debug: bool, } diff --git a/controller/src/storage.rs b/controller/src/storage.rs index e118eb3c..43fdc538 100644 --- a/controller/src/storage.rs +++ b/controller/src/storage.rs @@ -35,58 +35,58 @@ pub struct Storage; impl Storage { /* Logs */ - pub fn get_latest_log_file() -> PathBuf { + pub fn latest_log_file() -> PathBuf { PathBuf::from(LOGS_DIRECTORY).join(LATEST_LOG_FILE) } /* Nodes */ - pub fn get_nodes_directory() -> PathBuf { + pub fn nodes_directory() -> PathBuf { PathBuf::from(NODES_DIRECTORY) } - pub fn get_node_file(name: &str) -> PathBuf { - Storage::get_nodes_directory().join(format!("{}.toml", name)) + pub fn node_file(name: &str) -> PathBuf { + Storage::nodes_directory().join(format!("{}.toml", name)) } /* Groups */ - pub fn get_groups_directory() -> PathBuf { + pub fn groups_directory() -> PathBuf { PathBuf::from(GROUPS_DIRECTORY) } - pub fn get_group_file(name: &str) -> PathBuf { - Storage::get_groups_directory().join(format!("{}.toml", name)) + pub fn group_file(name: &str) -> PathBuf { + Storage::groups_directory().join(format!("{}.toml", name)) } /* Auth */ - pub fn get_users_directory() -> PathBuf { + pub fn users_directory() -> PathBuf { PathBuf::from(AUTH_DIRECTORY).join(USERS_DIRECTORY) } - pub fn get_user_file(name: &str) -> PathBuf { - Storage::get_users_directory().join(format!("{}.toml", name)) + pub fn user_file(name: &str) -> PathBuf { + Storage::users_directory().join(format!("{}.toml", name)) } /* Configs */ - pub fn get_configs_directory() -> PathBuf { + pub fn configs_directory() -> PathBuf { PathBuf::from(CONFIG_DIRECTORY) } - pub fn get_primary_config_file() -> PathBuf { - Storage::get_configs_directory().join(PRIMARY_CONFIG_FILE) + pub fn primary_config_file() -> PathBuf { + Storage::configs_directory().join(PRIMARY_CONFIG_FILE) } /* Wasm Configs */ - pub fn get_wasm_plugins_config_file() -> PathBuf { - Storage::get_configs_directory().join(WASM_PLUGINS_CONFIG_FILE) + pub fn wasm_plugins_config_file() -> PathBuf { + Storage::configs_directory().join(WASM_PLUGINS_CONFIG_FILE) } - pub fn get_wasm_engine_config_file() -> PathBuf { - Storage::get_configs_directory().join(WASM_ENGINE_CONFIG_FILE) + pub fn wasm_engine_config_file() -> PathBuf { + Storage::configs_directory().join(WASM_ENGINE_CONFIG_FILE) } /* Plugins */ - pub fn get_plugins_directory() -> PathBuf { + pub fn plugins_directory() -> PathBuf { PathBuf::from(PLUGINS_DIRECTORY) } - pub fn get_data_directory_for_plugin(name: &str) -> PathBuf { + pub fn data_directory_for_plugin(name: &str) -> PathBuf { PathBuf::from(DATA_DIRECTORY).join(name) } - pub fn get_config_directory_for_plugin(name: &str) -> PathBuf { - Storage::get_configs_directory().join(name) + pub fn config_directory_for_plugin(name: &str) -> PathBuf { + Storage::configs_directory().join(name) } } From baa0537fef2f8881e4a23ac671f67e60ec30ba7b Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:50:33 +0100 Subject: [PATCH 17/74] More plugin stuff --- controller/Cargo.toml | 1 + controller/src/application/node.rs | 43 ++-- controller/src/application/plugin.rs | 18 +- controller/src/application/plugin/manager.rs | 10 + .../src/application/plugin/runtime/wasm.rs | 29 ++- .../application/plugin/runtime/wasm/config.rs | 1 + .../application/plugin/runtime/wasm/init.rs | 5 +- .../application/plugin/runtime/wasm/node.rs | 199 +++++++++++++++++- controller/src/application/server.rs | 4 + controller/src/application/server/manager.rs | 5 +- protocol/wit/plugin.wit | 23 +- 11 files changed, 293 insertions(+), 45 deletions(-) diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 00787de8..066d5d2e 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -34,6 +34,7 @@ toml = "0.8.19" # Async runtime tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.31" # API url = { version = "2.5.4", features = ["serde"] } diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index 318cf122..ca8510a9 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -1,8 +1,13 @@ use anyhow::Result; +use common::network::HostAndPort; +use getset::Getters; use serde::{Deserialize, Serialize}; use url::Url; -use super::plugin::WrappedNode; +use super::{ + plugin::WrappedNode, + server::{Resources, Spec}, +}; pub mod manager; @@ -32,10 +37,23 @@ impl Node { } } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Getters)] +pub struct Allocation { + #[getset(get = "pub")] + pub ports: Vec, + #[getset(get = "pub")] + pub resources: Resources, + #[getset(get = "pub")] + pub spec: Spec, +} + +#[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct Capabilities { + #[getset(get = "pub")] memory: Option, + #[getset(get = "pub")] max_allocations: Option, + #[getset(get = "pub")] child: Option, } @@ -48,25 +66,8 @@ pub enum LifecycleStatus { Active, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Getters)] pub struct RemoteController { + #[getset(get = "pub")] address: Url, } - -impl Capabilities { - pub fn get_memory(&self) -> Option { - self.memory - } - pub fn get_max_allocations(&self) -> Option { - self.max_allocations - } - pub fn get_child(&self) -> Option<&str> { - self.child.as_deref() - } -} - -impl RemoteController { - pub fn get_address(&self) -> &Url { - &self.address - } -} diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index f27a8f56..721a247b 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -1,8 +1,12 @@ use anyhow::Result; +use common::network::HostAndPort; use tokio::task::JoinHandle; use tonic::async_trait; -use super::node::{Capabilities, RemoteController}; +use super::{ + node::{Capabilities, RemoteController}, + server::{manager::StartRequest, Server}, +}; pub mod manager; mod runtime; @@ -20,6 +24,9 @@ pub trait GenericPlugin { remote: &RemoteController, ) -> Result; + /* Shutdown */ + fn shutdown(&self) -> JoinHandle>; + /* Ticking */ fn tick(&self) -> JoinHandle>; } @@ -27,6 +34,15 @@ pub trait GenericPlugin { pub trait GenericNode { /* Ticking */ fn tick(&self) -> JoinHandle>; + + /* Prepare */ + fn allocate(&self, request: &StartRequest) -> JoinHandle>>; + fn free(&self, ports: &[HostAndPort]) -> JoinHandle>; + + /* Servers */ + fn start(&self, token: &str, server: &Server) -> JoinHandle>; + fn restart(&self, server: &Server) -> JoinHandle>; + fn stop(&self, server: &Server) -> JoinHandle>; } pub struct Information { diff --git a/controller/src/application/plugin/manager.rs b/controller/src/application/plugin/manager.rs index 85b9f1e0..f3cad355 100644 --- a/controller/src/application/plugin/manager.rs +++ b/controller/src/application/plugin/manager.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use anyhow::Result; +use futures::future::join_all; use simplelog::info; use crate::config::Config; @@ -42,6 +43,15 @@ impl PluginManager { } pub async fn shutdown(&mut self) -> Result<()> { + let tasks = join_all(self.plugins.values().map(|plugin| plugin.shutdown())).await; + + for task in tasks { + if let Err(error) = task { + return Err(error.into()); + } else if let Ok(Err(error)) = task { + return Err(error); + } + } Ok(()) } } diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index 5858cd99..c9885605 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -83,6 +83,27 @@ impl GenericPlugin for Plugin { } } + fn shutdown(&self) -> JoinHandle> { + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_plugin() + .call_shutdown(store.lock().await.as_context_mut(), instance) + .await + { + Ok(result) => result.map_err(|errors| { + anyhow!(errors + .iter() + .map(|error| format!("Scope: {}, Message: {}", error.scope, error.message)) + .collect::>() + .join("\n")) + }), + Err(error) => Err(error), + } + }) + } + fn tick(&self) -> JoinHandle> { let (bindings, store, instance) = self.get(); spawn(async move { @@ -139,9 +160,9 @@ impl From for Information { impl From<&Capabilities> for bridge::Capabilities { fn from(val: &Capabilities) -> Self { bridge::Capabilities { - memory: val.get_memory(), - max_allocations: val.get_max_allocations(), - child: val.get_child().map(|value| value.to_string()), + memory: *val.memory(), + max_allocations: *val.max_allocations(), + child: val.child().as_ref().map(|value| value.to_string()), } } } @@ -149,7 +170,7 @@ impl From<&Capabilities> for bridge::Capabilities { impl From<&RemoteController> for bridge::RemoteController { fn from(val: &RemoteController) -> Self { bridge::RemoteController { - address: val.get_address().to_string(), + address: val.address().to_string(), } } } diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs index cdab85a7..38c7aec5 100644 --- a/controller/src/application/plugin/runtime/wasm/config.rs +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -2,6 +2,7 @@ use std::{fs, path::PathBuf}; use anyhow::Result; use common::config::LoadFromTomlFile; +use getset::Getters; use regex::Regex; use serde::{Deserialize, Serialize}; use simplelog::warn; diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index efd82242..1eddaffb 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -126,7 +126,10 @@ impl Plugin { config_directory: &Path, ) -> Result { let mut engine_config = wasmtime::Config::new(); - engine_config.wasm_component_model(true).async_support(true); + engine_config + .wasm_component_model(true) + .async_support(true) + .epoch_interruption(true); if let Err(error) = engine_config.cache_config_load(Storage::wasm_engine_config_file()) { warn!("Failed to enable caching for wasmtime engine: {}", error); } diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs index 8ea3992b..dad32222 100644 --- a/controller/src/application/plugin/runtime/wasm/node.rs +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -1,12 +1,20 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; +use common::network::HostAndPort; use tokio::{spawn, sync::Mutex, task::JoinHandle}; use wasmtime::{component::ResourceAny, AsContextMut, Store}; -use crate::application::plugin::GenericNode; +use crate::application::{ + node::Allocation, + plugin::GenericNode, + server::{manager::StartRequest, DiskRetention, Resources, Server, Spec}, +}; -use super::{generated, PluginState}; +use super::{ + generated::{self, exports::plugin::system::bridge}, + PluginState, +}; pub struct PluginNode { bindings: Arc, @@ -44,7 +52,7 @@ impl GenericNode for PluginNode { spawn(async move { match bindings .plugin_system_bridge() - .generic_plugin() + .generic_node() .call_tick(store.lock().await.as_context_mut(), instance) .await { @@ -59,4 +67,189 @@ impl GenericNode for PluginNode { } }) } + + fn allocate(&self, request: &StartRequest) -> JoinHandle>> { + let proposal: bridge::ServerProposal = request.into(); + + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_node() + .call_allocate(store.lock().await.as_context_mut(), instance, &proposal) + .await + { + Ok(result) => result + .map(|ports| { + ports + .into_iter() + .map(|port| HostAndPort::new(port.host, port.port)) + .collect() + }) + .map_err(|error| anyhow!(error)), + Err(error) => Err(error), + } + }) + } + + fn free(&self, ports: &[HostAndPort]) -> JoinHandle> { + let ports = ports.iter().map(|port| port.into()).collect::>(); + + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_node() + .call_free(store.lock().await.as_context_mut(), instance, &ports) + .await + { + Ok(()) => Ok(()), + Err(error) => Err(error), + } + }) + } + + fn start(&self, token: &str, server: &Server) -> JoinHandle> { + let token = token.to_string(); + let server = server.into(); + + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_node() + .call_start( + store.lock().await.as_context_mut(), + instance, + &token, + &server, + ) + .await + { + Ok(()) => Ok(()), + Err(error) => Err(error), + } + }) + } + + fn restart(&self, server: &Server) -> JoinHandle> { + let server = server.into(); + + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_node() + .call_restart(store.lock().await.as_context_mut(), instance, &server) + .await + { + Ok(()) => Ok(()), + Err(error) => Err(error), + } + }) + } + + fn stop(&self, server: &Server) -> JoinHandle> { + let server = server.into(); + + let (bindings, store, instance) = self.get(); + spawn(async move { + match bindings + .plugin_system_bridge() + .generic_node() + .call_stop(store.lock().await.as_context_mut(), instance, &server) + .await + { + Ok(()) => Ok(()), + Err(error) => Err(error), + } + }) + } +} + +impl From<&HostAndPort> for bridge::Address { + fn from(val: &HostAndPort) -> Self { + bridge::Address { + host: val.host.clone(), + port: val.port, + } + } +} + +impl From<(&String, &String)> for bridge::KeyValue { + fn from(val: (&String, &String)) -> Self { + bridge::KeyValue { + key: val.0.clone(), + value: val.1.clone(), + } + } +} + +impl From<&DiskRetention> for bridge::DiskRetention { + fn from(val: &DiskRetention) -> Self { + match val { + DiskRetention::Permanent => bridge::DiskRetention::Permanent, + DiskRetention::Temporary => bridge::DiskRetention::Temporary, + } + } +} + +impl From<&Spec> for bridge::Spec { + fn from(val: &Spec) -> Self { + bridge::Spec { + settings: val + .settings() + .iter() + .map(|setting| setting.into()) + .collect(), + environment: val.environment().iter().map(|env| env.into()).collect(), + disk_retention: val.disk_retention().into(), + image: val.image().clone(), + } + } +} + +impl From<&Resources> for bridge::Resources { + fn from(val: &Resources) -> Self { + bridge::Resources { + memory: *val.memory(), + swap: *val.swap(), + cpu: *val.cpu(), + io: *val.io(), + disk: *val.disk(), + ports: *val.ports(), + } + } +} + +impl From<&Allocation> for bridge::Allocation { + fn from(val: &Allocation) -> Self { + bridge::Allocation { + ports: val.ports.iter().map(|address| address.into()).collect(), + resources: val.resources().into(), + spec: (&val.spec).into(), + } + } +} + +impl From<&Server> for bridge::Server { + fn from(val: &Server) -> Self { + bridge::Server { + name: val.name().clone(), + uuid: val.uuid().to_string(), + deployment: val.group().clone(), + allocation: val.allocation().into(), + } + } +} + +impl From<&StartRequest> for bridge::ServerProposal { + fn from(val: &StartRequest) -> Self { + bridge::ServerProposal { + name: val.name().clone(), + deployment: val.group().clone(), + resources: val.resources().into(), + spec: val.spec().into(), + } + } } diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 4b1bfab7..987d87f9 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; use tokio::time::Instant; use uuid::Uuid; +use super::node::Allocation; + pub mod manager; #[derive(Getters, MutGetters)] @@ -18,6 +20,8 @@ pub struct Server { group: Option, #[getset(get = "pub")] node: String, + #[getset(get = "pub")] + allocation: Allocation, /* Users */ #[getset(get = "pub")] diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 1c4734bb..694db23a 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -5,7 +5,6 @@ use getset::Getters; use tokio::time::Instant; use uuid::Uuid; - use super::{Resources, Server, Spec}; pub struct ServerManager { @@ -67,9 +66,13 @@ pub struct StartRequest { /* Server */ #[getset(get = "pub")] name: String, + #[getset(get = "pub")] group: Option, + #[getset(get = "pub")] nodes: Vec, + #[getset(get = "pub")] resources: Resources, + #[getset(get = "pub")] spec: Spec, priority: i32, } diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 24593496..9bbdaf42 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -129,7 +129,7 @@ interface bridge { cpu: u32, io: u32, disk: u32, - addresses: u32, + ports: u32, } record setting { @@ -137,7 +137,7 @@ interface bridge { value: string, } - variant retention { + variant disk-retention { permanent, temporary, } @@ -145,20 +145,16 @@ interface bridge { record spec { settings: list, environment: list, - disk-retention: retention, + disk-retention: disk-retention, image: string, } record allocation { - addresses: list
, + ports: list
, resources: resources, spec: spec, } - record auth { - token: string, - } - record server-proposal { name: string, deployment: option, @@ -171,17 +167,16 @@ interface bridge { uuid: uuid, deployment: option, allocation: allocation, - auth: auth, } resource generic-node { constructor(cloud-identifier: string, name: string, id: option, capabilities: capabilities, controller: remote-controller); tick: func() -> result<_, scoped-errors>; - allocate-addresses: func(server: server-proposal) -> result, error-message>; - deallocate-addresses: func(addresses: list
); - start-server: func(server: server); - restart-server: func(server: server); - stop-server: func(server: server); + allocate: func(server: server-proposal) -> result, error-message>; + free: func(addresses: list
); + start: func(token: string, server: server); + restart: func(server: server); + stop: func(server: server); } resource generic-plugin { From 8ed5f0dcf9740265e8d7172778caea32071ac8de Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 16:27:53 +0100 Subject: [PATCH 18/74] Move token into server struct to prevent reverse HashMap lookups --- controller/src/application/plugin.rs | 2 +- controller/src/application/plugin/runtime/wasm/node.rs | 5 ++--- controller/src/application/server.rs | 2 ++ protocol/wit/plugin.wit | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index 721a247b..0ddf483f 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -40,7 +40,7 @@ pub trait GenericNode { fn free(&self, ports: &[HostAndPort]) -> JoinHandle>; /* Servers */ - fn start(&self, token: &str, server: &Server) -> JoinHandle>; + fn start(&self, server: &Server) -> JoinHandle>; fn restart(&self, server: &Server) -> JoinHandle>; fn stop(&self, server: &Server) -> JoinHandle>; } diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs index dad32222..e963c037 100644 --- a/controller/src/application/plugin/runtime/wasm/node.rs +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -109,8 +109,7 @@ impl GenericNode for PluginNode { }) } - fn start(&self, token: &str, server: &Server) -> JoinHandle> { - let token = token.to_string(); + fn start(&self, server: &Server) -> JoinHandle> { let server = server.into(); let (bindings, store, instance) = self.get(); @@ -121,7 +120,6 @@ impl GenericNode for PluginNode { .call_start( store.lock().await.as_context_mut(), instance, - &token, &server, ) .await @@ -239,6 +237,7 @@ impl From<&Server> for bridge::Server { uuid: val.uuid().to_string(), deployment: val.group().clone(), allocation: val.allocation().into(), + token: val.token().clone() } } } diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 987d87f9..cbb70a28 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -22,6 +22,8 @@ pub struct Server { node: String, #[getset(get = "pub")] allocation: Allocation, + #[getset(get = "pub")] + token: String, /* Users */ #[getset(get = "pub")] diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 9bbdaf42..58b8018e 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -167,6 +167,7 @@ interface bridge { uuid: uuid, deployment: option, allocation: allocation, + token: string, } resource generic-node { @@ -174,7 +175,7 @@ interface bridge { tick: func() -> result<_, scoped-errors>; allocate: func(server: server-proposal) -> result, error-message>; free: func(addresses: list
); - start: func(token: string, server: server); + start: func(server: server); restart: func(server: server); stop: func(server: server); } From 84d1e4dcd2dd3197d3cf78b4695a7c259cc457c7 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:38:23 +0100 Subject: [PATCH 19/74] Server start/restart/stop --- controller/src/application.rs | 12 +- controller/src/application/auth.rs | 25 ++ controller/src/application/auth/validator.rs | 109 ++++++ controller/src/application/group.rs | 23 ++ controller/src/application/group/manager.rs | 51 +-- controller/src/application/node.rs | 19 +- controller/src/application/node/manager.rs | 40 +-- .../application/plugin/runtime/wasm/config.rs | 1 - .../application/plugin/runtime/wasm/node.rs | 8 +- controller/src/application/server.rs | 12 + controller/src/application/server/manager.rs | 336 +++++++++++++++++- .../src/application/server/manager/action.rs | 177 +++++++++ controller/src/main.rs | 2 + controller/src/storage.rs | 3 +- 14 files changed, 739 insertions(+), 79 deletions(-) create mode 100644 controller/src/application/auth.rs create mode 100644 controller/src/application/auth/validator.rs create mode 100644 controller/src/application/server/manager/action.rs diff --git a/controller/src/application.rs b/controller/src/application.rs index af94b699..21d8905b 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -7,6 +7,7 @@ use std::{ }; use anyhow::Result; +use auth::validator::{AuthValidator, WrappedAuthValidator}; use getset::{Getters, MutGetters}; use group::manager::GroupManager; use node::manager::NodeManager; @@ -21,6 +22,7 @@ use tokio::{ use crate::{config::Config, task::WrappedTask}; +mod auth; mod group; mod node; mod plugin; @@ -39,6 +41,9 @@ pub struct Controller { /* Tasks */ tasks: (TaskSender, Receiver), + /* Auth */ + validator: WrappedAuthValidator, + /* Components */ #[getset(get = "pub", get_mut = "pub")] plugins: PluginManager, @@ -56,6 +61,8 @@ pub struct Controller { impl Controller { pub async fn init(config: Config) -> Result { + let validator = AuthValidator::init()?; + let plugins = PluginManager::init(&config).await?; let nodes = NodeManager::init(&plugins).await?; let groups = GroupManager::init(&nodes).await?; @@ -64,6 +71,7 @@ impl Controller { Ok(Self { running: Arc::new(AtomicBool::new(true)), tasks: channel(TASK_BUFFER), + validator, plugins, nodes, groups, @@ -104,7 +112,9 @@ impl Controller { self.groups.tick(&self.config, &mut self.servers).await?; // Tick server manager - self.servers.tick().await?; + self.servers + .tick(&self.config, &self.nodes, &mut self.groups, &self.validator) + .await?; Ok(()) } diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs new file mode 100644 index 00000000..3900cc9d --- /dev/null +++ b/controller/src/application/auth.rs @@ -0,0 +1,25 @@ + +use uuid::Uuid; + +pub mod validator; + +const DEFAULT_ADMIN_USERNAME: &str = "admin"; + +pub type AuthToken = String; + +#[derive(Clone)] +pub enum Authorization { + User(AdminUser), + Server(Uuid), +} + +#[derive(Clone)] +pub struct AdminUser { + pub username: String, +} + +impl AdminUser { + pub fn new(username: String) -> Self { + Self { username } + } +} diff --git a/controller/src/application/auth/validator.rs b/controller/src/application/auth/validator.rs new file mode 100644 index 00000000..4e99843c --- /dev/null +++ b/controller/src/application/auth/validator.rs @@ -0,0 +1,109 @@ +use std::{collections::HashMap, fs, sync::Arc}; + +use anyhow::Result; +use common::{config::SaveToTomlFile, file::for_each_content_toml}; +use simplelog::{error, info}; +use stored::StoredUser; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::storage::Storage; + +use super::{AdminUser, AuthToken, Authorization}; + +pub type WrappedAuthValidator = Arc; + +pub struct AuthValidator { + tokens: RwLock>, +} + +impl AuthValidator { + pub fn init() -> Result { + info!("Loading users..."); + let mut tokens = HashMap::new(); + + let directory = Storage::users_directory(); + if !directory.exists() { + fs::create_dir_all(&directory)?; + } + + for (_, _, name, value) in + for_each_content_toml::(&directory, "Failed to read user from file")? + { + info!("Loaded user {}", name); + tokens.insert( + value.token().clone(), + Authorization::User(AdminUser::new(name)), + ); + } + + info!("Loaded {} user(s)", tokens.len()); + Ok(Arc::new(Self { + tokens: RwLock::new(tokens), + })) + } + + pub async fn has_access(&self, token: &str) -> Option { + self.tokens.read().await.get(token).cloned() + } + + pub async fn unregister(&self, token: &str) { + self.tokens.write().await.remove(token); + } + + pub async fn register_server(&self, uuid: Uuid) -> String { + let token = format!( + "sctl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + + self.tokens + .write() + .await + .insert(token.clone(), Authorization::Server(uuid)); + + token + } + + pub async fn register_user(&self, username: &str) -> Option { + let token = format!( + "actl_{}{}", + Uuid::new_v4().as_simple(), + Uuid::new_v4().as_simple() + ); + if let Err(error) = StoredUser::new(&token).save(&Storage::user_file(username), true) { + error!("Failed to save user({}) to file: {}", username, error); + return None; + } + self.tokens.write().await.insert( + token.clone(), + Authorization::User(AdminUser::new(username.to_string())), + ); + + Some(token) + } +} + +mod stored { + use common::config::{LoadFromTomlFile, SaveToTomlFile}; + use getset::Getters; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Getters)] + pub struct StoredUser { + #[getset(get = "pub")] + token: String, + } + + impl StoredUser { + pub fn new(token: &str) -> Self { + Self { + token: token.to_string(), + } + } + } + + impl LoadFromTomlFile for StoredUser {} + impl SaveToTomlFile for StoredUser {} +} diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index 25773b9e..b5f82527 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -129,6 +129,29 @@ impl Group { Ok(()) } + + pub fn set_server_active(&mut self, server_uuid: &Uuid) { + self.servers.retain(|server| { + if let AssociatedServer::Queueing(uuid) = server { + if uuid == server_uuid { + return false; + } + } + true + }); + self.servers.push(AssociatedServer::Active(*server_uuid)); + } + + pub fn remove_server(&mut self, server_uuid: &Uuid) { + self.servers.retain(|server| { + if let AssociatedServer::Active(uuid) = server { + if uuid == server_uuid { + return false; + } + } + true + }); + } } #[derive(Serialize, Deserialize, Clone, Default)] diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs index f7bc1efb..eaafe4a1 100644 --- a/controller/src/application/group/manager.rs +++ b/controller/src/application/group/manager.rs @@ -32,7 +32,7 @@ impl GroupManager { { info!("Loading group {}", name); - value.get_nodes_mut().retain(|node| { + value.nodes_mut().retain(|node| { if !nodes.has_node(node) { warn!("Node {} is not loaded, skipping node {}", node, name); return false; @@ -47,18 +47,22 @@ impl GroupManager { info!("Loaded {} group(s)", groups.len()); Ok(Self { groups }) } + + pub fn get_group_mut(&mut self, name: &str) -> Option<&mut Group> { + self.groups.get_mut(name) + } } impl Group { pub fn new(name: &str, group: StoredGroup) -> Self { Self { name: name.to_string(), - status: group.get_status().clone(), - nodes: group.get_nodes().clone(), - constraints: group.get_constraints().clone(), - scaling: group.get_scaling().clone(), - resources: group.get_resources().clone(), - spec: group.get_spec().clone(), + status: group.status().clone(), + nodes: group.nodes().clone(), + constraints: group.constraints().clone(), + scaling: group.scaling().clone(), + resources: group.resources().clone(), + spec: group.spec().clone(), id_allocator: NumberAllocator::new(1..usize::MAX), servers: vec![], } @@ -81,6 +85,7 @@ impl GroupManager { mod stored { use common::config::{LoadFromTomlFile, SaveToTomlFile}; + use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; use crate::application::{ @@ -89,45 +94,27 @@ mod stored { server::{Resources, Spec}, }; - #[derive(Serialize, Deserialize)] + #[derive(Serialize, Deserialize, Getters, MutGetters)] pub struct StoredGroup { /* Settings */ + #[getset(get = "pub", get_mut = "pub")] status: LifecycleStatus, /* Where? */ + #[getset(get = "pub", get_mut = "pub")] nodes: Vec, + #[getset(get = "pub", get_mut = "pub")] constraints: StartConstraints, + #[getset(get = "pub", get_mut = "pub")] scaling: ScalingPolicy, /* How? */ + #[getset(get = "pub", get_mut = "pub")] resources: Resources, + #[getset(get = "pub", get_mut = "pub")] spec: Spec, } - impl StoredGroup { - pub fn get_status(&self) -> &LifecycleStatus { - &self.status - } - pub fn get_nodes(&self) -> &Vec { - &self.nodes - } - pub fn get_nodes_mut(&mut self) -> &mut Vec { - &mut self.nodes - } - pub fn get_constraints(&self) -> &StartConstraints { - &self.constraints - } - pub fn get_scaling(&self) -> &ScalingPolicy { - &self.scaling - } - pub fn get_resources(&self) -> &Resources { - &self.resources - } - pub fn get_spec(&self) -> &Spec { - &self.spec - } - } - impl LoadFromTomlFile for StoredGroup {} impl SaveToTomlFile for StoredGroup {} } diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index ca8510a9..a18bf564 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -2,11 +2,12 @@ use anyhow::Result; use common::network::HostAndPort; use getset::Getters; use serde::{Deserialize, Serialize}; +use tokio::task::JoinHandle; use url::Url; use super::{ plugin::WrappedNode, - server::{Resources, Spec}, + server::{manager::StartRequest, Resources, Server, Spec}, }; pub mod manager; @@ -35,6 +36,22 @@ impl Node { self.instance.tick(); Ok(()) } + + pub fn allocate(&self, request: &StartRequest) -> JoinHandle>>> { + self.instance.allocate(request) + } + pub fn free(&self, ports: &[HostAndPort]) -> JoinHandle> { + self.instance.free(ports) + } + pub fn start(&self, server: &Server) -> JoinHandle> { + self.instance.start(server) + } + pub fn restart(&self, server: &Server) -> JoinHandle> { + self.instance.restart(server) + } + pub fn stop(&self, server: &Server) -> JoinHandle> { + self.instance.stop(server) + } } #[derive(Getters)] diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs index 5158c6d9..cffe165b 100644 --- a/controller/src/application/node/manager.rs +++ b/controller/src/application/node/manager.rs @@ -31,12 +31,12 @@ impl NodeManager { { info!("Loading node {}", name); - let plugin = match plugins.get_plugin(value.get_plugin()) { + let plugin = match plugins.get_plugin(value.plugin()) { Some(plugin) => plugin, None => { warn!( "Plugin {} is not loaded, skipping node {}", - value.get_plugin(), + value.plugin(), name ); continue; @@ -44,7 +44,7 @@ impl NodeManager { }; match plugin - .init_node(&name, value.get_capabilities(), value.get_controller()) + .init_node(&name, value.capabilities(), value.controller()) .await { Ok(instance) => { @@ -62,17 +62,21 @@ impl NodeManager { pub fn has_node(&self, name: &str) -> bool { self.nodes.contains_key(name) } + + pub fn get_node(&self, name: &str) -> Option<&Node> { + self.nodes.get(name) + } } impl Node { pub fn new(name: &str, node: StoredNode, instance: WrappedNode) -> Self { Self { - plugin: node.get_plugin().to_string(), + plugin: node.plugin().to_string(), instance, name: name.to_owned(), - capabilities: node.get_capabilities().clone(), - status: node.get_status().clone(), - controller: node.get_controller().clone(), + capabilities: node.capabilities().clone(), + status: node.status().clone(), + controller: node.controller().clone(), } } } @@ -93,36 +97,26 @@ impl NodeManager { mod stored { use common::config::{LoadFromTomlFile, SaveToTomlFile}; + use getset::Getters; use serde::{Deserialize, Serialize}; use crate::application::node::{Capabilities, LifecycleStatus, RemoteController}; - #[derive(Serialize, Deserialize)] + #[derive(Serialize, Deserialize, Getters)] pub struct StoredNode { /* Settings */ + #[getset(get = "pub")] plugin: String, + #[getset(get = "pub")] capabilities: Capabilities, + #[getset(get = "pub")] status: LifecycleStatus, /* Controller */ + #[getset(get = "pub")] controller: RemoteController, } - impl StoredNode { - pub fn get_plugin(&self) -> &str { - &self.plugin - } - pub fn get_capabilities(&self) -> &Capabilities { - &self.capabilities - } - pub fn get_status(&self) -> &LifecycleStatus { - &self.status - } - pub fn get_controller(&self) -> &RemoteController { - &self.controller - } - } - impl LoadFromTomlFile for StoredNode {} impl SaveToTomlFile for StoredNode {} } diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs index 38c7aec5..cdab85a7 100644 --- a/controller/src/application/plugin/runtime/wasm/config.rs +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -2,7 +2,6 @@ use std::{fs, path::PathBuf}; use anyhow::Result; use common::config::LoadFromTomlFile; -use getset::Getters; use regex::Regex; use serde::{Deserialize, Serialize}; use simplelog::warn; diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs index e963c037..b51cd844 100644 --- a/controller/src/application/plugin/runtime/wasm/node.rs +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -117,11 +117,7 @@ impl GenericNode for PluginNode { match bindings .plugin_system_bridge() .generic_node() - .call_start( - store.lock().await.as_context_mut(), - instance, - &server, - ) + .call_start(store.lock().await.as_context_mut(), instance, &server) .await { Ok(()) => Ok(()), @@ -237,7 +233,7 @@ impl From<&Server> for bridge::Server { uuid: val.uuid().to_string(), deployment: val.group().clone(), allocation: val.allocation().into(), - token: val.token().clone() + token: val.token().clone(), } } } diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index cbb70a28..94eb39db 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -33,6 +33,8 @@ pub struct Server { #[getset(get = "pub", get_mut = "pub")] health: Health, #[getset(get = "pub", get_mut = "pub")] + state: State, + #[getset(get = "pub", get_mut = "pub")] flags: Flags, } @@ -61,6 +63,15 @@ pub enum DiskRetention { Permanent, } +#[derive(PartialEq, Clone)] +pub enum State { + Starting, + Preparing, + Restarting, + Running, + Stopping, +} + #[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct FallbackPolicy { #[getset(get = "pub")] @@ -91,6 +102,7 @@ pub struct Health { timeout: Duration, } +#[derive(Default)] pub struct Flags { /* Required for the deployment system */ pub stop: Option, diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 694db23a..120e2799 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -1,27 +1,44 @@ -use std::collections::{HashMap, VecDeque}; +use std::{ + collections::{BinaryHeap, HashMap}, + mem, +}; use anyhow::Result; +use common::network::HostAndPort; use getset::Getters; -use tokio::time::Instant; +use simplelog::{debug, warn}; +use tokio::{task::JoinHandle, time::Instant}; use uuid::Uuid; -use super::{Resources, Server, Spec}; +use crate::{ + application::{ + auth::validator::WrappedAuthValidator, group::manager::GroupManager, + node::manager::NodeManager, + }, + config::Config, +}; + +use super::{Resources, Server, Spec, State}; + + mod action; pub struct ServerManager { /* Servers */ servers: HashMap, /* Requests */ - start_requests: VecDeque, - stop_requests: VecDeque, + start_requests: BinaryHeap, + restart_requests: Vec, + stop_requests: Vec, } impl ServerManager { pub async fn init() -> Result { Ok(Self { servers: HashMap::new(), - start_requests: VecDeque::new(), - stop_requests: VecDeque::new(), + start_requests: BinaryHeap::new(), + restart_requests: vec![], + stop_requests: vec![], }) } @@ -33,21 +50,246 @@ impl ServerManager { } pub fn schedule_start(&mut self, request: StartRequest) { - self.start_requests.push_back(request); + self.start_requests.push(request); + } + pub fn schedule_restart(&mut self, request: RestartRequest) { + self.restart_requests.push(request); } pub fn schedule_stop(&mut self, request: StopRequest) { - self.stop_requests.push_back(request); + self.stop_requests.push(request); } pub fn schedule_stops(&mut self, requests: Vec) { - for request in requests.into_iter() { - self.stop_requests.push_back(request); - } + self.stop_requests.extend(requests); } } // Ticking impl ServerManager { - pub async fn tick(&mut self) -> Result<()> { + pub async fn tick( + &mut self, + config: &Config, + nodes: &NodeManager, + groups: &mut GroupManager, + validator: &WrappedAuthValidator, + ) -> Result<()> { + // Check health of servers + for server in self.servers.values() { + if server.health.is_dead() { + match server.state { + State::Starting | State::Running => { + warn!("Unit {} failed to establish online status within the expected startup time of {:.2?}.", server.name, config.restart_timeout()); + } + _ => { + warn!("Server {} has not checked in for {:.2?}, indicating a potential error.", server.name, server.health.timeout); + } + } + self.restart_requests + .push(RestartRequest::new(None, &server.uuid)); + } + } + + // Stop all servers that have been requested to stop + let mut requests = vec![]; + while let Some(mut request) = self.stop_requests.pop() { + if let Some(when) = request.when { + if when > Instant::now() { + requests.push(request); + continue; + } + } + + let mut reinsert = false; + request.stage = match mem::replace(&mut request.stage, ActionStage::Queued) { + ActionStage::Queued => { + debug!("Freeing resources for server {}", request.server); + match Self::free(&request, &mut self.servers, nodes) { + Ok(handle) => { + reinsert = true; + ActionStage::Freeing(handle) + } + Err(error) => { + warn!("Failed to free server {}: {}", request.server, error); + ActionStage::Failed + } + } + } + ActionStage::Freeing(handle) => { + if handle.is_finished() { + handle.await??; + debug!("Stopping server {}", request.server); + match Self::stop(&request, &mut self.servers, nodes, groups, validator).await { + Ok(handle) => { + reinsert = true; + ActionStage::Running(handle) + } + Err(error) => { + warn!("Failed to stop server {}: {}", request.server, error); + ActionStage::Failed + } + } + } else { + reinsert = true; + ActionStage::Freeing(handle) + } + } + ActionStage::Running(handle) => { + if handle.is_finished() { + handle.await??; + debug!("Server {} has been stopped", request.server); + ActionStage::Finished + } else { + reinsert = true; + ActionStage::Running(handle) + } + } + _ => ActionStage::Finished, + }; + if reinsert { + requests.push(request); + } + } + self.stop_requests.extend(requests); + + // Restart all servers that have been requested to restart + let mut requests = vec![]; + while let Some(mut request) = self.restart_requests.pop() { + if let Some(when) = request.when { + if when > Instant::now() { + requests.push(request); + continue; + } + } + + let mut reinsert = false; + request.stage = match mem::replace(&mut request.stage, ActionStage::Queued) { + ActionStage::Queued => { + debug!("Restarting server {}", request.server); + match Self::restart(&request, &mut self.servers, config, nodes) { + Ok(handle) => { + reinsert = true; + ActionStage::Running(handle) + } + Err(error) => { + warn!("Failed to restart server {}: {}", request.server, error); + ActionStage::Failed + } + } + } + ActionStage::Running(handle) => { + if handle.is_finished() { + handle.await??; + debug!("Server {} has been restarted", request.server); + ActionStage::Finished + } else { + reinsert = true; + ActionStage::Running(handle) + } + } + _ => ActionStage::Finished, + }; + if reinsert { + requests.push(request); + } + } + self.restart_requests.extend(requests); + + // Start all servers that have been requested to start + let mut requests = vec![]; + while let Some(mut request) = self.start_requests.pop() { + if request.nodes.is_empty() { + warn!( + "Server {} has no nodes available to start on.", + request.name + ); + continue; + } + + if let Some(when) = request.when { + if when > Instant::now() { + requests.push(request); + continue; + } + } + + let mut reinsert = false; + request.stage = match mem::replace(&mut request.stage, StartStage::Queued) { + StartStage::Queued => { + debug!("Allocating resources for server {}", request.name); + match Self::allocate(0, &request, nodes) { + Ok(handle) => { + reinsert = true; + StartStage::Allocating((0, handle)) + } + Err(error) => { + warn!( + "Failed to allocate resources for server {}: {}", + request.name, error + ); + reinsert = false; + StartStage::Failed + } + } + } + StartStage::Allocating((index, handle)) => { + reinsert = true; + if handle.is_finished() { + let ports = handle.await?; + if let Ok(ports) = ports { + debug!("Creating server {}", request.name); + match Self::start( + index, + &request, + ports, + &mut self.servers, + config, + nodes, + groups, + validator, + ) + .await + { + Ok(handle) => StartStage::Creating(handle), + Err(error) => { + warn!("Failed to create server {}: {}", request.name, error); + reinsert = false; + StartStage::Failed + } + } + } else { + match Self::allocate(index + 1, &request, nodes) { + Ok(handle) => StartStage::Allocating((index + 1, handle)), + Err(error) => { + warn!( + "Failed to allocate resources for server {}: {}", + request.name, error + ); + reinsert = false; + StartStage::Failed + } + } + } + } else { + StartStage::Allocating((index, handle)) + } + } + StartStage::Creating(handle) => { + if handle.is_finished() { + handle.await??; + debug!("Server {} has been created", request.name); + StartStage::Started + } else { + reinsert = true; + StartStage::Creating(handle) + } + } + _ => StartStage::Started, + }; + if reinsert { + requests.push(request); + } + } + self.start_requests.extend(requests); + Ok(()) } @@ -74,12 +316,50 @@ pub struct StartRequest { resources: Resources, #[getset(get = "pub")] spec: Spec, + #[getset(get = "pub")] priority: i32, + + /* Stage */ + #[getset(get = "pub")] + stage: StartStage, } +#[derive(Getters)] +pub struct RestartRequest { + /* Request */ + when: Option, + server: Uuid, + + /* Stage */ + #[getset(get = "pub")] + stage: ActionStage, +} + +#[derive(Getters)] pub struct StopRequest { + /* Request */ when: Option, server: Uuid, + + /* Stage */ + #[getset(get = "pub")] + stage: ActionStage, +} + +enum ActionStage { + Queued, + Freeing(JoinHandle>), + Running(JoinHandle>), + Finished, + Failed, +} + +enum StartStage { + Queued, + Allocating((usize, JoinHandle>>)), + Creating(JoinHandle>), + Started, + Failed, } impl StartRequest { @@ -101,6 +381,17 @@ impl StartRequest { nodes: nodes.to_vec(), resources: resources.clone(), spec: spec.clone(), + stage: StartStage::Queued, + } + } +} + +impl RestartRequest { + pub fn new(when: Option, server: &Uuid) -> Self { + Self { + when, + server: *server, + stage: ActionStage::Queued, } } } @@ -110,6 +401,25 @@ impl StopRequest { Self { when, server: *server, + stage: ActionStage::Queued, } } } + +impl Ord for StartRequest { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.priority.cmp(&other.priority) + } +} +impl PartialOrd for StartRequest { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for StartRequest {} +impl PartialEq for StartRequest { + fn eq(&self, other: &Self) -> bool { + self.priority == other.priority + } +} diff --git a/controller/src/application/server/manager/action.rs b/controller/src/application/server/manager/action.rs new file mode 100644 index 00000000..7491ed8c --- /dev/null +++ b/controller/src/application/server/manager/action.rs @@ -0,0 +1,177 @@ +use std::collections::HashMap; + +use anyhow::{anyhow, Result}; +use common::network::HostAndPort; +use simplelog::{error, warn}; +use tokio::task::JoinHandle; +use uuid::Uuid; + +use crate::{ + application::{ + auth::validator::WrappedAuthValidator, + group::manager::GroupManager, + node::{manager::NodeManager, Allocation}, + server::{Flags, Health, Server, State}, + }, + config::Config, +}; + +use super::{RestartRequest, ServerManager, StartRequest, StopRequest}; + +impl ServerManager { + pub fn allocate( + index: usize, + request: &StartRequest, + nodes: &NodeManager, + ) -> Result>>> { + if let Some(name) = request.nodes.get(index) { + let node = nodes.get_node(name); + if let Some(node) = node { + Ok(node.allocate(request)) + } else { + Err(anyhow!( + "Node {} not found while trying to allocate ports for server {}", + name, + request.name + )) + } + } else { + Err(anyhow!( + "Index of node in request is out of bounds. Has someone tampered with the request?" + )) + } + } + pub async fn start( + index: usize, + request: &StartRequest, + ports: Vec, + servers: &mut HashMap, + config: &Config, + nodes: &NodeManager, + groups: &mut GroupManager, + validator: &WrappedAuthValidator, + ) -> Result>> { + if let Some(name) = request.nodes.get(index) { + let node = nodes.get_node(name); + if let Some(node) = node { + let mut server = Server { + uuid: request.uuid, + name: request.name.clone(), + group: request.group.clone(), + node: name.clone(), + allocation: Allocation { + ports, + resources: request.resources.clone(), + spec: request.spec.clone(), + }, + connected_users: 0, + token: validator.register_server(request.uuid).await, + health: Health::new(*config.startup_timeout(), *config.heartbeat_timeout()), + state: State::Starting, + flags: Flags::default(), + }; + let handle = node.start(&server); + if let Some(group) = &server.group { + if let Some(group) = groups.get_group_mut(group) { + group.set_server_active(&server.uuid); + } else { + warn!("Group {} not found while trying to start server {}. Removing group from server", group, request.name); + server.group = None; + } + } + servers.insert(server.uuid, server); + Ok(handle) + } else { + Err(anyhow!( + "Node {} not found while trying to allocate ports for server {}", + name, + request.name + )) + } + } else { + Err(anyhow!( + "Index of node in request is out of bounds. Has someone tampered with the request?" + )) + } + } + pub fn restart( + request: &RestartRequest, + servers: &mut HashMap, + config: &Config, + nodes: &NodeManager, + ) -> Result>> { + if let Some(server) = servers.get_mut(&request.server) { + if let Some(node) = nodes.get_node(&server.node) { + server.state = State::Restarting; + server.health = Health::new(*config.startup_timeout(), *config.heartbeat_timeout()); + Ok(node.restart(server)) + } else { + Err(anyhow!( + "Node {} not found while trying to restart {}", + server.node, + request.server + )) + } + } else { + Err(anyhow!( + "Server {} not found while trying to restart", + request.server + )) + } + } + pub fn free(request: &StopRequest, servers: &mut HashMap, nodes: &NodeManager) -> Result>> { + if let Some(server) = servers.get(&request.server) { + if let Some(node) = nodes.get_node(&server.node) { + Ok(node.free(&server.allocation.ports)) + } else { + Err(anyhow!( + "Node {} not found while trying to free resources {}", + server.node, + request.server + )) + } + } else { + Err(anyhow!( + "Server {} not found while trying to free resources", + request.server + )) + } + } + pub async fn stop( + request: &StopRequest, + servers: &mut HashMap, + nodes: &NodeManager, + groups: &mut GroupManager, + validator: &WrappedAuthValidator, + ) -> Result>> { + if let Some(server) = servers.get_mut(&request.server) { + if let Some(node) = nodes.get_node(&server.node) { + if let Some(group) = &server.group { + if let Some(group) = groups.get_group_mut(group) { + group.remove_server(&server.uuid); + } else { + error!("Group {} not found while trying to stop server {}. Removing group from server", group, server.name); + server.group = None; + } + } + validator.unregister(&server.token).await; + + // TODO: Cleanup connected users + // TODO: Cleanup subscriptions + + Ok(node.stop(server)) + } else { + Err(anyhow!( + "Node {} not found while trying to stop {}", + server.node, + request.server + )) + } + } else { + Err(anyhow!( + "Server {} not found while trying to stop", + request.server + )) + } + } +} diff --git a/controller/src/main.rs b/controller/src/main.rs index cbd64eff..5f18027c 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -1,3 +1,5 @@ +#![feature(extract_if)] + use anyhow::Result; use application::Controller; use clap::{ArgAction, Parser}; diff --git a/controller/src/storage.rs b/controller/src/storage.rs index 43fdc538..c0c72ba5 100644 --- a/controller/src/storage.rs +++ b/controller/src/storage.rs @@ -16,7 +16,6 @@ const NODES_DIRECTORY: &str = "nodes"; const GROUPS_DIRECTORY: &str = "groups"; /* Auth */ -const AUTH_DIRECTORY: &str = "auth"; const USERS_DIRECTORY: &str = "users"; /* Configs */ @@ -57,7 +56,7 @@ impl Storage { /* Auth */ pub fn users_directory() -> PathBuf { - PathBuf::from(AUTH_DIRECTORY).join(USERS_DIRECTORY) + PathBuf::from(USERS_DIRECTORY) } pub fn user_file(name: &str) -> PathBuf { Storage::users_directory().join(format!("{}.toml", name)) From b19c51bf24d2fd2cea8f9702e1fe9baea1a3a08c Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:42:14 +0100 Subject: [PATCH 20/74] Clippy --- controller/src/application/auth.rs | 1 - controller/src/application/server/manager.rs | 28 ++++++++++--------- .../src/application/server/manager/action.rs | 6 +++- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs index 3900cc9d..a624ac38 100644 --- a/controller/src/application/auth.rs +++ b/controller/src/application/auth.rs @@ -1,4 +1,3 @@ - use uuid::Uuid; pub mod validator; diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 120e2799..1b859d3b 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -20,7 +20,7 @@ use crate::{ use super::{Resources, Server, Spec, State}; - mod action; +mod action; pub struct ServerManager { /* Servers */ @@ -117,20 +117,22 @@ impl ServerManager { if handle.is_finished() { handle.await??; debug!("Stopping server {}", request.server); - match Self::stop(&request, &mut self.servers, nodes, groups, validator).await { - Ok(handle) => { - reinsert = true; - ActionStage::Running(handle) - } - Err(error) => { - warn!("Failed to stop server {}: {}", request.server, error); - ActionStage::Failed + match Self::stop(&request, &mut self.servers, nodes, groups, validator) + .await + { + Ok(handle) => { + reinsert = true; + ActionStage::Running(handle) + } + Err(error) => { + warn!("Failed to stop server {}: {}", request.server, error); + ActionStage::Failed + } } + } else { + reinsert = true; + ActionStage::Freeing(handle) } - } else { - reinsert = true; - ActionStage::Freeing(handle) - } } ActionStage::Running(handle) => { if handle.is_finished() { diff --git a/controller/src/application/server/manager/action.rs b/controller/src/application/server/manager/action.rs index 7491ed8c..9a408cd1 100644 --- a/controller/src/application/server/manager/action.rs +++ b/controller/src/application/server/manager/action.rs @@ -119,7 +119,11 @@ impl ServerManager { )) } } - pub fn free(request: &StopRequest, servers: &mut HashMap, nodes: &NodeManager) -> Result>> { + pub fn free( + request: &StopRequest, + servers: &mut HashMap, + nodes: &NodeManager, + ) -> Result>> { if let Some(server) = servers.get(&request.server) { if let Some(node) = nodes.get_node(&server.node) { Ok(node.free(&server.allocation.ports)) From 1fe867a26ddc6ad581bf5e76a86ae2f00ce43fd7 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:50:07 +0100 Subject: [PATCH 21/74] Users --- controller/Cargo.toml | 2 +- controller/src/application.rs | 33 +++++++-- controller/src/application/auth/validator.rs | 8 +-- controller/src/application/group.rs | 12 ++-- controller/src/application/group/manager.rs | 5 +- controller/src/application/node/manager.rs | 5 +- .../plugin/runtime/wasm/ext/file.rs | 5 +- .../plugin/runtime/wasm/ext/http.rs | 72 ++++++++++--------- .../application/plugin/runtime/wasm/init.rs | 39 +++++----- .../application/plugin/runtime/wasm/node.rs | 6 +- controller/src/application/server.rs | 36 ++++++++-- controller/src/application/server/manager.rs | 64 +++++++++-------- .../src/application/server/manager/action.rs | 29 ++++---- controller/src/application/user.rs | 18 +++++ controller/src/application/user/manager.rs | 50 +++++++++++++ controller/src/application/user/transfer.rs | 1 + controller/src/task.rs | 41 +++++++++-- 17 files changed, 291 insertions(+), 135 deletions(-) create mode 100644 controller/src/application/user.rs create mode 100644 controller/src/application/user/manager.rs create mode 100644 controller/src/application/user/transfer.rs diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 066d5d2e..cc077fd7 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0.217", features = ["derive"] } toml = "0.8.19" # Async runtime -tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "macros"] } +tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "fs", "macros"] } futures = "0.3.31" # API diff --git a/controller/src/application.rs b/controller/src/application.rs index 21d8905b..c5282fc7 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -19,19 +19,21 @@ use tokio::{ sync::mpsc::{channel, Receiver, Sender}, time::interval, }; +use user::manager::UserManager; -use crate::{config::Config, task::WrappedTask}; +use crate::{config::Config, task::Task}; mod auth; mod group; mod node; mod plugin; mod server; +mod user; const TICK_RATE: u64 = 20; const TASK_BUFFER: usize = 128; -pub type TaskSender = Sender; +pub type TaskSender = Sender; #[derive(Getters, MutGetters)] pub struct Controller { @@ -39,7 +41,7 @@ pub struct Controller { running: Arc, /* Tasks */ - tasks: (TaskSender, Receiver), + tasks: (TaskSender, Receiver), /* Auth */ validator: WrappedAuthValidator, @@ -53,6 +55,8 @@ pub struct Controller { groups: GroupManager, #[getset(get = "pub", get_mut = "pub")] servers: ServerManager, + #[getset(get = "pub", get_mut = "pub")] + users: UserManager, /* Config */ #[getset(get = "pub")] @@ -61,12 +65,14 @@ pub struct Controller { impl Controller { pub async fn init(config: Config) -> Result { - let validator = AuthValidator::init()?; + let validator = AuthValidator::init().await?; let plugins = PluginManager::init(&config).await?; let nodes = NodeManager::init(&plugins).await?; let groups = GroupManager::init(&nodes).await?; - let servers = ServerManager::init().await?; + + let servers = ServerManager::init(); + let users = UserManager::init(); Ok(Self { running: Arc::new(AtomicBool::new(true)), @@ -76,6 +82,7 @@ impl Controller { nodes, groups, servers, + users, config, }) } @@ -89,7 +96,7 @@ impl Controller { while self.running.load(Ordering::Relaxed) { select! { _ = interval.tick() => self.tick().await?, - task = self.tasks.1.recv() => if let Some(mut task) = task { + task = self.tasks.1.recv() => if let Some(task) = task { task.run(self).await?; } } @@ -113,15 +120,27 @@ impl Controller { // Tick server manager self.servers - .tick(&self.config, &self.nodes, &mut self.groups, &self.validator) + .tick( + &self.config, + &self.nodes, + &mut self.groups, + &mut self.users, + &self.validator, + ) .await?; + // Tick user manager + self.users.tick().await?; + Ok(()) } async fn shutdown(&mut self) -> Result<()> { info!("Starting shutdown sequence..."); + // Shutdown user manager + self.users.shutdown().await?; + // Shutdown server manager self.servers.shutdown().await?; diff --git a/controller/src/application/auth/validator.rs b/controller/src/application/auth/validator.rs index 4e99843c..5e662fc1 100644 --- a/controller/src/application/auth/validator.rs +++ b/controller/src/application/auth/validator.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, fs, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use anyhow::Result; use common::{config::SaveToTomlFile, file::for_each_content_toml}; use simplelog::{error, info}; use stored::StoredUser; -use tokio::sync::RwLock; +use tokio::{fs, sync::RwLock}; use uuid::Uuid; use crate::storage::Storage; @@ -18,13 +18,13 @@ pub struct AuthValidator { } impl AuthValidator { - pub fn init() -> Result { + pub async fn init() -> Result { info!("Loading users..."); let mut tokens = HashMap::new(); let directory = Storage::users_directory(); if !directory.exists() { - fs::create_dir_all(&directory)?; + fs::create_dir_all(&directory).await?; } for (_, _, name, value) in diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index b5f82527..06c5e9c8 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -70,14 +70,14 @@ impl Group { if server.flags().should_stop() && to_stop > 0 { debug!( "Server {} is empty and reached the timeout, stopping it...", - server.name() + server.id() ); - requests.push(StopRequest::new(None, server.uuid())); + requests.push(StopRequest::new(None, server.id())); to_stop -= 1; } else { debug!( "Server {} is empty, starting stop timer...", - server.name() + server.id() ); server .flags_mut() @@ -86,7 +86,7 @@ impl Group { } else if server.flags().is_stop_set() { debug!( "Server {} is no longer empty, clearing stop timer...", - server.name() + server.id() ); server.flags_mut().clear_stop(); } @@ -118,10 +118,10 @@ impl Group { &self.spec, ); self.servers - .push(AssociatedServer::Queueing(*request.uuid())); + .push(AssociatedServer::Queueing(*request.id().uuid())); debug!( "Scheduled server({}) start for group {}", - request.name(), + request.id(), self.name ); servers.schedule_start(request); diff --git a/controller/src/application/group/manager.rs b/controller/src/application/group/manager.rs index eaafe4a1..c1f1149a 100644 --- a/controller/src/application/group/manager.rs +++ b/controller/src/application/group/manager.rs @@ -1,9 +1,10 @@ -use std::{collections::HashMap, fs, vec}; +use std::{collections::HashMap, vec}; use anyhow::Result; use common::{allocator::NumberAllocator, file::for_each_content_toml}; use simplelog::{info, warn}; use stored::StoredGroup; +use tokio::fs; use crate::{ application::{node::manager::NodeManager, server::manager::ServerManager}, @@ -24,7 +25,7 @@ impl GroupManager { let directory = Storage::groups_directory(); if !directory.exists() { - fs::create_dir_all(&directory)?; + fs::create_dir_all(&directory).await?; } for (_, _, name, mut value) in diff --git a/controller/src/application/node/manager.rs b/controller/src/application/node/manager.rs index cffe165b..ccc303d0 100644 --- a/controller/src/application/node/manager.rs +++ b/controller/src/application/node/manager.rs @@ -1,9 +1,10 @@ -use std::{collections::HashMap, fs}; +use std::collections::HashMap; use anyhow::Result; use common::file::for_each_content_toml; use simplelog::{error, info, warn}; use stored::StoredNode; +use tokio::fs; use crate::{ application::plugin::{manager::PluginManager, WrappedNode}, @@ -23,7 +24,7 @@ impl NodeManager { let directory = Storage::nodes_directory(); if !directory.exists() { - fs::create_dir_all(&directory)?; + fs::create_dir_all(&directory).await?; } for (_, _, name, value) in diff --git a/controller/src/application/plugin/runtime/wasm/ext/file.rs b/controller/src/application/plugin/runtime/wasm/ext/file.rs index 350e0c14..b477e212 100644 --- a/controller/src/application/plugin/runtime/wasm/ext/file.rs +++ b/controller/src/application/plugin/runtime/wasm/ext/file.rs @@ -1,4 +1,4 @@ -use std::fs; +use tokio::fs::remove_dir_all; use crate::application::plugin::runtime::wasm::{ generated::plugin::system::{ @@ -10,7 +10,8 @@ use crate::application::plugin::runtime::wasm::{ impl system::file::Host for PluginState { async fn remove_dir_all(&mut self, directory: Directory) -> Result<(), ErrorMessage> { - fs::remove_dir_all(self.get_directory(&self.name, &directory)?) + remove_dir_all(self.get_directory(&self.name, &directory)?) + .await .map_err(|error| format!("Failed to remove directory: {}", error)) } } diff --git a/controller/src/application/plugin/runtime/wasm/ext/http.rs b/controller/src/application/plugin/runtime/wasm/ext/http.rs index 25fc4f5b..48d2d3d2 100644 --- a/controller/src/application/plugin/runtime/wasm/ext/http.rs +++ b/controller/src/application/plugin/runtime/wasm/ext/http.rs @@ -1,4 +1,5 @@ use simplelog::warn; +use tokio::task::spawn_blocking; use crate::application::plugin::runtime::wasm::{ generated::plugin::system::{ @@ -17,41 +18,44 @@ impl system::http::Host for PluginState { headers: Vec
, body: Option>, ) -> Option { - let mut request = match method { - Method::Get => minreq::get(url), - Method::Patch => minreq::patch(url), - Method::Post => minreq::post(url), - Method::Put => minreq::put(url), - Method::Delete => minreq::delete(url), - }; - if let Some(body) = body { - request = request.with_body(body); - } - for header in headers { - request = request.with_header(&header.key, &header.value); - } - let response = match request.send() { - Ok(response) => response, - Err(error) => { - warn!( - "Failed to send HTTP request for plugin {}: {}", - self.name, error - ); - return None; + let name = self.name.clone(); + spawn_blocking(move || { + let mut request = match method { + Method::Get => minreq::get(url), + Method::Patch => minreq::patch(url), + Method::Post => minreq::post(url), + Method::Put => minreq::put(url), + Method::Delete => minreq::delete(url), + }; + if let Some(body) = body { + request = request.with_body(body); } - }; - Some(Response { - status_code: response.status_code as u32, - reason_phrase: response.reason_phrase.clone(), - headers: response - .headers - .iter() - .map(|header| Header { - key: header.0.clone(), - value: header.1.clone(), - }) - .collect(), - bytes: response.into_bytes(), + for header in headers { + request = request.with_header(&header.key, &header.value); + } + let response = match request.send() { + Ok(response) => response, + Err(error) => { + warn!("Failed to send HTTP request for plugin {}: {}", name, error); + return None; + } + }; + Some(Response { + status_code: response.status_code as u32, + reason_phrase: response.reason_phrase.clone(), + headers: response + .headers + .iter() + .map(|header| Header { + key: header.0.clone(), + value: header.1.clone(), + }) + .collect(), + bytes: response.into_bytes(), + }) }) + .await + .ok() + .flatten() } } diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index 1eddaffb..61818343 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -1,14 +1,9 @@ -use std::{ - collections::HashMap, - fs::{self}, - path::Path, - sync::Arc, -}; +use std::{collections::HashMap, path::Path, sync::Arc}; use anyhow::Result; use common::file::for_each_content; use simplelog::{error, info, warn}; -use tokio::sync::Mutex; +use tokio::{fs, sync::Mutex}; use wasmtime::{ component::{Component, Linker}, Engine, Store, @@ -36,7 +31,7 @@ pub async fn init_wasm_plugins( let directory = Storage::plugins_directory(); if !directory.exists() { - fs::create_dir_all(&directory)?; + fs::create_dir_all(&directory).await?; } let amount = plugins.len(); @@ -59,20 +54,24 @@ pub async fn init_wasm_plugins( let config_directory = Storage::config_directory_for_plugin(&name); let data_directory = Storage::data_directory_for_plugin(&name); if !config_directory.exists() { - fs::create_dir_all(&config_directory).unwrap_or_else(|error| { - warn!( - "Failed to create configs directory for driver {}: {}", - name, error - ) - }); + fs::create_dir_all(&config_directory) + .await + .unwrap_or_else(|error| { + warn!( + "Failed to create configs directory for driver {}: {}", + name, error + ) + }); } if !data_directory.exists() { - fs::create_dir_all(&data_directory).unwrap_or_else(|error| { - warn!( - "Failed to create data directory for driver {}: {}", - name, error - ) - }); + fs::create_dir_all(&data_directory) + .await + .unwrap_or_else(|error| { + warn!( + "Failed to create data directory for driver {}: {}", + name, error + ) + }); } info!("Compiling plugin '{}'...", name); diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs index b51cd844..e7fce954 100644 --- a/controller/src/application/plugin/runtime/wasm/node.rs +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -229,8 +229,8 @@ impl From<&Allocation> for bridge::Allocation { impl From<&Server> for bridge::Server { fn from(val: &Server) -> Self { bridge::Server { - name: val.name().clone(), - uuid: val.uuid().to_string(), + name: val.id().name().clone(), + uuid: val.id().uuid().to_string(), deployment: val.group().clone(), allocation: val.allocation().into(), token: val.token().clone(), @@ -241,7 +241,7 @@ impl From<&Server> for bridge::Server { impl From<&StartRequest> for bridge::ServerProposal { fn from(val: &StartRequest) -> Self { bridge::ServerProposal { - name: val.name().clone(), + name: val.id().name().clone(), deployment: val.group().clone(), resources: val.resources().into(), spec: val.spec().into(), diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 94eb39db..79f25186 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -1,4 +1,8 @@ -use std::{collections::HashMap, time::Duration}; +use std::{ + collections::HashMap, + fmt::{self, Display, Formatter}, + time::Duration, +}; use getset::{Getters, MutGetters}; use serde::{Deserialize, Serialize}; @@ -13,9 +17,7 @@ pub mod manager; pub struct Server { /* Settings */ #[getset(get = "pub")] - name: String, - #[getset(get = "pub")] - uuid: Uuid, + id: NameAndUuid, #[getset(get = "pub")] group: Option, #[getset(get = "pub")] @@ -38,6 +40,14 @@ pub struct Server { flags: Flags, } +#[derive(Clone, Getters, MutGetters)] +pub struct NameAndUuid { + #[getset(get = "pub", get_mut = "pub")] + name: String, + #[getset(get = "pub", get_mut = "pub")] + uuid: Uuid, +} + #[derive(Serialize, Deserialize, Clone, Default, Getters)] pub struct Resources { #[getset(get = "pub")] @@ -137,3 +147,21 @@ impl Health { Instant::now() > self.next_check } } + +impl NameAndUuid { + pub fn generate(name: String) -> Self { + Self { + name, + uuid: Uuid::new_v4(), + } + } + pub fn new(name: String, uuid: Uuid) -> Self { + Self { name, uuid } + } +} + +impl Display for NameAndUuid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 1b859d3b..20f4f5f1 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -13,12 +13,12 @@ use uuid::Uuid; use crate::{ application::{ auth::validator::WrappedAuthValidator, group::manager::GroupManager, - node::manager::NodeManager, + node::manager::NodeManager, user::manager::UserManager, }, config::Config, }; -use super::{Resources, Server, Spec, State}; +use super::{NameAndUuid, Resources, Server, Spec, State}; mod action; @@ -33,13 +33,13 @@ pub struct ServerManager { } impl ServerManager { - pub async fn init() -> Result { - Ok(Self { + pub fn init() -> Self { + Self { servers: HashMap::new(), start_requests: BinaryHeap::new(), restart_requests: vec![], stop_requests: vec![], - }) + } } pub fn get_server(&self, uuid: &Uuid) -> Option<&Server> { @@ -70,6 +70,7 @@ impl ServerManager { config: &Config, nodes: &NodeManager, groups: &mut GroupManager, + users: &mut UserManager, validator: &WrappedAuthValidator, ) -> Result<()> { // Check health of servers @@ -77,14 +78,14 @@ impl ServerManager { if server.health.is_dead() { match server.state { State::Starting | State::Running => { - warn!("Unit {} failed to establish online status within the expected startup time of {:.2?}.", server.name, config.restart_timeout()); + warn!("Unit {} failed to establish online status within the expected startup time of {:.2?}.", server.id, config.restart_timeout()); } _ => { - warn!("Server {} has not checked in for {:.2?}, indicating a potential error.", server.name, server.health.timeout); + warn!("Server {} has not checked in for {:.2?}, indicating a potential error.", server.id, server.health.timeout); } } self.restart_requests - .push(RestartRequest::new(None, &server.uuid)); + .push(RestartRequest::new(None, server.id())); } } @@ -117,8 +118,15 @@ impl ServerManager { if handle.is_finished() { handle.await??; debug!("Stopping server {}", request.server); - match Self::stop(&request, &mut self.servers, nodes, groups, validator) - .await + match Self::stop( + &request, + &mut self.servers, + nodes, + groups, + users, + validator, + ) + .await { Ok(handle) => { reinsert = true; @@ -199,10 +207,7 @@ impl ServerManager { let mut requests = vec![]; while let Some(mut request) = self.start_requests.pop() { if request.nodes.is_empty() { - warn!( - "Server {} has no nodes available to start on.", - request.name - ); + warn!("Server {} has no nodes available to start on.", request.id); continue; } @@ -216,7 +221,7 @@ impl ServerManager { let mut reinsert = false; request.stage = match mem::replace(&mut request.stage, StartStage::Queued) { StartStage::Queued => { - debug!("Allocating resources for server {}", request.name); + debug!("Allocating resources for server {}", request.id); match Self::allocate(0, &request, nodes) { Ok(handle) => { reinsert = true; @@ -225,7 +230,7 @@ impl ServerManager { Err(error) => { warn!( "Failed to allocate resources for server {}: {}", - request.name, error + request.id, error ); reinsert = false; StartStage::Failed @@ -237,7 +242,7 @@ impl ServerManager { if handle.is_finished() { let ports = handle.await?; if let Ok(ports) = ports { - debug!("Creating server {}", request.name); + debug!("Creating server {}", request.id); match Self::start( index, &request, @@ -252,7 +257,7 @@ impl ServerManager { { Ok(handle) => StartStage::Creating(handle), Err(error) => { - warn!("Failed to create server {}: {}", request.name, error); + warn!("Failed to create server {}: {}", request.id, error); reinsert = false; StartStage::Failed } @@ -263,7 +268,7 @@ impl ServerManager { Err(error) => { warn!( "Failed to allocate resources for server {}: {}", - request.name, error + request.id, error ); reinsert = false; StartStage::Failed @@ -277,7 +282,7 @@ impl ServerManager { StartStage::Creating(handle) => { if handle.is_finished() { handle.await??; - debug!("Server {} has been created", request.name); + debug!("Server {} has been created", request.id); StartStage::Started } else { reinsert = true; @@ -303,13 +308,11 @@ impl ServerManager { #[derive(Getters)] pub struct StartRequest { /* Request */ - #[getset(get = "pub")] - uuid: Uuid, when: Option, /* Server */ #[getset(get = "pub")] - name: String, + id: NameAndUuid, #[getset(get = "pub")] group: Option, #[getset(get = "pub")] @@ -330,7 +333,7 @@ pub struct StartRequest { pub struct RestartRequest { /* Request */ when: Option, - server: Uuid, + server: NameAndUuid, /* Stage */ #[getset(get = "pub")] @@ -341,7 +344,7 @@ pub struct RestartRequest { pub struct StopRequest { /* Request */ when: Option, - server: Uuid, + server: NameAndUuid, /* Stage */ #[getset(get = "pub")] @@ -375,10 +378,9 @@ impl StartRequest { spec: &Spec, ) -> Self { Self { - uuid: Uuid::new_v4(), + id: NameAndUuid::generate(name), when, priority, - name, group, nodes: nodes.to_vec(), resources: resources.clone(), @@ -389,20 +391,20 @@ impl StartRequest { } impl RestartRequest { - pub fn new(when: Option, server: &Uuid) -> Self { + pub fn new(when: Option, server: &NameAndUuid) -> Self { Self { when, - server: *server, + server: server.clone(), stage: ActionStage::Queued, } } } impl StopRequest { - pub fn new(when: Option, server: &Uuid) -> Self { + pub fn new(when: Option, server: &NameAndUuid) -> Self { Self { when, - server: *server, + server: server.clone(), stage: ActionStage::Queued, } } diff --git a/controller/src/application/server/manager/action.rs b/controller/src/application/server/manager/action.rs index 9a408cd1..630b829e 100644 --- a/controller/src/application/server/manager/action.rs +++ b/controller/src/application/server/manager/action.rs @@ -12,6 +12,7 @@ use crate::{ group::manager::GroupManager, node::{manager::NodeManager, Allocation}, server::{Flags, Health, Server, State}, + user::manager::UserManager, }, config::Config, }; @@ -32,7 +33,7 @@ impl ServerManager { Err(anyhow!( "Node {} not found while trying to allocate ports for server {}", name, - request.name + request.id )) } } else { @@ -55,8 +56,7 @@ impl ServerManager { let node = nodes.get_node(name); if let Some(node) = node { let mut server = Server { - uuid: request.uuid, - name: request.name.clone(), + id: request.id.clone(), group: request.group.clone(), node: name.clone(), allocation: Allocation { @@ -65,7 +65,7 @@ impl ServerManager { spec: request.spec.clone(), }, connected_users: 0, - token: validator.register_server(request.uuid).await, + token: validator.register_server(request.id.uuid).await, health: Health::new(*config.startup_timeout(), *config.heartbeat_timeout()), state: State::Starting, flags: Flags::default(), @@ -73,19 +73,19 @@ impl ServerManager { let handle = node.start(&server); if let Some(group) = &server.group { if let Some(group) = groups.get_group_mut(group) { - group.set_server_active(&server.uuid); + group.set_server_active(&server.id.uuid); } else { - warn!("Group {} not found while trying to start server {}. Removing group from server", group, request.name); + warn!("Group {} not found while trying to start server {}. Removing group from server", group, request.id); server.group = None; } } - servers.insert(server.uuid, server); + servers.insert(server.id.uuid, server); Ok(handle) } else { Err(anyhow!( "Node {} not found while trying to allocate ports for server {}", name, - request.name + request.id )) } } else { @@ -100,7 +100,7 @@ impl ServerManager { config: &Config, nodes: &NodeManager, ) -> Result>> { - if let Some(server) = servers.get_mut(&request.server) { + if let Some(server) = servers.get_mut(request.server.uuid()) { if let Some(node) = nodes.get_node(&server.node) { server.state = State::Restarting; server.health = Health::new(*config.startup_timeout(), *config.heartbeat_timeout()); @@ -124,7 +124,7 @@ impl ServerManager { servers: &mut HashMap, nodes: &NodeManager, ) -> Result>> { - if let Some(server) = servers.get(&request.server) { + if let Some(server) = servers.get(request.server.uuid()) { if let Some(node) = nodes.get_node(&server.node) { Ok(node.free(&server.allocation.ports)) } else { @@ -146,21 +146,22 @@ impl ServerManager { servers: &mut HashMap, nodes: &NodeManager, groups: &mut GroupManager, + users: &mut UserManager, validator: &WrappedAuthValidator, ) -> Result>> { - if let Some(server) = servers.get_mut(&request.server) { + if let Some(server) = servers.get_mut(request.server.uuid()) { if let Some(node) = nodes.get_node(&server.node) { if let Some(group) = &server.group { if let Some(group) = groups.get_group_mut(group) { - group.remove_server(&server.uuid); + group.remove_server(server.id.uuid()); } else { - error!("Group {} not found while trying to stop server {}. Removing group from server", group, server.name); + error!("Group {} not found while trying to stop server {}. Removing group from server", group, server.id); server.group = None; } } validator.unregister(&server.token).await; - // TODO: Cleanup connected users + users.remove_users_on_server(server.id.uuid()); // TODO: Cleanup subscriptions Ok(node.stop(server)) diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs new file mode 100644 index 00000000..1f788f90 --- /dev/null +++ b/controller/src/application/user.rs @@ -0,0 +1,18 @@ +use transfer::Transfer; +use uuid::Uuid; + +use super::server::NameAndUuid; + +pub mod manager; +pub mod transfer; + +pub struct User { + name: String, + uuid: Uuid, + server: CurrentServer, +} + +pub enum CurrentServer { + Connected(NameAndUuid), + Transfering(Transfer), +} diff --git a/controller/src/application/user/manager.rs b/controller/src/application/user/manager.rs new file mode 100644 index 00000000..5a53051f --- /dev/null +++ b/controller/src/application/user/manager.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; + +use anyhow::Result; +use simplelog::info; +use uuid::Uuid; + +use super::{CurrentServer, User}; + +pub struct UserManager { + users: HashMap, +} + +impl UserManager { + pub fn init() -> Self { + Self { + users: HashMap::new(), + } + } + + pub fn remove_users_on_server(&mut self, server: &Uuid) -> u32 { + let mut amount = 0; + self.users.retain(|_, user| { + if let CurrentServer::Connected(current) = &user.server { + if current.uuid() == server { + info!( + "User {}[{}] disconnected from server {}", + user.name, + user.uuid.to_string(), + current.name(), + ); + amount += 1; + return false; + } + } + true + }); + amount + } +} + +// Ticking +impl UserManager { + pub async fn tick(&mut self) -> Result<()> { + Ok(()) + } + + pub async fn shutdown(&mut self) -> Result<()> { + Ok(()) + } +} diff --git a/controller/src/application/user/transfer.rs b/controller/src/application/user/transfer.rs new file mode 100644 index 00000000..4006c1ef --- /dev/null +++ b/controller/src/application/user/transfer.rs @@ -0,0 +1 @@ +pub struct Transfer {} diff --git a/controller/src/task.rs b/controller/src/task.rs index 874b324c..ba77ee5d 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -1,11 +1,42 @@ -use anyhow::Result; +use std::any::Any; + +use anyhow::{anyhow, Result}; +use tokio::sync::oneshot::{channel, Sender}; use tonic::async_trait; -use crate::application::Controller; +use crate::application::{Controller, TaskSender}; + +type BoxedTask = Box; +type BoxedAny = Box; + +pub struct Task { + task: BoxedTask, + sender: Sender>, +} + +impl Task { + pub async fn create(controller: TaskSender, task: BoxedTask) -> Result { + let (sender, receiver) = channel(); + controller + .send(Task { task, sender }) + .await + .map_err(|_| anyhow!("Failed to send task to controller"))?; + Ok(*receiver.await??.downcast::().map_err(|_| { + anyhow!( + "Failed to downcast task result to the expected type. Check task implementation" + ) + })?) + } -pub type WrappedTask = Box; + pub async fn run(mut self, controller: &mut Controller) -> Result<()> { + let task = self.task.run(controller).await; + self.sender + .send(task) + .map_err(|_| anyhow!("Failed to send task result to the task sender")) + } +} #[async_trait] -pub trait Task { - async fn run(&mut self, controller: &mut Controller) -> Result<()>; +pub trait GenericTask { + async fn run(&mut self, controller: &mut Controller) -> Result; } From 98bb2e8dc3756c063f8da5b8977088aea9122ac9 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:35:23 +0100 Subject: [PATCH 22/74] Rewrite gRPCs --- Cargo.toml | 8 +- cli/build.rs | 2 +- clients/wrapper/build.rs | 2 +- controller/build.rs | 4 +- controller/src/main.rs | 1 + controller/src/network.rs | 2 + controller/src/network/client.rs | 6 + controller/src/network/manage.rs | 6 + protocol/grpc/admin/admin.proto | 233 ---------------------------- protocol/grpc/client/channel.proto | 12 ++ protocol/grpc/client/group.proto | 9 ++ protocol/grpc/client/server.proto | 14 ++ protocol/grpc/client/service.proto | 52 +++++++ protocol/grpc/client/transfer.proto | 24 +++ protocol/grpc/client/user.proto | 13 ++ protocol/grpc/common/common.proto | 13 ++ protocol/grpc/manage/group.proto | 28 ++++ protocol/grpc/manage/node.proto | 17 ++ protocol/grpc/manage/plugin.proto | 9 ++ protocol/grpc/manage/resource.proto | 24 +++ protocol/grpc/manage/server.proto | 64 ++++++++ protocol/grpc/manage/service.proto | 52 +++++++ protocol/grpc/manage/transfer.proto | 19 +++ protocol/grpc/manage/user.proto | 13 ++ protocol/grpc/unit/unit.proto | 116 -------------- 25 files changed, 386 insertions(+), 357 deletions(-) create mode 100644 controller/src/network.rs create mode 100644 controller/src/network/client.rs create mode 100644 controller/src/network/manage.rs delete mode 100644 protocol/grpc/admin/admin.proto create mode 100644 protocol/grpc/client/channel.proto create mode 100644 protocol/grpc/client/group.proto create mode 100644 protocol/grpc/client/server.proto create mode 100644 protocol/grpc/client/service.proto create mode 100644 protocol/grpc/client/transfer.proto create mode 100644 protocol/grpc/client/user.proto create mode 100644 protocol/grpc/common/common.proto create mode 100644 protocol/grpc/manage/group.proto create mode 100644 protocol/grpc/manage/node.proto create mode 100644 protocol/grpc/manage/plugin.proto create mode 100644 protocol/grpc/manage/resource.proto create mode 100644 protocol/grpc/manage/server.proto create mode 100644 protocol/grpc/manage/service.proto create mode 100644 protocol/grpc/manage/transfer.proto create mode 100644 protocol/grpc/manage/user.proto delete mode 100644 protocol/grpc/unit/unit.proto diff --git a/Cargo.toml b/Cargo.toml index d23f2ab5..51b2da4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,12 @@ members = [ "controller", # Drivers - "plugins/pterodactyl", - "plugins/local", + #"plugins/pterodactyl", + #"plugins/local", # Clients - "cli", - "clients/wrapper", + #"cli", + #"clients/wrapper", ] [workspace.metadata] diff --git a/cli/build.rs b/cli/build.rs index d2020d3f..97389dc1 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -80,7 +80,7 @@ fn generate_grpc_code() -> Result<(), Box> { tonic_build::configure() .build_server(false) .compile_protos( - &[format!("{}/admin/admin.proto", PROTO_PATH)], + &[format!("{}/manage/manage.proto", PROTO_PATH)], &[PROTO_PATH], )?; Ok(()) diff --git a/clients/wrapper/build.rs b/clients/wrapper/build.rs index 09a6dea2..aaf63977 100644 --- a/clients/wrapper/build.rs +++ b/clients/wrapper/build.rs @@ -79,6 +79,6 @@ fn get_protocol_version_info() -> Result> { fn generate_grpc_code() -> Result<(), Box> { tonic_build::configure() .build_server(false) - .compile_protos(&[format!("{}/unit/unit.proto", PROTO_PATH)], &[PROTO_PATH])?; + .compile_protos(&[format!("{}/client/service.proto", PROTO_PATH)], &[PROTO_PATH])?; Ok(()) } diff --git a/controller/build.rs b/controller/build.rs index 17a5c7a0..e3802ab2 100644 --- a/controller/build.rs +++ b/controller/build.rs @@ -81,8 +81,8 @@ fn generate_grpc_code() -> Result<(), Box> { .build_client(false) .compile_protos( &[ - format!("{}/admin/admin.proto", PROTO_PATH), - format!("{}/unit/unit.proto", PROTO_PATH), + format!("{}/manage/service.proto", PROTO_PATH), + format!("{}/client/service.proto", PROTO_PATH), ], &[PROTO_PATH], )?; diff --git a/controller/src/main.rs b/controller/src/main.rs index 5f18027c..86e845b1 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -10,6 +10,7 @@ use storage::Storage; use tokio::time::Instant; mod application; +mod network; mod config; mod storage; mod task; diff --git a/controller/src/network.rs b/controller/src/network.rs new file mode 100644 index 00000000..eed8a9f9 --- /dev/null +++ b/controller/src/network.rs @@ -0,0 +1,2 @@ +mod manage; +mod client; \ No newline at end of file diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs new file mode 100644 index 00000000..4adeb361 --- /dev/null +++ b/controller/src/network/client.rs @@ -0,0 +1,6 @@ +#[allow(clippy::all)] +pub mod proto { + use tonic::include_proto; + + include_proto!("client"); +} \ No newline at end of file diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs new file mode 100644 index 00000000..97fde488 --- /dev/null +++ b/controller/src/network/manage.rs @@ -0,0 +1,6 @@ +#[allow(clippy::all)] +pub mod proto { + use tonic::include_proto; + + include_proto!("manage"); +} \ No newline at end of file diff --git a/protocol/grpc/admin/admin.proto b/protocol/grpc/admin/admin.proto deleted file mode 100644 index 53519f90..00000000 --- a/protocol/grpc/admin/admin.proto +++ /dev/null @@ -1,233 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.atomic.cloud.grpc.admin"; - -package admin; - -import "google/protobuf/empty.proto"; -import "google/protobuf/wrappers.proto"; - -// Admin Service -service AdminService { - rpc RequestStop(google.protobuf.Empty) returns (google.protobuf.Empty); - - // Resource Management - rpc SetResourceStatus(ResourceManagement.SetResourceStatusRequest) returns (google.protobuf.Empty); - rpc DeleteResource(ResourceManagement.DeleteResourceRequest) returns (google.protobuf.Empty); - - // Driver Management - rpc GetDrivers(google.protobuf.Empty) returns (DriverManagement.DriverListResponse); - - // Cloudlet Management - rpc CreateCloudlet(CloudletManagement.CloudletValue) returns (google.protobuf.Empty); - rpc GetCloudlet(google.protobuf.StringValue) returns (CloudletManagement.CloudletValue); - rpc GetCloudlets(google.protobuf.Empty) returns (CloudletManagement.CloudletListResponse); - - // Deployment Management - rpc CreateDeployment(DeploymentManagement.DeploymentValue) returns (google.protobuf.Empty); - rpc GetDeployment(google.protobuf.StringValue) returns (DeploymentManagement.DeploymentValue); - rpc GetDeployments(google.protobuf.Empty) returns (DeploymentManagement.DeploymentListResponse); - - // Unit Management - rpc GetUnit(google.protobuf.StringValue) returns (UnitManagement.UnitValue); - rpc GetUnits(google.protobuf.Empty) returns (UnitManagement.UnitListResponse); - - // User Management - rpc GetUsers(google.protobuf.Empty) returns (UserManagement.UserListResponse); - - // Transfer Management - rpc TransferUsers(TransferManagement.TransferUsersRequest) returns (google.protobuf.UInt32Value); - - // Information - rpc GetProtocolVersion(google.protobuf.Empty) returns (google.protobuf.UInt32Value); - rpc GetControllerVersion(google.protobuf.Empty) returns (google.protobuf.StringValue); -} - -// Resource Management -message ResourceManagement { - message SetResourceStatusRequest { - ResourceCategory category = 1; - string id = 2; - ResourceStatus status = 3; - } - - message DeleteResourceRequest { - ResourceCategory category = 1; - string id = 2; - } - - enum ResourceCategory { - CLOUDLET = 0; - DEPLOYMENT = 1; - UNIT = 2; - } - - enum ResourceStatus { - ACTIVE = 0; - INACTIVE = 1; - } -} - -// Driver Management -message DriverManagement { - message DriverListResponse { - repeated string drivers = 1; - } -} - -// Cloudlet Management -message CloudletManagement { - message CloudletListResponse { - repeated string cloudlets = 1; - } - - message CloudletValue { - string name = 1; - string driver = 2; - - optional uint32 memory = 3; - optional uint32 maxAllocations = 4; - optional string child = 5; - string controllerAddress = 6; - } -} - -// Deployment Management -message DeploymentManagement { - message DeploymentListResponse { - repeated string deployments = 1; - } - - message DeploymentValue { - string name = 1; - repeated string cloudlets = 2; - Constraints constraints = 3; - optional Scaling scaling = 4; - UnitManagement.UnitResources resources = 5; - UnitManagement.UnitSpec spec = 6; - - message Constraints { - uint32 minimum = 1; - uint32 maximum = 2; - int32 priority = 3; - } - - message Scaling { - float startThreshold = 1; - bool stopEmptyUnits = 2; - } - } -} - -// Unit Management -message UnitManagement { - message UnitListResponse { - repeated SimpleUnitValue units = 1; - } - - message SimpleUnitValue { - string name = 1; - string uuid = 2; - optional string deployment = 3; - string cloudlet = 4; - } - - message UnitValue { - string name = 1; - string uuid = 2; - optional string deployment = 3; - string cloudlet = 4; - UnitAllocation allocation = 5; - uint32 connectedUsers = 6; - string authToken = 7; - UnitState state = 8; - bool rediness = 9; - } - - message UnitAllocation { - repeated Common.Address addresses = 1; - UnitResources resources = 2; - UnitSpec spec = 3; - } - - message UnitResources { - uint32 memory = 1; - uint32 swap = 2; - uint32 cpu = 3; - uint32 io = 4; - uint32 disk = 5; - uint32 addresses = 6; - } - - message UnitSpec { - string image = 1; - uint32 maxPlayers = 2; - repeated Common.KeyValue settings = 3; - repeated Common.KeyValue environment = 4; - optional Retention diskRetention = 5; - optional Fallback fallback = 6; - - message Fallback { - bool enabled = 1; - int32 priority = 2; - } - - enum Retention { - TEMPORARY = 0; - PERMANENT = 1; - } - } - - enum UnitState { - STARTING = 0; - PREPARING = 1; - RESTARTING = 2; - RUNNING = 3; - STOPPING = 4; - } -} - -// User Management -message UserManagement { - message UserListResponse { - repeated UserValue users = 1; - } - - message UserValue { - string name = 1; - string uuid = 2; - } -} - -// Transfer Management -message TransferManagement { - message TransferTargetValue { - TargetType targetType = 1; - optional string target = 2; - - enum TargetType { - UNIT = 0; - DEPLOYMENT = 1; - FALLBACK = 2; - } - } - - message TransferUsersRequest { - repeated string userUuids = 1; - TransferTargetValue target = 2; - } -} - -// Common -message Common { - message KeyValue { - string key = 1; - string value = 2; - } - - message Address { - string host = 1; - uint32 port = 2; - } -} \ No newline at end of file diff --git a/protocol/grpc/client/channel.proto b/protocol/grpc/client/channel.proto new file mode 100644 index 00000000..7afd8b69 --- /dev/null +++ b/protocol/grpc/client/channel.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package client; + +message Channel { + message Msg { + string name = 1; + uint32 id = 2; + string data = 3; + uint64 time = 4; // timestamp (e.g. epoch time) + } +} \ No newline at end of file diff --git a/protocol/grpc/client/group.proto b/protocol/grpc/client/group.proto new file mode 100644 index 00000000..cd9de252 --- /dev/null +++ b/protocol/grpc/client/group.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package client; + +message Group { + message List { + repeated string groups = 1; + } +} \ No newline at end of file diff --git a/protocol/grpc/client/server.proto b/protocol/grpc/client/server.proto new file mode 100644 index 00000000..2a6c24c8 --- /dev/null +++ b/protocol/grpc/client/server.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package client; + +message Server { + message List { + repeated Short servers = 1; + } + message Short { + string name = 1; + string id = 2; + optional string group = 3; + } +} \ No newline at end of file diff --git a/protocol/grpc/client/service.proto b/protocol/grpc/client/service.proto new file mode 100644 index 00000000..e4bd2f3d --- /dev/null +++ b/protocol/grpc/client/service.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.atomic.cloud.grpc.client"; + +package client; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +import "client/user.proto"; +import "client/transfer.proto"; +import "client/channel.proto"; +import "client/server.proto"; +import "client/group.proto"; + +service ClientService { + // Heartbeat + rpc Beat(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Ready state + rpc SetReady(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc SetNotReady(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Health + rpc SetRunning(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc RequestStop(google.protobuf.Empty) returns (google.protobuf.Empty); + + // User operations + rpc UserConnected(User.ConnectedReq) returns (google.protobuf.Empty); + rpc UserDisconnected(User.DisconnectedReq) returns (google.protobuf.Empty); + + // Transfer operations + rpc SubscribeToTransfers(google.protobuf.Empty) returns (stream Transfer.Res); + rpc RequestTransfer(Transfer.Req) returns (google.protobuf.UInt32Value); + + // Channel operations + rpc PublishMessage(Channel.Msg) returns (google.protobuf.UInt32Value); + rpc UnsubscribeFromChannel(google.protobuf.StringValue) returns (google.protobuf.Empty); + rpc SubscribeToChannel(google.protobuf.StringValue) returns (stream Channel.Msg); + + // Server/Group info + rpc GetServers(google.protobuf.Empty) returns (Server.List); + rpc GetGroups(google.protobuf.Empty) returns (Group.List); + + // Housekeeping + rpc Reset(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Version info + rpc GetProtoVer(google.protobuf.Empty) returns (google.protobuf.UInt32Value); + rpc GetCtrlVer(google.protobuf.Empty) returns (google.protobuf.StringValue); +} \ No newline at end of file diff --git a/protocol/grpc/client/transfer.proto b/protocol/grpc/client/transfer.proto new file mode 100644 index 00000000..a332595a --- /dev/null +++ b/protocol/grpc/client/transfer.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package client; + +message Transfer { + message Target { + enum Type { + SERVER = 0; + GROUP = 1; + FALLBACK = 2; + } + Type type = 1; + string target = 2; + } + message Req { + repeated string ids = 1; + Target target = 2; + } + message Res { + string id = 1; + string host = 2; + uint32 port = 3; + } +} \ No newline at end of file diff --git a/protocol/grpc/client/user.proto b/protocol/grpc/client/user.proto new file mode 100644 index 00000000..b3917078 --- /dev/null +++ b/protocol/grpc/client/user.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package client; + +message User { + message ConnectedReq { + string id = 1; + string name = 2; + } + message DisconnectedReq { + string id = 1; + } +} \ No newline at end of file diff --git a/protocol/grpc/common/common.proto b/protocol/grpc/common/common.proto new file mode 100644 index 00000000..76443fed --- /dev/null +++ b/protocol/grpc/common/common.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package common; + +message KeyValue { + string key = 1; + string value = 2; +} + +message Address { + string host = 1; + uint32 port = 2; +} \ No newline at end of file diff --git a/protocol/grpc/manage/group.proto b/protocol/grpc/manage/group.proto new file mode 100644 index 00000000..e61a8b21 --- /dev/null +++ b/protocol/grpc/manage/group.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package manage; + +import "manage/server.proto"; + +message Group { + message Item { + string name = 1; + repeated string nodes = 2; + Constraints cons = 3; + Scaling scaling = 4; + Server.Resources res = 5; + Server.Spec spec = 6; + } + message Constraints { + uint32 min = 1; + uint32 max = 2; + int32 prio = 3; + } + message Scaling { + float startThreshold = 1; + bool stopEmpty = 2; + } + message List { + repeated string groups = 1; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/node.proto b/protocol/grpc/manage/node.proto new file mode 100644 index 00000000..ac406e9f --- /dev/null +++ b/protocol/grpc/manage/node.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package manage; + +message Node { + message Item { + string name = 1; + string plugin = 2; + uint32 mem = 3; + uint32 max = 4; + string child = 5; + string ctrlAddr = 6; + } + message List { + repeated string nodes = 1; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/plugin.proto b/protocol/grpc/manage/plugin.proto new file mode 100644 index 00000000..e15d44d9 --- /dev/null +++ b/protocol/grpc/manage/plugin.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package manage; + +message Plugin { + message List { + repeated string plugins = 1; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/resource.proto b/protocol/grpc/manage/resource.proto new file mode 100644 index 00000000..1d2f69e3 --- /dev/null +++ b/protocol/grpc/manage/resource.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package manage; + +message Resource { + enum Category { + NODE = 0; + GROUP = 1; + SERVER = 2; + } + message SetReq { + enum Status { + ACTIVE = 0; + INACTIVE = 1; + } + Category cat = 1; + string id = 2; + Status status = 3; + } + message DelReq { + Category cat = 1; + string id = 2; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/server.proto b/protocol/grpc/manage/server.proto new file mode 100644 index 00000000..585981ca --- /dev/null +++ b/protocol/grpc/manage/server.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package manage; + +import "common/common.proto"; + +message Server { + message List { + repeated Short servers = 1; + } + message Short { + string name = 1; + string id = 2; + optional string group = 3; + string node = 4; + } + message Detail { + string name = 1; + string id = 2; + optional string group = 3; + string node = 4; + Allocation alloc = 5; + uint32 users = 6; + string token = 7; + State state = 8; + bool ready = 9; + } + message Allocation { + repeated common.Address ports = 1; + Resources res = 2; + Spec spec = 3; + } + message Resources { + uint32 mem = 1; + uint32 swap = 2; + uint32 cpu = 3; + uint32 io = 4; + uint32 disk = 5; + uint32 ports = 6; + } + message Spec { + string img = 1; + uint32 maxPlayers = 2; + repeated common.KeyValue settings = 3; + repeated common.KeyValue env = 4; + Retention retention = 5; + Fallback fallback = 6; + } + message Fallback { + bool enabled = 1; + int32 prio = 2; + } + enum Retention { + TEMP = 0; + PERM = 1; + } + enum State { + START = 0; + PREP = 1; + RESTART = 2; + RUN = 3; + STOP = 4; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/service.proto b/protocol/grpc/manage/service.proto new file mode 100644 index 00000000..f3f6ef5d --- /dev/null +++ b/protocol/grpc/manage/service.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.atomic.cloud.grpc.manage"; + +package manage; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +import "manage/resource.proto"; +import "manage/plugin.proto"; +import "manage/node.proto"; +import "manage/group.proto"; +import "manage/server.proto"; +import "manage/user.proto"; +import "manage/transfer.proto"; + +service ManageService { + rpc RequestStop(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Resource operations + rpc SetResource(Resource.SetReq) returns (google.protobuf.Empty); + rpc DeleteResource(Resource.DelReq) returns (google.protobuf.Empty); + + // Plugin operations + rpc GetPlugins(google.protobuf.Empty) returns (Plugin.List); + + // Node operations + rpc CreateNode(Node.Item) returns (google.protobuf.Empty); + rpc GetNode(google.protobuf.StringValue) returns (Node.Item); + rpc GetNodes(google.protobuf.Empty) returns (Node.List); + + // Group operations + rpc CreateGroup(Group.Item) returns (google.protobuf.Empty); + rpc GetGroup(google.protobuf.StringValue) returns (Group.Item); + rpc GetGroups(google.protobuf.Empty) returns (Group.List); + + // Server operations + rpc GetServer(google.protobuf.StringValue) returns (Server.Detail); + rpc GetServers(google.protobuf.Empty) returns (Server.List); + + // User operations + rpc GetUsers(google.protobuf.Empty) returns (User.List); + + // Transfer operations + rpc TransferUsers(Transfer.Req) returns (google.protobuf.UInt32Value); + + // Version info + rpc GetProtoVer(google.protobuf.Empty) returns (google.protobuf.UInt32Value); + rpc GetCtrlVer(google.protobuf.Empty) returns (google.protobuf.StringValue); +} \ No newline at end of file diff --git a/protocol/grpc/manage/transfer.proto b/protocol/grpc/manage/transfer.proto new file mode 100644 index 00000000..57724408 --- /dev/null +++ b/protocol/grpc/manage/transfer.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package manage; + +message Transfer { + message Target { + enum Type { + SERVER = 0; + GROUP = 1; + FALLBACK = 2; + } + Type type = 1; + string target = 2; + } + message Req { + repeated string ids = 1; + Target target = 2; + } +} \ No newline at end of file diff --git a/protocol/grpc/manage/user.proto b/protocol/grpc/manage/user.proto new file mode 100644 index 00000000..57009679 --- /dev/null +++ b/protocol/grpc/manage/user.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package manage; + +message User { + message List { + repeated Item users = 1; + } + message Item { + string name = 1; + string id = 2; + } +} \ No newline at end of file diff --git a/protocol/grpc/unit/unit.proto b/protocol/grpc/unit/unit.proto deleted file mode 100644 index f4f7a80a..00000000 --- a/protocol/grpc/unit/unit.proto +++ /dev/null @@ -1,116 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.atomic.cloud.grpc.unit"; - -package unit; - -import "google/protobuf/empty.proto"; -import "google/protobuf/wrappers.proto"; - -// Unit Service -service UnitService { - // Heartbeat Management - rpc BeatHeart(google.protobuf.Empty) returns (google.protobuf.Empty); - - // Ready State Management - rpc MarkReady(google.protobuf.Empty) returns (google.protobuf.Empty); - rpc MarkNotReady(google.protobuf.Empty) returns (google.protobuf.Empty); - - // Health Management - rpc MarkRunning(google.protobuf.Empty) returns (google.protobuf.Empty); - rpc RequestStop(google.protobuf.Empty) returns (google.protobuf.Empty); // Implies not ready - - // User Management - rpc UserConnected(UserManagement.UserConnectedRequest) returns (google.protobuf.Empty); - rpc UserDisconnected(UserManagement.UserDisconnectedRequest) returns (google.protobuf.Empty); - - // Transfer Management - rpc SubscribeToTransfers(google.protobuf.Empty) returns (stream TransferManagement.ResolvedTransferResponse); - rpc TransferUsers(TransferManagement.TransferUsersRequest) returns (google.protobuf.UInt32Value); - - // Channel Management - rpc SendMessageToChannel(ChannelManagement.ChannelMessageValue) returns (google.protobuf.UInt32Value); - rpc UnsubscribeFromChannel(google.protobuf.StringValue) returns (google.protobuf.Empty); - rpc SubscribeToChannel(google.protobuf.StringValue) returns (stream ChannelManagement.ChannelMessageValue); - - // Unit Information - rpc GetUnits(google.protobuf.Empty) returns (UnitInformation.UnitListResponse); - - // Deployment Information - rpc GetDeployments(google.protobuf.Empty) returns (DeploymentInformation.DeploymentListResponse); - - // Housekeeping - rpc Reset(google.protobuf.Empty) returns (google.protobuf.Empty); - - // Information - rpc GetProtocolVersion(google.protobuf.Empty) returns (google.protobuf.UInt32Value); - rpc GetControllerVersion(google.protobuf.Empty) returns (google.protobuf.StringValue); -} - -// User Management -message UserManagement { - message UserConnectedRequest { - string name = 1; - string uuid = 2; - } - - message UserDisconnectedRequest { - string uuid = 1; - } -} - -// Transfer Management -message TransferManagement { - message TransferTargetValue { - TargetType targetType = 1; - optional string target = 2; - - enum TargetType { - UNIT = 0; - DEPLOYMENT = 1; - FALLBACK = 2; - } - } - - message TransferUsersRequest { - repeated string userUuids = 1; - TransferTargetValue target = 2; - } - - message ResolvedTransferResponse { - string userUuid = 1; - string host = 2; - uint32 port = 3; - } -} - -// Channel Management -message ChannelManagement { - message ChannelMessageValue { - string channel = 1; - uint32 id = 2; - string data = 3; - uint64 timestamp = 4; - } -} - -// Unit Information -message UnitInformation { - message UnitListResponse { - repeated SimpleUnitValue units = 1; - } - - message SimpleUnitValue { - string name = 1; - string uuid = 2; - optional string deployment = 3; - } -} - -// Deployment Information -message DeploymentInformation { - message DeploymentListResponse { - repeated string deployments = 1; - } -} \ No newline at end of file From 427451bdaccfc5fe8325b9872054129076bdaf86 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:10:53 +0100 Subject: [PATCH 23/74] gRPCs --- controller/src/network/client.rs | 21 +++++++++++++++++++++ controller/src/network/client/beat.rs | 16 ++++++++++++++++ controller/src/network/client/ready.rs | 17 +++++++++++++++++ controller/src/network/client/running.rs | 16 ++++++++++++++++ controller/src/network/client/stop.rs | 16 ++++++++++++++++ controller/src/network/manage.rs | 14 ++++++++++++++ controller/src/task.rs | 10 +++++----- protocol/grpc/client/channel.proto | 2 +- protocol/grpc/client/service.proto | 2 +- protocol/grpc/manage/node.proto | 2 +- 10 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 controller/src/network/client/beat.rs create mode 100644 controller/src/network/client/ready.rs create mode 100644 controller/src/network/client/running.rs create mode 100644 controller/src/network/client/stop.rs diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index 4adeb361..ee909116 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -1,6 +1,27 @@ +use proto::client_service_server::ClientService; +use tonic::{async_trait, Request, Response, Status}; + +use crate::application::TaskSender; + +mod beat; +mod ready; +mod running; +mod stop; + #[allow(clippy::all)] pub mod proto { use tonic::include_proto; include_proto!("client"); +} + +pub struct ClientServiceImpl { + queue: TaskSender, +} + +#[async_trait] +impl ClientService for ClientServiceImpl { + async fn beat(&self, _request: Request<()>) -> Result, Status> { + todo!() + } } \ No newline at end of file diff --git a/controller/src/network/client/beat.rs b/controller/src/network/client/beat.rs new file mode 100644 index 00000000..da7a6229 --- /dev/null +++ b/controller/src/network/client/beat.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{application::Controller, task::{BoxedAny, GenericTask}}; + +pub struct BeatTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for BeatTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/controller/src/network/client/ready.rs b/controller/src/network/client/ready.rs new file mode 100644 index 00000000..f519a038 --- /dev/null +++ b/controller/src/network/client/ready.rs @@ -0,0 +1,17 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{application::Controller, task::{BoxedAny, GenericTask}}; + +pub struct SetReadyTask { + server: Uuid, + ready: bool, +} + +#[async_trait] +impl GenericTask for SetReadyTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/controller/src/network/client/running.rs b/controller/src/network/client/running.rs new file mode 100644 index 00000000..7aaf498a --- /dev/null +++ b/controller/src/network/client/running.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{application::Controller, task::{BoxedAny, GenericTask}}; + +pub struct SetRunningTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for SetRunningTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/controller/src/network/client/stop.rs b/controller/src/network/client/stop.rs new file mode 100644 index 00000000..c96da8f7 --- /dev/null +++ b/controller/src/network/client/stop.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{application::Controller, task::{BoxedAny, GenericTask}}; + +pub struct RequestStopTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for RequestStopTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs index 97fde488..36c37970 100644 --- a/controller/src/network/manage.rs +++ b/controller/src/network/manage.rs @@ -1,6 +1,20 @@ +use proto::manage_service_server::ManageService; +use tonic::async_trait; + +use crate::application::TaskSender; + #[allow(clippy::all)] pub mod proto { use tonic::include_proto; include_proto!("manage"); +} + +pub struct ManageServiceImpl { + queue: TaskSender, +} + +#[async_trait] +impl ManageService for ManageServiceImpl { + } \ No newline at end of file diff --git a/controller/src/task.rs b/controller/src/task.rs index ba77ee5d..b4531a32 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -6,8 +6,8 @@ use tonic::async_trait; use crate::application::{Controller, TaskSender}; -type BoxedTask = Box; -type BoxedAny = Box; +type BoxedTask = Box; +pub type BoxedAny = Box; pub struct Task { task: BoxedTask, @@ -15,12 +15,12 @@ pub struct Task { } impl Task { - pub async fn create(controller: TaskSender, task: BoxedTask) -> Result { + pub async fn create(queue: TaskSender, task: BoxedTask) -> Result { let (sender, receiver) = channel(); - controller + queue .send(Task { task, sender }) .await - .map_err(|_| anyhow!("Failed to send task to controller"))?; + .map_err(|_| anyhow!("Failed to send task to task queue"))?; Ok(*receiver.await??.downcast::().map_err(|_| { anyhow!( "Failed to downcast task result to the expected type. Check task implementation" diff --git a/protocol/grpc/client/channel.proto b/protocol/grpc/client/channel.proto index 7afd8b69..dbf6afc9 100644 --- a/protocol/grpc/client/channel.proto +++ b/protocol/grpc/client/channel.proto @@ -7,6 +7,6 @@ message Channel { string name = 1; uint32 id = 2; string data = 3; - uint64 time = 4; // timestamp (e.g. epoch time) + uint64 timestamp = 4; // timestamp (e.g. epoch time) } } \ No newline at end of file diff --git a/protocol/grpc/client/service.proto b/protocol/grpc/client/service.proto index e4bd2f3d..70e14382 100644 --- a/protocol/grpc/client/service.proto +++ b/protocol/grpc/client/service.proto @@ -32,7 +32,7 @@ service ClientService { // Transfer operations rpc SubscribeToTransfers(google.protobuf.Empty) returns (stream Transfer.Res); - rpc RequestTransfer(Transfer.Req) returns (google.protobuf.UInt32Value); + rpc RequestTransfers(Transfer.Req) returns (google.protobuf.UInt32Value); // Channel operations rpc PublishMessage(Channel.Msg) returns (google.protobuf.UInt32Value); diff --git a/protocol/grpc/manage/node.proto b/protocol/grpc/manage/node.proto index ac406e9f..f8cbe2af 100644 --- a/protocol/grpc/manage/node.proto +++ b/protocol/grpc/manage/node.proto @@ -6,7 +6,7 @@ message Node { message Item { string name = 1; string plugin = 2; - uint32 mem = 3; + uint32 memory = 3; uint32 max = 4; string child = 5; string ctrlAddr = 6; From 58a5c9614e782e6198817b555d60fbebf71fbc7a Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:46:42 +0100 Subject: [PATCH 24/74] Networking. Yay --- Cargo.toml | 3 +- common/src/error.rs | 38 ++++ common/src/lib.rs | 1 + controller/Cargo.toml | 3 +- controller/src/application.rs | 23 ++- controller/src/application/auth.rs | 2 +- .../auth/{validator.rs => service.rs} | 8 +- controller/src/application/server/manager.rs | 20 +- .../src/application/server/manager/action.rs | 13 +- controller/src/main.rs | 38 ++-- controller/src/network.rs | 88 ++++++++- controller/src/network/auth.rs | 32 ++++ controller/src/network/client.rs | 174 ++++++++++++++++-- controller/src/network/client/beat.rs | 11 +- controller/src/network/client/group.rs | 19 ++ controller/src/network/client/health.rs | 30 +++ controller/src/network/client/ready.rs | 13 +- .../network/client/{running.rs => reset.rs} | 11 +- controller/src/network/client/server.rs | 19 ++ controller/src/network/client/transfer.rs | 19 ++ controller/src/network/client/user.rs | 30 +++ controller/src/network/manage.rs | 163 ++++++++++++++-- controller/src/network/manage/group.rs | 32 ++++ controller/src/network/manage/node.rs | 32 ++++ controller/src/network/manage/plugin.rs | 16 ++ .../{client/stop.rs => manage/power.rs} | 12 +- controller/src/network/manage/resource.rs | 25 +++ controller/src/network/manage/server.rs | 24 +++ controller/src/network/manage/transfer.rs | 16 ++ controller/src/network/manage/user.rs | 16 ++ controller/src/network/proto.rs | 17 ++ controller/src/task.rs | 4 +- protocol/grpc/client/service.proto | 8 +- protocol/grpc/client/transfer.proto | 4 +- protocol/grpc/manage/service.proto | 4 +- protocol/grpc/manage/transfer.proto | 2 +- 36 files changed, 857 insertions(+), 113 deletions(-) create mode 100644 common/src/error.rs rename controller/src/application/auth/{validator.rs => service.rs} (94%) create mode 100644 controller/src/network/auth.rs create mode 100644 controller/src/network/client/group.rs create mode 100644 controller/src/network/client/health.rs rename controller/src/network/client/{running.rs => reset.rs} (59%) create mode 100644 controller/src/network/client/server.rs create mode 100644 controller/src/network/client/transfer.rs create mode 100644 controller/src/network/client/user.rs create mode 100644 controller/src/network/manage/group.rs create mode 100644 controller/src/network/manage/node.rs create mode 100644 controller/src/network/manage/plugin.rs rename controller/src/network/{client/stop.rs => manage/power.rs} (60%) create mode 100644 controller/src/network/manage/resource.rs create mode 100644 controller/src/network/manage/server.rs create mode 100644 controller/src/network/manage/transfer.rs create mode 100644 controller/src/network/manage/user.rs create mode 100644 controller/src/network/proto.rs diff --git a/Cargo.toml b/Cargo.toml index 51b2da4f..f1f3ede7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,4 @@ members = [ protocol-version = 6 [profile.release] -lto = true -strip = true +lto = true \ No newline at end of file diff --git a/common/src/error.rs b/common/src/error.rs new file mode 100644 index 00000000..469e18d9 --- /dev/null +++ b/common/src/error.rs @@ -0,0 +1,38 @@ +use std::backtrace::BacktraceStatus; + +use anyhow::Error; +use simplelog::error; + +pub struct CloudError(); + +impl CloudError { + pub fn print_fancy(error: &Error, critical: bool) { + let exit_message = if critical { + "An error occurred causing the controller to exit. The controller cannot continue after this error." + } else { + "An error occurred, but the controller can continue. The controller may not function as expected." + }; + + error!("{}", exit_message); + error!("If you believe this error was not caused by the runtime, for example: a missing network connection, please report this error to the developers."); + error!("Create a new issue on the GitHub repository at the following link: https://github.com/HttpRafa/atomic-cloud with the information below:"); + + error!("Error: {}", error); + error + .chain() + .skip(1) + .for_each(|error| error!(" Caused by: {}", error)); + + match error.backtrace().status() { + BacktraceStatus::Captured => { + error!("Backtrace:"); + format!("{}", error.backtrace()) + .lines() + .for_each(|line| error!("{}", line)); + } + _ => { + error!("Backtrace is not available. Ensure you run the program with `RUST_BACKTRACE=1` to enable backtraces."); + } + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 124b989a..b3cf8314 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,5 +1,6 @@ pub mod allocator; pub mod config; +pub mod error; pub mod file; pub mod init; pub mod name; diff --git a/controller/Cargo.toml b/controller/Cargo.toml index cc077fd7..f96b93c3 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -23,7 +23,7 @@ ctrlc = "3.4.5" uuid = { version = "1.12.1", features = ["v4"] } # Command line arguments -clap = { version = "4.5.27", features = ["derive"] } +clap = { version = "4.5.28", features = ["derive"] } # Regex parsing regex = "1.11.1" @@ -34,6 +34,7 @@ toml = "0.8.19" # Async runtime tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "fs", "macros"] } +tokio-stream = "0.1.17" futures = "0.3.31" # API diff --git a/controller/src/application.rs b/controller/src/application.rs index c5282fc7..a41438b4 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -7,7 +7,7 @@ use std::{ }; use anyhow::Result; -use auth::validator::{AuthValidator, WrappedAuthValidator}; +use auth::service::AuthService; use getset::{Getters, MutGetters}; use group::manager::GroupManager; use node::manager::NodeManager; @@ -21,9 +21,9 @@ use tokio::{ }; use user::manager::UserManager; -use crate::{config::Config, task::Task}; +use crate::{config::Config, network::NetworkStack, task::Task}; -mod auth; +pub mod auth; mod group; mod node; mod plugin; @@ -44,7 +44,7 @@ pub struct Controller { tasks: (TaskSender, Receiver), /* Auth */ - validator: WrappedAuthValidator, + auth: Arc, /* Components */ #[getset(get = "pub", get_mut = "pub")] @@ -65,7 +65,7 @@ pub struct Controller { impl Controller { pub async fn init(config: Config) -> Result { - let validator = AuthValidator::init().await?; + let auth = AuthService::init().await?; let plugins = PluginManager::init(&config).await?; let nodes = NodeManager::init(&plugins).await?; @@ -77,7 +77,7 @@ impl Controller { Ok(Self { running: Arc::new(AtomicBool::new(true)), tasks: channel(TASK_BUFFER), - validator, + auth, plugins, nodes, groups, @@ -91,6 +91,8 @@ impl Controller { // Setup signal handlers self.setup_handlers()?; + let network = NetworkStack::start(&self.config, &self.auth, &self.tasks.0); + // Main loop let mut interval = interval(Duration::from_millis(1000 / TICK_RATE)); while self.running.load(Ordering::Relaxed) { @@ -103,7 +105,7 @@ impl Controller { } // Shutdown - self.shutdown().await?; + self.shutdown(network).await?; Ok(()) } @@ -125,7 +127,7 @@ impl Controller { &self.nodes, &mut self.groups, &mut self.users, - &self.validator, + &self.auth, ) .await?; @@ -135,7 +137,7 @@ impl Controller { Ok(()) } - async fn shutdown(&mut self) -> Result<()> { + async fn shutdown(&mut self, network: NetworkStack) -> Result<()> { info!("Starting shutdown sequence..."); // Shutdown user manager @@ -153,6 +155,9 @@ impl Controller { // Shutdown plugin manager self.plugins.shutdown().await?; + // Shutdown network stack + network.shutdown().await?; + info!("Shutdown complete. Bye :)"); Ok(()) } diff --git a/controller/src/application/auth.rs b/controller/src/application/auth.rs index a624ac38..4de20f35 100644 --- a/controller/src/application/auth.rs +++ b/controller/src/application/auth.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -pub mod validator; +pub mod service; const DEFAULT_ADMIN_USERNAME: &str = "admin"; diff --git a/controller/src/application/auth/validator.rs b/controller/src/application/auth/service.rs similarity index 94% rename from controller/src/application/auth/validator.rs rename to controller/src/application/auth/service.rs index 5e662fc1..d2949070 100644 --- a/controller/src/application/auth/validator.rs +++ b/controller/src/application/auth/service.rs @@ -11,14 +11,12 @@ use crate::storage::Storage; use super::{AdminUser, AuthToken, Authorization}; -pub type WrappedAuthValidator = Arc; - -pub struct AuthValidator { +pub struct AuthService { tokens: RwLock>, } -impl AuthValidator { - pub async fn init() -> Result { +impl AuthService { + pub async fn init() -> Result> { info!("Loading users..."); let mut tokens = HashMap::new(); diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 20f4f5f1..b60bfcb9 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -1,6 +1,7 @@ use std::{ collections::{BinaryHeap, HashMap}, mem, + sync::Arc, }; use anyhow::Result; @@ -12,8 +13,8 @@ use uuid::Uuid; use crate::{ application::{ - auth::validator::WrappedAuthValidator, group::manager::GroupManager, - node::manager::NodeManager, user::manager::UserManager, + auth::service::AuthService, group::manager::GroupManager, node::manager::NodeManager, + user::manager::UserManager, }, config::Config, }; @@ -71,7 +72,7 @@ impl ServerManager { nodes: &NodeManager, groups: &mut GroupManager, users: &mut UserManager, - validator: &WrappedAuthValidator, + auth: &Arc, ) -> Result<()> { // Check health of servers for server in self.servers.values() { @@ -118,15 +119,8 @@ impl ServerManager { if handle.is_finished() { handle.await??; debug!("Stopping server {}", request.server); - match Self::stop( - &request, - &mut self.servers, - nodes, - groups, - users, - validator, - ) - .await + match Self::stop(&request, &mut self.servers, nodes, groups, users, auth) + .await { Ok(handle) => { reinsert = true; @@ -251,7 +245,7 @@ impl ServerManager { config, nodes, groups, - validator, + auth, ) .await { diff --git a/controller/src/application/server/manager/action.rs b/controller/src/application/server/manager/action.rs index 630b829e..6f01f202 100644 --- a/controller/src/application/server/manager/action.rs +++ b/controller/src/application/server/manager/action.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use anyhow::{anyhow, Result}; use common::network::HostAndPort; @@ -8,7 +8,7 @@ use uuid::Uuid; use crate::{ application::{ - auth::validator::WrappedAuthValidator, + auth::service::AuthService, group::manager::GroupManager, node::{manager::NodeManager, Allocation}, server::{Flags, Health, Server, State}, @@ -42,6 +42,7 @@ impl ServerManager { )) } } + #[allow(clippy::too_many_arguments)] pub async fn start( index: usize, request: &StartRequest, @@ -50,7 +51,7 @@ impl ServerManager { config: &Config, nodes: &NodeManager, groups: &mut GroupManager, - validator: &WrappedAuthValidator, + auth: &Arc, ) -> Result>> { if let Some(name) = request.nodes.get(index) { let node = nodes.get_node(name); @@ -65,7 +66,7 @@ impl ServerManager { spec: request.spec.clone(), }, connected_users: 0, - token: validator.register_server(request.id.uuid).await, + token: auth.register_server(request.id.uuid).await, health: Health::new(*config.startup_timeout(), *config.heartbeat_timeout()), state: State::Starting, flags: Flags::default(), @@ -147,7 +148,7 @@ impl ServerManager { nodes: &NodeManager, groups: &mut GroupManager, users: &mut UserManager, - validator: &WrappedAuthValidator, + auth: &Arc, ) -> Result>> { if let Some(server) = servers.get_mut(request.server.uuid()) { if let Some(node) = nodes.get_node(&server.node) { @@ -159,7 +160,7 @@ impl ServerManager { server.group = None; } } - validator.unregister(&server.token).await; + auth.unregister(&server.token).await; users.remove_users_on_server(server.id.uuid()); // TODO: Cleanup subscriptions diff --git a/controller/src/main.rs b/controller/src/main.rs index 86e845b1..10658413 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -3,15 +3,15 @@ use anyhow::Result; use application::Controller; use clap::{ArgAction, Parser}; -use common::init::CloudInit; +use common::{error::CloudError, init::CloudInit}; use config::Config; use simplelog::info; use storage::Storage; use tokio::time::Instant; mod application; -mod network; mod config; +mod network; mod storage; mod task; @@ -21,20 +21,26 @@ include!(concat!(env!("OUT_DIR"), "/build_info.rs")); pub const AUTHORS: [&str; 1] = ["HttpRafa"]; #[tokio::main] -async fn main() -> Result<()> { - let arguments = Arguments::parse(); - CloudInit::init_logging(arguments.debug, false, Storage::latest_log_file()); - CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); - - let beginning = Instant::now(); - info!("Starting cloud version v{}...", VERSION); - info!("Initializing controller..."); - - let mut controller = Controller::init(Config::parse()?).await?; - info!("Loaded cloud in {:.2?}", beginning.elapsed()); - controller.run().await?; - - Ok(()) +async fn main() { + if let Err(error) = run().await { + CloudError::print_fancy(&error, true); + } + + async fn run() -> Result<()> { + let arguments = Arguments::parse(); + CloudInit::init_logging(arguments.debug, false, Storage::latest_log_file()); + CloudInit::print_ascii_art("Atomic Cloud", &VERSION, &AUTHORS); + + let beginning = Instant::now(); + info!("Starting cloud version v{}...", VERSION); + info!("Initializing controller..."); + + let mut controller = Controller::init(Config::parse()?).await?; + info!("Loaded cloud in {:.2?}", beginning.elapsed()); + controller.run().await?; + + Ok(()) + } } #[derive(Parser)] diff --git a/controller/src/network.rs b/controller/src/network.rs index eed8a9f9..5100f67b 100644 --- a/controller/src/network.rs +++ b/controller/src/network.rs @@ -1,2 +1,88 @@ +use std::{net::SocketAddr, sync::Arc}; + +use anyhow::Result; +use auth::AuthInterceptor; +use client::ClientServiceImpl; +use common::error::CloudError; +use manage::ManageServiceImpl; +use proto::{ + client::client_service_server::ClientServiceServer, + manage::manage_service_server::ManageServiceServer, +}; +use simplelog::info; +use tokio::{ + spawn, + sync::watch::{channel, Receiver, Sender}, + task::JoinHandle, +}; +use tonic::transport::Server; + +use crate::{ + application::{auth::service::AuthService, TaskSender}, + config::Config, +}; + +mod auth; +mod client; mod manage; -mod client; \ No newline at end of file +mod proto; + +pub struct NetworkStack { + shutdown: Sender, + handle: JoinHandle<()>, +} + +impl NetworkStack { + pub fn start(config: &Config, auth: &Arc, queue: &TaskSender) -> Self { + info!("Starting network stack..."); + + let (sender, receiver) = channel(false); + let bind = *config.network_bind(); + let auth = auth.clone(); + let queue = queue.clone(); + + let task = spawn(async move { + if let Err(error) = run(bind, auth, queue, receiver).await { + CloudError::print_fancy(&error, false); + } + }); + + return Self { + shutdown: sender, + handle: task, + }; + + async fn run( + bind: SocketAddr, + auth: Arc, + queue: TaskSender, + mut shutdown: Receiver, + ) -> Result<()> { + let auth_interceptor = AuthInterceptor(auth); + info!("Controller listening on {}", bind); + + Server::builder() + .add_service(ManageServiceServer::with_interceptor( + ManageServiceImpl(queue.clone()), + auth_interceptor.clone(), + )) + .add_service(ClientServiceServer::with_interceptor( + ClientServiceImpl(queue), + auth_interceptor, + )) + .serve_with_shutdown(bind, async { + shutdown.changed().await.ok(); + }) + .await?; + + Ok(()) + } + } + + pub async fn shutdown(self) -> Result<()> { + info!("Stopping network stack..."); + let _ = self.shutdown.send(true); // Ignore error if receiver is dropped + self.handle.await?; + Ok(()) + } +} diff --git a/controller/src/network/auth.rs b/controller/src/network/auth.rs new file mode 100644 index 00000000..d893e1c5 --- /dev/null +++ b/controller/src/network/auth.rs @@ -0,0 +1,32 @@ +use futures::executor::block_on; +use std::sync::Arc; +use tonic::{service::Interceptor, Request, Status}; + +use crate::application::auth::{service::AuthService, Authorization}; + +#[derive(Clone)] +pub struct AuthInterceptor(pub Arc); + +impl Interceptor for AuthInterceptor { + fn call(&mut self, mut request: Request<()>) -> Result, Status> { + let metadata = request.metadata(); + let token = metadata.get("authorization").and_then(|t| t.to_str().ok()); + if let Some(token) = token { + match block_on(self.0.has_access(token)) { + Some(Authorization::User(user)) => { + request.extensions_mut().insert(user); + Ok(request) + } + Some(Authorization::Server(server)) => { + request.extensions_mut().insert(server); + Ok(request) + } + _ => Err(Status::unauthenticated( + "Invalid authorization token provided", + )), + } + } else { + Err(Status::unauthenticated("No authorization token provided")) + } + } +} diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index ee909116..d3982cf3 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -1,27 +1,169 @@ -use proto::client_service_server::ClientService; +use anyhow::Result; +use beat::BeatTask; +use common::error::CloudError; +use health::{RequestStopTask, SetRunningTask}; +use ready::SetReadyTask; +use tokio_stream::wrappers::ReceiverStream; use tonic::{async_trait, Request, Response, Status}; +use uuid::Uuid; -use crate::application::TaskSender; +use crate::{ + application::TaskSender, + task::{BoxedTask, Task}, + VERSION, +}; + +use super::proto::client::{ + self, + channel::Msg, + client_service_server::ClientService, + transfer::{TransferReq, TransferRes}, + user::{ConnectedReq, DisconnectedReq}, +}; mod beat; +mod group; +mod health; mod ready; -mod running; -mod stop; - -#[allow(clippy::all)] -pub mod proto { - use tonic::include_proto; - - include_proto!("client"); -} +mod reset; +mod server; +mod transfer; +mod user; -pub struct ClientServiceImpl { - queue: TaskSender, -} +pub struct ClientServiceImpl(pub TaskSender); #[async_trait] impl ClientService for ClientServiceImpl { - async fn beat(&self, _request: Request<()>) -> Result, Status> { + type SubscribeToTransfersStream = ReceiverStream>; + type SubscribeToChannelStream = ReceiverStream>; + + // Heartbeat + async fn beat(&self, mut request: Request<()>) -> Result, Status> { + Ok(Response::new( + self.execute_task::<(), _, _>(&mut request, |_, server| Box::new(BeatTask { server })) + .await?, + )) + } + + // Ready state + async fn set_ready(&self, mut request: Request) -> Result, Status> { + Ok(Response::new( + self.execute_task::<(), _, _>(&mut request, |request, server| { + Box::new(SetReadyTask { + server, + ready: *request.get_ref(), + }) + }) + .await?, + )) + } + + // Health + async fn set_running(&self, mut request: Request<()>) -> Result, Status> { + Ok(Response::new( + self.execute_task::<(), _, _>(&mut request, |_, server| { + Box::new(SetRunningTask { server }) + }) + .await?, + )) + } + async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { + Ok(Response::new( + self.execute_task::<(), _, _>(&mut request, |_, server| { + Box::new(RequestStopTask { server }) + }) + .await?, + )) + } + + // User + async fn user_connected( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn user_disconnected( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + // Transfer + async fn transfer_users( + &self, + _request: Request, + ) -> Result, Status> { todo!() } -} \ No newline at end of file + async fn subscribe_to_transfers( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Channel + async fn publish_message(&self, _request: Request) -> Result, Status> { + todo!() + } + async fn subscribe_to_channel( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + // Server + async fn get_servers( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Group + async fn get_groups( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Housekeeping + async fn reset(&self, _request: Request<()>) -> Result, Status> { + todo!() + } + + // Version info + async fn get_proto_ver(&self, _request: Request<()>) -> Result, Status> { + Ok(Response::new(VERSION.protocol)) + } + async fn get_ctrl_ver(&self, _request: Request<()>) -> Result, Status> { + Ok(Response::new(format!("{}", VERSION))) + } +} + +impl ClientServiceImpl { + async fn execute_task( + &self, + request: &mut Request, + task: F, + ) -> Result + where + F: FnOnce(&mut Request, Uuid) -> BoxedTask, + { + let server = *match request.extensions().get::() { + Some(server) => server, + None => return Err(Status::permission_denied("Not linked to server")), + }; + match Task::create::(&self.0, task(request, server)).await { + Ok(value) => Ok(value), + Err(error) => { + CloudError::print_fancy(&error, false); + Err(Status::internal(error.to_string())) + } + } + } +} diff --git a/controller/src/network/client/beat.rs b/controller/src/network/client/beat.rs index da7a6229..fcc5db79 100644 --- a/controller/src/network/client/beat.rs +++ b/controller/src/network/client/beat.rs @@ -2,15 +2,18 @@ use anyhow::Result; use tonic::async_trait; use uuid::Uuid; -use crate::{application::Controller, task::{BoxedAny, GenericTask}}; +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; pub struct BeatTask { - server: Uuid, + pub server: Uuid, } #[async_trait] impl GenericTask for BeatTask { async fn run(&mut self, _controller: &mut Controller) -> Result { - todo!() + Ok(Box::new(())) } -} \ No newline at end of file +} diff --git a/controller/src/network/client/group.rs b/controller/src/network/client/group.rs new file mode 100644 index 00000000..be78b864 --- /dev/null +++ b/controller/src/network/client/group.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct GetGroupsTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for GetGroupsTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/client/health.rs b/controller/src/network/client/health.rs new file mode 100644 index 00000000..b9e2330d --- /dev/null +++ b/controller/src/network/client/health.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct SetRunningTask { + pub server: Uuid, +} + +pub struct RequestStopTask { + pub server: Uuid, +} + +#[async_trait] +impl GenericTask for SetRunningTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + Ok(Box::new(())) + } +} + +#[async_trait] +impl GenericTask for RequestStopTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + Ok(Box::new(())) + } +} diff --git a/controller/src/network/client/ready.rs b/controller/src/network/client/ready.rs index f519a038..91f026b1 100644 --- a/controller/src/network/client/ready.rs +++ b/controller/src/network/client/ready.rs @@ -2,16 +2,19 @@ use anyhow::Result; use tonic::async_trait; use uuid::Uuid; -use crate::{application::Controller, task::{BoxedAny, GenericTask}}; +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; pub struct SetReadyTask { - server: Uuid, - ready: bool, + pub server: Uuid, + pub ready: bool, } #[async_trait] impl GenericTask for SetReadyTask { async fn run(&mut self, _controller: &mut Controller) -> Result { - todo!() + Ok(Box::new(())) } -} \ No newline at end of file +} diff --git a/controller/src/network/client/running.rs b/controller/src/network/client/reset.rs similarity index 59% rename from controller/src/network/client/running.rs rename to controller/src/network/client/reset.rs index 7aaf498a..6e8f16bc 100644 --- a/controller/src/network/client/running.rs +++ b/controller/src/network/client/reset.rs @@ -2,15 +2,18 @@ use anyhow::Result; use tonic::async_trait; use uuid::Uuid; -use crate::{application::Controller, task::{BoxedAny, GenericTask}}; +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; -pub struct SetRunningTask { +pub struct ResetTask { server: Uuid, } #[async_trait] -impl GenericTask for SetRunningTask { +impl GenericTask for ResetTask { async fn run(&mut self, _controller: &mut Controller) -> Result { todo!() } -} \ No newline at end of file +} diff --git a/controller/src/network/client/server.rs b/controller/src/network/client/server.rs new file mode 100644 index 00000000..ca0ae1b1 --- /dev/null +++ b/controller/src/network/client/server.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct GetServersTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for GetServersTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/client/transfer.rs b/controller/src/network/client/transfer.rs new file mode 100644 index 00000000..1fd9477c --- /dev/null +++ b/controller/src/network/client/transfer.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct TransferUsersTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for TransferUsersTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/client/user.rs b/controller/src/network/client/user.rs new file mode 100644 index 00000000..2bac26fd --- /dev/null +++ b/controller/src/network/client/user.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use tonic::async_trait; +use uuid::Uuid; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct UserConnectedTask { + server: Uuid, +} + +pub struct UserDisconnectedTask { + server: Uuid, +} + +#[async_trait] +impl GenericTask for UserConnectedTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for UserDisconnectedTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs index 36c37970..201bca06 100644 --- a/controller/src/network/manage.rs +++ b/controller/src/network/manage.rs @@ -1,20 +1,157 @@ -use proto::manage_service_server::ManageService; -use tonic::async_trait; +use anyhow::Result; +use common::error::CloudError; +use power::RequestStopTask; +use tonic::{async_trait, Request, Response, Status}; -use crate::application::TaskSender; +use crate::{ + application::{auth::AdminUser, TaskSender}, + task::{BoxedTask, Task}, + VERSION, +}; -#[allow(clippy::all)] -pub mod proto { - use tonic::include_proto; +use super::proto::manage::{ + self, + manage_service_server::ManageService, + resource::{DelReq, SetReq}, + transfer::TransferReq, +}; - include_proto!("manage"); -} +mod group; +mod node; +mod plugin; +mod power; +mod resource; +mod server; +mod transfer; +mod user; -pub struct ManageServiceImpl { - queue: TaskSender, -} +pub struct ManageServiceImpl(pub TaskSender); #[async_trait] impl ManageService for ManageServiceImpl { - -} \ No newline at end of file + // Power + async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { + Ok(Response::new( + self.execute_task::<(), _, _>(&mut request, |_, _| Box::new(RequestStopTask())) + .await?, + )) + } + + // Resource + async fn set_resource(&self, _request: Request) -> Result, Status> { + todo!() + } + async fn delete_resource(&self, _request: Request) -> Result, Status> { + todo!() + } + + // Plugin + async fn get_plugins( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Node + async fn create_node( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn get_node( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn get_nodes( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Group + async fn create_group( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn get_group( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn get_groups( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Server + async fn get_server( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + async fn get_servers( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // User + async fn get_users( + &self, + _request: Request<()>, + ) -> Result, Status> { + todo!() + } + + // Transfer + async fn transfer_users( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + // Version info + async fn get_proto_ver(&self, _request: Request<()>) -> Result, Status> { + Ok(Response::new(VERSION.protocol)) + } + async fn get_ctrl_ver(&self, _request: Request<()>) -> Result, Status> { + Ok(Response::new(format!("{}", VERSION))) + } +} + +impl ManageServiceImpl { + async fn execute_task( + &self, + request: &mut Request, + task: F, + ) -> Result + where + F: FnOnce(&mut Request, AdminUser) -> BoxedTask, + { + let server = match request.extensions().get::() { + Some(server) => server, + None => return Err(Status::permission_denied("Not linked to user")), + } + .clone(); + match Task::create::(&self.0, task(request, server)).await { + Ok(value) => Ok(value), + Err(error) => { + CloudError::print_fancy(&error, false); + Err(Status::internal(error.to_string())) + } + } + } +} diff --git a/controller/src/network/manage/group.rs b/controller/src/network/manage/group.rs new file mode 100644 index 00000000..43fedbbd --- /dev/null +++ b/controller/src/network/manage/group.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct CreateGroupTask {} +pub struct GetGroupTask {} +pub struct GetGroupsTask {} + +#[async_trait] +impl GenericTask for CreateGroupTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for GetGroupTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for GetGroupsTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage/node.rs b/controller/src/network/manage/node.rs new file mode 100644 index 00000000..5718a8d0 --- /dev/null +++ b/controller/src/network/manage/node.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct CreateNodeTask {} +pub struct GetNodeTask {} +pub struct GetNodesTask {} + +#[async_trait] +impl GenericTask for CreateNodeTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for GetNodeTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for GetNodesTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage/plugin.rs b/controller/src/network/manage/plugin.rs new file mode 100644 index 00000000..efde58ad --- /dev/null +++ b/controller/src/network/manage/plugin.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct GetPluginsTask {} + +#[async_trait] +impl GenericTask for GetPluginsTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/client/stop.rs b/controller/src/network/manage/power.rs similarity index 60% rename from controller/src/network/client/stop.rs rename to controller/src/network/manage/power.rs index c96da8f7..06c6b3d1 100644 --- a/controller/src/network/client/stop.rs +++ b/controller/src/network/manage/power.rs @@ -1,16 +1,16 @@ use anyhow::Result; use tonic::async_trait; -use uuid::Uuid; -use crate::{application::Controller, task::{BoxedAny, GenericTask}}; +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; -pub struct RequestStopTask { - server: Uuid, -} +pub struct RequestStopTask(); #[async_trait] impl GenericTask for RequestStopTask { async fn run(&mut self, _controller: &mut Controller) -> Result { todo!() } -} \ No newline at end of file +} diff --git a/controller/src/network/manage/resource.rs b/controller/src/network/manage/resource.rs new file mode 100644 index 00000000..94c7b348 --- /dev/null +++ b/controller/src/network/manage/resource.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct SetResourceTask {} + +pub struct DeleteResourceTask {} + +#[async_trait] +impl GenericTask for SetResourceTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for DeleteResourceTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage/server.rs b/controller/src/network/manage/server.rs new file mode 100644 index 00000000..34361a87 --- /dev/null +++ b/controller/src/network/manage/server.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct GetServerTask {} +pub struct GetServersTask {} + +#[async_trait] +impl GenericTask for GetServerTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} + +#[async_trait] +impl GenericTask for GetServersTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage/transfer.rs b/controller/src/network/manage/transfer.rs new file mode 100644 index 00000000..40134e6d --- /dev/null +++ b/controller/src/network/manage/transfer.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct TransferUsersTask {} + +#[async_trait] +impl GenericTask for TransferUsersTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/manage/user.rs b/controller/src/network/manage/user.rs new file mode 100644 index 00000000..b922c503 --- /dev/null +++ b/controller/src/network/manage/user.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use tonic::async_trait; + +use crate::{ + application::Controller, + task::{BoxedAny, GenericTask}, +}; + +pub struct GetUsersTask {} + +#[async_trait] +impl GenericTask for GetUsersTask { + async fn run(&mut self, _controller: &mut Controller) -> Result { + todo!() + } +} diff --git a/controller/src/network/proto.rs b/controller/src/network/proto.rs new file mode 100644 index 00000000..0592d20d --- /dev/null +++ b/controller/src/network/proto.rs @@ -0,0 +1,17 @@ +pub mod common { + use tonic::include_proto; + + include_proto!("common"); +} + +pub mod manage { + use tonic::include_proto; + + include_proto!("manage"); +} + +pub mod client { + use tonic::include_proto; + + include_proto!("client"); +} diff --git a/controller/src/task.rs b/controller/src/task.rs index b4531a32..eb65d3fe 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -6,7 +6,7 @@ use tonic::async_trait; use crate::application::{Controller, TaskSender}; -type BoxedTask = Box; +pub type BoxedTask = Box; pub type BoxedAny = Box; pub struct Task { @@ -15,7 +15,7 @@ pub struct Task { } impl Task { - pub async fn create(queue: TaskSender, task: BoxedTask) -> Result { + pub async fn create(queue: &TaskSender, task: BoxedTask) -> Result { let (sender, receiver) = channel(); queue .send(Task { task, sender }) diff --git a/protocol/grpc/client/service.proto b/protocol/grpc/client/service.proto index 70e14382..29676098 100644 --- a/protocol/grpc/client/service.proto +++ b/protocol/grpc/client/service.proto @@ -19,8 +19,7 @@ service ClientService { rpc Beat(google.protobuf.Empty) returns (google.protobuf.Empty); // Ready state - rpc SetReady(google.protobuf.Empty) returns (google.protobuf.Empty); - rpc SetNotReady(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc SetReady(google.protobuf.BoolValue) returns (google.protobuf.Empty); // Health rpc SetRunning(google.protobuf.Empty) returns (google.protobuf.Empty); @@ -31,12 +30,11 @@ service ClientService { rpc UserDisconnected(User.DisconnectedReq) returns (google.protobuf.Empty); // Transfer operations - rpc SubscribeToTransfers(google.protobuf.Empty) returns (stream Transfer.Res); - rpc RequestTransfers(Transfer.Req) returns (google.protobuf.UInt32Value); + rpc TransferUsers(Transfer.TransferReq) returns (google.protobuf.UInt32Value); + rpc SubscribeToTransfers(google.protobuf.Empty) returns (stream Transfer.TransferRes); // Channel operations rpc PublishMessage(Channel.Msg) returns (google.protobuf.UInt32Value); - rpc UnsubscribeFromChannel(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc SubscribeToChannel(google.protobuf.StringValue) returns (stream Channel.Msg); // Server/Group info diff --git a/protocol/grpc/client/transfer.proto b/protocol/grpc/client/transfer.proto index a332595a..edfb2e61 100644 --- a/protocol/grpc/client/transfer.proto +++ b/protocol/grpc/client/transfer.proto @@ -12,11 +12,11 @@ message Transfer { Type type = 1; string target = 2; } - message Req { + message TransferReq { repeated string ids = 1; Target target = 2; } - message Res { + message TransferRes { string id = 1; string host = 2; uint32 port = 3; diff --git a/protocol/grpc/manage/service.proto b/protocol/grpc/manage/service.proto index f3f6ef5d..18fc5dd5 100644 --- a/protocol/grpc/manage/service.proto +++ b/protocol/grpc/manage/service.proto @@ -8,6 +8,8 @@ package manage; import "google/protobuf/empty.proto"; import "google/protobuf/wrappers.proto"; +import "common/common.proto"; + import "manage/resource.proto"; import "manage/plugin.proto"; import "manage/node.proto"; @@ -44,7 +46,7 @@ service ManageService { rpc GetUsers(google.protobuf.Empty) returns (User.List); // Transfer operations - rpc TransferUsers(Transfer.Req) returns (google.protobuf.UInt32Value); + rpc TransferUsers(Transfer.TransferReq) returns (google.protobuf.UInt32Value); // Version info rpc GetProtoVer(google.protobuf.Empty) returns (google.protobuf.UInt32Value); diff --git a/protocol/grpc/manage/transfer.proto b/protocol/grpc/manage/transfer.proto index 57724408..ad24b9ee 100644 --- a/protocol/grpc/manage/transfer.proto +++ b/protocol/grpc/manage/transfer.proto @@ -12,7 +12,7 @@ message Transfer { Type type = 1; string target = 2; } - message Req { + message TransferReq { repeated string ids = 1; Target target = 2; } From bebedf8ba193c97d42383a05e33a143eb75fa831 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:53:18 +0100 Subject: [PATCH 25/74] More fancy stuff --- controller/src/network/client.rs | 38 +++++++------------------------- controller/src/network/manage.rs | 33 +++++---------------------- controller/src/task.rs | 25 ++++++++++++++++++++- 3 files changed, 37 insertions(+), 59 deletions(-) diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index d3982cf3..043022a4 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -1,6 +1,5 @@ use anyhow::Result; use beat::BeatTask; -use common::error::CloudError; use health::{RequestStopTask, SetRunningTask}; use ready::SetReadyTask; use tokio_stream::wrappers::ReceiverStream; @@ -9,7 +8,7 @@ use uuid::Uuid; use crate::{ application::TaskSender, - task::{BoxedTask, Task}, + task::Task, VERSION, }; @@ -40,15 +39,17 @@ impl ClientService for ClientServiceImpl { // Heartbeat async fn beat(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - self.execute_task::<(), _, _>(&mut request, |_, server| Box::new(BeatTask { server })) - .await?, + Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { + Box::new(BeatTask { server }) + }) + .await?, )) } // Ready state async fn set_ready(&self, mut request: Request) -> Result, Status> { Ok(Response::new( - self.execute_task::<(), _, _>(&mut request, |request, server| { + Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |request, server| { Box::new(SetReadyTask { server, ready: *request.get_ref(), @@ -61,7 +62,7 @@ impl ClientService for ClientServiceImpl { // Health async fn set_running(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - self.execute_task::<(), _, _>(&mut request, |_, server| { + Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { Box::new(SetRunningTask { server }) }) .await?, @@ -69,7 +70,7 @@ impl ClientService for ClientServiceImpl { } async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - self.execute_task::<(), _, _>(&mut request, |_, server| { + Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { Box::new(RequestStopTask { server }) }) .await?, @@ -144,26 +145,3 @@ impl ClientService for ClientServiceImpl { Ok(Response::new(format!("{}", VERSION))) } } - -impl ClientServiceImpl { - async fn execute_task( - &self, - request: &mut Request, - task: F, - ) -> Result - where - F: FnOnce(&mut Request, Uuid) -> BoxedTask, - { - let server = *match request.extensions().get::() { - Some(server) => server, - None => return Err(Status::permission_denied("Not linked to server")), - }; - match Task::create::(&self.0, task(request, server)).await { - Ok(value) => Ok(value), - Err(error) => { - CloudError::print_fancy(&error, false); - Err(Status::internal(error.to_string())) - } - } - } -} diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs index 201bca06..80c2f9b8 100644 --- a/controller/src/network/manage.rs +++ b/controller/src/network/manage.rs @@ -1,11 +1,10 @@ use anyhow::Result; -use common::error::CloudError; use power::RequestStopTask; use tonic::{async_trait, Request, Response, Status}; use crate::{ application::{auth::AdminUser, TaskSender}, - task::{BoxedTask, Task}, + task::Task, VERSION, }; @@ -32,8 +31,10 @@ impl ManageService for ManageServiceImpl { // Power async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - self.execute_task::<(), _, _>(&mut request, |_, _| Box::new(RequestStopTask())) - .await?, + Task::execute_task::<(), AdminUser, _, _>(&self.0, &mut request, |_, _| { + Box::new(RequestStopTask()) + }) + .await?, )) } @@ -131,27 +132,3 @@ impl ManageService for ManageServiceImpl { Ok(Response::new(format!("{}", VERSION))) } } - -impl ManageServiceImpl { - async fn execute_task( - &self, - request: &mut Request, - task: F, - ) -> Result - where - F: FnOnce(&mut Request, AdminUser) -> BoxedTask, - { - let server = match request.extensions().get::() { - Some(server) => server, - None => return Err(Status::permission_denied("Not linked to user")), - } - .clone(); - match Task::create::(&self.0, task(request, server)).await { - Ok(value) => Ok(value), - Err(error) => { - CloudError::print_fancy(&error, false); - Err(Status::internal(error.to_string())) - } - } - } -} diff --git a/controller/src/task.rs b/controller/src/task.rs index eb65d3fe..ce14df2b 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -1,8 +1,9 @@ use std::any::Any; use anyhow::{anyhow, Result}; +use common::error::CloudError; use tokio::sync::oneshot::{channel, Sender}; -use tonic::async_trait; +use tonic::{async_trait, Request, Status}; use crate::application::{Controller, TaskSender}; @@ -15,6 +16,28 @@ pub struct Task { } impl Task { + pub async fn execute_task( + queue: &TaskSender, + request: &mut Request, + task: F, + ) -> Result + where + F: FnOnce(&mut Request, D) -> BoxedTask, + { + let data = match request.extensions().get::() { + Some(data) => data, + None => return Err(Status::permission_denied("Not linked")), + } + .clone(); + match Task::create::(queue, task(request, data)).await { + Ok(value) => Ok(value), + Err(error) => { + CloudError::print_fancy(&error, false); + Err(Status::internal(error.to_string())) + } + } + } + pub async fn create(queue: &TaskSender, task: BoxedTask) -> Result { let (sender, receiver) = channel(); queue From eeec1a0574db58a770334aef4405b05d776c6f00 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:55:32 +0100 Subject: [PATCH 26/74] Naming --- controller/src/network/client.rs | 14 +++++--------- controller/src/network/manage.rs | 2 +- controller/src/task.rs | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index 043022a4..0c10056d 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -6,11 +6,7 @@ use tokio_stream::wrappers::ReceiverStream; use tonic::{async_trait, Request, Response, Status}; use uuid::Uuid; -use crate::{ - application::TaskSender, - task::Task, - VERSION, -}; +use crate::{application::TaskSender, task::Task, VERSION}; use super::proto::client::{ self, @@ -39,7 +35,7 @@ impl ClientService for ClientServiceImpl { // Heartbeat async fn beat(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { + Task::execute::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { Box::new(BeatTask { server }) }) .await?, @@ -49,7 +45,7 @@ impl ClientService for ClientServiceImpl { // Ready state async fn set_ready(&self, mut request: Request) -> Result, Status> { Ok(Response::new( - Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |request, server| { + Task::execute::<(), Uuid, _, _>(&self.0, &mut request, |request, server| { Box::new(SetReadyTask { server, ready: *request.get_ref(), @@ -62,7 +58,7 @@ impl ClientService for ClientServiceImpl { // Health async fn set_running(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { + Task::execute::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { Box::new(SetRunningTask { server }) }) .await?, @@ -70,7 +66,7 @@ impl ClientService for ClientServiceImpl { } async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - Task::execute_task::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { + Task::execute::<(), Uuid, _, _>(&self.0, &mut request, |_, server| { Box::new(RequestStopTask { server }) }) .await?, diff --git a/controller/src/network/manage.rs b/controller/src/network/manage.rs index 80c2f9b8..59ef0ce9 100644 --- a/controller/src/network/manage.rs +++ b/controller/src/network/manage.rs @@ -31,7 +31,7 @@ impl ManageService for ManageServiceImpl { // Power async fn request_stop(&self, mut request: Request<()>) -> Result, Status> { Ok(Response::new( - Task::execute_task::<(), AdminUser, _, _>(&self.0, &mut request, |_, _| { + Task::execute::<(), AdminUser, _, _>(&self.0, &mut request, |_, _| { Box::new(RequestStopTask()) }) .await?, diff --git a/controller/src/task.rs b/controller/src/task.rs index ce14df2b..e703fe30 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -16,7 +16,7 @@ pub struct Task { } impl Task { - pub async fn execute_task( + pub async fn execute( queue: &TaskSender, request: &mut Request, task: F, From c672046d088ed0126e6985529645ec7264d5fb03 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:42:26 +0100 Subject: [PATCH 27/74] Implement some tasks --- controller/src/application.rs | 4 ++ controller/src/application/plugin.rs | 4 +- controller/src/application/server.rs | 22 +++++----- controller/src/application/server/manager.rs | 4 +- .../src/application/server/manager/action.rs | 7 +-- controller/src/network/client.rs | 4 +- controller/src/network/client/beat.rs | 13 ++++-- controller/src/network/client/health.rs | 6 +-- controller/src/network/client/ready.rs | 13 ++++-- controller/src/task.rs | 43 +++++++++++++++---- 10 files changed, 81 insertions(+), 39 deletions(-) diff --git a/controller/src/application.rs b/controller/src/application.rs index a41438b4..6d5dc3b0 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -170,4 +170,8 @@ impl Controller { }) .map_err(|error| error.into()) } + + pub fn signal_shutdown(&self) { + self.running.store(false, Ordering::Relaxed); + } } diff --git a/controller/src/application/plugin.rs b/controller/src/application/plugin.rs index 0ddf483f..1d66584e 100644 --- a/controller/src/application/plugin.rs +++ b/controller/src/application/plugin.rs @@ -11,8 +11,8 @@ use super::{ pub mod manager; mod runtime; -pub type WrappedPlugin = Box; -pub type WrappedNode = Box; +pub type WrappedPlugin = Box; +pub type WrappedNode = Box; #[async_trait] pub trait GenericPlugin { diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index 79f25186..a03583cb 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use getset::{Getters, MutGetters}; +use getset::{Getters, MutGetters, Setters}; use serde::{Deserialize, Serialize}; use tokio::time::Instant; use uuid::Uuid; @@ -13,7 +13,7 @@ use super::node::Allocation; pub mod manager; -#[derive(Getters, MutGetters)] +#[derive(Getters, Setters, MutGetters)] pub struct Server { /* Settings */ #[getset(get = "pub")] @@ -33,11 +33,13 @@ pub struct Server { /* States */ #[getset(get = "pub", get_mut = "pub")] - health: Health, + heart: Heart, #[getset(get = "pub", get_mut = "pub")] state: State, #[getset(get = "pub", get_mut = "pub")] flags: Flags, + #[getset(get = "pub", set = "pub")] + ready: bool, } #[derive(Clone, Getters, MutGetters)] @@ -107,8 +109,8 @@ pub struct Spec { fallback: FallbackPolicy, } -pub struct Health { - next_check: Instant, +pub struct Heart { + next_beat: Instant, timeout: Duration, } @@ -133,18 +135,18 @@ impl Flags { } } -impl Health { +impl Heart { pub fn new(startup_time: Duration, timeout: Duration) -> Self { Self { - next_check: Instant::now() + startup_time, + next_beat: Instant::now() + startup_time, timeout, } } - pub fn reset(&mut self) { - self.next_check = Instant::now() + self.timeout; + pub fn beat(&mut self) { + self.next_beat = Instant::now() + self.timeout; } pub fn is_dead(&self) -> bool { - Instant::now() > self.next_check + Instant::now() > self.next_beat } } diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index b60bfcb9..5141036a 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -76,13 +76,13 @@ impl ServerManager { ) -> Result<()> { // Check health of servers for server in self.servers.values() { - if server.health.is_dead() { + if server.heart.is_dead() { match server.state { State::Starting | State::Running => { warn!("Unit {} failed to establish online status within the expected startup time of {:.2?}.", server.id, config.restart_timeout()); } _ => { - warn!("Server {} has not checked in for {:.2?}, indicating a potential error.", server.id, server.health.timeout); + warn!("Server {} has not checked in for {:.2?}, indicating a potential error.", server.id, server.heart.timeout); } } self.restart_requests diff --git a/controller/src/application/server/manager/action.rs b/controller/src/application/server/manager/action.rs index 6f01f202..122ca8e9 100644 --- a/controller/src/application/server/manager/action.rs +++ b/controller/src/application/server/manager/action.rs @@ -11,7 +11,7 @@ use crate::{ auth::service::AuthService, group::manager::GroupManager, node::{manager::NodeManager, Allocation}, - server::{Flags, Health, Server, State}, + server::{Flags, Heart, Server, State}, user::manager::UserManager, }, config::Config, @@ -67,9 +67,10 @@ impl ServerManager { }, connected_users: 0, token: auth.register_server(request.id.uuid).await, - health: Health::new(*config.startup_timeout(), *config.heartbeat_timeout()), + heart: Heart::new(*config.startup_timeout(), *config.heartbeat_timeout()), state: State::Starting, flags: Flags::default(), + ready: false, }; let handle = node.start(&server); if let Some(group) = &server.group { @@ -104,7 +105,7 @@ impl ServerManager { if let Some(server) = servers.get_mut(request.server.uuid()) { if let Some(node) = nodes.get_node(&server.node) { server.state = State::Restarting; - server.health = Health::new(*config.startup_timeout(), *config.heartbeat_timeout()); + server.heart = Heart::new(*config.startup_timeout(), *config.heartbeat_timeout()); Ok(node.restart(server)) } else { Err(anyhow!( diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index 0c10056d..aa22d316 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -6,7 +6,7 @@ use tokio_stream::wrappers::ReceiverStream; use tonic::{async_trait, Request, Response, Status}; use uuid::Uuid; -use crate::{application::TaskSender, task::Task, VERSION}; +use crate::{application::{Controller, TaskSender}, task::{BoxedAny, Task}, VERSION}; use super::proto::client::{ self, @@ -140,4 +140,4 @@ impl ClientService for ClientServiceImpl { async fn get_ctrl_ver(&self, _request: Request<()>) -> Result, Status> { Ok(Response::new(format!("{}", VERSION))) } -} +} \ No newline at end of file diff --git a/controller/src/network/client/beat.rs b/controller/src/network/client/beat.rs index fcc5db79..b40b5d8d 100644 --- a/controller/src/network/client/beat.rs +++ b/controller/src/network/client/beat.rs @@ -1,10 +1,10 @@ use anyhow::Result; -use tonic::async_trait; +use tonic::{async_trait, Status}; use uuid::Uuid; use crate::{ application::Controller, - task::{BoxedAny, GenericTask}, + task::{BoxedAny, GenericTask, Task}, }; pub struct BeatTask { @@ -13,7 +13,12 @@ pub struct BeatTask { #[async_trait] impl GenericTask for BeatTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { - Ok(Box::new(())) + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers_mut().get_server_mut(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + server.heart_mut().beat(); + Task::new_empty() } } diff --git a/controller/src/network/client/health.rs b/controller/src/network/client/health.rs index b9e2330d..386d6907 100644 --- a/controller/src/network/client/health.rs +++ b/controller/src/network/client/health.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ application::Controller, - task::{BoxedAny, GenericTask}, + task::{BoxedAny, GenericTask, Task}, }; pub struct SetRunningTask { @@ -18,13 +18,13 @@ pub struct RequestStopTask { #[async_trait] impl GenericTask for SetRunningTask { async fn run(&mut self, _controller: &mut Controller) -> Result { - Ok(Box::new(())) + Task::new_empty() } } #[async_trait] impl GenericTask for RequestStopTask { async fn run(&mut self, _controller: &mut Controller) -> Result { - Ok(Box::new(())) + Task::new_empty() } } diff --git a/controller/src/network/client/ready.rs b/controller/src/network/client/ready.rs index 91f026b1..8871f43c 100644 --- a/controller/src/network/client/ready.rs +++ b/controller/src/network/client/ready.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ application::Controller, - task::{BoxedAny, GenericTask}, + task::{BoxedAny, GenericTask, Task}, }; pub struct SetReadyTask { @@ -14,7 +14,12 @@ pub struct SetReadyTask { #[async_trait] impl GenericTask for SetReadyTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { - Ok(Box::new(())) + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers_mut().get_server_mut(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + server.set_ready(self.ready); + Task::new_empty() } -} +} \ No newline at end of file diff --git a/controller/src/task.rs b/controller/src/task.rs index e703fe30..ca774955 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -1,7 +1,8 @@ -use std::any::Any; +use std::any::{type_name, Any}; use anyhow::{anyhow, Result}; use common::error::CloudError; +use simplelog::debug; use tokio::sync::oneshot::{channel, Sender}; use tonic::{async_trait, Request, Status}; @@ -30,7 +31,7 @@ impl Task { } .clone(); match Task::create::(queue, task(request, data)).await { - Ok(value) => Ok(value), + Ok(value) => value, Err(error) => { CloudError::print_fancy(&error, false); Err(Status::internal(error.to_string())) @@ -38,17 +39,25 @@ impl Task { } } - pub async fn create(queue: &TaskSender, task: BoxedTask) -> Result { + pub async fn create( + queue: &TaskSender, + task: BoxedTask, + ) -> Result> { let (sender, receiver) = channel(); queue .send(Task { task, sender }) .await .map_err(|_| anyhow!("Failed to send task to task queue"))?; - Ok(*receiver.await??.downcast::().map_err(|_| { - anyhow!( - "Failed to downcast task result to the expected type. Check task implementation" - ) - })?) + let result = receiver.await??; + match result.downcast::() { + Ok(result) => Ok(Ok(*result)), + Err(result) => match result.downcast::() { + Ok(result) => Ok(Err(*result)), + Err(_) => Err(anyhow!( + "Failed to downcast task result to the expected type. Check task implementation" + )), + }, + } } pub async fn run(mut self, controller: &mut Controller) -> Result<()> { @@ -57,9 +66,25 @@ impl Task { .send(task) .map_err(|_| anyhow!("Failed to send task result to the task sender")) } + + pub fn new_ok(value: T) -> Result { + Ok(Box::new(value)) + } + + pub fn new_empty() -> Result { + Self::new_ok(()) + } + + pub fn new_err(value: Status) -> Result { + Ok(Box::new(value)) + } + + pub fn new_link_error() -> Result { + Self::new_err(Status::failed_precondition("Not linked")) + } } #[async_trait] pub trait GenericTask { async fn run(&mut self, controller: &mut Controller) -> Result; -} +} \ No newline at end of file From 7cd40aae30c01f5d9ab401cb807b4c11b2feca67 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:47:38 +0100 Subject: [PATCH 28/74] More tasks --- controller/src/application.rs | 17 ++---- controller/src/application/group.rs | 2 +- controller/src/application/server.rs | 6 +- controller/src/application/server/manager.rs | 14 +++-- controller/src/application/user.rs | 2 +- controller/src/application/user/manager.rs | 60 +++++++++++++++++++- controller/src/network/client/beat.rs | 2 +- controller/src/network/client/health.rs | 17 ++++-- controller/src/network/client/ready.rs | 2 +- controller/src/network/client/reset.rs | 2 +- controller/src/network/client/user.rs | 19 ++++++- controller/src/task.rs | 1 + 12 files changed, 112 insertions(+), 32 deletions(-) diff --git a/controller/src/application.rs b/controller/src/application.rs index 6d5dc3b0..65416a6b 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -24,10 +24,10 @@ use user::manager::UserManager; use crate::{config::Config, network::NetworkStack, task::Task}; pub mod auth; +pub mod server; mod group; mod node; mod plugin; -mod server; mod user; const TICK_RATE: u64 = 20; @@ -47,16 +47,11 @@ pub struct Controller { auth: Arc, /* Components */ - #[getset(get = "pub", get_mut = "pub")] - plugins: PluginManager, - #[getset(get = "pub", get_mut = "pub")] - nodes: NodeManager, - #[getset(get = "pub", get_mut = "pub")] - groups: GroupManager, - #[getset(get = "pub", get_mut = "pub")] - servers: ServerManager, - #[getset(get = "pub", get_mut = "pub")] - users: UserManager, + pub plugins: PluginManager, + pub nodes: NodeManager, + pub groups: GroupManager, + pub servers: ServerManager, + pub users: UserManager, /* Config */ #[getset(get = "pub")] diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index 06c5e9c8..e69ce7b7 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -72,7 +72,7 @@ impl Group { "Server {} is empty and reached the timeout, stopping it...", server.id() ); - requests.push(StopRequest::new(None, server.id())); + requests.push(StopRequest::new(None, server.id().clone())); to_stop -= 1; } else { debug!( diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index a03583cb..f0d109f9 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -28,16 +28,16 @@ pub struct Server { token: String, /* Users */ - #[getset(get = "pub")] + #[getset(get = "pub", set = "pub")] connected_users: u32, /* States */ #[getset(get = "pub", get_mut = "pub")] heart: Heart, #[getset(get = "pub", get_mut = "pub")] - state: State, - #[getset(get = "pub", get_mut = "pub")] flags: Flags, + #[getset(get = "pub", get_mut = "pub", set = "pub")] + state: State, #[getset(get = "pub", set = "pub")] ready: bool, } diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 5141036a..6a819a38 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -50,6 +50,10 @@ impl ServerManager { self.servers.get_mut(uuid) } + pub fn resolve_server(&self, uuid: &Uuid) -> Option { + self.servers.get(&uuid).map(|server| server.id.clone()) + } + pub fn schedule_start(&mut self, request: StartRequest) { self.start_requests.push(request); } @@ -86,7 +90,7 @@ impl ServerManager { } } self.restart_requests - .push(RestartRequest::new(None, server.id())); + .push(RestartRequest::new(None, server.id().clone())); } } @@ -385,20 +389,20 @@ impl StartRequest { } impl RestartRequest { - pub fn new(when: Option, server: &NameAndUuid) -> Self { + pub fn new(when: Option, server: NameAndUuid) -> Self { Self { when, - server: server.clone(), + server, stage: ActionStage::Queued, } } } impl StopRequest { - pub fn new(when: Option, server: &NameAndUuid) -> Self { + pub fn new(when: Option, server: NameAndUuid) -> Self { Self { when, - server: server.clone(), + server, stage: ActionStage::Queued, } } diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs index 1f788f90..31d6b26b 100644 --- a/controller/src/application/user.rs +++ b/controller/src/application/user.rs @@ -15,4 +15,4 @@ pub struct User { pub enum CurrentServer { Connected(NameAndUuid), Transfering(Transfer), -} +} \ No newline at end of file diff --git a/controller/src/application/user/manager.rs b/controller/src/application/user/manager.rs index 5a53051f..7a2c16cc 100644 --- a/controller/src/application/user/manager.rs +++ b/controller/src/application/user/manager.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; use anyhow::Result; -use simplelog::info; +use simplelog::{info, warn}; use uuid::Uuid; +use crate::application::server::Server; + use super::{CurrentServer, User}; pub struct UserManager { @@ -36,6 +38,62 @@ impl UserManager { }); amount } + + pub fn user_connected(&mut self, server: &mut Server, name: String, uuid: Uuid) { + // Update server user count + server.set_connected_users(server.connected_users() + 1); + + // Update internal user list + if let Some(user) = self.users.get_mut(&uuid) { + match &user.server { + CurrentServer::Connected(_) => { + warn!( + "User {}[{}] was never flagged as transferring but switched to server {}", + name, + uuid.to_string(), + server.id(), + ); + } + CurrentServer::Transfering(_) => { + info!( + "User {}[{}] successfully transferred to server {}", + name, + uuid.to_string(), + server.id(), + ); + } + } + user.server = CurrentServer::Connected(server.id().clone()); + } else { + info!("User {}[{}] connected to server {}", name, uuid.to_string(), server.id()); + self.users.insert(uuid, User { + name, + uuid, + server: CurrentServer::Connected(server.id().clone()), + }); + } + } + + pub fn user_disconnected(&mut self, server: &mut Server, uuid: &Uuid) { + // Update server user count + server.set_connected_users(server.connected_users() - 1); + + // Update internal user list + if let Some(user) = self.users.get(uuid) { + if let CurrentServer::Connected(current) = &user.server { + // Verify that the user is connected to the server + if current.uuid() == server.id().uuid() { + info!( + "User {}[{}] disconnected from server {}", + user.name, + user.uuid.to_string(), + server.id(), + ); + self.users.remove(uuid); + } + } + } + } } // Ticking diff --git a/controller/src/network/client/beat.rs b/controller/src/network/client/beat.rs index b40b5d8d..f5536034 100644 --- a/controller/src/network/client/beat.rs +++ b/controller/src/network/client/beat.rs @@ -14,7 +14,7 @@ pub struct BeatTask { #[async_trait] impl GenericTask for BeatTask { async fn run(&mut self, controller: &mut Controller) -> Result { - let server = match controller.servers_mut().get_server_mut(&self.server) { + let server = match controller.servers.get_server_mut(&self.server) { Some(server) => server, None => return Task::new_link_error(), }; diff --git a/controller/src/network/client/health.rs b/controller/src/network/client/health.rs index 386d6907..9017cf4b 100644 --- a/controller/src/network/client/health.rs +++ b/controller/src/network/client/health.rs @@ -3,8 +3,7 @@ use tonic::async_trait; use uuid::Uuid; use crate::{ - application::Controller, - task::{BoxedAny, GenericTask, Task}, + application::{server::{manager::StopRequest, State}, Controller}, task::{BoxedAny, GenericTask, Task} }; pub struct SetRunningTask { @@ -17,14 +16,24 @@ pub struct RequestStopTask { #[async_trait] impl GenericTask for SetRunningTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers.get_server_mut(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + server.set_state(State::Running); Task::new_empty() } } #[async_trait] impl GenericTask for RequestStopTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers.resolve_server(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + controller.servers.schedule_stop(StopRequest::new(None, server)); Task::new_empty() } } diff --git a/controller/src/network/client/ready.rs b/controller/src/network/client/ready.rs index 8871f43c..38c0577b 100644 --- a/controller/src/network/client/ready.rs +++ b/controller/src/network/client/ready.rs @@ -15,7 +15,7 @@ pub struct SetReadyTask { #[async_trait] impl GenericTask for SetReadyTask { async fn run(&mut self, controller: &mut Controller) -> Result { - let server = match controller.servers_mut().get_server_mut(&self.server) { + let server = match controller.servers.get_server_mut(&self.server) { Some(server) => server, None => return Task::new_link_error(), }; diff --git a/controller/src/network/client/reset.rs b/controller/src/network/client/reset.rs index 6e8f16bc..afe38228 100644 --- a/controller/src/network/client/reset.rs +++ b/controller/src/network/client/reset.rs @@ -16,4 +16,4 @@ impl GenericTask for ResetTask { async fn run(&mut self, _controller: &mut Controller) -> Result { todo!() } -} +} \ No newline at end of file diff --git a/controller/src/network/client/user.rs b/controller/src/network/client/user.rs index 2bac26fd..46f6e6a7 100644 --- a/controller/src/network/client/user.rs +++ b/controller/src/network/client/user.rs @@ -4,27 +4,40 @@ use uuid::Uuid; use crate::{ application::Controller, - task::{BoxedAny, GenericTask}, + task::{BoxedAny, GenericTask, Task}, }; pub struct UserConnectedTask { server: Uuid, + uuid: Uuid, + name: String, } pub struct UserDisconnectedTask { server: Uuid, + uuid: Uuid, } #[async_trait] impl GenericTask for UserConnectedTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers.get_server_mut(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + controller.users.user_connected(server, self.name.clone(), self.uuid.clone()); todo!() } } #[async_trait] impl GenericTask for UserDisconnectedTask { - async fn run(&mut self, _controller: &mut Controller) -> Result { + async fn run(&mut self, controller: &mut Controller) -> Result { + let server = match controller.servers.get_server_mut(&self.server) { + Some(server) => server, + None => return Task::new_link_error(), + }; + controller.users.user_disconnected(server, &self.uuid); todo!() } } diff --git a/controller/src/task.rs b/controller/src/task.rs index ca774955..b34ed2b7 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -30,6 +30,7 @@ impl Task { None => return Err(Status::permission_denied("Not linked")), } .clone(); + debug!("Executing task with a return type of: {}", type_name::()); match Task::create::(queue, task(request, data)).await { Ok(value) => value, Err(error) => { From b6c3a861cc36f0469bde1aab6d97df949965de51 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:19:54 +0100 Subject: [PATCH 29/74] CLI --- Cargo.toml | 2 +- cli/build.rs | 2 +- cli/src/application.rs | 12 ++++++-- cli/src/application/menu.rs | 24 ++++++--------- cli/src/application/menu/connection.rs | 2 ++ cli/src/application/menu/connection/start.rs | 6 ++-- .../menu/connection/user/transfer_user.rs | 19 +++--------- cli/src/application/menu/create_profile.rs | 24 ++++++++------- cli/src/application/menu/delete_profile.rs | 8 +++-- cli/src/application/menu/load_profile.rs | 7 +++-- cli/src/application/menu/start.rs | 8 +++-- cli/src/application/network.rs | 2 +- cli/src/args.rs | 7 ----- cli/src/main.rs | 29 +++++++++++++------ cli/src/storage.rs | 2 +- common/src/error.rs | 8 ++--- 16 files changed, 83 insertions(+), 79 deletions(-) delete mode 100644 cli/src/args.rs diff --git a/Cargo.toml b/Cargo.toml index f1f3ede7..66a5c355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ #"plugins/local", # Clients - #"cli", + "cli", #"clients/wrapper", ] diff --git a/cli/build.rs b/cli/build.rs index 97389dc1..587d4e7e 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -80,7 +80,7 @@ fn generate_grpc_code() -> Result<(), Box> { tonic_build::configure() .build_server(false) .compile_protos( - &[format!("{}/manage/manage.proto", PROTO_PATH)], + &[format!("{}/manage/service.proto", PROTO_PATH)], &[PROTO_PATH], )?; Ok(()) diff --git a/cli/src/application.rs b/cli/src/application.rs index a422317e..77cad579 100644 --- a/cli/src/application.rs +++ b/cli/src/application.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use common::error::{FancyError}; use menu::{start::StartMenu, MenuResult}; use profile::Profiles; use simplelog::info; @@ -17,12 +19,16 @@ impl Cli { } } - pub async fn start(&mut self) { + pub async fn start(&mut self) -> Result<()> { loop { - if StartMenu::show(&mut self.profiles).await == MenuResult::Exit { - break; + match StartMenu::show(&mut self.profiles).await { + MenuResult::Exit => break, + MenuResult::Error(error) => { FancyError::print_fancy(&error, false); break; }, + _ => {} } } info!("ℹ Goodbye!"); + + Ok(()) } } diff --git a/cli/src/application/menu.rs b/cli/src/application/menu.rs index 8cfd19af..021a6e18 100644 --- a/cli/src/application/menu.rs +++ b/cli/src/application/menu.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, str::FromStr}; -use anyhow::Result; -use inquire::{validator::ValueRequiredValidator, Confirm, CustomType, MultiSelect, Select, Text}; +use anyhow::{Error, Result}; +use inquire::{validator::ValueRequiredValidator, Confirm, CustomType, InquireError, MultiSelect, Select, Text}; mod connection; mod create_profile; @@ -9,60 +9,54 @@ mod delete_profile; mod load_profile; pub mod start; -#[derive(PartialEq)] pub enum MenuResult { Success, Aborted, Failed, Exit, + Error(Error), } pub struct MenuUtils; impl MenuUtils { - pub fn text(message: &str, help: &str) -> Result { + pub fn text(message: &str, help: &str) -> Result { Text::new(message) .with_validator(ValueRequiredValidator::default()) .with_help_message(help) .prompt() - .map_err(|error| error.into()) } pub fn parsed_value( message: &str, help: &str, error: &str, - ) -> Result { + ) -> Result { CustomType::::new(message) .with_error_message(error) .with_help_message(help) .prompt() - .map_err(|error| error.into()) } - pub fn confirm(message: &str) -> Result { + pub fn confirm(message: &str) -> Result { Confirm::new(message) .with_help_message("Type y or n") .prompt() - .map_err(|error| error.into()) } - pub fn select(message: &str, help: &str, options: Vec) -> Result { + pub fn select(message: &str, help: &str, options: Vec) -> Result { Select::new(message, options) .with_help_message(help) .prompt() - .map_err(|error| error.into()) } - pub fn select_no_help(message: &str, options: Vec) -> Result { + pub fn select_no_help(message: &str, options: Vec) -> Result { Select::new(message, options) .prompt() - .map_err(|error| error.into()) } - pub fn multi_select_no_help(message: &str, options: Vec) -> Result> { + pub fn multi_select_no_help(message: &str, options: Vec) -> Result, InquireError> { MultiSelect::new(message, options) .prompt() - .map_err(|error| error.into()) } } diff --git a/cli/src/application/menu/connection.rs b/cli/src/application/menu/connection.rs index 23d88075..a9be3f39 100644 --- a/cli/src/application/menu/connection.rs +++ b/cli/src/application/menu/connection.rs @@ -1,3 +1,4 @@ +use common::error::FancyError; use loading::Loading; use start::ConnectionStartMenu; @@ -37,6 +38,7 @@ impl ConnectionMenu { Err(error) => { progress.fail(format!("Failed to connect to the controller: {}", error)); progress.end(); + FancyError::print_fancy(&error, false); MenuResult::Failed } } diff --git a/cli/src/application/menu/connection/start.rs b/cli/src/application/menu/connection/start.rs index 3b62a65e..a9e07eed 100644 --- a/cli/src/application/menu/connection/start.rs +++ b/cli/src/application/menu/connection/start.rs @@ -89,8 +89,10 @@ impl ConnectionStartMenu { profiles: &mut Profiles, ) -> MenuResult { loop { - if Self::show_internal(profile, connection, profiles).await == MenuResult::Exit { - return MenuResult::Success; + match Self::show_internal(profile, connection, profiles).await { + MenuResult::Exit => return MenuResult::Success, + MenuResult::Error(error) => return MenuResult::Error(error), + _ => {} } } } diff --git a/cli/src/application/menu/connection/user/transfer_user.rs b/cli/src/application/menu/connection/user/transfer_user.rs index 3550041a..e115fe77 100644 --- a/cli/src/application/menu/connection/user/transfer_user.rs +++ b/cli/src/application/menu/connection/user/transfer_user.rs @@ -3,27 +3,16 @@ use loading::Loading; use simplelog::debug; use crate::application::{ - menu::{MenuResult, MenuUtils}, - network::{ - proto::{ - transfer_management::{ - transfer_target_value::TargetType, TransferTargetValue, TransferUsersRequest, - }, - unit_management::SimpleUnitValue, - user_management::UserValue, - }, - EstablishedConnection, - }, - profile::{Profile, Profiles}, + menu::{MenuResult, MenuUtils}, network::{proto::{server, user}, EstablishedConnection}, profile::{Profile, Profiles} }; pub struct TransferUserMenu; // TODO: Maybe dont request everything at once, but only what is needed struct Data { - users: Vec, - units: Vec, - deployments: Vec, + users: Vec, + servers: Vec, + groups: Vec, } impl TransferUserMenu { diff --git a/cli/src/application/menu/create_profile.rs b/cli/src/application/menu/create_profile.rs index 1b9bcf96..718cb30e 100644 --- a/cli/src/application/menu/create_profile.rs +++ b/cli/src/application/menu/create_profile.rs @@ -1,6 +1,6 @@ +use common::error::{FancyError}; use inquire::{ - validator::{Validation, ValueRequiredValidator}, - Password, Text, + validator::{Validation, ValueRequiredValidator}, InquireError, Password, Text }; use loading::Loading; use simplelog::debug; @@ -33,9 +33,9 @@ impl CreateProfileMenu { } let name = match prompt.prompt() { Ok(name) => name, - Err(error) => { - debug!("{}", error); - return MenuResult::Aborted; + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, + _ => return MenuResult::Error(error.into()) } }; @@ -47,9 +47,9 @@ impl CreateProfileMenu { .prompt() { Ok(authorization) => authorization, - Err(error) => { - debug!("{}", error); - return MenuResult::Aborted; + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, + _ => return MenuResult::Error(error.into()) } }; @@ -59,9 +59,9 @@ impl CreateProfileMenu { "Please enter a valid URL", ) { Ok(url) => url, - Err(error) => { - debug!("{}", error); - return MenuResult::Aborted; + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, + _ => return MenuResult::Error(error.into()) } }; @@ -80,6 +80,7 @@ impl CreateProfileMenu { Err(error) => { progress.fail(format!("Failed to connect to the controller: {}", error)); progress.end(); + FancyError::print_fancy(&error, false); return MenuResult::Failed; } } @@ -87,6 +88,7 @@ impl CreateProfileMenu { if let Err(error) = profiles.create_profile(&profile) { progress.fail(format!("Failed to create profile: {}", error)); progress.end(); + FancyError::print_fancy(&error, false); return MenuResult::Failed; } progress.success("Profile created successfully"); diff --git a/cli/src/application/menu/delete_profile.rs b/cli/src/application/menu/delete_profile.rs index 83311d9e..c7b06bd1 100644 --- a/cli/src/application/menu/delete_profile.rs +++ b/cli/src/application/menu/delete_profile.rs @@ -1,3 +1,4 @@ +use inquire::InquireError; use loading::Loading; use simplelog::debug; @@ -27,15 +28,16 @@ impl DeleteProfileMenu { err )); progress.end(); + MenuResult::Failed } } } Ok(false) | Err(_) => MenuResult::Aborted, }, - Err(err) => { - debug!("{}", err); - MenuResult::Aborted + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Aborted, + _ => MenuResult::Error(error.into()) } } } diff --git a/cli/src/application/menu/load_profile.rs b/cli/src/application/menu/load_profile.rs index 260097c5..47ee3406 100644 --- a/cli/src/application/menu/load_profile.rs +++ b/cli/src/application/menu/load_profile.rs @@ -1,3 +1,4 @@ +use inquire::InquireError; use simplelog::debug; use crate::application::profile::Profiles; @@ -14,9 +15,9 @@ impl LoadProfileMenu { options, ) { Ok(profile) => ConnectionMenu::show(profile, profiles).await, - Err(error) => { - debug!("{}", error); - MenuResult::Aborted + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Aborted, + _ => MenuResult::Error(error.into()) } } } diff --git a/cli/src/application/menu/start.rs b/cli/src/application/menu/start.rs index 125800aa..96fba79c 100644 --- a/cli/src/application/menu/start.rs +++ b/cli/src/application/menu/start.rs @@ -3,6 +3,8 @@ use std::{ vec, }; +use anyhow::{Error, Result}; +use inquire::InquireError; use simplelog::debug; use crate::application::profile::Profiles; @@ -53,9 +55,9 @@ impl StartMenu { Selection::DeleteProfile => DeleteProfileMenu::show(profiles).await, Selection::Exit => MenuResult::Exit, }, - Err(error) => { - debug!("{}", error); - MenuResult::Exit + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Exit, + _ => MenuResult::Error(error.into()) } } } diff --git a/cli/src/application/network.rs b/cli/src/application/network.rs index db3b21f7..0dc459ba 100644 --- a/cli/src/application/network.rs +++ b/cli/src/application/network.rs @@ -26,7 +26,7 @@ use super::profile::Profile; pub mod proto { use tonic::include_proto; - include_proto!("admin"); + include_proto!("manage"); } pub struct EstablishedConnection { diff --git a/cli/src/args.rs b/cli/src/args.rs deleted file mode 100644 index b141bad2..00000000 --- a/cli/src/args.rs +++ /dev/null @@ -1,7 +0,0 @@ -use clap::{ArgAction, Parser}; - -#[derive(Parser)] -pub struct Args { - #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] - pub debug: bool, -} diff --git a/cli/src/main.rs b/cli/src/main.rs index e4ef7e2e..fa96606e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,11 +1,10 @@ +use anyhow::Result; use application::Cli; -use args::Args; -use clap::Parser; -use common::init::CloudInit; +use clap::{ArgAction, Parser}; +use common::{error::FancyError, init::CloudInit}; use storage::Storage; mod application; -mod args; mod storage; // Include the build information generated by build.rs @@ -15,10 +14,22 @@ pub const AUTHORS: [&str; 1] = ["HttpRafa"]; #[tokio::main] async fn main() { - let args = Args::parse(); - CloudInit::init_logging(args.debug, true, Storage::get_latest_log_file()); - CloudInit::print_ascii_art("Atomic Cloud CLI", &VERSION, &AUTHORS); + if let Err(error) = run().await { + FancyError::print_fancy(&error, true); + } - let mut cli = Cli::new().await; - cli.start().await + async fn run() -> Result<()> { + let args = Arguments::parse(); + CloudInit::init_logging(args.debug, true, Storage::get_latest_log_file()); + CloudInit::print_ascii_art("Atomic Cloud CLI", &VERSION, &AUTHORS); + + let mut cli = Cli::new().await; + cli.start().await + } +} + +#[derive(Parser)] +pub struct Arguments { + #[clap(short, long, help = "Enable debug mode", action = ArgAction::SetTrue)] + pub debug: bool, } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index f0f9d180..fcbcdb59 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -6,7 +6,7 @@ This makes it easier to change them in the future use std::path::PathBuf; /* Cli */ -const CLI_DIRECTORY: &str = "atomic-cli"; +const CLI_DIRECTORY: &str = "cli"; /* LOGS */ const LOGS_DIRECTORY: &str = "logs"; diff --git a/common/src/error.rs b/common/src/error.rs index 469e18d9..ef97907b 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -3,14 +3,14 @@ use std::backtrace::BacktraceStatus; use anyhow::Error; use simplelog::error; -pub struct CloudError(); +pub struct FancyError(); -impl CloudError { +impl FancyError { pub fn print_fancy(error: &Error, critical: bool) { let exit_message = if critical { - "An error occurred causing the controller to exit. The controller cannot continue after this error." + "An error occurred causing the application to exit. The application cannot continue after this error." } else { - "An error occurred, but the controller can continue. The controller may not function as expected." + "An error occurred, but the application can continue. The application may not function as expected." }; error!("{}", exit_message); From fada6a6f048824e1070ffbc6577d7f0f69a00c5a Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:06:36 +0100 Subject: [PATCH 30/74] CLI --- cli/src/application.rs | 12 +- cli/src/application/menu.rs | 24 ++- cli/src/application/menu/connection.rs | 10 +- .../menu/connection/cloudlet/get_cloudlet.rs | 85 -------- .../menu/connection/cloudlet/mod.rs | 3 - .../menu/connection/deployment/mod.rs | 3 - .../menu/connection/general/get_versions.rs | 8 +- .../menu/connection/general/request_stop.rs | 11 +- .../create_group.rs} | 125 +++++------ .../get_deployment.rs => group/get_group.rs} | 74 +++---- .../get_cloudlets.rs => group/get_groups.rs} | 28 +-- .../application/menu/connection/group/mod.rs | 3 + .../create_node.rs} | 78 +++---- .../menu/connection/node/get_node.rs | 82 ++++++++ .../get_deployments.rs => node/get_nodes.rs} | 28 +-- .../application/menu/connection/node/mod.rs | 3 + .../connection/resource/delete_resource.rs | 76 ++++--- .../menu/connection/resource/mod.rs | 2 +- ...set_resource_status.rs => set_resource.rs} | 75 +++---- .../get_unit.rs => server/get_server.rs} | 98 ++++----- .../get_units.rs => server/get_servers.rs} | 30 +-- .../application/menu/connection/server/mod.rs | 2 + cli/src/application/menu/connection/start.rs | 118 +++++------ .../application/menu/connection/unit/mod.rs | 2 - .../application/menu/connection/user/mod.rs | 2 +- .../{transfer_user.rs => transfer_users.rs} | 87 ++++---- cli/src/application/menu/create_profile.rs | 35 ++-- cli/src/application/menu/delete_profile.rs | 17 +- cli/src/application/menu/load_profile.rs | 9 +- cli/src/application/menu/start.rs | 18 +- cli/src/application/network.rs | 198 +++++++++--------- cli/src/application/profile.rs | 87 ++------ cli/src/main.rs | 4 +- cli/src/storage.rs | 14 +- controller/src/application.rs | 2 +- controller/src/application/node.rs | 2 +- .../src/application/plugin/runtime/wasm.rs | 2 +- controller/src/application/server/manager.rs | 2 +- controller/src/application/user.rs | 2 +- controller/src/application/user/manager.rs | 16 +- controller/src/main.rs | 4 +- controller/src/network.rs | 4 +- controller/src/network/client.rs | 8 +- controller/src/network/client/beat.rs | 2 +- controller/src/network/client/health.rs | 10 +- controller/src/network/client/ready.rs | 2 +- controller/src/network/client/reset.rs | 2 +- controller/src/network/client/user.rs | 4 +- controller/src/task.rs | 12 +- protocol/grpc/client/channel.proto | 2 +- protocol/grpc/client/transfer.proto | 2 +- protocol/grpc/manage/group.proto | 6 +- protocol/grpc/manage/node.proto | 7 +- protocol/grpc/manage/resource.proto | 4 +- protocol/grpc/manage/server.proto | 26 +-- protocol/grpc/manage/transfer.proto | 2 +- protocol/wit/plugin.wit | 2 +- 57 files changed, 758 insertions(+), 818 deletions(-) delete mode 100644 cli/src/application/menu/connection/cloudlet/get_cloudlet.rs delete mode 100644 cli/src/application/menu/connection/cloudlet/mod.rs delete mode 100644 cli/src/application/menu/connection/deployment/mod.rs rename cli/src/application/menu/connection/{deployment/create_deployment.rs => group/create_group.rs} (68%) rename cli/src/application/menu/connection/{deployment/get_deployment.rs => group/get_group.rs} (68%) rename cli/src/application/menu/connection/{cloudlet/get_cloudlets.rs => group/get_groups.rs} (51%) create mode 100644 cli/src/application/menu/connection/group/mod.rs rename cli/src/application/menu/connection/{cloudlet/create_cloudlet.rs => node/create_node.rs} (59%) create mode 100644 cli/src/application/menu/connection/node/get_node.rs rename cli/src/application/menu/connection/{deployment/get_deployments.rs => node/get_nodes.rs} (50%) create mode 100644 cli/src/application/menu/connection/node/mod.rs rename cli/src/application/menu/connection/resource/{set_resource_status.rs => set_resource.rs} (57%) rename cli/src/application/menu/connection/{unit/get_unit.rs => server/get_server.rs} (59%) rename cli/src/application/menu/connection/{unit/get_units.rs => server/get_servers.rs} (53%) create mode 100644 cli/src/application/menu/connection/server/mod.rs delete mode 100644 cli/src/application/menu/connection/unit/mod.rs rename cli/src/application/menu/connection/user/{transfer_user.rs => transfer_users.rs} (55%) diff --git a/cli/src/application.rs b/cli/src/application.rs index 77cad579..e71bf6cf 100644 --- a/cli/src/application.rs +++ b/cli/src/application.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use common::error::{FancyError}; +use common::error::FancyError; use menu::{start::StartMenu, MenuResult}; use profile::Profiles; use simplelog::info; @@ -13,17 +13,17 @@ pub struct Cli { } impl Cli { - pub async fn new() -> Cli { - Cli { - profiles: Profiles::load_all(), - } + pub async fn new() -> Result { + Ok(Cli { + profiles: Profiles::init()?, + }) } pub async fn start(&mut self) -> Result<()> { loop { match StartMenu::show(&mut self.profiles).await { MenuResult::Exit => break, - MenuResult::Error(error) => { FancyError::print_fancy(&error, false); break; }, + MenuResult::Failed(error) => FancyError::print_fancy(&error, false), _ => {} } } diff --git a/cli/src/application/menu.rs b/cli/src/application/menu.rs index 021a6e18..7ce09f2e 100644 --- a/cli/src/application/menu.rs +++ b/cli/src/application/menu.rs @@ -1,7 +1,9 @@ use std::{fmt::Display, str::FromStr}; use anyhow::{Error, Result}; -use inquire::{validator::ValueRequiredValidator, Confirm, CustomType, InquireError, MultiSelect, Select, Text}; +use inquire::{ + validator::ValueRequiredValidator, Confirm, CustomType, InquireError, MultiSelect, Select, Text, +}; mod connection; mod create_profile; @@ -12,9 +14,8 @@ pub mod start; pub enum MenuResult { Success, Aborted, - Failed, + Failed(Error), Exit, - Error(Error), } pub struct MenuUtils; @@ -44,19 +45,24 @@ impl MenuUtils { .prompt() } - pub fn select(message: &str, help: &str, options: Vec) -> Result { + pub fn select( + message: &str, + help: &str, + options: Vec, + ) -> Result { Select::new(message, options) .with_help_message(help) .prompt() } pub fn select_no_help(message: &str, options: Vec) -> Result { - Select::new(message, options) - .prompt() + Select::new(message, options).prompt() } - pub fn multi_select_no_help(message: &str, options: Vec) -> Result, InquireError> { - MultiSelect::new(message, options) - .prompt() + pub fn multi_select_no_help( + message: &str, + options: Vec, + ) -> Result, InquireError> { + MultiSelect::new(message, options).prompt() } } diff --git a/cli/src/application/menu/connection.rs b/cli/src/application/menu/connection.rs index a9be3f39..ffea741b 100644 --- a/cli/src/application/menu/connection.rs +++ b/cli/src/application/menu/connection.rs @@ -1,4 +1,3 @@ -use common::error::FancyError; use loading::Loading; use start::ConnectionStartMenu; @@ -9,12 +8,12 @@ use crate::{ use super::MenuResult; -mod cloudlet; -mod deployment; mod general; +mod group; +mod node; mod resource; +mod server; mod start; -mod unit; mod user; pub struct ConnectionMenu; @@ -38,8 +37,7 @@ impl ConnectionMenu { Err(error) => { progress.fail(format!("Failed to connect to the controller: {}", error)); progress.end(); - FancyError::print_fancy(&error, false); - MenuResult::Failed + MenuResult::Failed(error) } } } diff --git a/cli/src/application/menu/connection/cloudlet/get_cloudlet.rs b/cli/src/application/menu/connection/cloudlet/get_cloudlet.rs deleted file mode 100644 index d4fdd497..00000000 --- a/cli/src/application/menu/connection/cloudlet/get_cloudlet.rs +++ /dev/null @@ -1,85 +0,0 @@ -use loading::Loading; -use simplelog::info; - -use crate::application::{ - menu::{MenuResult, MenuUtils}, - network::{proto::cloudlet_management::CloudletValue, EstablishedConnection}, - profile::{Profile, Profiles}, -}; - -pub struct GetCloudletMenu; - -impl GetCloudletMenu { - pub async fn show( - profile: &mut Profile, - connection: &mut EstablishedConnection, - _profiles: &mut Profiles, - ) -> MenuResult { - let progress = Loading::default(); - progress.text(format!( - "Retrieving available cloudlets from controller \"{}\"...", - profile.name - )); - - match connection.client.get_cloudlets().await { - Ok(cloudlets) => { - progress.success("Cloudlet data retrieved successfully 👍"); - progress.end(); - match MenuUtils::select_no_help( - "Select a cloudlet to view more details:", - cloudlets, - ) { - Ok(cloudlet) => { - let progress = Loading::default(); - progress.text(format!( - "Fetching details for cloudlet \"{}\" from controller \"{}\"...", - cloudlet, profile.name - )); - - match connection.client.get_cloudlet(&cloudlet).await { - Ok(details) => { - progress.success("Cloudlet details retrieved successfully 👍"); - progress.end(); - Self::display_details(&details); - MenuResult::Success - } - Err(err) => { - progress.fail(format!("{}", err)); - progress.end(); - MenuResult::Failed - } - } - } - Err(_) => MenuResult::Aborted, - } - } - Err(err) => { - progress.fail(format!("{}", err)); - progress.end(); - MenuResult::Failed - } - } - } - - fn display_details(cloudlet: &CloudletValue) { - info!(" 🖥 Cloudlet Information"); - info!(" Name: {}", cloudlet.name); - info!(" Driver: {}", cloudlet.driver); - if let Some(memory) = &cloudlet.memory { - info!(" Memory: {} MiB", memory); - } - if let Some(max_allocations) = &cloudlet.max_allocations { - info!( - " Max Allocations: {} Units", - max_allocations - ); - } - if let Some(child) = &cloudlet.child { - info!(" Child Node: {}", child); - } - info!( - " Controller Address: {}", - cloudlet.controller_address - ); - } -} diff --git a/cli/src/application/menu/connection/cloudlet/mod.rs b/cli/src/application/menu/connection/cloudlet/mod.rs deleted file mode 100644 index 0267c577..00000000 --- a/cli/src/application/menu/connection/cloudlet/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod create_cloudlet; -pub mod get_cloudlet; -pub mod get_cloudlets; diff --git a/cli/src/application/menu/connection/deployment/mod.rs b/cli/src/application/menu/connection/deployment/mod.rs deleted file mode 100644 index 2363d306..00000000 --- a/cli/src/application/menu/connection/deployment/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod create_deployment; -pub mod get_deployment; -pub mod get_deployments; diff --git a/cli/src/application/menu/connection/general/get_versions.rs b/cli/src/application/menu/connection/general/get_versions.rs index a66da704..95805ad3 100644 --- a/cli/src/application/menu/connection/general/get_versions.rs +++ b/cli/src/application/menu/connection/general/get_versions.rs @@ -27,7 +27,7 @@ impl GetVersionsMenu { match Self::get_required_data(connection).await { Ok((version, protocol)) => { - progress.success("Version data retrieved successfully 👍"); + progress.success("Data retrieved successfully 👍"); progress.end(); info!(" 🖥 Controller Info"); info!(" Version: {}", version); @@ -40,14 +40,14 @@ impl GetVersionsMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result<(String, u32)> { - let version = connection.client.get_controller_version().await?; - let protocol = connection.client.get_protocol_version().await?; + let version = connection.client.get_ctrl_ver().await?; + let protocol = connection.client.get_proto_ver().await?; Ok((version, protocol)) } } diff --git a/cli/src/application/menu/connection/general/request_stop.rs b/cli/src/application/menu/connection/general/request_stop.rs index 0497e1ed..d6fa1519 100644 --- a/cli/src/application/menu/connection/general/request_stop.rs +++ b/cli/src/application/menu/connection/general/request_stop.rs @@ -1,5 +1,6 @@ use std::{thread, time::Duration}; +use inquire::InquireError; use loading::Loading; use crate::application::{ @@ -34,11 +35,15 @@ impl RequestStopMenu { error )); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Ok(false) | Err(_) => MenuResult::Aborted, - } + Ok(false) => MenuResult::Aborted, + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Aborted, + _ => MenuResult::Failed(error.into()) + }, + } } } diff --git a/cli/src/application/menu/connection/deployment/create_deployment.rs b/cli/src/application/menu/connection/group/create_group.rs similarity index 68% rename from cli/src/application/menu/connection/deployment/create_deployment.rs rename to cli/src/application/menu/connection/group/create_group.rs index b98793fb..221966cc 100644 --- a/cli/src/application/menu/connection/deployment/create_deployment.rs +++ b/cli/src/application/menu/connection/group/create_group.rs @@ -3,23 +3,18 @@ use std::{fmt::Display, str::FromStr, vec}; use anyhow::{anyhow, Result}; use inquire::{ validator::{Validation, ValueRequiredValidator}, - MultiSelect, Text, + InquireError, MultiSelect, Text, }; use loading::Loading; -use simplelog::debug; use crate::application::{ menu::{MenuResult, MenuUtils}, network::{ proto::{ common::KeyValue, - deployment_management::{ - deployment_value::{Constraints, Scaling}, - DeploymentValue, - }, - unit_management::{ - unit_spec::{Fallback, Retention}, - UnitResources, UnitSpec, + manage::{ + group::{self, Constraints, Scaling}, + server::{DiskRetention, Fallback, Resources, Spec}, }, }, EstablishedConnection, @@ -27,14 +22,14 @@ use crate::application::{ profile::{Profile, Profiles}, }; -pub struct CreateDeploymentMenu; +pub struct CreateGroupMenu; struct Data { - deployments: Vec, - cloudlets: Vec, + groups: Vec, + nodes: Vec, } -impl CreateDeploymentMenu { +impl CreateGroupMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -42,7 +37,7 @@ impl CreateDeploymentMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Retrieving all existing deployments from the controller \"{}\"...", + "Retrieving all existing groups from the controller \"{}\"...", profile.name )); @@ -51,61 +46,60 @@ impl CreateDeploymentMenu { progress.success("Data retrieved successfully 👍"); progress.end(); - match Self::collect_deployment(&data) { + match Self::collect_group(&data) { Ok(deployment) => { let progress = Loading::default(); progress.text(format!( - "Creating deployment \"{}\" on the controller \"{}\"...", + "Creating group \"{}\" on the controller \"{}\"...", deployment.name, profile.name )); - match connection.client.create_deployment(deployment).await { + match connection.client.create_group(deployment).await { Ok(_) => { - progress.success("Deployment created successfully 👍. Remember to set the deployment to active, or the controller won't start units."); + progress.success("Group created successfully 👍. Remember to set the deployment to active, or the controller won't start units."); progress.end(); MenuResult::Success } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(error) => { - debug!("{}", error); - MenuResult::Failed - } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result { - let deployments = connection.client.get_deployments().await?; - let cloudlets = connection.client.get_cloudlets().await?; - Ok(Data { - deployments, - cloudlets, - }) + let groups = connection.client.get_groups().await?; + let nodes = connection.client.get_nodes().await?; + Ok(Data { groups, nodes }) } - fn collect_deployment(data: &Data) -> Result { - let name = Self::get_deployment_name(data.deployments.clone())?; - let cloudlets = Self::get_cloudlets(data.cloudlets.clone())?; + fn collect_group(data: &Data) -> Result { + let name = Self::get_group_name(data.groups.clone())?; + let nodes = Self::get_nodes(data.nodes.clone())?; let constraints = Self::collect_constraints()?; let scaling = Self::collect_scaling()?; let resources = Self::collect_resources()?; let spec = Self::collect_specification()?; - Ok(DeploymentValue { + Ok(group::Item { name, - cloudlets, + nodes, constraints: Some(constraints), scaling: Some(scaling), resources: Some(resources), @@ -113,7 +107,7 @@ impl CreateDeploymentMenu { }) } - fn get_deployment_name(used_names: Vec) -> Result { + fn get_group_name(used_names: Vec) -> Result { Text::new("What would you like to name this deployment?") .with_help_message("Examples: lobby, mode-xyz") .with_validator(ValueRequiredValidator::default()) @@ -127,47 +121,40 @@ impl CreateDeploymentMenu { } }) .prompt() - .map_err(|error| error.into()) } - fn get_cloudlets(cloudlets: Vec) -> Result> { - MultiSelect::new("What cloudlets should this deployment use?", cloudlets) - .prompt() - .map_err(|error| error.into()) + fn get_nodes(nodes: Vec) -> Result, InquireError> { + MultiSelect::new("What nodes should this deployment use?", nodes).prompt() } - fn collect_constraints() -> Result { - let minimum = MenuUtils::parsed_value( + fn collect_constraints() -> Result { + let min = MenuUtils::parsed_value( "What is the minimum number of units that should always be online?", "Example: 1", "Please enter a valid number", )?; - let maximum = MenuUtils::parsed_value( + let max = MenuUtils::parsed_value( "What is the maximum number of units that should always be online?", "Example: 10", "Please enter a valid number", )?; - let priority = MenuUtils::parsed_value("How important is this deployment compared to others? (This refers to one tick of the controller)", "Example: 0", "Please enter a valid number")?; + let prio = MenuUtils::parsed_value("How important is this deployment compared to others? (This refers to one tick of the controller)", "Example: 0", "Please enter a valid number")?; - Ok(Constraints { - minimum, - maximum, - priority, - }) + Ok(Constraints { min, max, prio }) } - fn collect_scaling() -> Result { + fn collect_scaling() -> Result { let start_threshold = MenuUtils::parsed_value::("At what percentage (0-100) of the max player count should the controller start a new unit?", "Example: 50", "Please enter a valid number")? / 100.0; - let stop_empty_units = + let stop_empty = MenuUtils::confirm("Should the controller stop units that are empty for too long?")?; Ok(Scaling { start_threshold, - stop_empty_units, + stop_empty, }) } - fn collect_resources() -> Result { + fn collect_resources() -> Result { let memory = MenuUtils::parsed_value( "How much memory should each unit have?", "Example: 2048", @@ -193,24 +180,24 @@ impl CreateDeploymentMenu { "Example: 2048", "Please enter a valid number", )?; - let addresses = MenuUtils::parsed_value( + let ports = MenuUtils::parsed_value( "How many addresses/ports should each unit have?", "Example: 5", "Please enter a valid number", )?; - Ok(UnitResources { + Ok(Resources { memory, swap, cpu, io, disk, - addresses, + ports, }) } - fn collect_specification() -> Result { - let image = MenuUtils::text("Which image should the unit use?", "Example: ubuntu:latest")?; + fn collect_specification() -> Result { + let img = MenuUtils::text("Which image should the unit use?", "Example: ubuntu:latest")?; let max_players = MenuUtils::parsed_value( "What is the maximum number of players per unit?", "Example: 20", @@ -222,38 +209,38 @@ impl CreateDeploymentMenu { "Please check your syntax. Something seems wrong.", )? .key_values; - let environment = MenuUtils::parsed_value::( + let env = MenuUtils::parsed_value::( "What environment variables should the controller pass to the driver when starting a unit?", "Format: key=value,key=value,key=value,...", "Please check your syntax something is wrong", )? .key_values; - let disk_retention = MenuUtils::select_no_help( + let retention = MenuUtils::select_no_help( "Should the unit's disk be retained after the unit stops?", - vec![Retention::Temporary, Retention::Permanent], + vec![DiskRetention::Temporary, DiskRetention::Permanent], )?; let fallback = Self::collect_fallback()?; - Ok(UnitSpec { - image, + Ok(Spec { + img, max_players, settings, - environment, - disk_retention: Some(disk_retention as i32), + env, + retention: Some(retention as i32), fallback: Some(fallback), }) } - fn collect_fallback() -> Result { + fn collect_fallback() -> Result { let enabled = MenuUtils::confirm("Should the controller treat these units as fallback units?")?; - let priority = MenuUtils::parsed_value( + let prio = MenuUtils::parsed_value( "What is the priority of this fallback deployment?", "Example: 0", "Please enter a valid number", )?; - Ok(Fallback { enabled, priority }) + Ok(Fallback { enabled, prio }) } } diff --git a/cli/src/application/menu/connection/deployment/get_deployment.rs b/cli/src/application/menu/connection/group/get_group.rs similarity index 68% rename from cli/src/application/menu/connection/deployment/get_deployment.rs rename to cli/src/application/menu/connection/group/get_group.rs index 45566048..6b8b7801 100644 --- a/cli/src/application/menu/connection/deployment/get_deployment.rs +++ b/cli/src/application/menu/connection/group/get_group.rs @@ -1,15 +1,16 @@ +use inquire::InquireError; use loading::Loading; use simplelog::{info, warn}; use crate::application::{ menu::{MenuResult, MenuUtils}, - network::{proto::deployment_management::DeploymentValue, EstablishedConnection}, + network::{proto::manage::group, EstablishedConnection}, profile::{Profile, Profiles}, }; -pub struct GetDeploymentMenu; +pub struct GetGroupMenu; -impl GetDeploymentMenu { +impl GetGroupMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -21,9 +22,9 @@ impl GetDeploymentMenu { profile.name )); - match connection.client.get_deployments().await { + match connection.client.get_groups().await { Ok(deployments) => { - progress.success("Deployment data retrieved successfully 👍"); + progress.success("Data retrieved successfully 👍"); progress.end(); match MenuUtils::select_no_help( @@ -37,9 +38,9 @@ impl GetDeploymentMenu { deployment, profile.name )); - match connection.client.get_deployment(&deployment).await { + match connection.client.get_group(&deployment).await { Ok(deployment_details) => { - progress.success("Deployment details retrieved successfully 👍"); + progress.success("Group details retrieved successfully 👍"); progress.end(); Self::display_details(&deployment_details); @@ -48,75 +49,74 @@ impl GetDeploymentMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(_) => MenuResult::Aborted, + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - fn display_details(deployment_details: &DeploymentValue) { - info!(" 🖥 Deployment Details"); - info!(" Name: {}", deployment_details.name); + fn display_details(group: &group::Item) { + info!(" 🖥 Group Details"); + info!(" Name: {}", group.name); - if !deployment_details.cloudlets.is_empty() { - info!(" Cloudlets:"); - for cloudlet in &deployment_details.cloudlets { - info!(" - {}", cloudlet); + if !group.nodes.is_empty() { + info!(" Nodes:"); + for node in &group.nodes { + info!(" - {}", node); } } else { - warn!(" Cloudlets: None"); + warn!(" Nodes: None"); } - if let Some(constraints) = &deployment_details.constraints { + if let Some(constraints) = &group.constraints { info!(" Constraints:"); - info!(" Minimum: {}", constraints.minimum); - info!(" Maximum: {}", constraints.maximum); - info!(" Priority: {}", constraints.priority); + info!(" Minimum: {}", constraints.min); + info!(" Maximum: {}", constraints.max); + info!(" Priority: {}", constraints.prio); } else { warn!(" Constraints: None"); } - if let Some(scaling) = &deployment_details.scaling { + if let Some(scaling) = &group.scaling { info!(" Scaling:"); info!( " Start Threshold: {}%", scaling.start_threshold * 100.0 ); - info!( - " Stop Empty Units: {}", - scaling.stop_empty_units - ); + info!(" Stop Empty: {}", scaling.stop_empty); } else { warn!(" Scaling: None"); } - if let Some(resources) = &deployment_details.resources { + if let Some(resources) = &group.resources { info!(" Resources per Unit:"); info!(" Memory: {} MiB", resources.memory); info!(" Swap: {} MiB", resources.swap); info!(" CPU Cores: {}", resources.cpu / 100); info!(" IO: {}", resources.io); info!(" Disk Space: {} MiB", resources.disk); - info!( - " Addresses/Ports: {}", - resources.addresses - ); + info!(" Addresses/Ports: {}", resources.ports); } else { warn!(" Resources per Unit: None"); } - if let Some(spec) = &deployment_details.spec { + if let Some(spec) = &group.spec { info!(" Specification:"); - info!(" Image: {}", spec.image); + info!(" Image: {}", spec.img); info!( " Max Players per Unit: {}", spec.max_players @@ -126,18 +126,18 @@ impl GetDeploymentMenu { info!(" - {}: {}", setting.key, setting.value); } info!(" Environment Variables:"); - for env in &spec.environment { + for env in &spec.env { info!(" - {}: {}", env.key, env.value); } info!( " Disk Retention: {}", - spec.disk_retention.unwrap_or(0) + spec.retention.unwrap_or(0) ); if let Some(fallback) = &spec.fallback { info!(" Fallback:"); info!(" Enabled: {}", fallback.enabled); - info!(" Priority: {}", fallback.priority); + info!(" Priority: {}", fallback.prio); } } else { warn!(" Specification: None"); diff --git a/cli/src/application/menu/connection/cloudlet/get_cloudlets.rs b/cli/src/application/menu/connection/group/get_groups.rs similarity index 51% rename from cli/src/application/menu/connection/cloudlet/get_cloudlets.rs rename to cli/src/application/menu/connection/group/get_groups.rs index 15ee0986..439ab21b 100644 --- a/cli/src/application/menu/connection/cloudlet/get_cloudlets.rs +++ b/cli/src/application/menu/connection/group/get_groups.rs @@ -7,9 +7,9 @@ use crate::application::{ profile::{Profile, Profiles}, }; -pub struct GetCloudletsMenu; +pub struct GetGroupsMenu; -impl GetCloudletsMenu { +impl GetGroupsMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -17,32 +17,32 @@ impl GetCloudletsMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Requesting cloudlet list from controller \"{}\"...", + "Requesting group list from controller \"{}\"...", profile.name )); - match connection.client.get_cloudlets().await { - Ok(cloudlets) => { - progress.success("Cloudlet data retrieved successfully 👍"); + match connection.client.get_groups().await { + Ok(groups) => { + progress.success("Data retrieved successfully 👍"); progress.end(); - Self::display_cloudlets(&cloudlets); + Self::display_groups(&groups); MenuResult::Success } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - fn display_cloudlets(cloudlets: &[String]) { - info!(" 🖥 Available Cloudlets"); - if cloudlets.is_empty() { - info!(" No cloudlets found."); + fn display_groups(groups: &[String]) { + info!(" 🖥 Available Groups"); + if groups.is_empty() { + info!(" No groups found."); } else { - for cloudlet in cloudlets { - info!(" - {}", cloudlet); + for group in groups { + info!(" - {}", group); } } } diff --git a/cli/src/application/menu/connection/group/mod.rs b/cli/src/application/menu/connection/group/mod.rs new file mode 100644 index 00000000..25c3b52f --- /dev/null +++ b/cli/src/application/menu/connection/group/mod.rs @@ -0,0 +1,3 @@ +pub mod create_group; +pub mod get_group; +pub mod get_groups; diff --git a/cli/src/application/menu/connection/cloudlet/create_cloudlet.rs b/cli/src/application/menu/connection/node/create_node.rs similarity index 59% rename from cli/src/application/menu/connection/cloudlet/create_cloudlet.rs rename to cli/src/application/menu/connection/node/create_node.rs index 4db5349a..6c09efb4 100644 --- a/cli/src/application/menu/connection/cloudlet/create_cloudlet.rs +++ b/cli/src/application/menu/connection/node/create_node.rs @@ -1,25 +1,24 @@ use anyhow::Result; use inquire::{ validator::{Validation, ValueRequiredValidator}, - Text, + InquireError, Text, }; use loading::Loading; -use simplelog::debug; use crate::application::{ menu::{MenuResult, MenuUtils}, - network::{proto::cloudlet_management::CloudletValue, EstablishedConnection}, + network::{proto::manage::node, EstablishedConnection}, profile::{Profile, Profiles}, }; -pub struct CreateCloudletMenu; +pub struct CreateNodeMenu; struct Data { - cloudlets: Vec, - drivers: Vec, + nodes: Vec, + plugins: Vec, } -impl CreateCloudletMenu { +impl CreateNodeMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -27,7 +26,7 @@ impl CreateCloudletMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Retrieving all existing cloudlets from the controller \"{}\"...", + "Retrieving all existing nodes from the controller \"{}\"...", profile.name )); @@ -37,14 +36,14 @@ impl CreateCloudletMenu { progress.end(); match Self::collect_cloudlet(&data) { - Ok(cloudlet) => { + Ok(node) => { let progress = Loading::default(); progress.text(format!( - "Creating cloudlet \"{}\" on the controller \"{}\"...", - cloudlet.name, profile.name + "Creating node \"{}\" on the controller \"{}\"...", + node.name, profile.name )); - match connection.client.create_cloudlet(cloudlet).await { + match connection.client.create_node(node).await { Ok(_) => { progress.success("Cloudlet created successfully 👍. Remember to set the cloudlet to active, or the controller won't start units."); progress.end(); @@ -53,53 +52,55 @@ impl CreateCloudletMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(error) => { - debug!("{}", error); - MenuResult::Failed - } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result { - let cloudlets = connection.client.get_cloudlets().await?; - let drivers = connection.client.get_drivers().await?; - Ok(Data { cloudlets, drivers }) + let nodes = connection.client.get_nodes().await?; + let plugins = connection.client.get_plugins().await?; + Ok(Data { nodes, plugins }) } - fn collect_cloudlet(data: &Data) -> Result { - let name = Self::get_cloudlet_name(data.cloudlets.clone())?; - let driver = MenuUtils::select("Which driver should the controller use to communicate with the backend of this cloudlet?", "This is essential for the controller to know how to communicate with the backend of this cloudlet. For example, is it a Pterodactyl node or a simple Docker host?", data.drivers.to_vec())?; + fn collect_cloudlet(data: &Data) -> Result { + let name = Self::get_node_name(data.nodes.clone())?; + let plugin = MenuUtils::select("Which plugin should the controller use to communicate with the backend of this node?", "This is essential for the controller to know how to communicate with the backend of this node. For example, is it a Pterodactyl node or a simple Docker host?", data.plugins.to_vec())?; let child = Self::get_child_node()?; let memory = Self::get_memory_limit()?; - let max_allocations = Self::get_allocations_limit()?; - let controller_address = MenuUtils::parsed_value( - "What is the hostname or address where the unit can reach the controller once started?", + let max = Self::get_servers_limit()?; + let ctrl_addr = MenuUtils::parsed_value( + "What is the hostname or address where the server can reach the controller once started?", "Example: https://cloud.your-network.net", "Please enter a valid URL", )?; - Ok(CloudletValue { + Ok(node::Item { name, - driver, + plugin, memory, - max_allocations, + max, child, - controller_address, + ctrl_addr, }) } - fn get_cloudlet_name(used_names: Vec) -> Result { + fn get_node_name(used_names: Vec) -> Result { Text::new("What would you like to name this cloudlet?") .with_help_message("Examples: hetzner-01, home-01, local-01") .with_validator(ValueRequiredValidator::default()) @@ -113,10 +114,9 @@ impl CreateCloudletMenu { } }) .prompt() - .map_err(|error| error.into()) } - fn get_memory_limit() -> Result> { + fn get_memory_limit() -> Result, InquireError> { match MenuUtils::confirm( "Would you like to limit the amount of memory the controller can use on this cloudlet?", )? { @@ -129,16 +129,16 @@ impl CreateCloudletMenu { } } - fn get_allocations_limit() -> Result> { - match MenuUtils::confirm("Would you like to limit the number of units the controller can start on this cloudlet?")? + fn get_servers_limit() -> Result, InquireError> { + match MenuUtils::confirm("Would you like to limit the number of servers the controller can start on this cloudlet?")? { false => Ok(None), - true => Ok(Some(MenuUtils::parsed_value("How many units should the controller be allowed to start on this cloudlet?", "Example: 15", "Please enter a valid number")?)) + true => Ok(Some(MenuUtils::parsed_value("How many servers should the controller be allowed to start on this node?", "Example: 15", "Please enter a valid number")?)) } } - fn get_child_node() -> Result> { - match MenuUtils::confirm("Does the specified driver need additional information to determine which node it should use in the backend? This is required when a driver manages multiple nodes.")? { + fn get_child_node() -> Result, InquireError> { + match MenuUtils::confirm("Does the specified plugin need additional information to determine which node it should use in the backend? This is required when a plugin manages multiple nodes.")? { false => Ok(None), true => { Ok(Some(Text::new("What is the name of the child node the controller should use?") diff --git a/cli/src/application/menu/connection/node/get_node.rs b/cli/src/application/menu/connection/node/get_node.rs new file mode 100644 index 00000000..9e35d180 --- /dev/null +++ b/cli/src/application/menu/connection/node/get_node.rs @@ -0,0 +1,82 @@ +use inquire::InquireError; +use loading::Loading; +use simplelog::info; + +use crate::application::{ + menu::{MenuResult, MenuUtils}, + network::{proto::manage::node, EstablishedConnection}, + profile::{Profile, Profiles}, +}; + +pub struct GetNodeMenu; + +impl GetNodeMenu { + pub async fn show( + profile: &mut Profile, + connection: &mut EstablishedConnection, + _profiles: &mut Profiles, + ) -> MenuResult { + let progress = Loading::default(); + progress.text(format!( + "Retrieving available nodes from controller \"{}\"...", + profile.name + )); + + match connection.client.get_nodes().await { + Ok(nodes) => { + progress.success("Data retrieved successfully 👍"); + progress.end(); + match MenuUtils::select_no_help("Select a nodes to view more details:", nodes) { + Ok(node) => { + let progress = Loading::default(); + progress.text(format!( + "Fetching details for node \"{}\" from controller \"{}\"...", + node, profile.name + )); + + match connection.client.get_node(&node).await { + Ok(details) => { + progress.success("Node details retrieved successfully 👍"); + progress.end(); + Self::display_details(&details); + MenuResult::Success + } + Err(error) => { + progress.fail(format!("{}", error)); + progress.end(); + MenuResult::Failed(error) + } + } + } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, + } + } + Err(error) => { + progress.fail(format!("{}", error)); + progress.end(); + MenuResult::Failed(error) + } + } + } + + fn display_details(node: &node::Item) { + info!(" 🖥 Node Information"); + info!(" Name: {}", node.name); + info!(" Plugin: {}", node.plugin); + if let Some(memory) = &node.memory { + info!(" Memory: {} MiB", memory); + } + if let Some(max) = &node.max { + info!(" Max Servers: {} Units", max); + } + if let Some(child) = &node.child { + info!(" Child Node: {}", child); + } + info!(" Controller Address: {}", node.ctrl_addr); + } +} diff --git a/cli/src/application/menu/connection/deployment/get_deployments.rs b/cli/src/application/menu/connection/node/get_nodes.rs similarity index 50% rename from cli/src/application/menu/connection/deployment/get_deployments.rs rename to cli/src/application/menu/connection/node/get_nodes.rs index a840dc16..16186f06 100644 --- a/cli/src/application/menu/connection/deployment/get_deployments.rs +++ b/cli/src/application/menu/connection/node/get_nodes.rs @@ -7,9 +7,9 @@ use crate::application::{ profile::{Profile, Profiles}, }; -pub struct GetDeploymentsMenu; +pub struct GetNodesMenu; -impl GetDeploymentsMenu { +impl GetNodesMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -17,32 +17,32 @@ impl GetDeploymentsMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Requesting deployment list from controller \"{}\"...", + "Requesting nodes list from controller \"{}\"...", profile.name )); - match connection.client.get_deployments().await { - Ok(deployments) => { - progress.success("Deployment data retrieved successfully 👍"); + match connection.client.get_nodes().await { + Ok(nodes) => { + progress.success("Data retrieved successfully 👍"); progress.end(); - Self::display_deployments(&deployments); + Self::display_details(&nodes); MenuResult::Success } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - fn display_deployments(deployments: &[String]) { - info!(" 🖥 Available Deployments"); - if deployments.is_empty() { - info!(" No deployments found."); + fn display_details(nodes: &[String]) { + info!(" 🖥 Available Nodes"); + if nodes.is_empty() { + info!(" No nodes found."); } else { - for deployment in deployments { - info!(" - {}", deployment); + for node in nodes { + info!(" - {}", node); } } } diff --git a/cli/src/application/menu/connection/node/mod.rs b/cli/src/application/menu/connection/node/mod.rs new file mode 100644 index 00000000..135a1f34 --- /dev/null +++ b/cli/src/application/menu/connection/node/mod.rs @@ -0,0 +1,3 @@ +pub mod create_node; +pub mod get_node; +pub mod get_nodes; diff --git a/cli/src/application/menu/connection/resource/delete_resource.rs b/cli/src/application/menu/connection/resource/delete_resource.rs index f28b6682..2e107369 100644 --- a/cli/src/application/menu/connection/resource/delete_resource.rs +++ b/cli/src/application/menu/connection/resource/delete_resource.rs @@ -1,13 +1,13 @@ use anyhow::Result; +use inquire::InquireError; use loading::Loading; -use simplelog::debug; use crate::application::{ menu::{MenuResult, MenuUtils}, network::{ - proto::{ - resource_management::{DeleteResourceRequest, ResourceCategory}, - unit_management::SimpleUnitValue, + proto::manage::{ + resource::{Category, DelReq}, + server, }, EstablishedConnection, }, @@ -18,9 +18,9 @@ pub struct DeleteResourceMenu; // TODO: Maybe dont request everything at once, but only what is needed struct Data { - cloudlets: Vec, - deployments: Vec, - units: Vec, + nodes: Vec, + groups: Vec, + servers: Vec, } impl DeleteResourceMenu { @@ -54,71 +54,65 @@ impl DeleteResourceMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(error) => { - debug!("{}", error); - MenuResult::Failed - } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result { - let cloudlets = connection.client.get_cloudlets().await?; - let deployments = connection.client.get_deployments().await?; - let units = connection.client.get_units().await?; + let nodes = connection.client.get_nodes().await?; + let groups = connection.client.get_groups().await?; + let servers = connection.client.get_servers().await?; Ok(Data { - cloudlets, - deployments, - units, + nodes, + groups, + servers, }) } - fn collect_delete_resource(data: &Data) -> Result { + fn collect_delete_resource(data: &Data) -> Result { let category = MenuUtils::select_no_help( "What type of resource to you want to delete?", - vec![ - ResourceCategory::Cloudlet, - ResourceCategory::Deployment, - ResourceCategory::Unit, - ], + vec![Category::Node, Category::Group, Category::Server], )?; match category { - ResourceCategory::Cloudlet => { - let cloudlet = MenuUtils::select_no_help( - "Select the cloudlet to delete", - data.cloudlets.clone(), - )?; - Ok(DeleteResourceRequest { + Category::Node => { + let cloudlet = + MenuUtils::select_no_help("Select the node to delete", data.nodes.clone())?; + Ok(DelReq { category: category as i32, id: cloudlet, }) } - ResourceCategory::Deployment => { - let deployment = MenuUtils::select_no_help( - "Select the deployment to delete", - data.deployments.clone(), - )?; - Ok(DeleteResourceRequest { + Category::Group => { + let deployment = + MenuUtils::select_no_help("Select the group to delete", data.groups.clone())?; + Ok(DelReq { category: category as i32, id: deployment, }) } - ResourceCategory::Unit => { + Category::Server => { let unit = - MenuUtils::select_no_help("Select the unit to delete", data.units.clone())?; - Ok(DeleteResourceRequest { + MenuUtils::select_no_help("Select the server to delete", data.servers.clone())?; + Ok(DelReq { category: category as i32, - id: unit.uuid, + id: unit.id, }) } } diff --git a/cli/src/application/menu/connection/resource/mod.rs b/cli/src/application/menu/connection/resource/mod.rs index 024b03ce..0027950d 100644 --- a/cli/src/application/menu/connection/resource/mod.rs +++ b/cli/src/application/menu/connection/resource/mod.rs @@ -1,2 +1,2 @@ pub mod delete_resource; -pub mod set_resource_status; +pub mod set_resource; diff --git a/cli/src/application/menu/connection/resource/set_resource_status.rs b/cli/src/application/menu/connection/resource/set_resource.rs similarity index 57% rename from cli/src/application/menu/connection/resource/set_resource_status.rs rename to cli/src/application/menu/connection/resource/set_resource.rs index 37267708..6d9ee483 100644 --- a/cli/src/application/menu/connection/resource/set_resource_status.rs +++ b/cli/src/application/menu/connection/resource/set_resource.rs @@ -1,25 +1,25 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; +use inquire::InquireError; use loading::Loading; -use simplelog::debug; use crate::application::{ menu::{MenuResult, MenuUtils}, network::{ - proto::resource_management::{ResourceCategory, ResourceStatus, SetResourceStatusRequest}, + proto::manage::resource::{set_req, Category, SetReq}, EstablishedConnection, }, profile::{Profile, Profiles}, }; -pub struct SetResourceStatusMenu; +pub struct SetResourceMenu; // TODO: Maybe dont request everything at once, but only what is needed struct Data { - cloudlets: Vec, - deployments: Vec, + nodes: Vec, + groups: Vec, } -impl SetResourceStatusMenu { +impl SetResourceMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -41,7 +41,7 @@ impl SetResourceStatusMenu { let progress = Loading::default(); progress.text("Changing resource..."); - match connection.client.set_resource_status(request).await { + match connection.client.set_resource(request).await { Ok(_) => { progress.success("Resource changed successfully 👍."); progress.end(); @@ -50,70 +50,61 @@ impl SetResourceStatusMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(error) => { - debug!("{}", error); - MenuResult::Failed - } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result { - let cloudlets = connection.client.get_cloudlets().await?; - let deployments = connection.client.get_deployments().await?; - Ok(Data { - cloudlets, - deployments, - }) + let nodes = connection.client.get_nodes().await?; + let groups = connection.client.get_groups().await?; + Ok(Data { nodes, groups }) } - fn collect_set_resource_status_request(data: &Data) -> Result { + fn collect_set_resource_status_request(data: &Data) -> Result { let status = MenuUtils::select_no_help( "What is the new status of this resource?", - vec![ResourceStatus::Active, ResourceStatus::Inactive], + vec![set_req::Status::Active, set_req::Status::Inactive], )?; let category = MenuUtils::select_no_help( "What type of resource to you want to change?", - vec![ - ResourceCategory::Cloudlet, - ResourceCategory::Deployment, - ResourceCategory::Unit, - ], + vec![Category::Node, Category::Group, Category::Server], )?; match category { - ResourceCategory::Cloudlet => { - let cloudlet = MenuUtils::select_no_help( - "Select the cloudlet to change", - data.cloudlets.clone(), - )?; - Ok(SetResourceStatusRequest { + Category::Node => { + let node = + MenuUtils::select_no_help("Select the node to change", data.nodes.clone())?; + Ok(SetReq { category: category as i32, - id: cloudlet, + id: node, status: status as i32, }) } - ResourceCategory::Deployment => { - let deployment = MenuUtils::select_no_help( - "Select the deployment to change", - data.deployments.clone(), - )?; - Ok(SetResourceStatusRequest { + Category::Group => { + let group = + MenuUtils::select_no_help("Select the group to change", data.groups.clone())?; + Ok(SetReq { category: category as i32, - id: deployment, + id: group, status: status as i32, }) } - ResourceCategory::Unit => Err(anyhow!("Not implemented yet")), + Category::Server => Err(InquireError::OperationInterrupted), } } } diff --git a/cli/src/application/menu/connection/unit/get_unit.rs b/cli/src/application/menu/connection/server/get_server.rs similarity index 59% rename from cli/src/application/menu/connection/unit/get_unit.rs rename to cli/src/application/menu/connection/server/get_server.rs index b0956a53..e1d566c9 100644 --- a/cli/src/application/menu/connection/unit/get_unit.rs +++ b/cli/src/application/menu/connection/server/get_server.rs @@ -1,26 +1,16 @@ -use std::fmt::Display; - +use inquire::InquireError; use loading::Loading; use simplelog::{info, warn}; use crate::application::{ menu::{MenuResult, MenuUtils}, - network::{ - proto::unit_management::{SimpleUnitValue, UnitValue}, - EstablishedConnection, - }, + network::{proto::manage::server, EstablishedConnection}, profile::{Profile, Profiles}, }; -impl Display for SimpleUnitValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name) - } -} +pub struct GetServerMenu; -pub struct GetUnitMenu; - -impl GetUnitMenu { +impl GetServerMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -28,25 +18,25 @@ impl GetUnitMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Retrieving available units from controller \"{}\"", + "Retrieving available servers from controller \"{}\"", profile.name )); - match connection.client.get_units().await { - Ok(units) => { - progress.success("Unit data retrieved successfully 👍"); + match connection.client.get_servers().await { + Ok(servers) => { + progress.success("Data retrieved successfully 👍"); progress.end(); - match MenuUtils::select_no_help("Select a unit to view more details:", units) { - Ok(unit) => { + match MenuUtils::select_no_help("Select a server to view more details:", servers) { + Ok(server) => { let progress = Loading::default(); progress.text(format!( - "Fetching details for unit \"{}\" from controller \"{}\"...", - profile.name, unit + "Fetching details for server \"{}\" from controller \"{}\"...", + profile.name, server )); - match connection.client.get_unit(&unit.uuid).await { + match connection.client.get_server(&server.id).await { Ok(unit) => { - progress.success("Unit details retrieved successfully 👍"); + progress.success("Details retrieved successfully 👍"); progress.end(); Self::display_details(&unit); MenuResult::Success @@ -54,39 +44,41 @@ impl GetUnitMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(_) => MenuResult::Aborted, + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - fn display_details(unit: &UnitValue) { - info!(" 🖥 Unit Info"); - info!(" Name: {}", unit.name); - info!(" UUID: {}", unit.uuid); - if let Some(deployment) = &unit.deployment { - info!(" Deployment: {}", deployment); + fn display_details(server: &server::Detail) { + info!(" 🖥 Server Info"); + info!(" Name: {}", server.name); + info!(" UUID: {}", server.id); + if let Some(group) = &server.group { + info!(" Group: {}", group); } else { - info!(" Deployment: None"); + info!(" Group: None"); } - info!(" Cloudlet: {}", unit.cloudlet); - if let Some(allocation) = &unit.allocation { + info!(" Node: {}", server.name); + if let Some(allocation) = &server.allocation { info!(" Allocation: "); info!(" Allocations: "); - for address in &allocation.addresses { - info!( - " - {}:{}", - address.host, address.port - ); + for port in &allocation.ports { + info!(" - {}:{}", port.host, port.port); } if let Some(resources) = allocation.resources { info!(" Resources per unit: "); @@ -103,14 +95,14 @@ impl GetUnitMenu { ); info!( " Addresses/Ports: {}", - resources.addresses + resources.ports ); } else { warn!(" Resources per unit: None"); } if let Some(spec) = &allocation.spec { info!(" Specification: "); - info!(" Image: {}", spec.image); + info!(" Image: {}", spec.img); info!(" Settings: "); for setting in &spec.settings { info!( @@ -119,7 +111,7 @@ impl GetUnitMenu { ); } info!(" Environment Variables: "); - for environment in &spec.environment { + for environment in &spec.env { info!( " - {}: {}", environment.key, environment.value @@ -127,7 +119,7 @@ impl GetUnitMenu { } info!( " Disk Retention: {}", - spec.disk_retention.unwrap_or(0) + spec.retention.unwrap_or(0) ); if let Some(fallback) = spec.fallback { info!(" Fallback: "); @@ -135,10 +127,7 @@ impl GetUnitMenu { " Is fallback: {}", fallback.enabled ); - info!( - " Priority: {}", - fallback.priority - ); + info!(" Priority: {}", fallback.prio); } } else { warn!(" Specification: None"); @@ -146,12 +135,9 @@ impl GetUnitMenu { } else { warn!(" Scaling: None"); } - info!( - " Connected Users: {}", - unit.connected_users - ); - info!(" Auth Token: {}", unit.auth_token); - info!(" State: {}", unit.state); - info!(" Rediness: {}", unit.rediness); + info!(" Connected Users: {}", server.users); + info!(" Auth Token: {}", server.token); + info!(" State: {}", server.state); + info!(" Ready: {}", server.ready); } } diff --git a/cli/src/application/menu/connection/unit/get_units.rs b/cli/src/application/menu/connection/server/get_servers.rs similarity index 53% rename from cli/src/application/menu/connection/unit/get_units.rs rename to cli/src/application/menu/connection/server/get_servers.rs index 5d67d869..d28557c8 100644 --- a/cli/src/application/menu/connection/unit/get_units.rs +++ b/cli/src/application/menu/connection/server/get_servers.rs @@ -3,13 +3,13 @@ use simplelog::info; use crate::application::{ menu::MenuResult, - network::{proto::unit_management::SimpleUnitValue, EstablishedConnection}, + network::{proto::manage::server, EstablishedConnection}, profile::{Profile, Profiles}, }; -pub struct GetUnitsMenu; +pub struct GetServersMenu; -impl GetUnitsMenu { +impl GetServersMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -17,34 +17,34 @@ impl GetUnitsMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Requesting unit list from controller \"{}\"", + "Requesting server list from controller \"{}\"", profile.name )); - match connection.client.get_units().await { - Ok(units) => { - progress.success("Unit data retrieved successfully 👍"); + match connection.client.get_servers().await { + Ok(servers) => { + progress.success("Data retrieved successfully 👍"); progress.end(); - Self::display_details(&units); + Self::display_details(&servers); MenuResult::Success } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - fn display_details(units: &[SimpleUnitValue]) { - info!(" 🖥 Units"); - if units.is_empty() { - info!(" No units found"); + fn display_details(servers: &[server::Short]) { + info!(" 🖥 Servers"); + if servers.is_empty() { + info!(" No server found"); } else { - for unit in units { + for server in servers { info!( " - {}@{} ({})", - unit.name, unit.cloudlet, unit.uuid + server.name, server.node, server.id ); } } diff --git a/cli/src/application/menu/connection/server/mod.rs b/cli/src/application/menu/connection/server/mod.rs new file mode 100644 index 00000000..7aef546a --- /dev/null +++ b/cli/src/application/menu/connection/server/mod.rs @@ -0,0 +1,2 @@ +pub mod get_server; +pub mod get_servers; diff --git a/cli/src/application/menu/connection/start.rs b/cli/src/application/menu/connection/start.rs index a9e07eed..85ac3b6d 100644 --- a/cli/src/application/menu/connection/start.rs +++ b/cli/src/application/menu/connection/start.rs @@ -9,41 +9,35 @@ use crate::application::{ }; use super::{ - cloudlet::{ - create_cloudlet::CreateCloudletMenu, get_cloudlet::GetCloudletMenu, - get_cloudlets::GetCloudletsMenu, - }, - deployment::{ - create_deployment::CreateDeploymentMenu, get_deployment::GetDeploymentMenu, - get_deployments::GetDeploymentsMenu, - }, general::{get_versions::GetVersionsMenu, request_stop::RequestStopMenu}, - resource::{delete_resource::DeleteResourceMenu, set_resource_status::SetResourceStatusMenu}, - unit::{get_unit::GetUnitMenu, get_units::GetUnitsMenu}, - user::transfer_user::TransferUserMenu, + group::{create_group::CreateGroupMenu, get_group::GetGroupMenu, get_groups::GetGroupsMenu}, + node::{create_node::CreateNodeMenu, get_node::GetNodeMenu, get_nodes::GetNodesMenu}, + resource::{delete_resource::DeleteResourceMenu, set_resource::SetResourceMenu}, + server::{get_server::GetServerMenu, get_servers::GetServersMenu}, + user::transfer_users::TransferUsersMenu, }; enum Action { - // Resource Management - SetResourceStatus, + // Resource operations + SetResource, DeleteResource, - // Cloudlet Management - CreateCloudlet, - GetCloudlet, - GetCloudlets, + // Node operations + CreateNode, + GetNode, + GetNodes, - // Deployment Management - CreateDeployment, - GetDeployment, - GetDeployments, + // Group operations + CreateGroup, + GetGroup, + GetGroups, - // Unit Management - GetUnit, - GetUnits, + // Server operations + GetServer, + GetServers, - // User Management - TransferUser, + // Transfer operations + TransferUsers, // General RequestStop, @@ -56,21 +50,21 @@ enum Action { impl Display for Action { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Action::SetResourceStatus => write!(f, "Set status of a certain Resource"), + Action::SetResource => write!(f, "Set status of a certain Resource"), Action::DeleteResource => write!(f, "Delete Resource"), - Action::CreateCloudlet => write!(f, "Create Cloudlet"), - Action::GetCloudlet => write!(f, "Get information about a certain Cloudlet"), - Action::GetCloudlets => write!(f, "Get all Cloudlets"), + Action::CreateNode => write!(f, "Create Node"), + Action::GetNode => write!(f, "Get information about a certain Node"), + Action::GetNodes => write!(f, "Get all Nodes"), - Action::CreateDeployment => write!(f, "Create Deployment"), - Action::GetDeployment => write!(f, "Get information about a certain Deployment"), - Action::GetDeployments => write!(f, "Get all Deployments"), + Action::CreateGroup => write!(f, "Create Group"), + Action::GetGroup => write!(f, "Get information about a certain Group"), + Action::GetGroups => write!(f, "Get all Groups"), - Action::GetUnit => write!(f, "Get information about a certain Unit"), - Action::GetUnits => write!(f, "Get all Units"), + Action::GetServer => write!(f, "Get information about a certain Server"), + Action::GetServers => write!(f, "Get all Servers"), - Action::TransferUser => write!(f, "Transfer a user to a different Unit"), + Action::TransferUsers => write!(f, "Transfer a users to a different Server"), Action::RequestStop => write!(f, "Request stop of Controller"), Action::GetVersions => write!(f, "Get versions"), @@ -91,7 +85,7 @@ impl ConnectionStartMenu { loop { match Self::show_internal(profile, connection, profiles).await { MenuResult::Exit => return MenuResult::Success, - MenuResult::Error(error) => return MenuResult::Error(error), + MenuResult::Failed(error) => return MenuResult::Failed(error), _ => {} } } @@ -106,45 +100,37 @@ impl ConnectionStartMenu { "What do you want to do?", vec![ Action::RequestStop, - Action::TransferUser, - Action::SetResourceStatus, + Action::TransferUsers, + Action::SetResource, Action::DeleteResource, - Action::CreateCloudlet, - Action::CreateDeployment, - Action::GetCloudlet, - Action::GetDeployment, - Action::GetUnit, - Action::GetDeployments, - Action::GetCloudlets, - Action::GetUnits, + Action::CreateNode, + Action::CreateGroup, + Action::GetNode, + Action::GetGroup, + Action::GetServer, + Action::GetNodes, + Action::GetGroups, + Action::GetServers, Action::GetVersions, Action::DisconnectFromController, ], ) { Ok(selection) => match selection { - Action::SetResourceStatus => { - SetResourceStatusMenu::show(profile, connection, profiles).await - } + Action::SetResource => SetResourceMenu::show(profile, connection, profiles).await, Action::DeleteResource => { DeleteResourceMenu::show(profile, connection, profiles).await } - Action::CreateCloudlet => { - CreateCloudletMenu::show(profile, connection, profiles).await - } - Action::GetCloudlet => GetCloudletMenu::show(profile, connection, profiles).await, - Action::GetCloudlets => GetCloudletsMenu::show(profile, connection, profiles).await, - Action::CreateDeployment => { - CreateDeploymentMenu::show(profile, connection, profiles).await - } - Action::GetDeployment => { - GetDeploymentMenu::show(profile, connection, profiles).await - } - Action::GetDeployments => { - GetDeploymentsMenu::show(profile, connection, profiles).await + Action::CreateNode => CreateNodeMenu::show(profile, connection, profiles).await, + Action::GetNode => GetNodeMenu::show(profile, connection, profiles).await, + Action::GetNodes => GetNodesMenu::show(profile, connection, profiles).await, + Action::CreateGroup => CreateGroupMenu::show(profile, connection, profiles).await, + Action::GetGroup => GetGroupMenu::show(profile, connection, profiles).await, + Action::GetGroups => GetGroupsMenu::show(profile, connection, profiles).await, + Action::GetServer => GetServerMenu::show(profile, connection, profiles).await, + Action::GetServers => GetServersMenu::show(profile, connection, profiles).await, + Action::TransferUsers => { + TransferUsersMenu::show(profile, connection, profiles).await } - Action::GetUnit => GetUnitMenu::show(profile, connection, profiles).await, - Action::GetUnits => GetUnitsMenu::show(profile, connection, profiles).await, - Action::TransferUser => TransferUserMenu::show(profile, connection, profiles).await, Action::RequestStop => RequestStopMenu::show(profile, connection, profiles).await, Action::GetVersions => GetVersionsMenu::show(profile, connection, profiles).await, Action::DisconnectFromController => MenuResult::Exit, diff --git a/cli/src/application/menu/connection/unit/mod.rs b/cli/src/application/menu/connection/unit/mod.rs deleted file mode 100644 index 2f5ed64f..00000000 --- a/cli/src/application/menu/connection/unit/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod get_unit; -pub mod get_units; diff --git a/cli/src/application/menu/connection/user/mod.rs b/cli/src/application/menu/connection/user/mod.rs index 098c6e86..a1bd1efb 100644 --- a/cli/src/application/menu/connection/user/mod.rs +++ b/cli/src/application/menu/connection/user/mod.rs @@ -1 +1 @@ -pub mod transfer_user; +pub mod transfer_users; diff --git a/cli/src/application/menu/connection/user/transfer_user.rs b/cli/src/application/menu/connection/user/transfer_users.rs similarity index 55% rename from cli/src/application/menu/connection/user/transfer_user.rs rename to cli/src/application/menu/connection/user/transfer_users.rs index e115fe77..2ce0db1c 100644 --- a/cli/src/application/menu/connection/user/transfer_user.rs +++ b/cli/src/application/menu/connection/user/transfer_users.rs @@ -1,12 +1,21 @@ use anyhow::Result; +use inquire::InquireError; use loading::Loading; -use simplelog::debug; use crate::application::{ - menu::{MenuResult, MenuUtils}, network::{proto::{server, user}, EstablishedConnection}, profile::{Profile, Profiles} + menu::{MenuResult, MenuUtils}, + network::{ + proto::manage::{ + server, + transfer::{self, target, TransferReq}, + user, + }, + EstablishedConnection, + }, + profile::{Profile, Profiles}, }; -pub struct TransferUserMenu; +pub struct TransferUsersMenu; // TODO: Maybe dont request everything at once, but only what is needed struct Data { @@ -15,7 +24,7 @@ struct Data { groups: Vec, } -impl TransferUserMenu { +impl TransferUsersMenu { pub async fn show( profile: &mut Profile, connection: &mut EstablishedConnection, @@ -37,7 +46,7 @@ impl TransferUserMenu { let progress = Loading::default(); progress.text(format!( "Transferring {} users to target \"{}\"...", - request.user_uuids.len(), + request.ids.len(), request.target.as_ref().unwrap() )); @@ -50,77 +59,79 @@ impl TransferUserMenu { Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } - Err(error) => { - debug!("{}", error); - MenuResult::Failed - } + Err(error) => match error { + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } Err(error) => { progress.fail(format!("{}", error)); progress.end(); - MenuResult::Failed + MenuResult::Failed(error) } } } async fn get_required_data(connection: &mut EstablishedConnection) -> Result { let users = connection.client.get_users().await?; - let units = connection.client.get_units().await?; - let deployments = connection.client.get_deployments().await?; + let servers = connection.client.get_servers().await?; + let groups = connection.client.get_groups().await?; Ok(Data { users, - units, - deployments, + servers, + groups, }) } - fn collect_transfer_request(data: &Data) -> Result { + fn collect_transfer_request(data: &Data) -> Result { let users = MenuUtils::multi_select_no_help("Select the users to transfer", data.users.clone())?; let target = Self::collect_transfer_target(data)?; - Ok(TransferUsersRequest { - user_uuids: users.iter().map(|user| user.uuid.clone()).collect(), + Ok(TransferReq { + ids: users.iter().map(|user| user.id.clone()).collect(), target: Some(target), }) } - fn collect_transfer_target(data: &Data) -> Result { + fn collect_transfer_target(data: &Data) -> Result { match MenuUtils::select_no_help( "Select the target type", vec![ - TargetType::Unit, - TargetType::Deployment, - TargetType::Fallback, + target::Type::Server, + target::Type::Group, + target::Type::Fallback, ], )? { - TargetType::Unit => { - let unit = MenuUtils::select_no_help( - "Select the unit to transfer the user to", - data.units.clone(), + target::Type::Server => { + let server = MenuUtils::select_no_help( + "Select the server to transfer the user to", + data.servers.clone(), )?; - Ok(TransferTargetValue { - target_type: TargetType::Unit as i32, - target: Some(unit.uuid), + Ok(transfer::Target { + r#type: target::Type::Server as i32, + target: Some(server.id), }) } - TargetType::Deployment => { - let deployment = MenuUtils::select_no_help( - "Select the deployment to transfer the user to", - data.deployments.clone(), + target::Type::Group => { + let group = MenuUtils::select_no_help( + "Select the group to transfer the user to", + data.groups.clone(), )?; - Ok(TransferTargetValue { - target_type: TargetType::Deployment as i32, - target: Some(deployment), + Ok(transfer::Target { + r#type: target::Type::Group as i32, + target: Some(group), }) } - TargetType::Fallback => Ok(TransferTargetValue { - target_type: TargetType::Fallback as i32, + target::Type::Fallback => Ok(transfer::Target { + r#type: target::Type::Fallback as i32, target: None, }), } diff --git a/cli/src/application/menu/create_profile.rs b/cli/src/application/menu/create_profile.rs index 718cb30e..ebfd1b7f 100644 --- a/cli/src/application/menu/create_profile.rs +++ b/cli/src/application/menu/create_profile.rs @@ -1,9 +1,8 @@ -use common::error::{FancyError}; use inquire::{ - validator::{Validation, ValueRequiredValidator}, InquireError, Password, Text + validator::{Validation, ValueRequiredValidator}, + InquireError, Password, Text, }; use loading::Loading; -use simplelog::debug; use crate::{ application::profile::{Profile, Profiles}, @@ -34,9 +33,11 @@ impl CreateProfileMenu { let name = match prompt.prompt() { Ok(name) => name, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, - _ => return MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + return MenuResult::Aborted + } + _ => return MenuResult::Failed(error.into()), + }, }; let authorization = match Password::new("What is the authorization token for this profile?") @@ -48,9 +49,11 @@ impl CreateProfileMenu { { Ok(authorization) => authorization, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, - _ => return MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + return MenuResult::Aborted + } + _ => return MenuResult::Failed(error.into()), + }, }; let url = match MenuUtils::parsed_value( @@ -60,9 +63,11 @@ impl CreateProfileMenu { ) { Ok(url) => url, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => return MenuResult::Aborted, - _ => return MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + return MenuResult::Aborted + } + _ => return MenuResult::Failed(error.into()), + }, }; let progress = Loading::default(); @@ -80,16 +85,14 @@ impl CreateProfileMenu { Err(error) => { progress.fail(format!("Failed to connect to the controller: {}", error)); progress.end(); - FancyError::print_fancy(&error, false); - return MenuResult::Failed; + return MenuResult::Failed(error); } } progress.text(format!("Saving profile \"{}\"", name)); if let Err(error) = profiles.create_profile(&profile) { progress.fail(format!("Failed to create profile: {}", error)); progress.end(); - FancyError::print_fancy(&error, false); - return MenuResult::Failed; + return MenuResult::Failed(error); } progress.success("Profile created successfully"); progress.end(); diff --git a/cli/src/application/menu/delete_profile.rs b/cli/src/application/menu/delete_profile.rs index c7b06bd1..c2a315c5 100644 --- a/cli/src/application/menu/delete_profile.rs +++ b/cli/src/application/menu/delete_profile.rs @@ -1,6 +1,5 @@ use inquire::InquireError; use loading::Loading; -use simplelog::debug; use crate::application::profile::Profiles; @@ -22,23 +21,25 @@ impl DeleteProfileMenu { progress.end(); MenuResult::Success } - Err(err) => { + Err(error) => { progress.fail(format!( "Ops. Something went wrong while deleting the profile | {}", - err + error )); progress.end(); - - MenuResult::Failed + + MenuResult::Failed(error) } } } Ok(false) | Err(_) => MenuResult::Aborted, }, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Aborted, - _ => MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } } diff --git a/cli/src/application/menu/load_profile.rs b/cli/src/application/menu/load_profile.rs index 47ee3406..4268d704 100644 --- a/cli/src/application/menu/load_profile.rs +++ b/cli/src/application/menu/load_profile.rs @@ -1,5 +1,4 @@ use inquire::InquireError; -use simplelog::debug; use crate::application::profile::Profiles; @@ -16,9 +15,11 @@ impl LoadProfileMenu { ) { Ok(profile) => ConnectionMenu::show(profile, profiles).await, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Aborted, - _ => MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Aborted + } + _ => MenuResult::Failed(error.into()), + }, } } } diff --git a/cli/src/application/menu/start.rs b/cli/src/application/menu/start.rs index 96fba79c..f3747384 100644 --- a/cli/src/application/menu/start.rs +++ b/cli/src/application/menu/start.rs @@ -3,9 +3,7 @@ use std::{ vec, }; -use anyhow::{Error, Result}; use inquire::InquireError; -use simplelog::debug; use crate::application::profile::Profiles; @@ -24,10 +22,10 @@ enum Selection { impl Display for Selection { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Selection::LoadProfile => write!(f, "🖧 | Connect to existing controller"), - Selection::CreateProfile => write!(f, "+ | Add new controller"), - Selection::DeleteProfile => write!(f, "🗑 | Remove existing controller"), - Selection::Exit => write!(f, "✖ | Close application"), + Selection::LoadProfile => write!(f, "Connect to existing controller"), + Selection::CreateProfile => write!(f, "Add new controller"), + Selection::DeleteProfile => write!(f, "Remove existing controller"), + Selection::Exit => write!(f, "Close application"), } } } @@ -56,9 +54,11 @@ impl StartMenu { Selection::Exit => MenuResult::Exit, }, Err(error) => match error { - InquireError::OperationCanceled | InquireError::OperationInterrupted => MenuResult::Exit, - _ => MenuResult::Error(error.into()) - } + InquireError::OperationCanceled | InquireError::OperationInterrupted => { + MenuResult::Exit + } + _ => MenuResult::Failed(error.into()), + }, } } } diff --git a/cli/src/application/network.rs b/cli/src/application/network.rs index 0dc459ba..ea81d3cf 100644 --- a/cli/src/application/network.rs +++ b/cli/src/application/network.rs @@ -1,18 +1,14 @@ use std::fmt::Display; use anyhow::Result; -use proto::{ - admin_service_client::AdminServiceClient, - cloudlet_management::CloudletValue, - deployment_management::DeploymentValue, - resource_management::{ - DeleteResourceRequest, ResourceCategory, ResourceStatus, SetResourceStatusRequest, - }, - transfer_management::{ - transfer_target_value::TargetType, TransferTargetValue, TransferUsersRequest, - }, - unit_management::{unit_spec::Retention, SimpleUnitValue, UnitValue}, - user_management::UserValue, +use proto::manage::{ + group, + manage_service_client::ManageServiceClient, + node, + resource::{self, set_req, DelReq, SetReq}, + server::{self, DiskRetention}, + transfer::{self, target, TransferReq}, + user, }; use simplelog::warn; use tonic::{transport::Channel, Request}; @@ -24,9 +20,17 @@ use super::profile::Profile; #[allow(clippy::all)] pub mod proto { - use tonic::include_proto; + pub mod manage { + use tonic::include_proto; - include_proto!("manage"); + include_proto!("manage"); + } + + pub mod common { + use tonic::include_proto; + + include_proto!("common"); + } } pub struct EstablishedConnection { @@ -44,7 +48,7 @@ pub struct CloudConnection { //tls_config: Option, /* Client */ - client: Option>, + client: Option>, } impl CloudConnection { @@ -64,7 +68,7 @@ impl CloudConnection { let mut client = Self::from_profile(profile); client.connect().await?; - let protocol = match client.get_protocol_version().await { + let protocol = match client.get_proto_ver().await { Ok(version) => version, Err(error) => { warn!("⚠ Failed to get protocol version: {}", error); @@ -80,7 +84,7 @@ impl CloudConnection { } pub async fn connect(&mut self) -> Result<()> { - self.client = Some(AdminServiceClient::connect(self.address.to_string()).await?); + self.client = Some(ManageServiceClient::connect(self.address.to_string()).await?); Ok(()) } @@ -91,17 +95,13 @@ impl CloudConnection { Ok(()) } - pub async fn set_resource_status(&mut self, request: SetResourceStatusRequest) -> Result<()> { + pub async fn set_resource(&mut self, request: SetReq) -> Result<()> { let request = self.create_request(request); - self.client - .as_mut() - .unwrap() - .set_resource_status(request) - .await?; + self.client.as_mut().unwrap().set_resource(request).await?; Ok(()) } - pub async fn delete_resource(&mut self, request: DeleteResourceRequest) -> Result<()> { + pub async fn delete_resource(&mut self, request: DelReq) -> Result<()> { let request = self.create_request(request); self.client .as_mut() @@ -111,149 +111,141 @@ impl CloudConnection { Ok(()) } - pub async fn get_drivers(&mut self) -> Result> { + pub async fn get_plugins(&mut self) -> Result> { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_drivers(request) + .get_plugins(request) .await? .into_inner() - .drivers) + .plugins) } - pub async fn create_cloudlet(&mut self, cloudlet: CloudletValue) -> Result<()> { - let request = self.create_request(cloudlet); - self.client - .as_mut() - .unwrap() - .create_cloudlet(request) - .await?; + pub async fn create_node(&mut self, node: node::Item) -> Result<()> { + let request = self.create_request(node); + self.client.as_mut().unwrap().create_node(request).await?; Ok(()) } - pub async fn get_cloudlet(&mut self, name: &str) -> Result { + pub async fn get_node(&mut self, name: &str) -> Result { let request = self.create_request(name.to_string()); Ok(self .client .as_mut() .unwrap() - .get_cloudlet(request) + .get_node(request) .await? .into_inner()) } - pub async fn get_cloudlets(&mut self) -> Result> { + pub async fn get_nodes(&mut self) -> Result> { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_cloudlets(request) + .get_nodes(request) .await? .into_inner() - .cloudlets) + .nodes) } - pub async fn create_deployment(&mut self, deployment: DeploymentValue) -> Result<()> { - let request = self.create_request(deployment); - self.client - .as_mut() - .unwrap() - .create_deployment(request) - .await?; + pub async fn create_group(&mut self, group: group::Item) -> Result<()> { + let request = self.create_request(group); + self.client.as_mut().unwrap().create_group(request).await?; Ok(()) } - pub async fn get_deployment(&mut self, name: &str) -> Result { + pub async fn get_group(&mut self, name: &str) -> Result { let request = self.create_request(name.to_string()); Ok(self .client .as_mut() .unwrap() - .get_deployment(request) + .get_group(request) .await? .into_inner()) } - pub async fn get_deployments(&mut self) -> Result> { + pub async fn get_groups(&mut self) -> Result> { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_deployments(request) + .get_groups(request) .await? .into_inner() - .deployments) + .groups) } - pub async fn get_unit(&mut self, uuid: &str) -> Result { + pub async fn get_server(&mut self, uuid: &str) -> Result { let request = self.create_request(uuid.to_string()); Ok(self .client .as_mut() .unwrap() - .get_unit(request) + .get_server(request) .await? .into_inner()) } - pub async fn get_units(&mut self) -> Result> { + pub async fn get_servers(&mut self) -> Result> { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_units(request) + .get_servers(request) .await? .into_inner() - .units) + .servers) } - pub async fn get_protocol_version(&mut self) -> Result { + pub async fn get_users(&mut self) -> Result> { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_protocol_version(request) + .get_users(request) .await? - .into_inner()) + .into_inner() + .users) + } + + pub async fn transfer_users(&mut self, request: TransferReq) -> Result<()> { + let request = self.create_request(request); + self.client + .as_mut() + .unwrap() + .transfer_users(request) + .await?; + Ok(()) } - pub async fn get_controller_version(&mut self) -> Result { + pub async fn get_proto_ver(&mut self) -> Result { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_controller_version(request) + .get_proto_ver(request) .await? .into_inner()) } - pub async fn get_users(&mut self) -> Result> { + pub async fn get_ctrl_ver(&mut self) -> Result { let request = self.create_request(()); Ok(self .client .as_mut() .unwrap() - .get_users(request) + .get_ctrl_ver(request) .await? - .into_inner() - .users) - } - - pub async fn transfer_users(&mut self, request: TransferUsersRequest) -> Result<()> { - let request = self.create_request(request); - self.client - .as_mut() - .unwrap() - .transfer_users(request) - .await?; - Ok(()) + .into_inner()) } fn create_request(&self, data: T) -> Request { @@ -268,58 +260,72 @@ impl CloudConnection { } } -impl Display for Retention { +impl Display for DiskRetention { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Retention::Temporary => write!(f, "Temporary"), - Retention::Permanent => write!(f, "Permanent"), + DiskRetention::Temporary => write!(f, "Temporary"), + DiskRetention::Permanent => write!(f, "Permanent"), } } } -impl Display for UserValue { +impl Display for user::Item { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.name, self.id) + } +} + +impl Display for server::Short { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} ({})", self.name, self.uuid) + write!(f, "{} ({})", self.name, self.id) } } -impl Display for TargetType { +impl Display for target::Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TargetType::Unit => write!(f, "Unit"), - TargetType::Deployment => write!(f, "Deployment"), - TargetType::Fallback => write!(f, "Fallback"), + target::Type::Server => write!(f, "Server"), + target::Type::Group => write!(f, "Group"), + target::Type::Fallback => write!(f, "Fallback"), } } } -impl Display for TransferTargetValue { +impl Display for transfer::Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match TargetType::try_from(self.target_type) + match target::Type::try_from(self.r#type) .expect("There is something wrong with the target type") { - TargetType::Unit => write!(f, "Unit ({})", self.target.as_ref().unwrap()), - TargetType::Deployment => write!(f, "Deployment ({})", self.target.as_ref().unwrap()), - TargetType::Fallback => write!(f, "Fallback"), + target::Type::Server => write!( + f, + "Server ({})", + self.target.as_ref().unwrap_or(&String::from("None")) + ), + target::Type::Group => write!( + f, + "Group ({})", + self.target.as_ref().unwrap_or(&String::from("None")) + ), + target::Type::Fallback => write!(f, "Fallback"), } } } -impl Display for ResourceCategory { +impl Display for resource::Category { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ResourceCategory::Cloudlet => write!(f, "Cloudlet"), - ResourceCategory::Deployment => write!(f, "Deployment"), - ResourceCategory::Unit => write!(f, "Unit"), + resource::Category::Node => write!(f, "Node"), + resource::Category::Group => write!(f, "Group"), + resource::Category::Server => write!(f, "Server"), } } } -impl Display for ResourceStatus { +impl Display for set_req::Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ResourceStatus::Active => write!(f, "Active"), - ResourceStatus::Inactive => write!(f, "Inactive"), + set_req::Status::Active => write!(f, "Active"), + set_req::Status::Inactive => write!(f, "Inactive"), } } } diff --git a/cli/src/application/profile.rs b/cli/src/application/profile.rs index 1c013187..d46ea7db 100644 --- a/cli/src/application/profile.rs +++ b/cli/src/application/profile.rs @@ -4,8 +4,8 @@ use std::{ }; use anyhow::{anyhow, Result}; -use common::config::{LoadFromTomlFile, SaveToTomlFile}; -use loading::Loading; +use common::{config::SaveToTomlFile, file::for_each_content_toml}; +use simplelog::debug; use stored::StoredProfile; use url::Url; @@ -18,77 +18,24 @@ pub struct Profiles { } impl Profiles { - fn new() -> Self { - Profiles { profiles: vec![] } - } + pub fn init() -> Result { + debug!("Loading profiles..."); + let mut profiles = vec![]; - pub fn load_all() -> Self { - let progress = Loading::default(); - progress.text("Loading profiles"); - - let mut profiles = Self::new(); - let profiles_directory = Storage::get_profiles_folder(); - if !profiles_directory.exists() { - if let Err(error) = fs::create_dir_all(&profiles_directory) { - progress.warn(format!( - "Failed to create deployments directory: {}", - &error - )); - return profiles; - } + let directory = Storage::profiles_folder(); + if !directory.exists() { + fs::create_dir_all(&directory)?; } - let entries = match fs::read_dir(&profiles_directory) { - Ok(entries) => entries, - Err(error) => { - progress.warn(format!("Failed to read deployments directory: {}", &error)); - return profiles; - } - }; - - for entry in entries { - let entry = match entry { - Ok(entry) => entry, - Err(error) => { - progress.warn(format!( - "Failed to read entry in profiles directory: {}", - &error - )); - continue; - } - }; - - let path = entry.path(); - if path.is_dir() { - continue; - } - - let id: String = match path.file_stem() { - Some(name) => name.to_string_lossy().to_string(), - None => continue, - }; - - let profile = match StoredProfile::from_file(&path) { - Ok(profile) => profile, - Err(error) => { - progress.warn(format!( - "Failed to load profile from file '{}': {}", - path.display(), - &error - )); - continue; - } - }; - - progress.text(format!("Loading profile '{}'", id)); - let profile = Profile::from(&id, &profile); - - profiles.add_profile(profile); + for (_, _, name, value) in + for_each_content_toml::(&directory, "Failed to read profile from file")? + { + debug!("Loaded profile {}", name); + profiles.push(Profile::from(&name, &value)); } - progress.success(format!("Loaded {} profile(s)", profiles.profiles.len())); - progress.end(); - profiles + debug!("Loaded {} profile(s)", profiles.len()); + Ok(Self { profiles }) } pub fn create_profile(&mut self, profile: &Profile) -> Result<()> { @@ -164,7 +111,7 @@ impl Profile { } fn delete_file(&self) -> Result<()> { - let file_path = Storage::get_profile_file(&self.id); + let file_path = Storage::profile_file(&self.id); if file_path.exists() { fs::remove_file(file_path)?; } @@ -177,7 +124,7 @@ impl Profile { authorization: self.authorization.clone(), url: self.url.clone(), }; - stored_profile.save(&Storage::get_profile_file(&self.id), true) + stored_profile.save(&Storage::profile_file(&self.id), true) } pub fn compute_id(name: &str) -> String { diff --git a/cli/src/main.rs b/cli/src/main.rs index fa96606e..c3baa27a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -20,10 +20,10 @@ async fn main() { async fn run() -> Result<()> { let args = Arguments::parse(); - CloudInit::init_logging(args.debug, true, Storage::get_latest_log_file()); + CloudInit::init_logging(args.debug, true, Storage::latest_log_file()); CloudInit::print_ascii_art("Atomic Cloud CLI", &VERSION, &AUTHORS); - let mut cli = Cli::new().await; + let mut cli = Cli::new().await?; cli.start().await } } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index fcbcdb59..83182803 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -19,24 +19,24 @@ pub struct Storage; impl Storage { /* Base */ - pub fn get_cli_folder() -> PathBuf { + pub fn cli_folder() -> PathBuf { dirs::config_dir() .expect("Failed to get config directory for current user") .join(CLI_DIRECTORY) } /* Logs */ - pub fn get_latest_log_file() -> PathBuf { - Storage::get_cli_folder() + pub fn latest_log_file() -> PathBuf { + Storage::cli_folder() .join(LOGS_DIRECTORY) .join(LATEST_LOG_FILE) } /* Profiles */ - pub fn get_profiles_folder() -> PathBuf { - Storage::get_cli_folder().join(PROFILES_DIRECTORY) + pub fn profiles_folder() -> PathBuf { + Storage::cli_folder().join(PROFILES_DIRECTORY) } - pub fn get_profile_file(name: &str) -> PathBuf { - Storage::get_profiles_folder().join(format!("{}.toml", name)) + pub fn profile_file(name: &str) -> PathBuf { + Storage::profiles_folder().join(format!("{}.toml", name)) } } diff --git a/controller/src/application.rs b/controller/src/application.rs index 65416a6b..456cf908 100644 --- a/controller/src/application.rs +++ b/controller/src/application.rs @@ -24,10 +24,10 @@ use user::manager::UserManager; use crate::{config::Config, network::NetworkStack, task::Task}; pub mod auth; -pub mod server; mod group; mod node; mod plugin; +pub mod server; mod user; const TICK_RATE: u64 = 20; diff --git a/controller/src/application/node.rs b/controller/src/application/node.rs index a18bf564..f56f29a3 100644 --- a/controller/src/application/node.rs +++ b/controller/src/application/node.rs @@ -69,7 +69,7 @@ pub struct Capabilities { #[getset(get = "pub")] memory: Option, #[getset(get = "pub")] - max_allocations: Option, + max_servers: Option, #[getset(get = "pub")] child: Option, } diff --git a/controller/src/application/plugin/runtime/wasm.rs b/controller/src/application/plugin/runtime/wasm.rs index c9885605..d01989ca 100644 --- a/controller/src/application/plugin/runtime/wasm.rs +++ b/controller/src/application/plugin/runtime/wasm.rs @@ -161,7 +161,7 @@ impl From<&Capabilities> for bridge::Capabilities { fn from(val: &Capabilities) -> Self { bridge::Capabilities { memory: *val.memory(), - max_allocations: *val.max_allocations(), + max_servers: *val.max_servers(), child: val.child().as_ref().map(|value| value.to_string()), } } diff --git a/controller/src/application/server/manager.rs b/controller/src/application/server/manager.rs index 6a819a38..e2d69aa3 100644 --- a/controller/src/application/server/manager.rs +++ b/controller/src/application/server/manager.rs @@ -51,7 +51,7 @@ impl ServerManager { } pub fn resolve_server(&self, uuid: &Uuid) -> Option { - self.servers.get(&uuid).map(|server| server.id.clone()) + self.servers.get(uuid).map(|server| server.id.clone()) } pub fn schedule_start(&mut self, request: StartRequest) { diff --git a/controller/src/application/user.rs b/controller/src/application/user.rs index 31d6b26b..1f788f90 100644 --- a/controller/src/application/user.rs +++ b/controller/src/application/user.rs @@ -15,4 +15,4 @@ pub struct User { pub enum CurrentServer { Connected(NameAndUuid), Transfering(Transfer), -} \ No newline at end of file +} diff --git a/controller/src/application/user/manager.rs b/controller/src/application/user/manager.rs index 7a2c16cc..f505f115 100644 --- a/controller/src/application/user/manager.rs +++ b/controller/src/application/user/manager.rs @@ -65,12 +65,20 @@ impl UserManager { } user.server = CurrentServer::Connected(server.id().clone()); } else { - info!("User {}[{}] connected to server {}", name, uuid.to_string(), server.id()); - self.users.insert(uuid, User { + info!( + "User {}[{}] connected to server {}", name, + uuid.to_string(), + server.id() + ); + self.users.insert( uuid, - server: CurrentServer::Connected(server.id().clone()), - }); + User { + name, + uuid, + server: CurrentServer::Connected(server.id().clone()), + }, + ); } } diff --git a/controller/src/main.rs b/controller/src/main.rs index 10658413..24026a84 100644 --- a/controller/src/main.rs +++ b/controller/src/main.rs @@ -3,7 +3,7 @@ use anyhow::Result; use application::Controller; use clap::{ArgAction, Parser}; -use common::{error::CloudError, init::CloudInit}; +use common::{error::FancyError, init::CloudInit}; use config::Config; use simplelog::info; use storage::Storage; @@ -23,7 +23,7 @@ pub const AUTHORS: [&str; 1] = ["HttpRafa"]; #[tokio::main] async fn main() { if let Err(error) = run().await { - CloudError::print_fancy(&error, true); + FancyError::print_fancy(&error, true); } async fn run() -> Result<()> { diff --git a/controller/src/network.rs b/controller/src/network.rs index 5100f67b..d609c41e 100644 --- a/controller/src/network.rs +++ b/controller/src/network.rs @@ -3,7 +3,7 @@ use std::{net::SocketAddr, sync::Arc}; use anyhow::Result; use auth::AuthInterceptor; use client::ClientServiceImpl; -use common::error::CloudError; +use common::error::FancyError; use manage::ManageServiceImpl; use proto::{ client::client_service_server::ClientServiceServer, @@ -43,7 +43,7 @@ impl NetworkStack { let task = spawn(async move { if let Err(error) = run(bind, auth, queue, receiver).await { - CloudError::print_fancy(&error, false); + FancyError::print_fancy(&error, false); } }); diff --git a/controller/src/network/client.rs b/controller/src/network/client.rs index aa22d316..07f9a5c5 100644 --- a/controller/src/network/client.rs +++ b/controller/src/network/client.rs @@ -6,7 +6,11 @@ use tokio_stream::wrappers::ReceiverStream; use tonic::{async_trait, Request, Response, Status}; use uuid::Uuid; -use crate::{application::{Controller, TaskSender}, task::{BoxedAny, Task}, VERSION}; +use crate::{ + application::TaskSender, + task::Task, + VERSION, +}; use super::proto::client::{ self, @@ -140,4 +144,4 @@ impl ClientService for ClientServiceImpl { async fn get_ctrl_ver(&self, _request: Request<()>) -> Result, Status> { Ok(Response::new(format!("{}", VERSION))) } -} \ No newline at end of file +} diff --git a/controller/src/network/client/beat.rs b/controller/src/network/client/beat.rs index f5536034..13933756 100644 --- a/controller/src/network/client/beat.rs +++ b/controller/src/network/client/beat.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use tonic::{async_trait, Status}; +use tonic::async_trait; use uuid::Uuid; use crate::{ diff --git a/controller/src/network/client/health.rs b/controller/src/network/client/health.rs index 9017cf4b..41daff48 100644 --- a/controller/src/network/client/health.rs +++ b/controller/src/network/client/health.rs @@ -3,7 +3,11 @@ use tonic::async_trait; use uuid::Uuid; use crate::{ - application::{server::{manager::StopRequest, State}, Controller}, task::{BoxedAny, GenericTask, Task} + application::{ + server::{manager::StopRequest, State}, + Controller, + }, + task::{BoxedAny, GenericTask, Task}, }; pub struct SetRunningTask { @@ -33,7 +37,9 @@ impl GenericTask for RequestStopTask { Some(server) => server, None => return Task::new_link_error(), }; - controller.servers.schedule_stop(StopRequest::new(None, server)); + controller + .servers + .schedule_stop(StopRequest::new(None, server)); Task::new_empty() } } diff --git a/controller/src/network/client/ready.rs b/controller/src/network/client/ready.rs index 38c0577b..d04fa0ea 100644 --- a/controller/src/network/client/ready.rs +++ b/controller/src/network/client/ready.rs @@ -22,4 +22,4 @@ impl GenericTask for SetReadyTask { server.set_ready(self.ready); Task::new_empty() } -} \ No newline at end of file +} diff --git a/controller/src/network/client/reset.rs b/controller/src/network/client/reset.rs index afe38228..6e8f16bc 100644 --- a/controller/src/network/client/reset.rs +++ b/controller/src/network/client/reset.rs @@ -16,4 +16,4 @@ impl GenericTask for ResetTask { async fn run(&mut self, _controller: &mut Controller) -> Result { todo!() } -} \ No newline at end of file +} diff --git a/controller/src/network/client/user.rs b/controller/src/network/client/user.rs index 46f6e6a7..976cb0ef 100644 --- a/controller/src/network/client/user.rs +++ b/controller/src/network/client/user.rs @@ -25,7 +25,9 @@ impl GenericTask for UserConnectedTask { Some(server) => server, None => return Task::new_link_error(), }; - controller.users.user_connected(server, self.name.clone(), self.uuid.clone()); + controller + .users + .user_connected(server, self.name.clone(), self.uuid); todo!() } } diff --git a/controller/src/task.rs b/controller/src/task.rs index b34ed2b7..59ad42a1 100644 --- a/controller/src/task.rs +++ b/controller/src/task.rs @@ -1,7 +1,7 @@ use std::any::{type_name, Any}; use anyhow::{anyhow, Result}; -use common::error::CloudError; +use common::error::FancyError; use simplelog::debug; use tokio::sync::oneshot::{channel, Sender}; use tonic::{async_trait, Request, Status}; @@ -34,7 +34,7 @@ impl Task { match Task::create::(queue, task(request, data)).await { Ok(value) => value, Err(error) => { - CloudError::print_fancy(&error, false); + FancyError::print_fancy(&error, false); Err(Status::internal(error.to_string())) } } @@ -71,15 +71,15 @@ impl Task { pub fn new_ok(value: T) -> Result { Ok(Box::new(value)) } - + pub fn new_empty() -> Result { Self::new_ok(()) } - + pub fn new_err(value: Status) -> Result { Ok(Box::new(value)) } - + pub fn new_link_error() -> Result { Self::new_err(Status::failed_precondition("Not linked")) } @@ -88,4 +88,4 @@ impl Task { #[async_trait] pub trait GenericTask { async fn run(&mut self, controller: &mut Controller) -> Result; -} \ No newline at end of file +} diff --git a/protocol/grpc/client/channel.proto b/protocol/grpc/client/channel.proto index dbf6afc9..9d8d1abf 100644 --- a/protocol/grpc/client/channel.proto +++ b/protocol/grpc/client/channel.proto @@ -4,7 +4,7 @@ package client; message Channel { message Msg { - string name = 1; + string channel = 1; uint32 id = 2; string data = 3; uint64 timestamp = 4; // timestamp (e.g. epoch time) diff --git a/protocol/grpc/client/transfer.proto b/protocol/grpc/client/transfer.proto index edfb2e61..23024ab4 100644 --- a/protocol/grpc/client/transfer.proto +++ b/protocol/grpc/client/transfer.proto @@ -10,7 +10,7 @@ message Transfer { FALLBACK = 2; } Type type = 1; - string target = 2; + optional string target = 2; } message TransferReq { repeated string ids = 1; diff --git a/protocol/grpc/manage/group.proto b/protocol/grpc/manage/group.proto index e61a8b21..b66b3c38 100644 --- a/protocol/grpc/manage/group.proto +++ b/protocol/grpc/manage/group.proto @@ -8,9 +8,9 @@ message Group { message Item { string name = 1; repeated string nodes = 2; - Constraints cons = 3; - Scaling scaling = 4; - Server.Resources res = 5; + Constraints constraints = 3; + optional Scaling scaling = 4; + Server.Resources resources = 5; Server.Spec spec = 6; } message Constraints { diff --git a/protocol/grpc/manage/node.proto b/protocol/grpc/manage/node.proto index f8cbe2af..d42447bc 100644 --- a/protocol/grpc/manage/node.proto +++ b/protocol/grpc/manage/node.proto @@ -6,9 +6,10 @@ message Node { message Item { string name = 1; string plugin = 2; - uint32 memory = 3; - uint32 max = 4; - string child = 5; + + optional uint32 memory = 3; + optional uint32 max = 4; + optional string child = 5; string ctrlAddr = 6; } message List { diff --git a/protocol/grpc/manage/resource.proto b/protocol/grpc/manage/resource.proto index 1d2f69e3..6db5f76d 100644 --- a/protocol/grpc/manage/resource.proto +++ b/protocol/grpc/manage/resource.proto @@ -13,12 +13,12 @@ message Resource { ACTIVE = 0; INACTIVE = 1; } - Category cat = 1; + Category category = 1; string id = 2; Status status = 3; } message DelReq { - Category cat = 1; + Category category = 1; string id = 2; } } \ No newline at end of file diff --git a/protocol/grpc/manage/server.proto b/protocol/grpc/manage/server.proto index 585981ca..06c3902a 100644 --- a/protocol/grpc/manage/server.proto +++ b/protocol/grpc/manage/server.proto @@ -19,7 +19,7 @@ message Server { string id = 2; optional string group = 3; string node = 4; - Allocation alloc = 5; + Allocation allocation = 5; uint32 users = 6; string token = 7; State state = 8; @@ -27,11 +27,11 @@ message Server { } message Allocation { repeated common.Address ports = 1; - Resources res = 2; + Resources resources = 2; Spec spec = 3; } message Resources { - uint32 mem = 1; + uint32 memory = 1; uint32 swap = 2; uint32 cpu = 3; uint32 io = 4; @@ -43,22 +43,22 @@ message Server { uint32 maxPlayers = 2; repeated common.KeyValue settings = 3; repeated common.KeyValue env = 4; - Retention retention = 5; - Fallback fallback = 6; + optional DiskRetention retention = 5; + optional Fallback fallback = 6; } message Fallback { bool enabled = 1; int32 prio = 2; } - enum Retention { - TEMP = 0; - PERM = 1; + enum DiskRetention { + TEMPORARY = 0; + PERMANENT = 1; } enum State { - START = 0; - PREP = 1; - RESTART = 2; - RUN = 3; - STOP = 4; + STARTING = 0; + PREPARING = 1; + RESTARTING = 2; + RUNNING = 3; + STOPPING = 4; } } \ No newline at end of file diff --git a/protocol/grpc/manage/transfer.proto b/protocol/grpc/manage/transfer.proto index ad24b9ee..8d3b63fc 100644 --- a/protocol/grpc/manage/transfer.proto +++ b/protocol/grpc/manage/transfer.proto @@ -10,7 +10,7 @@ message Transfer { FALLBACK = 2; } Type type = 1; - string target = 2; + optional string target = 2; } message TransferReq { repeated string ids = 1; diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 58b8018e..28cd85dc 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -110,7 +110,7 @@ interface bridge { record capabilities { memory: option, - max-allocations: option, + max-servers: option, child: option, } From e563e2bfe9fb72a4f818b05a681bec7be73dd40d Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:10:00 +0100 Subject: [PATCH 31/74] Renaming --- .../menu/connection/group/create_group.rs | 50 +++++++++---------- .../menu/connection/group/get_group.rs | 20 ++++---- .../menu/connection/node/create_node.rs | 16 +++--- .../connection/resource/delete_resource.rs | 12 ++--- .../menu/connection/server/get_server.rs | 8 +-- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/cli/src/application/menu/connection/group/create_group.rs b/cli/src/application/menu/connection/group/create_group.rs index 221966cc..c81cbd0b 100644 --- a/cli/src/application/menu/connection/group/create_group.rs +++ b/cli/src/application/menu/connection/group/create_group.rs @@ -47,16 +47,16 @@ impl CreateGroupMenu { progress.end(); match Self::collect_group(&data) { - Ok(deployment) => { + Ok(group) => { let progress = Loading::default(); progress.text(format!( "Creating group \"{}\" on the controller \"{}\"...", - deployment.name, profile.name + group.name, profile.name )); - match connection.client.create_group(deployment).await { + match connection.client.create_group(group).await { Ok(_) => { - progress.success("Group created successfully 👍. Remember to set the deployment to active, or the controller won't start units."); + progress.success("Group created successfully 👍. Remember to set the group to active, or the controller won't start servers."); progress.end(); MenuResult::Success } @@ -108,13 +108,13 @@ impl CreateGroupMenu { } fn get_group_name(used_names: Vec) -> Result { - Text::new("What would you like to name this deployment?") + Text::new("What would you like to name this group?") .with_help_message("Examples: lobby, mode-xyz") .with_validator(ValueRequiredValidator::default()) .with_validator(move |name: &str| { if used_names.contains(&name.to_string()) { Ok(Validation::Invalid( - "A deployment with this name already exists".into(), + "A group with this name already exists".into(), )) } else { Ok(Validation::Valid) @@ -124,29 +124,29 @@ impl CreateGroupMenu { } fn get_nodes(nodes: Vec) -> Result, InquireError> { - MultiSelect::new("What nodes should this deployment use?", nodes).prompt() + MultiSelect::new("What nodes should this group use?", nodes).prompt() } fn collect_constraints() -> Result { let min = MenuUtils::parsed_value( - "What is the minimum number of units that should always be online?", + "What is the minimum number of servers that should always be online?", "Example: 1", "Please enter a valid number", )?; let max = MenuUtils::parsed_value( - "What is the maximum number of units that should always be online?", + "What is the maximum number of servers that should always be online?", "Example: 10", "Please enter a valid number", )?; - let prio = MenuUtils::parsed_value("How important is this deployment compared to others? (This refers to one tick of the controller)", "Example: 0", "Please enter a valid number")?; + let prio = MenuUtils::parsed_value("How important is this group compared to others? (This refers to one tick of the controller)", "Example: 0", "Please enter a valid number")?; Ok(Constraints { min, max, prio }) } fn collect_scaling() -> Result { - let start_threshold = MenuUtils::parsed_value::("At what percentage (0-100) of the max player count should the controller start a new unit?", "Example: 50", "Please enter a valid number")? / 100.0; + let start_threshold = MenuUtils::parsed_value::("At what percentage (0-100) of the max player count should the controller start a new server?", "Example: 50", "Please enter a valid number")? / 100.0; let stop_empty = - MenuUtils::confirm("Should the controller stop units that are empty for too long?")?; + MenuUtils::confirm("Should the controller stop servers that are empty for too long?")?; Ok(Scaling { start_threshold, @@ -156,32 +156,32 @@ impl CreateGroupMenu { fn collect_resources() -> Result { let memory = MenuUtils::parsed_value( - "How much memory should each unit have?", + "How much memory should each server have?", "Example: 2048", "Please enter a valid number", )?; let swap = MenuUtils::parsed_value( - "How much swap space should each unit have?", + "How much swap space should each server have?", "Example: 256", "Please enter a valid number", )?; let cpu = MenuUtils::parsed_value( - "How much CPU power should each unit have? (100 = one core)", + "How much CPU power should each server have? (100 = one core)", "Example: 500", "Please enter a valid number", )?; let io = MenuUtils::parsed_value( - "How many I/O operations should each unit be allowed to perform?", + "How many I/O operations should each server be allowed to perform?", "Example: 500", "Please enter a valid number", )?; let disk = MenuUtils::parsed_value( - "How much disk space should each unit use?", + "How much disk space should each server use?", "Example: 2048", "Please enter a valid number", )?; let ports = MenuUtils::parsed_value( - "How many addresses/ports should each unit have?", + "How many addresses/ports should each server have?", "Example: 5", "Please enter a valid number", )?; @@ -197,26 +197,26 @@ impl CreateGroupMenu { } fn collect_specification() -> Result { - let img = MenuUtils::text("Which image should the unit use?", "Example: ubuntu:latest")?; + let img = MenuUtils::text("Which image should the server use?", "Example: ubuntu:latest")?; let max_players = MenuUtils::parsed_value( - "What is the maximum number of players per unit?", + "What is the maximum number of players per server?", "Example: 20", "Please enter a valid number", )?; let settings = MenuUtils::parsed_value::( - "What settings should the controller pass to the driver when starting a unit?", + "What settings should the controller pass to the driver when starting a server?", "Format: key=value,key=value,key=value,...", "Please check your syntax. Something seems wrong.", )? .key_values; let env = MenuUtils::parsed_value::( - "What environment variables should the controller pass to the driver when starting a unit?", + "What environment variables should the controller pass to the driver when starting a server?", "Format: key=value,key=value,key=value,...", "Please check your syntax something is wrong", )? .key_values; let retention = MenuUtils::select_no_help( - "Should the unit's disk be retained after the unit stops?", + "Should the server's disk be retained after the server stops?", vec![DiskRetention::Temporary, DiskRetention::Permanent], )?; let fallback = Self::collect_fallback()?; @@ -233,9 +233,9 @@ impl CreateGroupMenu { fn collect_fallback() -> Result { let enabled = - MenuUtils::confirm("Should the controller treat these units as fallback units?")?; + MenuUtils::confirm("Should the controller treat these servers as fallback servers?")?; let prio = MenuUtils::parsed_value( - "What is the priority of this fallback deployment?", + "What is the priority of this fallback group?", "Example: 0", "Please enter a valid number", )?; diff --git a/cli/src/application/menu/connection/group/get_group.rs b/cli/src/application/menu/connection/group/get_group.rs index 6b8b7801..87ba2616 100644 --- a/cli/src/application/menu/connection/group/get_group.rs +++ b/cli/src/application/menu/connection/group/get_group.rs @@ -18,32 +18,32 @@ impl GetGroupMenu { ) -> MenuResult { let progress = Loading::default(); progress.text(format!( - "Fetching all available deployments from the controller \"{}\"...", + "Fetching all available groups from the controller \"{}\"...", profile.name )); match connection.client.get_groups().await { - Ok(deployments) => { + Ok(groups) => { progress.success("Data retrieved successfully 👍"); progress.end(); match MenuUtils::select_no_help( - "Select a deployment to view more details:", - deployments, + "Select a group to view more details:", + groups, ) { - Ok(deployment) => { + Ok(group) => { let progress = Loading::default(); progress.text(format!( - "Fetching details for deployment \"{}\" from controller \"{}\"...", - deployment, profile.name + "Fetching details for group \"{}\" from controller \"{}\"...", + group, profile.name )); - match connection.client.get_group(&deployment).await { - Ok(deployment_details) => { + match connection.client.get_group(&group).await { + Ok(group_details) => { progress.success("Group details retrieved successfully 👍"); progress.end(); - Self::display_details(&deployment_details); + Self::display_details(&group_details); MenuResult::Success } Err(error) => { diff --git a/cli/src/application/menu/connection/node/create_node.rs b/cli/src/application/menu/connection/node/create_node.rs index 6c09efb4..56eaaef0 100644 --- a/cli/src/application/menu/connection/node/create_node.rs +++ b/cli/src/application/menu/connection/node/create_node.rs @@ -35,7 +35,7 @@ impl CreateNodeMenu { progress.success("Data retrieved successfully 👍"); progress.end(); - match Self::collect_cloudlet(&data) { + match Self::collect_node(&data) { Ok(node) => { let progress = Loading::default(); progress.text(format!( @@ -45,7 +45,7 @@ impl CreateNodeMenu { match connection.client.create_node(node).await { Ok(_) => { - progress.success("Cloudlet created successfully 👍. Remember to set the cloudlet to active, or the controller won't start units."); + progress.success("Cloudlet created successfully 👍. Remember to set the node to active, or the controller won't start servers."); progress.end(); MenuResult::Success } @@ -78,7 +78,7 @@ impl CreateNodeMenu { Ok(Data { nodes, plugins }) } - fn collect_cloudlet(data: &Data) -> Result { + fn collect_node(data: &Data) -> Result { let name = Self::get_node_name(data.nodes.clone())?; let plugin = MenuUtils::select("Which plugin should the controller use to communicate with the backend of this node?", "This is essential for the controller to know how to communicate with the backend of this node. For example, is it a Pterodactyl node or a simple Docker host?", data.plugins.to_vec())?; let child = Self::get_child_node()?; @@ -101,13 +101,13 @@ impl CreateNodeMenu { } fn get_node_name(used_names: Vec) -> Result { - Text::new("What would you like to name this cloudlet?") + Text::new("What would you like to name this node?") .with_help_message("Examples: hetzner-01, home-01, local-01") .with_validator(ValueRequiredValidator::default()) .with_validator(move |name: &str| { if used_names.contains(&name.to_string()) { Ok(Validation::Invalid( - "A cloudlet with this name already exists".into(), + "A node with this name already exists".into(), )) } else { Ok(Validation::Valid) @@ -118,11 +118,11 @@ impl CreateNodeMenu { fn get_memory_limit() -> Result, InquireError> { match MenuUtils::confirm( - "Would you like to limit the amount of memory the controller can use on this cloudlet?", + "Would you like to limit the amount of memory the controller can use on this node?", )? { false => Ok(None), true => Ok(Some(MenuUtils::parsed_value( - "How much memory should the controller be allowed to use on this cloudlet?", + "How much memory should the controller be allowed to use on this node?", "Example: 1024", "Please enter a valid number", )?)), @@ -130,7 +130,7 @@ impl CreateNodeMenu { } fn get_servers_limit() -> Result, InquireError> { - match MenuUtils::confirm("Would you like to limit the number of servers the controller can start on this cloudlet?")? + match MenuUtils::confirm("Would you like to limit the number of servers the controller can start on this node?")? { false => Ok(None), true => Ok(Some(MenuUtils::parsed_value("How many servers should the controller be allowed to start on this node?", "Example: 15", "Please enter a valid number")?)) diff --git a/cli/src/application/menu/connection/resource/delete_resource.rs b/cli/src/application/menu/connection/resource/delete_resource.rs index 2e107369..d6f72e2d 100644 --- a/cli/src/application/menu/connection/resource/delete_resource.rs +++ b/cli/src/application/menu/connection/resource/delete_resource.rs @@ -92,27 +92,27 @@ impl DeleteResourceMenu { )?; match category { Category::Node => { - let cloudlet = + let node = MenuUtils::select_no_help("Select the node to delete", data.nodes.clone())?; Ok(DelReq { category: category as i32, - id: cloudlet, + id: node, }) } Category::Group => { - let deployment = + let group = MenuUtils::select_no_help("Select the group to delete", data.groups.clone())?; Ok(DelReq { category: category as i32, - id: deployment, + id: group, }) } Category::Server => { - let unit = + let server = MenuUtils::select_no_help("Select the server to delete", data.servers.clone())?; Ok(DelReq { category: category as i32, - id: unit.id, + id: server.id, }) } } diff --git a/cli/src/application/menu/connection/server/get_server.rs b/cli/src/application/menu/connection/server/get_server.rs index e1d566c9..f346de61 100644 --- a/cli/src/application/menu/connection/server/get_server.rs +++ b/cli/src/application/menu/connection/server/get_server.rs @@ -35,10 +35,10 @@ impl GetServerMenu { )); match connection.client.get_server(&server.id).await { - Ok(unit) => { + Ok(server) => { progress.success("Details retrieved successfully 👍"); progress.end(); - Self::display_details(&unit); + Self::display_details(&server); MenuResult::Success } Err(error) => { @@ -81,7 +81,7 @@ impl GetServerMenu { info!(" - {}:{}", port.host, port.port); } if let Some(resources) = allocation.resources { - info!(" Resources per unit: "); + info!(" Resources per server: "); info!(" Memory: {} MiB", resources.memory); info!(" Swap: {} MiB", resources.swap); info!( @@ -98,7 +98,7 @@ impl GetServerMenu { resources.ports ); } else { - warn!(" Resources per unit: None"); + warn!(" Resources per server: None"); } if let Some(spec) = &allocation.spec { info!(" Specification: "); From 59fb00965db6c61d11bfbbde219c2a64b336253f Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:11:24 +0100 Subject: [PATCH 32/74] Renaming --- controller/src/application/group.rs | 2 +- controller/src/application/plugin/runtime/wasm/node.rs | 4 ++-- controller/src/application/server.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controller/src/application/group.rs b/controller/src/application/group.rs index e69ce7b7..3894396e 100644 --- a/controller/src/application/group.rs +++ b/controller/src/application/group.rs @@ -107,7 +107,7 @@ impl Group { let id = self .id_allocator .allocate() - .expect("We reached the maximum unit count. Wow this is a lot of units"); + .expect("We reached the maximum server count. Wow this is a lot of servers"); let request = StartRequest::new( None, self.constraints.priority, diff --git a/controller/src/application/plugin/runtime/wasm/node.rs b/controller/src/application/plugin/runtime/wasm/node.rs index e7fce954..ee65c15b 100644 --- a/controller/src/application/plugin/runtime/wasm/node.rs +++ b/controller/src/application/plugin/runtime/wasm/node.rs @@ -231,7 +231,7 @@ impl From<&Server> for bridge::Server { bridge::Server { name: val.id().name().clone(), uuid: val.id().uuid().to_string(), - deployment: val.group().clone(), + group: val.group().clone(), allocation: val.allocation().into(), token: val.token().clone(), } @@ -242,7 +242,7 @@ impl From<&StartRequest> for bridge::ServerProposal { fn from(val: &StartRequest) -> Self { bridge::ServerProposal { name: val.id().name().clone(), - deployment: val.group().clone(), + group: val.group().clone(), resources: val.resources().into(), spec: val.spec().into(), } diff --git a/controller/src/application/server.rs b/controller/src/application/server.rs index f0d109f9..dfb2bcaf 100644 --- a/controller/src/application/server.rs +++ b/controller/src/application/server.rs @@ -116,7 +116,7 @@ pub struct Heart { #[derive(Default)] pub struct Flags { - /* Required for the deployment system */ + /* Required for the group system */ pub stop: Option, } From 1f9e933b2cb8cc1a81e65630958031aaf9a80916 Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:13:13 +0100 Subject: [PATCH 33/74] More renaming --- docs/index.md | 4 ++-- docs/installation/docker.md | 4 ++-- docs/usage/controller/index.md | 2 +- docs/usage/controller/plugins/index.md | 2 +- docs/usage/controller/templates/index.md | 4 ++-- protocol/wit/plugin.wit | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index fc0902bc..2e1f0eff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,13 +4,13 @@ Welcome to the **Atomic Cloud** project! Please note that the cloud is currently ## Documentation Status 🚧 -This documentation is a work in progress. We apologize for any grammatical errors or incomplete sections. We welcome contributions from the community—if you’d like to help improve the documentation, please consider submitting a Pull Request. +This documentation is a work in progress. We apologize for any grammatical errors or incomplete sections. We welcome contributions from the commservery—if you’d like to help improve the documentation, please consider submitting a Pull Request. ## Installation Atomic Cloud comprises two main components: the **Controller** and the **CLI**. Below are the recommended installation options: -- **Docker Installation**: We recommend installing via a Docker image for ease of deployment. Follow the instructions in our [Docker Installation Guide](installation/docker.md). +- **Docker Installation**: We recommend installing via a Docker image for ease of group. Follow the instructions in our [Docker Installation Guide](installation/docker.md). - **Standard Installation**: If you prefer not to use Docker, refer to our [Standard Installation Guide](installation/normal.md). ### Controller diff --git a/docs/installation/docker.md b/docs/installation/docker.md index 2b61697e..84a870af 100644 --- a/docs/installation/docker.md +++ b/docs/installation/docker.md @@ -21,8 +21,8 @@ services: - ./logs:/app/logs - ./auth:/app/auth - ./configs:/app/configs - - ./cloudlets:/app/cloudlets - - ./deployments:/app/deployments + - ./nodes:/app/nodes + - ./groups:/app/groups - ./drivers:/app/drivers ``` diff --git a/docs/usage/controller/index.md b/docs/usage/controller/index.md index e57bff9c..0ce766be 100644 --- a/docs/usage/controller/index.md +++ b/docs/usage/controller/index.md @@ -1,6 +1,6 @@ # What is the Controller? -The **Controller** is a vital component of the Atomic Cloud platform. It acts as the central management unit by: +The **Controller** is a vital component of the Atomic Cloud platform. It acts as the central management server by: - **Overseeing Nodes:** It monitors and manages the nodes in the cloud infrastructure. diff --git a/docs/usage/controller/plugins/index.md b/docs/usage/controller/plugins/index.md index 3c8b160c..3d011974 100644 --- a/docs/usage/controller/plugins/index.md +++ b/docs/usage/controller/plugins/index.md @@ -2,4 +2,4 @@ The Controller is built with extensibility at its core, supporting a wide array of plugin platforms. At present, the primary platform for developing plugins is **WebAssembly (WASM)**, leveraging the **WASI Preview 2** specification. This modern approach delivers a flexible and high-performance environment for plugin development. -If you prefer not to use a WASM plugin or have alternative ideas, you are welcome to implement your own plugin mechanism. We encourage community contributions—feel free to submit a pull request with your enhancements or alternative implementations. \ No newline at end of file +If you prefer not to use a WASM plugin or have alternative ideas, you are welcome to implement your own plugin mechanism. We encourage commservery contributions—feel free to submit a pull request with your enhancements or alternative implementations. \ No newline at end of file diff --git a/docs/usage/controller/templates/index.md b/docs/usage/controller/templates/index.md index b7a1048a..2983c602 100644 --- a/docs/usage/controller/templates/index.md +++ b/docs/usage/controller/templates/index.md @@ -19,7 +19,7 @@ Templates work by describing the desired state of your resources in a structured These define the actual components to be created. In a Minecraft server template, resources could include the server jar file, configuration files, mods, or plugins required to run the server. 3. **Outputs**: - These are values returned after the deployment is complete, such as resource IDs, connection URLs, or IP addresses that you might need to connect to your server. + These are values returned after the group is complete, such as resource IDs, connection URLs, or IP addresses that you might need to connect to your server. ### Example Template Structure @@ -79,7 +79,7 @@ startupScripts = [ ] [outputs] -# Output values after deployment +# Output values after group serverUrl = "http://{parameters.serverName}.atomiccloud.example.com:{parameters.serverPort}" ``` diff --git a/protocol/wit/plugin.wit b/protocol/wit/plugin.wit index 28cd85dc..15a277f8 100644 --- a/protocol/wit/plugin.wit +++ b/protocol/wit/plugin.wit @@ -157,7 +157,7 @@ interface bridge { record server-proposal { name: string, - deployment: option, + group: option, resources: resources, spec: spec, } @@ -165,7 +165,7 @@ interface bridge { record server { name: string, uuid: uuid, - deployment: option, + group: option, allocation: allocation, token: string, } From 588266ae82ba0c0eb16266598a0a05b0057f03ae Mon Sep 17 00:00:00 2001 From: HttpRafa <60099368+HttpRafa@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:15:44 +0100 Subject: [PATCH 34/74] More renaming --- .github/docker/entrypoint.sh | 6 +- .github/workflows/release.yml | 14 +- Makefile | 12 +- .../menu/connection/group/create_group.rs | 4 +- .../cloud/api/objects/LocalCloudUnit.java | 20 +-- .../atomic/cloud/api/transfer/Transfers.java | 20 +-- clients/jvm/build.gradle.kts | 2 +- .../common/connection/CloudConnection.java | 18 +-- .../common/transfer/TransferManager.java | 10 +- .../cloud/paper/command/DisposeCommand.java | 6 +- .../cloud/paper/command/SendCommand.java | 6 +- .../argument/TransferTargetArgument.java | 46 +++--- .../paper/listener/PlayerEventsListener.java | 2 +- .../cloud/paper/transfer/TransferHandler.java | 2 +- clients/wrapper/src/application/network.rs | 6 +- clients/wrapper/src/application/user.rs | 4 +- .../application/plugin/runtime/wasm/config.rs | 2 +- .../application/plugin/runtime/wasm/init.rs | 4 +- docker-compose.yml | 6 +- docs/features/backend.md | 2 +- docs/installation/docker.md | 6 +- plugins/local/src/driver.rs | 38 ++--- plugins/local/src/driver/cloudlet.rs | 116 +++++++-------- plugins/local/src/driver/cloudlet/unit.rs | 30 ++-- plugins/local/src/driver/template.rs | 2 +- plugins/local/src/log.rs | 8 +- plugins/local/src/main.rs | 8 +- plugins/local/src/storage.rs | 22 +-- plugins/pterodactyl/src/driver.rs | 34 ++--- plugins/pterodactyl/src/driver/backend.rs | 6 +- .../src/driver/backend/allocation.rs | 2 +- .../pterodactyl/src/driver/backend/server.rs | 2 +- plugins/pterodactyl/src/driver/cloudlet.rs | 132 +++++++++--------- plugins/pterodactyl/src/log.rs | 8 +- plugins/pterodactyl/src/main.rs | 8 +- 35 files changed, 307 insertions(+), 307 deletions(-) diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index bb167834..705da55e 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh # Define the destination folder -DEST_FOLDER="drivers/" +DEST_FOLDER="plugins/" # Create the destination folder if it does not exist if [ ! -d "$DEST_FOLDER" ]; then @@ -30,14 +30,14 @@ download_wasm() { # Check if the environment variable PTERODACTYL is set to true if [ "$PTERODACTYL" = "true" ]; then - WASM_URL="https://github.com/HttpRafa/atomic-cloud/releases/latest/download/pterodactyl-driver.wasm" + WASM_URL="https://github.com/HttpRafa/atomic-cloud/releases/latest/download/pterodactyl-plugin.wasm" WASM_FILE="$DEST_FOLDER/pterodactyl.wasm" download_wasm $WASM_URL $WASM_FILE fi # Check if the environment variable LOCAL is set to true if [ "$LOCAL" = "true" ]; then - WASM_URL="https://github.com/HttpRafa/atomic-cloud/releases/latest/download/local-driver.wasm" + WASM_URL="https://github.com/HttpRafa/atomic-cloud/releases/latest/download/local-plugin.wasm" WASM_FILE="$DEST_FOLDER/local.wasm" download_wasm $WASM_URL $WASM_FILE fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index abbd3919..f1f147a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,13 +73,13 @@ jobs: git checkout -b $BRANCH git push -u origin $BRANCH sed -i "s/client_version=0\\.0\\.0-nightly/client_version=${VERSION}/g" clients/jvm/gradle.properties - sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" drivers/pterodactyl/Cargo.toml - sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" drivers/local/Cargo.toml + sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" plugins/pterodactyl/Cargo.toml + sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" plugins/local/Cargo.toml sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" clients/wrapper/Cargo.toml sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" controller/Cargo.toml sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" common/Cargo.toml sed -i "s/version = \"0\\.0\\.0-nightly\"/version = \"${VERSION}\"/g" cli/Cargo.toml - git add clients/jvm/gradle.properties drivers/pterodactyl/Cargo.toml drivers/local/Cargo.toml clients/wrapper/Cargo.toml controller/Cargo.toml common/Cargo.toml cli/Cargo.toml + git add clients/jvm/gradle.properties plugins/pterodactyl/Cargo.toml plugins/local/Cargo.toml clients/wrapper/Cargo.toml controller/Cargo.toml common/Cargo.toml cli/Cargo.toml git commit -m "ci(release): bump version" git push @@ -114,8 +114,8 @@ jobs: cp ./target/x86_64-unknown-linux-gnu/release/wrapper wrapper-linux-x86_64 cp ./target/x86_64-pc-windows-gnu/release/wrapper.exe wrapper-windows-x86_64.exe - cp ./target/wasm32-wasip2/release/pterodactyl.wasm pterodactyl-driver.wasm - cp ./target/wasm32-wasip2/release/local.wasm local-driver.wasm + cp ./target/wasm32-wasip2/release/pterodactyl.wasm pterodactyl-plugin.wasm + cp ./target/wasm32-wasip2/release/local.wasm local-plugin.wasm cp $(find ./clients/jvm/paper/build -name "*-all.jar") paper-client.jar @@ -134,6 +134,6 @@ jobs: cli-windows-x86_64.exe wrapper-linux-x86_64 wrapper-windows-x86_64.exe - pterodactyl-driver.wasm - local-driver.wasm + pterodactyl-plugin.wasm + local-plugin.wasm paper-client.jar diff --git a/Makefile b/Makefile index 9cc2e83d..f3b1238e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: run run-controller build build-controller build-wrapper build-drivers clean fix +.PHONY: run run-controller build build-controller build-wrapper build-plugins clean fix # Configuration WASM_RUSTFLAGS = -Z wasi-exec-model=reactor @@ -7,7 +7,7 @@ WASM_TARGET = wasm32-wasip2 # Directories RUN_DIR = run OLD_RUN_DIR = run.old -DRIVER_DIR = $(RUN_DIR)/drivers/wasm +DRIVER_DIR = $(RUN_DIR)/plugins/wasm # Arguments CONTROLLER_ARGS = "--debug" @@ -42,7 +42,7 @@ fix: cargo clippy --fix --allow-dirty --allow-staged --all-targets --all-features ## Build target -build: build-controller build-cli build-wrapper build-drivers +build: build-controller build-cli build-wrapper build-plugins ## Run target run: run-controller @@ -67,12 +67,12 @@ build-cli: build-wrapper: cargo build -p wrapper --all-features --release -## Build drivers target -build-drivers: +## Build plugins target +build-plugins: $(SETENV) RUSTFLAGS="$(WASM_RUSTFLAGS)" cargo build -p pterodactyl --target $(WASM_TARGET) --release cargo build -p local --target $(WASM_TARGET) --release -# Create driver directory if it doesn't exist +# Create plugin directory if it doesn't exist $(DRIVER_DIR): $(MKDIR) $(DRIVER_DIR) \ No newline at end of file diff --git a/cli/src/application/menu/connection/group/create_group.rs b/cli/src/application/menu/connection/group/create_group.rs index c81cbd0b..ef9b3ced 100644 --- a/cli/src/application/menu/connection/group/create_group.rs +++ b/cli/src/application/menu/connection/group/create_group.rs @@ -204,13 +204,13 @@ impl CreateGroupMenu { "Please enter a valid number", )?; let settings = MenuUtils::parsed_value::( - "What settings should the controller pass to the driver when starting a server?", + "What settings should the controller pass to the plugin when starting a server?", "Format: key=value,key=value,key=value,...", "Please check your syntax. Something seems wrong.", )? .key_values; let env = MenuUtils::parsed_value::( - "What environment variables should the controller pass to the driver when starting a server?", + "What environment variables should the controller pass to the plugin when starting a server?", "Format: key=value,key=value,key=value,...", "Please check your syntax something is wrong", )? diff --git a/clients/jvm/api/src/main/java/io/atomic/cloud/api/objects/LocalCloudUnit.java b/clients/jvm/api/src/main/java/io/atomic/cloud/api/objects/LocalCloudUnit.java index 5c7ec3eb..1bec706a 100644 --- a/clients/jvm/api/src/main/java/io/atomic/cloud/api/objects/LocalCloudUnit.java +++ b/clients/jvm/api/src/main/java/io/atomic/cloud/api/objects/LocalCloudUnit.java @@ -5,24 +5,24 @@ public interface LocalCloudUnit { /** - * Shut down this unit instance. - * This will stop the unit and transfer all players to a different unit. - * How the unit is shutdown depends on the disk retention policy. - * If the unit is marked as permanent, it will not be deleted. - * If the unit is not marked as permanent, it will be killed and deleted. - * @return a future to be completed once the unit has been shut down + * Shut down this server instance. + * This will stop the server and transfer all players to a different server. + * How the server is shutdown depends on the disk retention policy. + * If the server is marked as permanent, it will not be deleted. + * If the server is not marked as permanent, it will be killed and deleted. + * @return a future to be completed once the server has been shut down */ CompletableFuture shutdown(); /** - * Mark this unit as ready - * @return a future to be completed once the unit has been marked as ready + * Mark this server as ready + * @return a future to be completed once the server has been marked as ready */ CompletableFuture markReady(); /** - * Mark this unit as not ready - * @return a future to be completed once the unit has been marked as not ready + * Mark this server as not ready + * @return a future to be completed once the server has been marked as not ready */ CompletableFuture markNotReady(); } diff --git a/clients/jvm/api/src/main/java/io/atomic/cloud/api/transfer/Transfers.java b/clients/jvm/api/src/main/java/io/atomic/cloud/api/transfer/Transfers.java index 97a2e243..dd0e233d 100644 --- a/clients/jvm/api/src/main/java/io/atomic/cloud/api/transfer/Transfers.java +++ b/clients/jvm/api/src/main/java/io/atomic/cloud/api/transfer/Transfers.java @@ -8,24 +8,24 @@ public interface Transfers { /** - * Sends a request to the controller to transfer the specified users to a new unit. - * @param unit The target unit to which the users should be transferred. - * @param userUUID A list of user UUIDs to transfer. These users must belong to the current unit; otherwise, the controller will return an error. + * Sends a request to the controller to transfer the specified users to a new server. + * @param server The target server to which the users should be transferred. + * @param userUUID A list of user UUIDs to transfer. These users must belong to the current server; otherwise, the controller will return an error. * @return The number of users successfully transferred. */ - CompletableFuture transferUsersToUnit(CloudUnit unit, UUID... userUUID); + CompletableFuture transferUsersToUnit(CloudUnit server, UUID... userUUID); /** - * Sends a request to the controller to transfer the specified users to a new unit on specific deployment. - * @param deployment The target deployment to which the users should be transferred. - * @param userUUID A list of user UUIDs to transfer. These users must belong to the current unit; otherwise, the controller will return an error. + * Sends a request to the controller to transfer the specified users to a new server on specific group. + * @param group The target group to which the users should be transferred. + * @param userUUID A list of user UUIDs to transfer. These users must belong to the current server; otherwise, the controller will return an error. * @return The number of users successfully transferred. */ - CompletableFuture transferUsersToDeployment(CloudDeployment deployment, UUID... userUUID); + CompletableFuture transferUsersToDeployment(CloudDeployment group, UUID... userUUID); /** - * Sends a request to the controller to transfer the specified users to a new unit marked as fallback. - * @param userUUID A list of user UUIDs to transfer. These users must belong to the current unit; otherwise, the controller will return an error. + * Sends a request to the controller to transfer the specified users to a new server marked as fallback. + * @param userUUID A list of user UUIDs to transfer. These users must belong to the current server; otherwise, the controller will return an error. * @return The number of users successfully transferred. */ CompletableFuture transferUsersToFallback(UUID... userUUID); diff --git a/clients/jvm/build.gradle.kts b/clients/jvm/build.gradle.kts index 021445be..2f6534d3 100644 --- a/clients/jvm/build.gradle.kts +++ b/clients/jvm/build.gradle.kts @@ -80,7 +80,7 @@ allprojects { sourceSets { main { proto { - srcDir("$rootDir/../../protocol/grpc/unit/") + srcDir("$rootDir/../../protocol/grpc/server/") } } } diff --git a/clients/jvm/common/src/main/java/io/atomic/cloud/common/connection/CloudConnection.java b/clients/jvm/common/src/main/java/io/atomic/cloud/common/connection/CloudConnection.java index ccba7b2c..999bf772 100644 --- a/clients/jvm/common/src/main/java/io/atomic/cloud/common/connection/CloudConnection.java +++ b/clients/jvm/common/src/main/java/io/atomic/cloud/common/connection/CloudConnection.java @@ -4,7 +4,7 @@ import com.google.protobuf.StringValue; import com.google.protobuf.UInt32Value; import io.atomic.cloud.common.cache.CachedObject; -import io.atomic.cloud.grpc.unit.*; +import io.atomic.cloud.grpc.server.*; import io.grpc.CallCredentials; import io.grpc.ManagedChannelBuilder; import io.grpc.Metadata; @@ -32,8 +32,8 @@ public class CloudConnection { // Cache values private final CachedObject protocolVersion = new CachedObject<>(); private final CachedObject controllerVersion = new CachedObject<>(); - private final CachedObject unitsInfo = new CachedObject<>(); - private final CachedObject deploymentsInfo = new CachedObject<>(); + private final CachedObject serversInfo = new CachedObject<>(); + private final CachedObject groupsInfo = new CachedObject<>(); public void connect() { var channel = ManagedChannelBuilder.forAddress(this.address.getHost(), this.address.getPort()); @@ -122,7 +122,7 @@ public void subscribeToChannel(String channel, StreamObserver getUnitsNow() { - var cached = this.unitsInfo.getValue(); + var cached = this.serversInfo.getValue(); if (cached.isEmpty()) { this.getUnits(); // Request value from controller } @@ -130,20 +130,20 @@ public Optional getUnitsNow() { } public CompletableFuture getUnits() { - var cached = this.unitsInfo.getValue(); + var cached = this.serversInfo.getValue(); if (cached.isPresent()) { return CompletableFuture.completedFuture(cached.get()); } var observer = new StreamObserverImpl(); this.client.getUnits(Empty.getDefaultInstance(), observer); return observer.future().thenApply((value) -> { - this.unitsInfo.setValue(value); + this.serversInfo.setValue(value); return value; }); } public Optional getDeploymentsNow() { - var cached = this.deploymentsInfo.getValue(); + var cached = this.groupsInfo.getValue(); if (cached.isEmpty()) { this.getDeployments(); // Request value from controller } @@ -151,14 +151,14 @@ public Optional getDeploymentsNow( } public CompletableFuture getDeployments() { - var cached = this.deploymentsInfo.getValue(); + var cached = this.groupsInfo.getValue(); if (cached.isPresent()) { return CompletableFuture.completedFuture(cached.get()); } var observer = new StreamObserverImpl(); this.client.getDeployments(Empty.getDefaultInstance(), observer); return observer.future().thenApply((value) -> { - this.deploymentsInfo.setValue(value); + this.groupsInfo.setValue(value); return value; }); } diff --git a/clients/jvm/common/src/main/java/io/atomic/cloud/common/transfer/TransferManager.java b/clients/jvm/common/src/main/java/io/atomic/cloud/common/transfer/TransferManager.java index f43dcfbe..8a0e1bfb 100644 --- a/clients/jvm/common/src/main/java/io/atomic/cloud/common/transfer/TransferManager.java +++ b/clients/jvm/common/src/main/java/io/atomic/cloud/common/transfer/TransferManager.java @@ -5,7 +5,7 @@ import io.atomic.cloud.api.objects.CloudUnit; import io.atomic.cloud.api.transfer.Transfers; import io.atomic.cloud.common.connection.CloudConnection; -import io.atomic.cloud.grpc.unit.TransferManagement; +import io.atomic.cloud.grpc.server.TransferManagement; import java.util.UUID; import java.util.concurrent.CompletableFuture; import lombok.AllArgsConstructor; @@ -17,11 +17,11 @@ public class TransferManager implements Transfers { private final CloudConnection connection; @Override - public CompletableFuture transferUsersToUnit(@NotNull CloudUnit unit, UUID @NotNull ... userUUID) { + public CompletableFuture transferUsersToUnit(@NotNull CloudUnit server, UUID @NotNull ... userUUID) { var builder = TransferManagement.TransferUsersRequest.newBuilder(); builder.setTarget(TransferManagement.TransferTargetValue.newBuilder() .setTargetType(TransferManagement.TransferTargetValue.TargetType.UNIT) - .setTarget(unit.uuid().toString()) + .setTarget(server.uuid().toString()) .build()); for (UUID uuid : userUUID) { builder.addUserUuids(uuid.toString()); @@ -31,11 +31,11 @@ public CompletableFuture transferUsersToUnit(@NotNull CloudUnit unit, U @Override public CompletableFuture transferUsersToDeployment( - @NotNull CloudDeployment deployment, UUID @NotNull ... userUUID) { + @NotNull CloudDeployment group, UUID @NotNull ... userUUID) { var builder = TransferManagement.TransferUsersRequest.newBuilder(); builder.setTarget(TransferManagement.TransferTargetValue.newBuilder() .setTargetType(TransferManagement.TransferTargetValue.TargetType.DEPLOYMENT) - .setTarget(deployment.name()) + .setTarget(group.name()) .build()); for (UUID uuid : userUUID) { builder.addUserUuids(uuid.toString()); diff --git a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/DisposeCommand.java b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/DisposeCommand.java index d5c791ed..d9e2fdfa 100644 --- a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/DisposeCommand.java +++ b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/DisposeCommand.java @@ -1,7 +1,7 @@ package io.atomic.cloud.paper.command; import com.mojang.brigadier.Command; -import io.atomic.cloud.grpc.unit.TransferManagement; +import io.atomic.cloud.grpc.server.TransferManagement; import io.atomic.cloud.paper.CloudPlugin; import io.atomic.cloud.paper.permission.Permissions; import io.papermc.paper.command.brigadier.Commands; @@ -19,9 +19,9 @@ public static void register(@NotNull Commands commands) { var sender = context.getSource().getSender(); var connection = CloudPlugin.INSTANCE.connection(); - sender.sendRichMessage("Marking unit as not ready"); + sender.sendRichMessage("Marking server as not ready"); connection.markNotReady().thenRun(() -> { - sender.sendRichMessage("Requesting to transfer all users to new units..."); + sender.sendRichMessage("Requesting to transfer all users to new servers..."); connection.transferUsers(TransferManagement.TransferUsersRequest.newBuilder() .addAllUserUuids(Bukkit.getOnlinePlayers().stream() .map(item -> item.getUniqueId().toString()) diff --git a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/SendCommand.java b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/SendCommand.java index 9038045b..d40a9be1 100644 --- a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/SendCommand.java +++ b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/SendCommand.java @@ -1,7 +1,7 @@ package io.atomic.cloud.paper.command; import com.mojang.brigadier.Command; -import io.atomic.cloud.grpc.unit.TransferManagement; +import io.atomic.cloud.grpc.server.TransferManagement; import io.atomic.cloud.paper.CloudPlugin; import io.atomic.cloud.paper.command.argument.TransferTargetArgument; import io.atomic.cloud.paper.permission.Permissions; @@ -62,10 +62,10 @@ public static void register(@NotNull Commands commands) { return "fallback"; } case TransferManagement.TransferTargetValue.TargetType.UNIT -> { - return "unit:" + target.getTarget(); + return "server:" + target.getTarget(); } case TransferManagement.TransferTargetValue.TargetType.DEPLOYMENT -> { - return "deployment:" + target.getTarget(); + return "group:" + target.getTarget(); } } return "unknown"; diff --git a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/argument/TransferTargetArgument.java b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/argument/TransferTargetArgument.java index 1e7fa48e..01746afd 100644 --- a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/argument/TransferTargetArgument.java +++ b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/command/argument/TransferTargetArgument.java @@ -7,9 +7,9 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import io.atomic.cloud.grpc.unit.DeploymentInformation; -import io.atomic.cloud.grpc.unit.TransferManagement; -import io.atomic.cloud.grpc.unit.UnitInformation; +import io.atomic.cloud.grpc.server.DeploymentInformation; +import io.atomic.cloud.grpc.server.TransferManagement; +import io.atomic.cloud.grpc.server.UnitInformation; import io.atomic.cloud.paper.CloudPlugin; import io.papermc.paper.command.brigadier.MessageComponentSerializer; import io.papermc.paper.command.brigadier.argument.CustomArgumentType; @@ -40,27 +40,27 @@ public class TransferTargetArgument } var type = valueSplit[0]; var identifier = valueSplit[1]; - if (type.equalsIgnoreCase("unit")) { + if (type.equalsIgnoreCase("server")) { var cached = CloudPlugin.INSTANCE.connection().getUnitsNow(); - if (cached.isEmpty()) throw createException("Fetching available units..."); - var unit = cached.get().getUnitsList().stream() + if (cached.isEmpty()) throw createException("Fetching available servers..."); + var server = cached.get().getUnitsList().stream() .filter(item -> item.getName().equalsIgnoreCase(identifier)) .findFirst(); - if (unit.isEmpty()) throw createException("\"" + identifier + "\" does not exist"); + if (server.isEmpty()) throw createException("\"" + identifier + "\" does not exist"); return TransferManagement.TransferTargetValue.newBuilder() .setTargetType(TransferManagement.TransferTargetValue.TargetType.UNIT) - .setTarget(unit.get().getUuid()) + .setTarget(server.get().getUuid()) .build(); - } else if (type.equalsIgnoreCase("deployment")) { + } else if (type.equalsIgnoreCase("group")) { var cached = CloudPlugin.INSTANCE.connection().getDeploymentsNow(); - if (cached.isEmpty()) throw createException("Fetching available deployments..."); - var deployment = cached.get().getDeploymentsList().stream() + if (cached.isEmpty()) throw createException("Fetching available groups..."); + var group = cached.get().getDeploymentsList().stream() .filter(item -> item.equalsIgnoreCase(identifier)) .findFirst(); - if (deployment.isEmpty()) throw createException("\"" + identifier + "\" does not exist"); + if (group.isEmpty()) throw createException("\"" + identifier + "\" does not exist"); return TransferManagement.TransferTargetValue.newBuilder() .setTargetType(TransferManagement.TransferTargetValue.TargetType.DEPLOYMENT) - .setTarget(deployment.get()) + .setTarget(group.get()) .build(); } throw createException("Unknown transfer target type: " + type); @@ -74,25 +74,25 @@ public class TransferTargetArgument .getUnits() .thenCombine(CloudPlugin.INSTANCE.connection().getDeployments(), SuggestionsData::new) .thenCompose(response -> { - response.units + response.servers .getUnitsList() - .forEach(unit -> builder.suggest( - "unit:" + unit.getName(), + .forEach(server -> builder.suggest( + "server:" + server.getName(), MessageComponentSerializer.message() - .serialize(Component.text(unit.getUuid()) + .serialize(Component.text(server.getUuid()) .color(NamedTextColor.BLUE)))); - response.deployments + response.groups .getDeploymentsList() - .forEach(deployment -> builder.suggest( - "deployment:" + deployment, + .forEach(group -> builder.suggest( + "group:" + group, MessageComponentSerializer.message() .serialize( - Component.text(deployment).color(NamedTextColor.BLUE)))); + Component.text(group).color(NamedTextColor.BLUE)))); builder.suggest( "fallback", MessageComponentSerializer.message() .serialize(Component.text( - "This option will try to transfer all users to a fallback unit") + "This option will try to transfer all users to a fallback server") .color(NamedTextColor.BLUE))); return builder.buildFuture(); }); @@ -109,5 +109,5 @@ public class TransferTargetArgument } private record SuggestionsData( - UnitInformation.UnitListResponse units, DeploymentInformation.DeploymentListResponse deployments) {} + UnitInformation.UnitListResponse servers, DeploymentInformation.DeploymentListResponse groups) {} } diff --git a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/listener/PlayerEventsListener.java b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/listener/PlayerEventsListener.java index 7789e0f3..c6a38a19 100644 --- a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/listener/PlayerEventsListener.java +++ b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/listener/PlayerEventsListener.java @@ -1,6 +1,6 @@ package io.atomic.cloud.paper.listener; -import io.atomic.cloud.grpc.unit.UserManagement; +import io.atomic.cloud.grpc.server.UserManagement; import io.atomic.cloud.paper.CloudPlugin; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; diff --git a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/transfer/TransferHandler.java b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/transfer/TransferHandler.java index 051d8132..b3488ae4 100644 --- a/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/transfer/TransferHandler.java +++ b/clients/jvm/paper/src/main/java/io/atomic/cloud/paper/transfer/TransferHandler.java @@ -1,7 +1,7 @@ package io.atomic.cloud.paper.transfer; import io.atomic.cloud.common.connection.CloudConnection; -import io.atomic.cloud.grpc.unit.TransferManagement; +import io.atomic.cloud.grpc.server.TransferManagement; import io.atomic.cloud.paper.CloudPlugin; import io.grpc.stub.StreamObserver; import java.util.UUID; diff --git a/clients/wrapper/src/application/network.rs b/clients/wrapper/src/application/network.rs index 6cc4f41a..00d6d541 100644 --- a/clients/wrapper/src/application/network.rs +++ b/clients/wrapper/src/application/network.rs @@ -7,7 +7,7 @@ use url::Url; use proto::{ transfer_management::ResolvedTransferResponse, - unit_service_client::UnitServiceClient, + server_service_client::UnitServiceClient, user_management::{UserConnectedRequest, UserDisconnectedRequest}, }; use tonic::{transport::Channel, Request, Response, Status, Streaming}; @@ -16,7 +16,7 @@ use tonic::{transport::Channel, Request, Response, Status, Streaming}; pub mod proto { use tonic::include_proto; - include_proto!("unit"); + include_proto!("server"); } pub type CloudConnectionHandle = Arc; @@ -55,7 +55,7 @@ impl CloudConnection { token = Some(value); } else { error!( - "Missing UNIT_TOKEN environment variable. Please set it to the token of this unit" + "Missing UNIT_TOKEN environment variable. Please set it to the token of this server" ); exit(1); } diff --git a/clients/wrapper/src/application/user.rs b/clients/wrapper/src/application/user.rs index 647b10b6..76065f88 100644 --- a/clients/wrapper/src/application/user.rs +++ b/clients/wrapper/src/application/user.rs @@ -22,7 +22,7 @@ impl Users { } pub async fn handle_connect(&mut self, name: String, uuid: Uuid) { - info!("{} connected to unit", name); + info!("{} connected to server", name); if let Err(error) = self .connection @@ -39,7 +39,7 @@ impl Users { pub async fn handle_disconnect(&mut self, name: String) { if let Some(user) = self.users.remove(&name) { - info!("{} disconnected from unit", user.name); + info!("{} disconnected from server", user.name); if let Err(error) = self .connection diff --git a/controller/src/application/plugin/runtime/wasm/config.rs b/controller/src/application/plugin/runtime/wasm/config.rs index cdab85a7..0b6b6390 100644 --- a/controller/src/application/plugin/runtime/wasm/config.rs +++ b/controller/src/application/plugin/runtime/wasm/config.rs @@ -102,7 +102,7 @@ impl PluginsConfig { .find(|plugin| match Regex::new(&plugin.name) { Ok(regex) => regex.is_match(name), Err(error) => { - warn!("Failed to compile driver name regex: {}", error); + warn!("Failed to compile plugin name regex: {}", error); false } }) diff --git a/controller/src/application/plugin/runtime/wasm/init.rs b/controller/src/application/plugin/runtime/wasm/init.rs index 61818343..502c185e 100644 --- a/controller/src/application/plugin/runtime/wasm/init.rs +++ b/controller/src/application/plugin/runtime/wasm/init.rs @@ -58,7 +58,7 @@ pub async fn init_wasm_plugins( .await .unwrap_or_else(|error| { warn!( - "Failed to create configs directory for driver {}: {}", + "Failed to create configs directory for plugin {}: {}", name, error ) }); @@ -68,7 +68,7 @@ pub async fn init_wasm_plugins( .await .unwrap_or_else(|error| { warn!( - "Failed to create data directory for driver {}: {}", + "Failed to create data directory for plugin {}: {}", name, error ) }); diff --git a/docker-compose.yml b/docker-compose.yml index e5eab4eb..0752acab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,6 @@ services: - ./run/logs:/app/logs - ./run/auth:/app/auth - ./run/configs:/app/configs - - ./run/cloudlets:/app/cloudlets - - ./run/deployments:/app/deployments - - ./run/drivers:/app/drivers \ No newline at end of file + - ./run/nodes:/app/nodes + - ./run/groups:/app/groups + - ./run/plugins:/app/plugins \ No newline at end of file diff --git a/docs/features/backend.md b/docs/features/backend.md index c450e9a7..7eb813fa 100644 --- a/docs/features/backend.md +++ b/docs/features/backend.md @@ -1,6 +1,6 @@ # Modular Backend -Atomic Cloud is built on a versatile driver system that abstracts and streamlines the process of initiating servers. This modular approach allows the platform to support various backends, ensuring flexibility and scalability. Currently, Atomic Cloud supports several backend types, including: +Atomic Cloud is built on a versatile plugin system that abstracts and streamlines the process of initiating servers. This modular approach allows the platform to support various backends, ensuring flexibility and scalability. Currently, Atomic Cloud supports several backend types, including: - **Pterodactyl** - **Docker** diff --git a/docs/installation/docker.md b/docs/installation/docker.md index 84a870af..e96bb04e 100644 --- a/docs/installation/docker.md +++ b/docs/installation/docker.md @@ -15,15 +15,15 @@ services: ports: - "12892:12892" environment: - - PTERODACTYL=true # Enable Pterodactyl driver installation - - LOCAL=true # Enable Local driver installation + - PTERODACTYL=true # Enable Pterodactyl plugin installation + - LOCAL=true # Enable Local plugin installation volumes: - ./logs:/app/logs - ./auth:/app/auth - ./configs:/app/configs - ./nodes:/app/nodes - ./groups:/app/groups - - ./drivers:/app/drivers + - ./plugins:/app/plugins ``` ## Step 2: Start the Container diff --git a/plugins/local/src/driver.rs b/plugins/local/src/driver.rs index 72947138..00ba0ee9 100644 --- a/plugins/local/src/driver.rs +++ b/plugins/local/src/driver.rs @@ -1,17 +1,17 @@ use std::{cell::UnsafeCell, fs, rc::Rc, sync::RwLock, time::Instant}; -use cloudlet::LocalCloudlet; +use node::LocalCloudlet; use common::{allocator::NumberAllocator, tick::TickResult}; use config::{Config, CLEANUP_TIMEOUT}; use template::Templates; use crate::{ - cloudlet::driver::{ + node::plugin::{ file::remove_dir_all, types::{ErrorMessage, ScopedErrors}, }, debug, error, - exports::cloudlet::driver::bridge::{ + exports::node::plugin::bridge::{ Capabilities, GenericCloudlet, GuestGenericCloudlet, GuestGenericDriver, Information, RemoteController, }, @@ -19,7 +19,7 @@ use crate::{ storage::Storage, }; -pub mod cloudlet; +pub mod node; mod config; mod template; @@ -41,8 +41,8 @@ pub struct Local { /* Templates */ templates: Rc>, - /* Cloudlets that this driver handles */ - cloudlets: RwLock>>, + /* Cloudlets that this plugin handles */ + nodes: RwLock>>, } impl Local { @@ -63,7 +63,7 @@ impl GuestGenericDriver for Local { config: UnsafeCell::new(None), port_allocator: UnsafeCell::new(None), templates: Rc::new(RwLock::new(Templates::new())), - cloudlets: RwLock::new(Vec::new()), + nodes: RwLock::new(Vec::new()), } } @@ -110,7 +110,7 @@ impl GuestGenericDriver for Local { } } - fn init_cloudlet( + fn init_node( &self, name: String, capabilities: Capabilities, @@ -128,21 +128,21 @@ impl GuestGenericDriver for Local { *wrapper.inner.port_allocator.get() = Some(self.get_port_allocator().clone()); *wrapper.inner.templates.get() = Some(self.templates.clone()); } - // Add cloudlet to cloudlets list - let mut cloudlets = self - .cloudlets + // Add node to nodes list + let mut nodes = self + .nodes .write() - .expect("Failed to get lock on cloudlets"); - cloudlets.push(wrapper.inner.clone()); + .expect("Failed to get lock on nodes"); + nodes.push(wrapper.inner.clone()); info!("Cloudlet {} was added", name); Ok(GenericCloudlet::new(wrapper)) } fn cleanup(&self) -> Result<(), ScopedErrors> { - let cloudlets = self - .cloudlets + let nodes = self + .nodes .read() - .expect("Failed to get lock on cloudlets"); + .expect("Failed to get lock on nodes"); info!("Starting cleanup process..."); let start_time = Instant::now(); let mut last_attempt = false; @@ -152,8 +152,8 @@ impl GuestGenericDriver for Local { last_attempt = true; } - let all_stopped = cloudlets.iter().try_fold(true, |all_stopped, cloudlet| { - match cloudlet.try_exit(last_attempt) { + let all_stopped = nodes.iter().try_fold(true, |all_stopped, node| { + match node.try_exit(last_attempt) { Ok(TickResult::Ok) => Ok(false), Ok(_) => Ok(all_stopped), Err(error) => Err(error), @@ -165,7 +165,7 @@ impl GuestGenericDriver for Local { } } - info!("All units should be stopped now. Removing temporary files..."); + info!("All servers should be stopped now. Removing temporary files..."); if let Err(error) = fs::remove_dir_all(Storage::get_temporary_folder()) { error!("Failed to remove temporary directory: {}", error); } diff --git a/plugins/local/src/driver/cloudlet.rs b/plugins/local/src/driver/cloudlet.rs index 4e3b0db3..c0e8cade 100644 --- a/plugins/local/src/driver/cloudlet.rs +++ b/plugins/local/src/driver/cloudlet.rs @@ -6,12 +6,12 @@ use std::{ use anyhow::Result; use common::{allocator::NumberAllocator, name::TimedName, tick::TickResult}; -use unit::LocalUnit; +use server::LocalUnit; use crate::{ - cloudlet::driver::types::{ErrorMessage, ScopedError, ScopedErrors}, + node::plugin::types::{ErrorMessage, ScopedError, ScopedErrors}, error, - exports::cloudlet::driver::bridge::{ + exports::node::plugin::bridge::{ Address, Capabilities, GuestGenericCloudlet, RemoteController, Retention, Unit, UnitProposal, }, @@ -21,17 +21,17 @@ use crate::{ use super::{config::Config, template::Templates, LocalCloudletWrapper}; -pub mod unit; +pub mod server; impl LocalCloudlet { pub fn tick(&self) -> Result<(), ScopedErrors> { - let mut units = self.get_units_mut(); + let mut servers = self.get_servers_mut(); let mut errors = ScopedErrors::new(); - units.retain_mut(|unit| match unit.tick() { + servers.retain_mut(|server| match server.tick() { Ok(result) => result == TickResult::Ok, Err(err) => { errors.push(ScopedError { - scope: unit.name.get_raw_name().to_string(), + scope: server.name.get_raw_name().to_string(), message: err.to_string(), }); true @@ -60,7 +60,7 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { controller, templates: UnsafeCell::new(None), port_allocator: UnsafeCell::new(None), - units: RwLock::new(vec![]), + servers: RwLock::new(vec![]), }), } } @@ -69,8 +69,8 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { self.inner.tick() } - fn allocate_addresses(&self, unit: UnitProposal) -> Result, ErrorMessage> { - let amount = unit.resources.addresses; + fn allocate_addresses(&self, server: UnitProposal) -> Result, ErrorMessage> { + let amount = server.resources.addresses; let mut ports = Vec::with_capacity(amount as usize); let mut allocator = self @@ -103,10 +103,10 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { } } - fn start_unit(&self, unit: Unit) { - let spec = &unit.allocation.spec; + fn start_server(&self, server: Unit) { + let spec = &server.allocation.spec; let name = - TimedName::new_no_identifier(&unit.name, spec.disk_retention == Retention::Permanent); + TimedName::new_no_identifier(&server.name, spec.disk_retention == Retention::Permanent); let template = match self .inner @@ -118,7 +118,7 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { Some(template) => template, None => { error!( - "Template {} not found for unit {}", + "Template {} not found for server {}", &spec.image, name.get_name() ); @@ -126,11 +126,11 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { } }; - let folder = Storage::get_unit_folder(&name, &spec.disk_retention); + let folder = Storage::get_server_folder(&name, &spec.disk_retention); if !folder.exists() { if let Err(err) = template.copy_to_folder(&folder) { error!( - "Failed to copy template for unit {}: {}", + "Failed to copy template for server {}: {}", name.get_name(), err ); @@ -138,10 +138,10 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { } } - let mut local_unit = LocalUnit::new(self, unit, &name, template); - if let Err(err) = local_unit.start() { + let mut local_server = LocalUnit::new(self, server, &name, template); + if let Err(err) = local_server.start() { error!( - "Failed to start unit {}: {}", + "Failed to start server {}: {}", name.get_raw_name(), err ); @@ -149,73 +149,73 @@ impl GuestGenericCloudlet for LocalCloudletWrapper { } info!( - "Successfully created child process for unit {}", + "Successfully created child process for server {}", name.get_raw_name() ); - self.inner.get_units_mut().push(local_unit); + self.inner.get_servers_mut().push(local_server); } - fn restart_unit(&self, unit: Unit) { - let mut units = self.inner.get_units_mut(); - if let Some(local_unit) = units + fn restart_server(&self, server: Unit) { + let mut servers = self.inner.get_servers_mut(); + if let Some(local_server) = servers .iter_mut() - .find(|u| u.name.get_raw_name() == unit.name) + .find(|u| u.name.get_raw_name() == server.name) { - if let Err(err) = local_unit.restart() { + if let Err(err) = local_server.restart() { error!( - "Failed to restart unit {}: {}", - unit.name, err + "Failed to restart server {}: {}", + server.name, err ); return; } info!( - "Child process of unit {} is restarting", - unit.name + "Child process of server {} is restarting", + server.name ); } else { - error!("Failed to restart unit {}: Unit was never started by this driver", unit.name); + error!("Failed to restart server {}: Unit was never started by this plugin", server.name); } } - fn stop_unit(&self, unit: Unit) { - let mut units = self.inner.get_units_mut(); - if let Some(local_unit) = units + fn stop_server(&self, server: Unit) { + let mut servers = self.inner.get_servers_mut(); + if let Some(local_server) = servers .iter_mut() - .find(|u| u.name.get_raw_name() == unit.name) + .find(|u| u.name.get_raw_name() == server.name) { - if unit.allocation.spec.disk_retention == Retention::Temporary { - if let Err(err) = local_unit.kill() { + if server.allocation.spec.disk_retention == Retention::Temporary { + if let Err(err) = local_server.kill() { error!( - "Failed to stop unit {}: {}", - unit.name, err + "Failed to stop server {}: {}", + server.name, err ); return; } info!( - "Child process of unit {} was killed", - unit.name + "Child process of server {} was killed", + server.name ); } else { - if let Err(err) = local_unit.stop() { + if let Err(err) = local_server.stop() { error!( - "Failed to stop unit {}: {}", - unit.name, err + "Failed to stop server {}: {}", + server.name, err ); return; } info!( - "Child process of unit {} is stopping", - unit.name + "Child process of server {} is stopping", + server.name ); } } else { - error!("Failed to stop unit {}: Unit was never started by this driver", unit.name); + error!("Failed to stop server {}: Unit was never started by this plugin", server.name); } } } pub struct LocalCloudlet { - /* Informations about the cloudlet */ + /* Informations about the node */ _name: String, pub config: UnsafeCell>>, controller: RemoteController, @@ -225,19 +225,19 @@ pub struct LocalCloudlet { /* Dynamic Resources */ pub port_allocator: UnsafeCell>>>>, - units: RwLock>, + servers: RwLock>, } impl LocalCloudlet { /* Dispose */ pub fn try_exit(&self, force: bool) -> Result { if force { - let mut units = self.get_units_mut(); + let mut servers = self.get_servers_mut(); let mut errors = ScopedErrors::new(); - for unit in units.iter_mut() { - if let Err(error) = unit.kill() { + for server in servers.iter_mut() { + if let Err(error) = server.kill() { errors.push(ScopedError { - scope: unit.name.get_raw_name().to_string(), + scope: server.name.get_raw_name().to_string(), message: error.to_string(), }); } @@ -248,7 +248,7 @@ impl LocalCloudlet { } match self.tick() { Ok(()) => { - if self.get_units().is_empty() { + if self.get_servers().is_empty() { Ok(TickResult::Drop) } else { Ok(TickResult::Ok) @@ -270,12 +270,12 @@ impl LocalCloudlet { // Safe as we are only borrowing the reference immutably unsafe { &*self.port_allocator.get() }.as_ref().unwrap() } - fn get_units(&self) -> RwLockReadGuard> { + fn get_servers(&self) -> RwLockReadGuard> { // Safe as we are only run on the same thread - self.units.read().unwrap() + self.servers.read().unwrap() } - fn get_units_mut(&self) -> RwLockWriteGuard> { + fn get_servers_mut(&self) -> RwLockWriteGuard> { // Safe as we are only run on the same thread - self.units.write().unwrap() + self.servers.write().unwrap() } } diff --git a/plugins/local/src/driver/cloudlet/unit.rs b/plugins/local/src/driver/cloudlet/unit.rs index c53895f8..f8117fc4 100644 --- a/plugins/local/src/driver/cloudlet/unit.rs +++ b/plugins/local/src/driver/cloudlet/unit.rs @@ -4,13 +4,13 @@ use anyhow::{anyhow, Result}; use common::{name::TimedName, tick::TickResult}; use crate::{ - cloudlet::driver::{ + node::plugin::{ file::remove_dir_all, process::{drop_process, kill_process, read_line_async, try_wait, StdReader}, types::{Directory, KeyValue}, }, - driver::{config::UNIT_STOP_TIMEOUT, template::Template, LocalCloudletWrapper}, - exports::cloudlet::driver::bridge::{Retention, Unit}, + plugin::{config::UNIT_STOP_TIMEOUT, template::Template, LocalCloudletWrapper}, + exports::node::plugin::bridge::{Retention, Unit}, info, storage::Storage, warn, @@ -30,7 +30,7 @@ pub enum UnitState { } pub struct LocalUnit { - pub unit: Unit, + pub server: Unit, pub state: UnitState, pub changed: Instant, pub pid: Option, @@ -43,33 +43,33 @@ pub struct LocalUnit { impl LocalUnit { pub fn new( node: &LocalCloudletWrapper, - mut unit: Unit, + mut server: Unit, name: &TimedName, template: Rc