Skip to content

Commit

Permalink
chore: add docker/bollard
Browse files Browse the repository at this point in the history
  • Loading branch information
fetchfern committed Apr 17, 2024
1 parent c43ac69 commit 6d24f10
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 15 deletions.
1 change: 1 addition & 0 deletions crates/alerion_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ pin-project-lite = "0.2.14"
reqwest = { version = "0.12.3" }
smallvec = { version = "1.13.2", features = ["serde"] }
directories = "5.0.1"
bollard = "0.16.1"
2 changes: 1 addition & 1 deletion crates/alerion_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub async fn alerion_main() -> anyhow::Result<()> {
let config = AlerionConfig::load(&project_dirs)?;

let server_pool = Arc::new(
ServerPool::builder(&config)
ServerPool::builder(&config)?
.fetch_servers()
.await?
.build()
Expand Down
91 changes: 77 additions & 14 deletions crates/alerion_core/src/servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use actix_web::HttpResponse;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::RwLock;
use uuid::Uuid;
use bollard::Docker;
use bollard::container::{Config, CreateContainerOptions};
use thiserror::Error;
use serde::{Serialize, Deserialize};

use crate::config::AlerionConfig;
use crate::websocket::conn::{ConnectionAddr, NetworkStatistics, PanelMessage, PerformanceStatisics, ServerMessage, ServerStatus};
Expand All @@ -15,17 +19,21 @@ use crate::websocket::relay::{AuthTracker, ClientConnection, ServerConnection};
pub struct ServerPoolBuilder {
servers: HashMap<Uuid, Arc<Server>>,
remote_api: Arc<remote::RemoteClient>,
docker: Arc<Docker>,
}

impl ServerPoolBuilder {
pub fn from_config(config: &AlerionConfig) -> Self {
Self {
pub fn from_config(config: &AlerionConfig) -> Result<Self, ServerError> {
let docker = Arc::new(Docker::connect_with_defaults()?);

Ok(Self {
servers: HashMap::new(),
remote_api: Arc::new(remote::RemoteClient::new(config)),
}
docker,
})
}

pub async fn fetch_servers(mut self) -> Result<ServerPoolBuilder, remote::ResponseError> {
pub async fn fetch_servers(mut self) -> Result<ServerPoolBuilder, ServerError> {
log::info!("Fetching existing servers on this node");

let servers = self.remote_api.get_servers().await?;
Expand All @@ -35,7 +43,12 @@ impl ServerPoolBuilder {

let uuid = s.uuid;
let info = ServerInfo::from_remote_info(s.settings);
let server = Server::new(uuid, info, Arc::clone(&self.remote_api));
let server = Server::new(
uuid,
info,
Arc::clone(&self.remote_api),
Arc::clone(&self.docker),
).await?;
self.servers.insert(uuid, server);
}

Expand All @@ -46,21 +59,23 @@ impl ServerPoolBuilder {
ServerPool {
servers: RwLock::new(self.servers),
remote_api: self.remote_api,
docker: self.docker,
}
}
}

pub struct ServerPool {
servers: RwLock<HashMap<Uuid, Arc<Server>>>,
remote_api: Arc<remote::RemoteClient>,
docker: Arc<Docker>,
}

impl ServerPool {
pub fn builder(config: &AlerionConfig) -> ServerPoolBuilder {
pub fn builder(config: &AlerionConfig) -> Result<ServerPoolBuilder, ServerError> {
ServerPoolBuilder::from_config(config)
}

pub async fn get_or_create_server(&self, uuid: Uuid) -> Result<Arc<Server>, remote::ResponseError> {
pub async fn get_or_create_server(&self, uuid: Uuid) -> Result<Arc<Server>, ServerError> {
// initially try to read, because most of the times we'll only need to read
// and we can therefore reduce waiting by a lot using a read-write lock.
let map = self.servers.read().await;
Expand All @@ -75,16 +90,16 @@ impl ServerPool {
}
}

pub async fn create_server(&self, uuid: Uuid) -> Result<Arc<Server>, remote::ResponseError> {
pub async fn create_server(&self, uuid: Uuid) -> Result<Arc<Server>, ServerError> {
log::info!("Creating server {uuid}");

let remote_api = Arc::clone(&self.remote_api);
let docker = Arc::clone(&self.docker);

let config = remote_api.get_server_configuration(uuid).await?;

let server_info = ServerInfo::from_remote_info(config.settings);

let server = Server::new(uuid, server_info, remote_api);
let server = Server::new(uuid, server_info, remote_api, docker).await?;
self.servers.write().await.insert(uuid, Arc::clone(&server));

Ok(server)
Expand All @@ -107,36 +122,84 @@ impl ServerInfo {
}
}

#[derive(Debug, Error)]
pub enum ServerError {
#[error("docker error: {0}")]
Docker(#[from] bollard::errors::Error),
#[error("panel remote API error: {0}")]
RemoteApi(#[from] remote::ResponseError),
}

#[derive(Serialize, Deserialize, Default, Copy, Clone, PartialEq, Eq, Hash)]
struct IntoStringZst;

impl From<IntoStringZst> for String {
fn from(value: IntoStringZst) -> Self {
String::new()
}
}

pub struct Server {
start_time: Instant,
uuid: Uuid,
container_id: String,
container_name: String,
websocket_id_counter: AtomicU32,
websockets: RwLock<HashMap<u32, ClientConnection>>,
sender_copy: Sender<(u32, PanelMessage)>,
server_info: ServerInfo,
remote_api: Arc<remote::RemoteClient>,
docker: Arc<Docker>,
}

impl Server {
pub fn new(uuid: Uuid, server_info: ServerInfo, remote_api: Arc<remote::RemoteClient>) -> Arc<Self> {
pub async fn new(
uuid: Uuid,
server_info: ServerInfo,
remote_api: Arc<remote::RemoteClient>,
docker: Arc<Docker>,
) -> Result<Arc<Self>, ServerError> {
let (send, recv) = channel(128);

let server = Arc::new(Self {
start_time: Instant::now(),
uuid,
container_id: format!("{}_container", uuid.as_hyphenated()),
container_name: format!("{}_container", uuid.as_hyphenated()),
websocket_id_counter: AtomicU32::new(0),
websockets: RwLock::new(HashMap::new()),
sender_copy: send,
server_info,
remote_api,
docker,
});

tokio::spawn(task_websocket_receiver(recv));
tokio::spawn(monitor_performance_metrics(Arc::clone(&server)));

server
server.create_docker_container().await?;

Ok(server)
}

async fn create_docker_container(&self) -> Result<(), ServerError> {
log::info!("Creating docker container for server {}", self.uuid.as_hyphenated());

let opts = CreateContainerOptions {
name: self.container_name.clone(),
platform: None,
};

let config: Config<IntoStringZst> = Config {
attach_stdin: Some(true),
attach_stdout: Some(true),
attach_stderr: Some(true),
..Config::default()
};

let response = self.docker.create_container(Some(opts), config).await?;

log::debug!("{response:#?}");

Ok(())
}

pub async fn setup_new_websocket<F>(&self, start_websocket: F) -> actix_web::Result<HttpResponse>
Expand Down

0 comments on commit 6d24f10

Please sign in to comment.