diff --git a/Cargo.lock b/Cargo.lock index 034e4e5..3554748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,6 +1004,7 @@ version = "0.1.0" dependencies = [ "actix-web", "bollard", + "derive_more", "env_logger", "fallible-iterator", "futures", diff --git a/Cargo.toml b/Cargo.toml index 0340466..2380de2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ tokio = { version = "1", features = ["full"] } rusqlite = { version = "0.29.0", features = ["bundled"] } actix-web = "4" env_logger = "*" +derive_more = "*" log = "*" serde = "*" fallible-iterator = "*" diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 1737077..cf023d0 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -12,6 +12,7 @@ pub struct ConfServer{ ip_base_limit: u8, } +#[derive(Clone)] pub struct Instance { id: i64, pub docker_id: String, @@ -114,6 +115,11 @@ impl InstanceStorage { }) } + pub fn remove_instance(&mut self, id: String) -> Result { + let mut stmt = self.con.prepare("DELETE from l_instances where id=?1")?; + stmt.execute([id]) + } + pub fn image_to_id(&self, image: String) -> Result { self.create_or_get_image_id(image, 0) } diff --git a/src/conf/storage.rs b/src/conf/storage.rs index 490da7e..9f1bfe1 100644 --- a/src/conf/storage.rs +++ b/src/conf/storage.rs @@ -37,6 +37,7 @@ impl MemStorage { 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( @@ -48,6 +49,37 @@ impl MemStorage { Ok(()) } + pub fn search_instance(&self, name: String) -> Option { + for cont in &self.containers { + if cont.0.domain == name { return cont.1.clone(); } + } + None + } + + pub fn stop_instance(&mut self, name: String) -> Option { + for cont in &mut self.containers { + if cont.0.domain == name { + return cont.1.take(); + } + } + None + } + + pub fn remove_instance(&mut self, name: String) -> Option<(Instance, Option)> { + for i in 0..self.containers.len() { + match self.containers.get(i) { + Some(c) => { + if c.0.domain == name { + self.storage.remove_instance(c.0.id.to_string().clone()); + return Some(self.containers.remove(i)); + } + } + None => break + } + } + None + } + pub fn loaded_instance(&mut self, ins: Instance, container: Container) { self.containers.push((ins, Some(container))); } diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 84d707e..42179aa 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,9 +1,11 @@ use bollard::Docker; +use bollard::errors::Error; use crate::deploy; +use crate::deploy::container::Container; use crate::conf::storage::MemStorage; use crate::conf::Instance; use std::sync::Mutex; -use log::{error}; +use log::error; pub struct Controller { driver: Docker, @@ -25,13 +27,22 @@ impl Controller { } pub async fn create_container(&self, domain: String, ip: String, image: String) -> String { - match self.load_container(None,domain.clone(),ip.clone(),image.clone()).await { - Ok(c) => { - let ret = c.get_id(); - self.storage.lock().unwrap().new_instance(c).unwrap(); - ret + let is_stored = self.storage.lock().unwrap().search_instance(domain.clone()); + match is_stored { + Some(c) => match c.docker_id { + Some(id) => id, + None => "Container without docker_id".to_string() }, - Err(_e) => "error creating container".to_string(), + None => { + match self.load_container(None,domain.clone(),ip.clone(),image.clone()).await { + Ok(c) => { + log::debug!("poisoned: {}",self.storage.is_poisoned()); + self.storage.try_lock().unwrap().new_instance(c.clone()).unwrap(); + c.get_id() + }, + Err(_e) => "error creating container".to_string(), + } + } } } @@ -39,7 +50,7 @@ impl Controller { let image = match self.storage.lock().unwrap().id_to_image(instance.image.clone()) { Ok(i) => i, Err(e) => { - log::error!("image not found: {}", e); + error!("image not found: {}", e); return }, }; @@ -51,7 +62,7 @@ impl Controller { Ok(c) => { self.storage.lock().unwrap().loaded_instance(instance, c); }, - Err(e) => log::error!("{}",e), + Err(e) => error!("{}",e), } } @@ -77,7 +88,7 @@ impl Controller { let data = match self.storage.lock().unwrap().get_instances_db() { Ok(d) => d, Err(e) => { - log::error!("instances can't be loaded: {}",e); + error!("instances can't be loaded: {}",e); return false }, }; @@ -88,5 +99,44 @@ impl Controller { return true; } - //pub async fn stop_container(&self, ) + async fn stop_given_container(&self, container:Container) -> Result { + match container.stop(&self.driver).await { + Ok(_i) => Ok(container.get_id()), + Err(e) => Err(e), + } + } + + async fn prune_given_container(&self, container:Container) -> Result { + match container.remove(&self.driver).await { + Ok(_i) => Ok(container.get_id()), + Err(e) => Err(e), + } + } + + pub async fn stop_container(&self, domain: String) -> Result { + match self.storage.lock().unwrap().stop_instance(domain) { + Some(c) => { + self.stop_given_container(c).await + }, + None => Err(Error::DockerResponseServerError { + status_code: 404, message: "container not found".to_string() + }), + } + } + + pub async fn delete_container(&self, domain: String) -> Result { + match self.storage.lock().unwrap().remove_instance(domain) { + Some(data) => { + match data.1 { + Some(c) => self.prune_given_container(c).await, + None => Ok(data.0.docker_id), + } + }, + None => Err(Error::DockerResponseServerError { + status_code: 404, message: "container not found".to_string() + }), + } + } } + + diff --git a/src/deploy/bind.rs b/src/deploy/bind.rs new file mode 100644 index 0000000..33537cd --- /dev/null +++ b/src/deploy/bind.rs @@ -0,0 +1,18 @@ +use std::vec; +use std::fs::{create_dir, canonicalize}; + +const BASE_PATH : &str = "mrdeploy_data/"; + +pub fn generate_bindpaths(mut name: String) -> Vec{ + name.insert_str(0,BASE_PATH); + let path = match canonicalize(name.clone()) { + Ok(p) => String::from(p.to_str().unwrap()), + Err(_e) => name, + }; + vec![format!("{}:{}", path, "/data")] +} + +pub fn check_fs(mut name: String) -> Result<(), std::io::Error> { + name.insert_str(0,BASE_PATH); + create_dir(name) +} diff --git a/src/deploy/container.rs b/src/deploy/container.rs index 23710d9..d5a24e2 100644 --- a/src/deploy/container.rs +++ b/src/deploy/container.rs @@ -1,13 +1,17 @@ use bollard::container::{StartContainerOptions, StopContainerOptions, CreateContainerOptions, + RemoveContainerOptions, Config, NetworkingConfig, ListContainersOptions}; -use bollard::models::{EndpointSettings, EndpointIpamConfig}; -use bollard::errors::Error; +use bollard::models::{EndpointSettings, EndpointIpamConfig, HostConfig}; +use bollard::errors::{Error, Error::IOError}; +use std::io::ErrorKind::AlreadyExists; use std::collections::hash_map::HashMap; +use crate::deploy::bind; +#[derive(Clone)] pub struct Container { pub name: String, //Must be the domain name pub start: bool, @@ -35,7 +39,6 @@ impl Container { docker_id: docker_id, database_id: None, }; - match container.start(docker).await { Ok(c) => { container.start=true; @@ -46,12 +49,22 @@ impl Container { } async fn start(&mut self, docker: bollard::Docker) -> Result{ - let ret = match &self.exist_container(docker.clone()).await? { - Some(_id) => true, + match bind::check_fs(self.name.clone()) { + Ok(_r) => (), + Err(ref e) if e.kind() == AlreadyExists => (), + Err(e) => return Err(IOError { err: e }), + } + let ret = match self.exist_container(docker.clone()).await? { + Some(id) => { + self.docker_id=Some(id); + true + }, None => { + log::info!("creating container with name: {}", self.name); self.docker_id = Some(docker.create_container(Some( create_options(self.name.clone())), - create_config(self.ip.clone(), + create_config(self.name.clone(), + self.ip.clone(), self.net.clone(), self.image.clone())) .await?.id); @@ -70,30 +83,40 @@ impl Container { } async fn exist_container(&self, docker: bollard::Docker) -> Result, Error> { - match &self.docker_id { - Some(_id) => { - let list = docker.list_containers( - Some(create_search_container(self.get_id().clone())) + let list = docker.list_containers( + Some(create_search_container(self.name.clone())) ).await?; - Ok (if list.len() > 0 { - list[0].id.clone() - } else { - None - }) + let mut comparable_name = self.name.clone(); + comparable_name.insert_str(0, "/"); + for cs in list { + match cs.names { + Some(names) => { + for name in names { + if name == comparable_name { + return Ok(cs.id.clone()); + } + } + }, + + None => (), } - - None => Ok(None), } - + Ok(None) } - async fn stop(&self, docker: bollard::Docker) -> Result<(), Error>{ - docker.stop_container(self.name.as_str(), Some(stop_options())).await + pub async fn stop(&self, docker: &bollard::Docker) -> Result<(), Error>{ + docker.stop_container(self.name.as_str(), Some(create_stop_options())).await + } + + pub async fn remove(&self, docker: &bollard::Docker) -> Result { + docker.remove_container(self.get_id().as_str(), Some(create_remove_option())).await?; + Ok(self.docker_id.clone().unwrap()) } } + fn create_search_container(name: String) -> ListContainersOptions { let mut filters = HashMap::new(); - filters.insert("id".to_string(), vec![name]); + filters.insert("name".to_string(), vec![name]); ListContainersOptions{ all: true, @@ -102,7 +125,7 @@ fn create_search_container(name: String) -> ListContainersOptions { } } -fn create_config(ip: String, net: String, image: String) -> Config { +fn create_config(name: String, ip: String, net: String, image: String) -> Config { let endpoint = EndpointSettings { ipam_config: Some(EndpointIpamConfig{ ipv4_address: Some(ip), @@ -119,11 +142,19 @@ fn create_config(ip: String, net: String, image: String) -> Config { Config { image: Some(image), env: Some(env), + host_config: Some(create_hostconfig(name)), networking_config: Some(net), ..Default::default() } } +fn create_hostconfig(name: String) -> HostConfig { + HostConfig { + binds: Some(bind::generate_bindpaths(name.clone())), + ..Default::default() + } +} + fn create_options(name: String) -> CreateContainerOptions { CreateContainerOptions{ name: name, @@ -131,8 +162,15 @@ fn create_options(name: String) -> CreateContainerOptions { } } -fn stop_options() -> StopContainerOptions{ +fn create_stop_options() -> StopContainerOptions { StopContainerOptions { t: 30, } } + +fn create_remove_option() -> RemoveContainerOptions { + RemoveContainerOptions{ + force: true, + ..Default::default() + } +} diff --git a/src/deploy/mod.rs b/src/deploy/mod.rs index bb35a35..d1994d8 100644 --- a/src/deploy/mod.rs +++ b/src/deploy/mod.rs @@ -1,2 +1,3 @@ pub mod container; pub mod network; +mod bind; diff --git a/src/server.rs b/src/server.rs index f3a013a..479dcc9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,10 +1,20 @@ -use actix_web::{get, post, put, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{get, patch, post, put, delete, web, App, HttpResponse, HttpServer, Responder}; use bollard::Docker; +use derive_more::{Display, Error}; +use log::info; use serde::Deserialize; use env_logger; use crate::controller::Controller; +#[derive(Debug, Display, Error)] +#[display(fmt = "my error: {}", name)] +pub struct ReturnedError { + name: String, +} + +impl actix_web::error::ResponseError for ReturnedError{} + #[derive(Debug, Deserialize)] struct ConainerParams { name: String, @@ -12,6 +22,11 @@ struct ConainerParams { image: String, } +#[derive(Debug, Deserialize)] +struct ConainerStopParams { + name: String, +} + #[get("/")] async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello world!") @@ -23,6 +38,24 @@ async fn create_container(controller: web::Data, controller.create_container(params.name.clone(), params.ip.clone(), params.image.clone()).await } +#[patch("/container/stop")] +async fn stop_container(controller: web::Data, + params: web::Query) -> Result { + match controller.stop_container(params.name.clone()).await { + Ok(response) => Ok(response), + Err(e) => Err(ReturnedError { name: e.to_string()}) + } +} + +#[delete("/container")] +async fn delete_container(controller: web::Data, + params: web::Query) -> Result { + match controller.delete_container(params.name.clone()).await { + Ok(response) => Ok(response), + Err(e) => Err(ReturnedError { name: e.to_string()}) + } +} + #[post("/echo")] async fn echo(req_body: String) -> impl Responder { HttpResponse::Ok().body(req_body) @@ -37,6 +70,7 @@ pub async fn start() -> std::io::Result<()> { Ok(d) => d, Err(e) => panic!("error:{}",e.to_string()), }; + env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); let controller = match Controller::new(docker, "customnetwork".to_string(), "172.20.0.0/24".to_string()).await { @@ -45,13 +79,14 @@ pub async fn start() -> std::io::Result<()> { }; 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_container) + .service(stop_container) + .service(delete_container) .service(echo) .route("/hey", web::get().to(manual_hello)) }) @@ -59,3 +94,7 @@ pub async fn start() -> std::io::Result<()> { .run() .await } + + + +