use crate::autoscaler::{Autoscaler, AutoscalerConfig};
use crate::error::Result;
use crate::metrics::ForgeMetrics;
use crate::moe::{BoxedMoERouter, DefaultMoERouter, MoERouter};
#[cfg(feature = "quic")]
use crate::networking::QuicConfig;
use crate::networking::HttpServerConfig;
use crate::nomad::NomadClient;
use crate::runtime::Forge;
use crate::storage::{BoxedStateStore, FileStore, MemoryStore};
use crate::types::Region;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::info;
#[derive(Debug, Clone)]
pub struct ForgeConfig {
pub nomad_api: Option<String>,
pub nomad_token: Option<String>,
pub etcd_endpoints: Vec<String>,
pub store_path: Option<PathBuf>,
pub http_config: HttpServerConfig,
#[cfg(feature = "quic")]
pub quic_config: QuicConfig,
pub autoscaler_config: AutoscalerConfig,
pub federation_regions: Vec<Region>,
pub metrics_enabled: bool,
pub node_name: String,
pub datacenter: String,
}
impl Default for ForgeConfig {
fn default() -> Self {
Self {
nomad_api: None,
nomad_token: None,
etcd_endpoints: Vec::new(),
store_path: None,
http_config: HttpServerConfig::default(),
#[cfg(feature = "quic")]
quic_config: QuicConfig::default(),
autoscaler_config: AutoscalerConfig::default(),
federation_regions: Vec::new(),
metrics_enabled: true,
node_name: hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "forge-node".to_string()),
datacenter: "dc1".to_string(),
}
}
}
pub struct ForgeBuilder {
config: ForgeConfig,
router: Option<BoxedMoERouter>,
store: Option<BoxedStateStore>,
}
impl ForgeBuilder {
pub fn new() -> Self {
Self {
config: ForgeConfig::default(),
router: None,
store: None,
}
}
pub fn with_nomad_api(mut self, url: impl Into<String>) -> Self {
self.config.nomad_api = Some(url.into());
self
}
pub fn with_nomad_token(mut self, token: impl Into<String>) -> Self {
self.config.nomad_token = Some(token.into());
self
}
pub fn with_etcd_endpoints(mut self, endpoints: Vec<impl Into<String>>) -> Self {
self.config.etcd_endpoints = endpoints.into_iter().map(|e| e.into()).collect();
self
}
pub fn with_store_path(mut self, path: impl Into<PathBuf>) -> Self {
self.config.store_path = Some(path.into());
self
}
pub fn with_moe_router<R: MoERouter + 'static>(mut self, router: R) -> Self {
self.router = Some(Arc::new(router));
self
}
pub fn with_store(mut self, store: BoxedStateStore) -> Self {
self.store = Some(store);
self
}
pub fn with_autoscaler(mut self, config: AutoscalerConfig) -> Self {
self.config.autoscaler_config = config;
self
}
pub fn with_http_config(mut self, config: HttpServerConfig) -> Self {
self.config.http_config = config;
self
}
pub fn with_http_addr(mut self, addr: &str) -> Result<Self> {
self.config.http_config = self.config.http_config.with_addr_str(addr)?;
Ok(self)
}
#[cfg(feature = "quic")]
pub fn with_quic_config(mut self, config: QuicConfig) -> Self {
self.config.quic_config = config;
self
}
pub fn with_federation(mut self, regions: Vec<impl Into<Region>>) -> Self {
self.config.federation_regions = regions.into_iter().map(|r| r.into()).collect();
self
}
pub fn with_node_name(mut self, name: impl Into<String>) -> Self {
self.config.node_name = name.into();
self
}
pub fn with_datacenter(mut self, dc: impl Into<String>) -> Self {
self.config.datacenter = dc.into();
self
}
pub fn with_metrics(mut self, enabled: bool) -> Self {
self.config.metrics_enabled = enabled;
self
}
pub fn build(self) -> Result<Forge> {
info!(
node = %self.config.node_name,
dc = %self.config.datacenter,
"Building Forge instance"
);
let nomad = match &self.config.nomad_api {
Some(url) => {
let mut client = NomadClient::new(url)?;
if let Some(token) = &self.config.nomad_token {
client = client.with_token(token);
}
Some(client)
}
None => None,
};
let store: BoxedStateStore = match self.store {
Some(s) => s,
None => {
if let Some(path) = &self.config.store_path {
Arc::new(FileStore::open(path)?) as BoxedStateStore
} else {
Arc::new(MemoryStore::new()) as BoxedStateStore
}
}
};
let router = self.router.unwrap_or_else(|| Arc::new(DefaultMoERouter::new()));
let autoscaler = Autoscaler::new(self.config.autoscaler_config.clone())?;
let metrics = if self.config.metrics_enabled {
Some(Arc::new(ForgeMetrics::new()?))
} else {
None
};
Ok(Forge::new(
self.config,
nomad,
store,
router,
autoscaler,
metrics,
))
}
}
impl Default for ForgeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_default() {
let forge = ForgeBuilder::new().build();
assert!(forge.is_ok());
}
#[test]
fn test_builder_with_nomad() {
let forge = ForgeBuilder::new()
.with_nomad_api("http://localhost:4646")
.with_nomad_token("secret-token")
.build();
assert!(forge.is_ok());
}
#[test]
fn test_builder_with_autoscaler() {
let config = AutoscalerConfig::default()
.upscale_threshold(0.9)
.downscale_threshold(0.2);
let forge = ForgeBuilder::new().with_autoscaler(config).build();
assert!(forge.is_ok());
}
#[test]
fn test_builder_with_custom_router() {
use crate::moe::RoundRobinMoERouter;
let forge = ForgeBuilder::new()
.with_moe_router(RoundRobinMoERouter::new())
.build();
assert!(forge.is_ok());
}
}