use std::collections::HashSet;
use tokio_util::sync::CancellationToken;
use crate::config::Service;
use crate::daemon::NodeOptions;
use crate::mirror::RestorePoint;
use crate::response::{
MirrorRestoreResponse, MirrorSyncResponse, RegisterResponse, StatusResponse,
};
use crate::root::FleetRoot;
use crate::{daemon, ops};
#[derive(Clone)]
pub struct Fleet {
root: FleetRoot,
}
impl Fleet {
pub fn open(url: &str) -> Result<Self, Error> {
Ok(Self {
root: FleetRoot::open(url)?,
})
}
#[doc(hidden)]
pub fn from_root(root: FleetRoot) -> Self {
Self { root }
}
pub fn url(&self) -> &str {
self.root.url()
}
pub async fn register(&self, database_url: &str) -> Result<RegisterResponse, Error> {
Ok(ops::register(&self.root, database_url).await?)
}
pub async fn status(&self, options: StatusOptions) -> Result<StatusResponse, Error> {
Ok(ops::status(&self.root, options.compactions, options.mirrors).await?)
}
pub async fn sync_mirror(
&self,
database_url: &str,
target: &str,
options: MirrorSyncOptions,
) -> Result<MirrorSyncResponse, Error> {
Ok(ops::mirror_sync(&self.root, database_url, target, options.rclone.as_deref()).await?)
}
pub async fn run_node(
&self,
options: NodeOptions,
shutdown: CancellationToken,
) -> Result<(), Error> {
let mut seen = HashSet::new();
if let Some(duplicate) = options
.services
.iter()
.find(|service| !seen.insert(**service))
{
return Err(Error::DuplicateService {
service: *duplicate,
});
}
daemon::run(self.root.clone(), options, shutdown).await?;
Ok(())
}
}
pub async fn mirror_restore(
backup_url: &str,
destination_url: &str,
point: RestorePoint,
) -> Result<MirrorRestoreResponse, Error> {
Ok(ops::mirror_restore(backup_url, destination_url, point).await?)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct StatusOptions {
compactions: bool,
mirrors: bool,
}
impl StatusOptions {
pub fn with_compactions(mut self, enabled: bool) -> Self {
self.compactions = enabled;
self
}
pub fn with_mirrors(mut self, enabled: bool) -> Self {
self.mirrors = enabled;
self
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MirrorSyncOptions {
rclone: Option<String>,
}
impl MirrorSyncOptions {
pub fn with_rclone(mut self, rclone: impl Into<String>) -> Self {
self.rclone = Some(rclone.into());
self
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid fleet root: {0}")]
FleetRootUrl(#[source] crate::registry::UrlError),
#[error("failed to open fleet root store: {0}")]
FleetRootStore(#[source] object_store::Error),
#[error(transparent)]
Url(#[from] crate::registry::UrlError),
#[error("object store error: {0}")]
Store(#[from] object_store::Error),
#[error("{url} is not registered; `sleet register` it first")]
NotRegistered {
url: String,
},
#[error("no enabled mirror target {target:?} applies to {url}")]
NoSuchMirrorTarget {
target: String,
url: String,
},
#[error(transparent)]
Service(#[from] crate::services::ServiceError),
#[error(transparent)]
Mirror(#[from] crate::mirror::MirrorError),
#[error("invalid node id: {0}")]
InvalidNodeId(String),
#[error("services lists {name:?} more than once", name = service.as_str())]
DuplicateService {
service: Service,
},
}
impl From<crate::root::OpenError> for Error {
fn from(error: crate::root::OpenError) -> Self {
match error {
crate::root::OpenError::Url(source) => Self::FleetRootUrl(source),
crate::root::OpenError::Store(source) => Self::FleetRootStore(source),
}
}
}
impl From<ops::OpsError> for Error {
fn from(error: ops::OpsError) -> Self {
match error {
ops::OpsError::Url(source) => Self::Url(source),
ops::OpsError::Store(source) => Self::Store(source),
ops::OpsError::NotRegistered { url } => Self::NotRegistered { url },
ops::OpsError::NoSuchTarget { target, url } => Self::NoSuchMirrorTarget { target, url },
ops::OpsError::Service(source) => Self::Service(source),
ops::OpsError::Mirror(source) => Self::Mirror(source),
}
}
}
impl From<daemon::DaemonError> for Error {
fn from(error: daemon::DaemonError) -> Self {
match error {
daemon::DaemonError::NodeId(reason) => Self::InvalidNodeId(reason),
daemon::DaemonError::Root(source) => source.into(),
}
}
}