1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-06-03 17:14:08 +02:00

sharding option

This commit is contained in:
Robin Appelman 2024-07-03 16:20:07 +02:00
commit ce0d3ff3e4
19 changed files with 248 additions and 121 deletions

View file

@ -14,6 +14,7 @@ use flate2::read::GzDecoder;
use futures_util::future::try_join_all;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use petname::petname;
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
@ -27,15 +28,14 @@ use tokio::fs::create_dir_all;
use tokio::fs::remove_dir_all;
use tokio::task::spawn;
use tokio::time::sleep;
use toml::Value;
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct CloudOptions {
name: Option<String>,
db: Database,
php: PhpVersion,
services: Vec<Service>,
app_packages: Vec<Utf8PathBuf>,
pub name: Option<String>,
pub db: Database,
pub php: PhpVersion,
pub services: Vec<Service>,
pub app_packages: Vec<Utf8PathBuf>,
}
impl CloudOptions {
@ -176,11 +176,9 @@ pub struct Cloud {
pub id: String,
pub network: String,
pub containers: Vec<String>,
pub php: PhpVersion,
pub db: Database,
pub ip: Option<IpAddr>,
pub workdir: Utf8PathBuf,
pub services: Vec<Service>,
pub options: CloudOptions,
pub pinned: bool,
pub address: String,
pub preset_config: HashMap<String, Value>,
@ -194,6 +192,7 @@ impl Cloud {
) -> Result<Self> {
let id = options
.name
.as_deref()
.map(|name| format!("haze-{}", name))
.unwrap_or_else(|| format!("haze-{}", petname(2, "-").unwrap()));
@ -209,12 +208,12 @@ impl Cloud {
let app_volumes = options
.app_packages
.into_iter()
.iter()
.map(|app_package| {
let app_name = app_package.file_name().unwrap().trim_end_matches(".tar.gz");
let app_dir = app_package_dir.join(app_name);
let app_package_file = fs::File::open(&app_package)
let app_package_file = fs::File::open(app_package)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to open app bundle {}", app_package))?;
if app_package.metadata().into_diagnostic()?.len() > 1024 * 1024 {
@ -296,7 +295,7 @@ impl Cloud {
if let Some(db_name) = options
.db
.spawn(docker, &id, &network)
.spawn(docker, &id, &network, "")
.await
.wrap_err("Failed to start database")?
{
@ -315,7 +314,7 @@ impl Cloud {
options
.services
.iter()
.map(|service| service.spawn(docker, &id, &network, config)),
.map(|service| service.spawn(docker, &id, &network, config, &options)),
)
.await?;
containers.extend(service_containers.iter().flatten().cloned());
@ -395,21 +394,20 @@ impl Cloud {
containers.push(container);
let services_clone = options.services.clone();
let options_clone = options.clone();
let cloud_id = id.clone();
let docker_clone = docker.clone();
spawn(async move {
if let Err(e) = try_join_all(
services_clone
.iter()
.map(|service| service.wait_for_start(&docker_clone, &cloud_id)),
)
if let Err(e) =
try_join_all(options_clone.services.iter().map(|service| {
service.wait_for_start(&docker_clone, &cloud_id, &options_clone)
}))
.await
{
println!("{:#}", e);
return;
}
for service in services_clone {
for service in options_clone.services {
match service.start_message(&docker_clone, &cloud_id).await {
Ok(Some(msg)) => {
println!("{}", msg);
@ -428,11 +426,9 @@ impl Cloud {
id,
network,
containers,
php: options.php,
db: options.db,
ip: Some(ip),
workdir,
services: options.services,
options,
pinned: false,
address,
preset_config,
@ -610,12 +606,16 @@ impl Cloud {
Cloud {
id,
network,
db,
php,
containers: service_ids,
ip,
workdir,
options: CloudOptions {
name: None,
php,
db,
services: found_services,
app_packages: vec![],
},
pinned,
address,
preset_config: HashMap::default(),
@ -645,18 +645,19 @@ impl Cloud {
}
pub async fn wait_for_start(&self, docker: &Docker) -> Result<()> {
self.php
self.options
.php
.wait_for_start(self.ip)
.await
.wrap_err("Failed to wait for php container")?;
self.db
self.options
.db
.wait_for_start(docker, &self.id)
.await
.wrap_err("Failed to wait for database container")?;
try_join_all(
self.services
.iter()
.map(|service| service.wait_for_start(docker, &self.id)),
self.services()
.map(|service| service.wait_for_start(docker, &self.id, &self.options)),
)
.await
.wrap_err("Failed to wait for service containers")?;
@ -706,4 +707,16 @@ impl Cloud {
.await
.into_diagnostic()
}
pub fn services(&self) -> impl Iterator<Item = &Service> {
self.options.services.iter()
}
pub fn db(&self) -> &Database {
&self.options.db
}
pub fn php(&self) -> &PhpVersion {
&self.options.php
}
}

View file

@ -177,6 +177,7 @@ impl Database {
docker: &Docker,
cloud_id: &str,
network: &str,
postfix: &str,
) -> Result<Option<String>> {
if matches!(self, Database::Sqlite) {
return Ok(None);
@ -191,7 +192,7 @@ impl Database {
.wrap_err("Failed to pull database image")?;
}
let options = Some(CreateContainerOptions {
name: format!("{}-db", cloud_id),
name: format!("{}-db{}", cloud_id, postfix),
..CreateContainerOptions::default()
});
let config = Config {
@ -208,7 +209,10 @@ impl Database {
networking_config: Some(NetworkingConfig {
endpoints_config: hashmap! {
network => EndpointSettings {
aliases: Some(vec![self.name().to_string()]),
aliases: Some(vec![
format!("{}{}", self.name(), postfix),
format!("db{}", postfix),
]),
..Default::default()
}
},
@ -320,7 +324,7 @@ impl Database {
};
timeout(Duration::from_secs(time), async {
while !self.is_healthy(docker, cloud_id).await? {
while !self.is_healthy(docker, cloud_id, "").await? {
sleep(Duration::from_millis(250)).await
}
Result::<(), Report>::Ok(())
@ -348,14 +352,14 @@ impl Database {
}
}
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
pub async fn is_healthy(&self, docker: &Docker, cloud_id: &str, postfix: &str) -> Result<bool> {
match self.family() {
DatabaseFamily::Sqlite => Ok(true),
DatabaseFamily::Mysql | DatabaseFamily::MariaDB => {
let mut output = Vec::new();
exec(
docker,
format!("{}-db", cloud_id),
format!("{}-db{}", cloud_id, postfix),
"root",
vec!["mysql", "-u", "haze", "-phaze", "-e", "SELECT 1"],
Vec::<String>::default(),
@ -368,7 +372,7 @@ impl Database {
DatabaseFamily::Postgres => {
let is_ready_status = exec(
docker,
format!("{}-db", cloud_id),
format!("{}-db{}", cloud_id, postfix),
"root",
vec!["pg_isready", "-U", "haze", "-q"],
Vec::<String>::default(),
@ -378,7 +382,7 @@ impl Database {
if is_ready_status == 0 {
let connect_status = exec(
docker,
format!("{}-db", cloud_id),
format!("{}-db{}", cloud_id, postfix),
"root",
vec!["psql", "-U", "haze", "-qtA", "-c", ""],
Vec::<String>::default(),
@ -394,7 +398,7 @@ impl Database {
let mut output = Vec::new();
exec(
docker,
format!("{}-db", cloud_id),
format!("{}-db{}", cloud_id, postfix),
"root",
vec!["sh", "-c", r#"echo "show user" | sqlplus -S system/haze"#],
Vec::<String>::default(),

View file

@ -64,15 +64,15 @@ async fn main() -> Result<()> {
HazeArgs::List { filter } => {
let list = Cloud::list(&docker, filter, &config).await?;
for cloud in list {
let mut services: Vec<_> = cloud.services.iter().map(Service::name).collect();
services.push(cloud.db.name());
let mut services: Vec<_> = cloud.services().map(Service::name).collect();
services.push(cloud.db().name());
let services = services.join(", ");
let pin = if cloud.pinned { "*" } else { "" };
println!(
"Cloud {}{}, {}, {}, running on {}",
cloud.id,
pin,
cloud.php.name(),
cloud.php().name(),
services,
cloud.address
);
@ -124,7 +124,7 @@ async fn main() -> Result<()> {
}
Some(ExecService::Db) => {
cloud
.db
.db()
.exec_sh(
&docker,
&cloud.id,
@ -156,7 +156,7 @@ async fn main() -> Result<()> {
}
HazeArgs::Db { filter, root } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;
cloud.db.exec(&docker, &cloud.id, root).await?;
cloud.db().exec(&docker, &cloud.id, root).await?;
}
HazeArgs::Open { filter } => {
let cloud = Cloud::get_by_filter(&docker, filter, &config).await?;
@ -342,7 +342,7 @@ async fn main() -> Result<()> {
let ip = cloud
.ip
.ok_or_else(|| Report::msg(format!("{} is not running", cloud.id)))?;
let db_type = match cloud.db.family() {
let db_type = match cloud.db().family() {
DatabaseFamily::Sqlite => {
return Err(Report::msg("sqlite is not supported with `haze env`"))
}
@ -353,7 +353,7 @@ async fn main() -> Result<()> {
DatabaseFamily::Postgres => "postgresql",
};
let db_ip = cloud
.db
.db()
.ip(&docker, &cloud.id)
.await
.ok_or_else(|| Report::msg(format!("{}-db is not running", cloud.id)))?;
@ -459,7 +459,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
.await?;
}
for service in &cloud.services {
for service in cloud.services() {
for app in service.apps() {
cloud
.exec(
@ -471,7 +471,7 @@ async fn setup(docker: &Docker, options: CloudOptions, config: &HazeConfig) -> R
.await?;
}
}
for service in &cloud.services {
for service in cloud.services() {
for cmd in service.post_setup(docker, &cloud.id, config).await? {
cloud
.exec(

View file

@ -53,8 +53,7 @@ impl ActiveInstances {
.await
.ok()?;
let service = cloud
.services
.iter()
.services()
.find(|service| service.name() == service_name)?;
let ip = service.get_ip(&self.docker, &cloud.id).await.ok()??;
SocketAddr::new(ip, service.proxy_port())

View file

@ -10,8 +10,11 @@ mod office;
mod onlyoffice;
mod push;
mod sftp;
// mod sharding;
mod sharded;
mod smb;
use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, Preset};
pub use crate::service::clam::{ClamIcap, ClamIcapTls};
use crate::service::dav::Dav;
@ -25,16 +28,17 @@ pub use crate::service::office::Office;
pub use crate::service::onlyoffice::OnlyOffice;
pub use crate::service::push::NotifyPush;
use crate::service::sftp::Sftp;
use crate::service::sharded::Sharding;
use crate::service::smb::Smb;
use bollard::models::ContainerState;
use bollard::Docker;
use enum_dispatch::enum_dispatch;
use miette::{IntoDiagnostic, Report, Result, WrapErr};
use serde_json::Value;
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::Duration;
use tokio::time::{sleep, timeout};
use toml::Value;
#[async_trait::async_trait]
#[enum_dispatch(Service)]
@ -51,25 +55,18 @@ pub trait ServiceTrait {
_cloud_id: &str,
_network: &str,
_config: &HazeConfig,
) -> Result<Option<String>> {
Ok(None)
_options: &CloudOptions,
) -> Result<Vec<String>> {
Ok(Vec::new())
}
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
let Some(container) = self.container_name(cloud_id) else {
return Ok(true);
};
let info = docker
.inspect_container(&container, None)
.await
.into_diagnostic()?;
Ok(matches!(
info.state,
Some(ContainerState {
running: Some(true),
..
})
))
async fn is_healthy(
&self,
_docker: &Docker,
_cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
Ok(true)
}
fn container_name(&self, _cloud_id: &str) -> Option<String> {
@ -189,6 +186,7 @@ pub enum Service {
Push(NotifyPush),
Smb(Smb),
Dav(Dav),
Sharding(Sharding),
Sftp(Sftp),
Kaspersky(Kaspersky),
KasperskyIcap(KasperskyIcap),
@ -212,6 +210,8 @@ impl Service {
"office" => Some(vec![Service::Office(Office)]),
"push" => Some(vec![Service::Push(NotifyPush)]),
"smb" => Some(vec![Service::Smb(Smb)]),
"sharded" => Some(vec![Service::Sharding(Sharding)]),
"sharding" => Some(vec![Service::Sharding(Sharding)]),
"dav" => Some(vec![Service::Dav(Dav)]),
"sftp" => Some(vec![Service::Sftp(Sftp)]),
"oc" => Some(vec![Service::Oc(Oc)]),
@ -231,9 +231,14 @@ impl Service {
}
}
pub async fn wait_for_start(&self, docker: &Docker, cloud_id: &str) -> Result<()> {
pub async fn wait_for_start(
&self,
docker: &Docker,
cloud_id: &str,
options: &CloudOptions,
) -> Result<()> {
timeout(Duration::from_secs(30), async {
while !self.is_healthy(docker, cloud_id).await? {
while !self.is_healthy(docker, cloud_id, options).await? {
sleep(Duration::from_millis(100)).await
}
Ok(())
@ -265,7 +270,12 @@ impl ServiceTrait for PresetService {
) -> Result<HashMap<String, Value>> {
let preset =
get_preset(&config.preset, &self.0).ok_or_else(|| Report::msg("invalid preset"))?;
Ok(preset.config.clone())
let config = preset
.config
.iter()
.map(|(k, v)| Ok((k.clone(), serde_json::to_value(v).into_diagnostic()?)))
.collect::<Result<HashMap<_, _>>>()?;
Ok(config)
}
async fn post_setup(

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
@ -34,6 +35,7 @@ impl ServiceTrait for ClamIcap {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ghcr.io/icewind1991/icap-clamav-service-tls";
pull_image(docker, image).await?;
@ -122,6 +124,7 @@ impl ServiceTrait for ClamIcapTls {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ghcr.io/icewind1991/icap-clamav-service-tls";
pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Dav {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ugeek/webdav:amd64";
pull_image(docker, image).await?;
@ -71,11 +73,6 @@ impl ServiceTrait for Dav {
&["files_external"]
}
// no need to wait for dav, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
_docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Imaginary {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "nextcloud/aio-imaginary:latest";
pull_image(docker, image).await?;
@ -66,11 +68,6 @@ impl ServiceTrait for Imaginary {
Some(format!("{}-imaginary", cloud_id))
}
// no need to wait for imaginary, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
_docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::{image_exists, pull_image};
@ -29,6 +30,7 @@ impl ServiceTrait for Kaspersky {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "kaspersky";
if !image_exists(docker, image).await {
@ -71,7 +73,12 @@ impl ServiceTrait for Kaspersky {
Ok(vec![id])
}
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
let exit = exec(
docker,
self.container_name(cloud_id).unwrap(),
@ -130,6 +137,7 @@ impl ServiceTrait for KasperskyIcap {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "kaspersky-icap";
if !image_exists(docker, image).await {
@ -172,19 +180,6 @@ impl ServiceTrait for KasperskyIcap {
Ok(vec![id])
}
// async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
// let exit = exec(
// docker,
// self.container_name(cloud_id),
// "root",
// vec!["curl", "localhost/licenseinfo"],
// vec![],
// Option::<Stdout>::None,
// )
// .await?;
// Ok(exit.to_result().is_ok())
// }
fn container_name(&self, cloud_id: &str) -> Option<String> {
Some(format!("{}-kaspersky-icap", cloud_id))
}

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for Ldap {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "icewind1991/haze-ldap";
pull_image(docker, image).await?;
@ -75,6 +77,15 @@ impl ServiceTrait for Ldap {
fn apps(&self) -> &'static [&'static str] {
&["user_ldap"]
}
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
self.is_running(docker, cloud_id).await
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -96,6 +107,7 @@ impl ServiceTrait for LdapAdmin {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "osixia/phpldapadmin";
pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Mail {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "rnwood/smtp4dev";
pull_image(docker, image).await?;
@ -66,11 +68,6 @@ impl ServiceTrait for Mail {
Some(format!("{}-mail", cloud_id))
}
// no need to wait for mail, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
_docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
@ -77,6 +78,7 @@ impl ServiceTrait for ObjectStore {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
pull_image(docker, self.image()).await?;
let options = Some(CreateContainerOptions {
@ -117,7 +119,15 @@ impl ServiceTrait for ObjectStore {
Ok(vec![id])
}
async fn is_healthy(&self, docker: &Docker, cloud_id: &str) -> Result<bool> {
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
_options: &CloudOptions,
) -> Result<bool> {
if !self.is_running(docker, cloud_id).await? {
return Ok(false);
}
match self {
ObjectStore::S3 | ObjectStore::S3mb => {
let mut output = Vec::new();

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::exec::exec;
use crate::image::pull_image;
@ -27,6 +28,7 @@ impl ServiceTrait for Oc {
cloud_id: &str,
network: &str,
config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "owncloud/server:10.12.2";
pull_image(docker, image).await?;
@ -78,11 +80,6 @@ impl ServiceTrait for Oc {
Some(format!("{}-oc", cloud_id))
}
// no need to wait for oc
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for Office {
cloud_id: &str,
network: &str,
config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "collabora/code";
pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -27,6 +28,7 @@ impl ServiceTrait for OnlyOffice {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "onlyoffice/documentserver";
pull_image(docker, image).await?;

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -26,6 +27,7 @@ impl ServiceTrait for NotifyPush {
cloud_id: &str,
network: &str,
config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "icewind1991/notify_push";
pull_image(docker, image).await?;
@ -79,10 +81,6 @@ impl ServiceTrait for NotifyPush {
&["notify_push"]
}
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
docker: &Docker,

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Sftp {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "atmoz/sftp:alpine";
pull_image(docker, image).await?;
@ -71,11 +73,6 @@ impl ServiceTrait for Sftp {
&["files_external"]
}
// no need to wait for dav, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
_docker: &Docker,

97
src/service/sharded.rs Normal file
View file

@ -0,0 +1,97 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::database::DatabaseFamily;
use crate::service::ServiceTrait;
use crate::Result;
use bollard::Docker;
use futures_util::future::try_join_all;
use maplit::hashmap;
use miette::Report;
use serde_json::json;
use serde_json::Value;
use std::collections::HashMap;
use std::convert::identity;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Sharding;
const SHARDS: &[&str] = &["-1", "-2"];
#[async_trait::async_trait]
impl ServiceTrait for Sharding {
fn name(&self) -> &str {
"sharding"
}
async fn spawn(
&self,
docker: &Docker,
cloud_id: &str,
network: &str,
_config: &HazeConfig,
options: &CloudOptions,
) -> Result<Vec<String>> {
if options.db.family() == DatabaseFamily::Sqlite {
return Err(Report::msg("Sharding is not supported with sqlite"));
}
let containers = try_join_all(
SHARDS
.iter()
.copied()
.map(|postfix| options.db.spawn(docker, cloud_id, network, postfix)),
)
.await?;
Ok(containers.into_iter().flatten().collect())
}
async fn is_healthy(
&self,
docker: &Docker,
cloud_id: &str,
options: &CloudOptions,
) -> Result<bool> {
let running = try_join_all(
SHARDS
.iter()
.copied()
.map(|postfix| options.db.is_healthy(docker, cloud_id, postfix)),
)
.await?;
Ok(running.iter().copied().all(identity))
}
fn config(
&self,
_docker: &Docker,
_cloud_id: &str,
_config: &HazeConfig,
) -> Result<HashMap<String, Value>> {
let shard_config = json!({
"filecache": {
"table": "filecache",
"primary_key": "fileid",
"shard_key": "storage",
"companion_tables": ["filecache_extended"],
"shards": [
{
"name": "haze",
"host": "db-1",
"tableprefix": "oc_",
"user": "haze",
"password": "haze",
},
{
"name": "haze",
"host": "db-2",
"tableprefix": "oc_",
"user": "haze",
"password": "haze",
}
],
}
});
Ok(hashmap! {String::from("db.sharding") => shard_config})
}
}

View file

@ -1,3 +1,4 @@
use crate::cloud::CloudOptions;
use crate::config::HazeConfig;
use crate::image::pull_image;
use crate::service::ServiceTrait;
@ -23,6 +24,7 @@ impl ServiceTrait for Smb {
cloud_id: &str,
network: &str,
_config: &HazeConfig,
_options: &CloudOptions,
) -> Result<Vec<String>> {
let image = "ghcr.io/servercontainers/samba:smbd-only-a3.18.0-s4.18.2-r0";
pull_image(docker, image).await?;
@ -75,11 +77,6 @@ impl ServiceTrait for Smb {
&["files_external"]
}
// no need to wait for smb, as it won't be used until the user logs in
async fn is_healthy(&self, _docker: &Docker, _cloud_id: &str) -> Result<bool> {
Ok(true)
}
async fn post_setup(
&self,
_docker: &Docker,