From d0f75d89a8d8347309db804b7ba03cce2e89616b Mon Sep 17 00:00:00 2001 From: groche97 Date: Wed, 6 Sep 2023 21:38:17 +0200 Subject: [PATCH] fix some database managements --- Cargo.lock | 2 + Cargo.toml | 2 + src/conf/mod.rs | 100 ++++++++++++++++++++++------------------ src/conf/storage.rs | 58 +++++++++++++++++++++++ src/controller/mod.rs | 73 +++++++++++++++++++++++++---- src/deploy/container.rs | 54 +++++++++++++++------- src/server.rs | 21 +++++++-- 7 files changed, 235 insertions(+), 75 deletions(-) create mode 100644 src/conf/storage.rs diff --git a/Cargo.lock b/Cargo.lock index 83130be..1b57a58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,8 +1005,10 @@ dependencies = [ "actix-web", "bollard", "env_logger", + "fallible-iterator", "futures", "rusqlite", + "serde", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 0fd3db3..132abf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ tokio = { version = "1", features = ["full"] } rusqlite = { version = "0.29.0", features = ["bundled"] } actix-web = "4" env_logger = "*" +serde = "*" +fallible-iterator = "*" [target.x86_64-unknown-linux-gnu] rustflags = [ diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 5a443d9..beacc4d 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -1,6 +1,7 @@ -use std::net::{Ipv4Addr, SocketAddrV4}; use rusqlite::{Connection, Result}; -use std::collections::hash_map::HashMap; +use fallible_iterator::FallibleIterator; + +pub mod storage; const PATH: &str = "mdeploy.db"; @@ -11,16 +12,15 @@ pub struct ConfServer{ ip_base_limit: u8, } -pub struct MInstance { +pub struct Instance { id: i64, - ip: String, - image: i64, + pub ip: String, + pub domain: String, + pub image: i64, } pub struct InstanceStorage { con: Connection, - instances: HashMap, - images: HashMap, } impl InstanceStorage { @@ -28,8 +28,6 @@ impl InstanceStorage { let con = Connection::open(PATH)?; let ret = Self { con : con, - instances: HashMap::new(), - images: HashMap::new(), }; ret.create_table()?; Ok(ret) @@ -45,75 +43,87 @@ impl InstanceStorage { ip TEXT, domain TEXT, image INTEGER, - FOREIGN KEY(INTEGER) REFERENCES images(id)); + FOREIGN KEY(image) REFERENCES images(id)); COMMIT;" ) } fn insert_image(&self, image: String) -> Result<()>{ - let mut stmt = self.con.prepare("INSERT INTO images values(?1)")?; + let mut stmt = self.con.prepare("INSERT INTO images(name) values(?1)")?; stmt.execute([image])?; Ok(()) } - fn load_instances(&mut self) -> Result<()> { + fn load_instances(&mut self) -> Result> { let mut stmt = self.con.prepare("select id, ip, domain, image from l_instances")?; let mut rows = stmt.query([])?; - for row in rows.next()? { - let mut domain = row.get::<_, String>(2)?; - self.instances.insert(domain, MInstance { - id: row.get::<_, i64>(0)?, - ip: row.get::<_, String>(1)?, - image: row.get::<_, i64>(3)? -, - }); - } - Ok(()) + rows.map(|r| Ok(Instance{ + id: r.get::<_, i64>(0)?, + ip: r.get::<_, String>(1)?, + domain: r.get::<_, String>(2)?, + image: r.get::<_, i64>(3)?, + })).collect() } - fn load_images(&mut self) -> Result<()> { + fn load_images(&mut self) -> Result> { let mut stmt = self.con.prepare("select id, name from images")?; let mut rows = stmt.query([])?; - for row in rows.next()? { - self.images.insert(row.get::<_, String>(1)?, row.get::<_, i64>(0)?); - } - Ok(()) + rows.map(|row| Ok((row.get::<_, String>(1)?, row.get::<_, i64>(0)?))).collect() } - fn get_image_id(&self, image: String) -> Result { + fn get_image_id(&self, image: String) -> Result> { let mut stmt = self.con.prepare("select id from images where name = ?1")?; let mut rows = stmt.query([image])?; - let id = rows.next()?.unwrap().get::<_, i64>(0)?; - Ok(id) + Ok(match rows.next()? { + Some(i) => Some(i.get::<_, i64>(0)?), + None => None, + }) } - fn get_instance_id(&self, domain: String) -> Result { + pub fn get_image_name(&self, id: i64) -> Result { + let mut stmt = self.con.prepare("select name from images where id = ?1")?; + let mut rows = stmt.query([id])?; + Ok(rows.next()?.unwrap().get::<_, String>(0)?) + } + + fn get_instance_id(&self, domain: String) -> Result> { let mut stmt = self.con.prepare("select id from l_instances where domain = ?1")?; let mut rows = stmt.query([domain])?; - let id = rows.next()?.unwrap().get::<_, i64>(0)?; - Ok(id) + Ok(match rows.next()? { + Some(i) => Some(i.get::<_, i64>(0)?), + None => None, + }) } - fn new_instance(&mut self, domain: String, image: String, ip: String) -> Result{ - let image_id = self.create_or_get_image_id(image.clone(), 0)?; - let mut stmt = self.con.prepare("INSERT INTO l_instances values(?1, ?2, ?3)")?; - stmt.execute([ip.clone(), image, image_id.to_string()])?; - let instance_id = self.get_instance_id(domain.clone())?; - self.instances.insert(domain.clone(), MInstance{ - id: instance_id, + pub fn new_instance(&mut self, ip: String, domain: String, image: i64) -> Result{ + //let image_id = self.create_or_get_image_id(image.clone(), 0)?; + let mut stmt = self.con.prepare("INSERT INTO l_instances(ip, domain, image) values(?1, ?2, ?3)")?; + stmt.execute([ip.clone(), + domain.clone(), + image.to_string()])?; + Ok(Instance { + id: image, ip: ip, - image: image_id - }); - Ok(instance_id) + domain: domain, + image: image, + }) + } + + pub fn image_to_id(&self, image: String) -> Result { + self.create_or_get_image_id(image, 0) } fn create_or_get_image_id(&self, image: String, mut depth: i64) -> Result { - if depth > 0 { + if depth > 1 { return Err(rusqlite::Error::QueryReturnedNoRows); } depth+=1; match self.get_image_id(image.clone()) { - Ok(id) => Ok(id), + Ok(Some(id)) => Ok(id), + Ok(None) => { + self.insert_image(image.clone())?; + self.create_or_get_image_id(image, depth) + } Err(_e) => { self.insert_image(image.clone())?; self.create_or_get_image_id(image, depth) diff --git a/src/conf/storage.rs b/src/conf/storage.rs new file mode 100644 index 0000000..6907852 --- /dev/null +++ b/src/conf/storage.rs @@ -0,0 +1,58 @@ +use std::collections::hash_map::HashMap; +use rusqlite::Error; +use crate::deploy::container::Container; +use crate::conf::InstanceStorage; +use crate::conf::Instance; + +pub struct MemStorage { + containers: Vec<(Instance, Option)>, + images: HashMap, + storage: InstanceStorage, +} + +impl MemStorage { + pub fn new() -> Result { + let mut storage = InstanceStorage::new()?; + let images = storage.load_images()? + .into_iter().map(|image|(image.0, image.1)) + .collect::>(); + Ok(Self{ + containers: Vec::new(), + images: images, + storage: storage, + }) + } + + fn image_to_id(&mut self, image: String) -> Result { + match self.images.get(&mut image.clone()) { + Some(id) => Ok(*id), + None => { + let id = self.storage.image_to_id(image.clone())?; + self.images.insert(image, id.clone()); + Ok(id) + }, + } + } + + pub fn id_to_image(&mut self, image: i64) -> Result{ + self.storage.get_image_name(image) + } + pub fn new_instance(&mut self, container: Container) -> Result<(), Error> { + let image_id = self.image_to_id(container.image.clone())?; + let ins = self.storage.new_instance( + container.ip.clone(), + container.name.clone(), + image_id)?; + self.containers.push((ins, Some(container))); + Ok(()) + } + + pub fn loaded_instance(&mut self, ins: Instance, container: Container) { + self.containers.push((ins, Some(container))); + } + + pub fn get_instances_db(&mut self) -> Result, Error> { + self.storage.load_instances() + } + +} diff --git a/src/controller/mod.rs b/src/controller/mod.rs index f01c5ca..991ae62 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,27 +1,82 @@ use bollard::Docker; use crate::deploy; +use crate::conf::storage::MemStorage; +use crate::conf::Instance; +use std::sync::Mutex; pub struct Controller { driver: Docker, network: String, + storage: Mutex, + started: bool, } impl Controller { pub async fn new(driver: Docker, network: String, range: String) -> Result{ deploy::network::Network::new(driver.clone(), network.clone(), range).await?; - Ok(Controller { + let cont = Self { driver: driver, network: network, - }) + storage: Mutex::new(MemStorage::new().unwrap()), + started: false, + }; + Ok(cont) } - pub async fn create_container(&self, domain: String, ip: String) -> String { - match deploy::container::Container::new(self.driver.clone(), - domain, - ip, - self.network.clone()).await { - Ok(c) => c.get_id(), - Err(e) => "error creating container".to_string(), + pub async fn create_container(&self, domain: String, ip: String, image: String) -> String { + match self.load_container(domain.clone(),ip.clone(),image.clone()).await { + Ok(c) => { + let ret = c.get_id(); + self.storage.lock().unwrap().new_instance(c).unwrap(); + ret + }, + Err(_e) => "error creating container".to_string(), } } + + pub async fn start_container_from_instance(&self, instance: Instance) { + let image = match self.storage.lock().unwrap().id_to_image(instance.image.clone()) { + Ok(i) => i, + Err(_e) => return, //meter logs aquĆ­ + }; + match self.load_container( + instance.domain.clone(), + instance.ip.clone(), + image).await { + Ok(c) => { + self.storage.lock().unwrap().loaded_instance(instance, c); + }, + Err(_e) => (), + } + } + + async fn load_container(&self, domain: String, + ip: String, + image: String) -> Result + { + deploy::container::Container::new(self.driver.clone(), + domain.clone(), + ip.clone(), + image.clone(), + self.network.clone()).await + } + + pub async fn is_started(&self) -> bool { + self.started + } + + pub async fn load_all_instances(&self) -> bool { + let data = match self.storage.lock().unwrap().get_instances_db() { + Ok(d) => d, + Err(_e) => return false, + }; + println!("instances {}", data.len()); + for instance in data { + self.start_container_from_instance(instance).await; + } + return true; + } + + //pub async fn stop_container(&self, ) } diff --git a/src/deploy/container.rs b/src/deploy/container.rs index ba235d8..3b26bcf 100644 --- a/src/deploy/container.rs +++ b/src/deploy/container.rs @@ -1,46 +1,58 @@ -use bollard::container::{StartContainerOptions, CreateContainerOptions, Config, NetworkingConfig, ListContainersOptions}; +use bollard::container::{StartContainerOptions, + StopContainerOptions, + CreateContainerOptions, + Config, + NetworkingConfig, + ListContainersOptions}; use bollard::models::{EndpointSettings, EndpointIpamConfig}; use bollard::errors::Error; use std::collections::hash_map::HashMap; pub struct Container { - name: String, - start: bool, - ip: String, - net: String, - id: Option, + pub name: String, //Must be the domain name + pub start: bool, + pub ip: String, + pub image: String, + pub net: String, + pub docker_id: Option, + pub database_id: Option } impl Container { pub async fn new(docker: bollard::Docker, name: String, ip: String, + image: String, net: String) -> Result { let mut container = Container { name: name, ip: ip, + image: image, net: net, start: false, - id: None, + docker_id: None, + database_id: None, }; - match container.create(docker).await { + match container.start(docker).await { Ok(c) => { container.start=true; - container.id = Some(c); + container.docker_id = Some(c); Ok(container) } Err(e) => Err(e), } } - async fn create(&self, docker: bollard::Docker) -> Result{ + async fn start(&self, docker: bollard::Docker) -> Result{ let id_container = match self.exist_container(docker.clone()).await? { Some(id_ret) => id_ret, None => docker.create_container(Some( create_options(self.name.clone())), - create_config(self.ip.clone(), self.net.clone())) + create_config(self.ip.clone(), + self.net.clone(), + self.image.clone())) .await?.id, }; docker.start_container(&self.name, None::>).await?; @@ -48,7 +60,7 @@ impl Container { } pub fn get_id(&self) -> String { - match &self.id { + match &self.docker_id { Some(id) => id.clone(), None => String::from(""), } @@ -58,17 +70,21 @@ impl Container { let list = docker.list_containers( Some(create_search_container(self.name.clone())) ).await?; + println!("{}", list.len()); Ok (if list.len() > 0 { list[0].id.clone() } else { None }) } -} + async fn stop(&self, docker: bollard::Docker) -> Result<(), Error>{ + docker.stop_container(self.name.as_str(), Some(stop_options())).await + } +} fn create_search_container(name: String) -> ListContainersOptions { let mut filters = HashMap::new(); - filters.insert("name".to_string(), vec![name]); + filters.insert("before".to_string(), vec![name]); ListContainersOptions{ all: true, @@ -77,7 +93,7 @@ fn create_search_container(name: String) -> ListContainersOptions { } } -fn create_config(ip: String, net: String) -> Config { +fn create_config(ip: String, net: String, image: String) -> Config { let endpoint = EndpointSettings { ipam_config: Some(EndpointIpamConfig{ ipv4_address: Some(ip), @@ -92,7 +108,7 @@ fn create_config(ip: String, net: String) -> Config { }; let env = vec!["EULA=TRUE".to_string()]; Config { - image: Some("itzg/minecraft-server:latest".to_string()), + image: Some(image), env: Some(env), networking_config: Some(net), ..Default::default() @@ -105,3 +121,9 @@ fn create_options(name: String) -> CreateContainerOptions { platform: None, } } + +fn stop_options() -> StopContainerOptions{ + StopContainerOptions { + t: 30, + } +} diff --git a/src/server.rs b/src/server.rs index 58e8a5c..f3a013a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,17 +1,26 @@ -use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{get, post, put, web, App, HttpResponse, HttpServer, Responder}; use bollard::Docker; +use serde::Deserialize; use env_logger; use crate::controller::Controller; +#[derive(Debug, Deserialize)] +struct ConainerParams { + name: String, + ip: String, + image: String, +} + #[get("/")] async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello world!") } -#[get("/create")] -async fn create(controller: web::Data) -> impl Responder { - controller.create_container("container1".to_string(), "172.20.0.5".to_string()).await +#[put("/container")] +async fn create_container(controller: web::Data, + params: web::Query) -> impl Responder { + controller.create_container(params.name.clone(), params.ip.clone(), params.image.clone()).await } #[post("/echo")] @@ -34,13 +43,15 @@ pub async fn start() -> std::io::Result<()> { Ok(c) => c, Err(e) => panic!("error: {}",e), }; + controller.load_all_instances().await; let data = web::Data::new(controller); env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); HttpServer::new(move || { App::new() + .wrap(actix_web::middleware::Logger::default()) .app_data(data.clone()) .service(hello) - .service(create) + .service(create_container) .service(echo) .route("/hey", web::get().to(manual_hello)) })