forge_orchestration/
builder.rs1use crate::autoscaler::{Autoscaler, AutoscalerConfig};
8use crate::error::Result;
9use crate::metrics::ForgeMetrics;
10use crate::moe::{BoxedMoERouter, DefaultMoERouter, MoERouter};
11use crate::networking::{HttpServerConfig, QuicConfig};
12use crate::nomad::NomadClient;
13use crate::runtime::Forge;
14use crate::storage::{BoxedStateStore, FileStore, MemoryStore};
15use crate::types::Region;
16use std::path::PathBuf;
17use std::sync::Arc;
18use tracing::info;
19
20#[derive(Debug, Clone)]
22pub struct ForgeConfig {
23 pub nomad_api: Option<String>,
25 pub nomad_token: Option<String>,
27 pub etcd_endpoints: Vec<String>,
29 pub store_path: Option<PathBuf>,
31 pub http_config: HttpServerConfig,
33 pub quic_config: QuicConfig,
35 pub autoscaler_config: AutoscalerConfig,
37 pub federation_regions: Vec<Region>,
39 pub metrics_enabled: bool,
41 pub node_name: String,
43 pub datacenter: String,
45}
46
47impl Default for ForgeConfig {
48 fn default() -> Self {
49 Self {
50 nomad_api: None,
51 nomad_token: None,
52 etcd_endpoints: Vec::new(),
53 store_path: None,
54 http_config: HttpServerConfig::default(),
55 quic_config: QuicConfig::default(),
56 autoscaler_config: AutoscalerConfig::default(),
57 federation_regions: Vec::new(),
58 metrics_enabled: true,
59 node_name: hostname::get()
60 .map(|h| h.to_string_lossy().to_string())
61 .unwrap_or_else(|_| "forge-node".to_string()),
62 datacenter: "dc1".to_string(),
63 }
64 }
65}
66
67pub struct ForgeBuilder {
69 config: ForgeConfig,
70 router: Option<BoxedMoERouter>,
71 store: Option<BoxedStateStore>,
72}
73
74impl ForgeBuilder {
75 pub fn new() -> Self {
77 Self {
78 config: ForgeConfig::default(),
79 router: None,
80 store: None,
81 }
82 }
83
84 pub fn with_nomad_api(mut self, url: impl Into<String>) -> Self {
86 self.config.nomad_api = Some(url.into());
87 self
88 }
89
90 pub fn with_nomad_token(mut self, token: impl Into<String>) -> Self {
92 self.config.nomad_token = Some(token.into());
93 self
94 }
95
96 pub fn with_etcd_endpoints(mut self, endpoints: Vec<impl Into<String>>) -> Self {
98 self.config.etcd_endpoints = endpoints.into_iter().map(|e| e.into()).collect();
99 self
100 }
101
102 pub fn with_store_path(mut self, path: impl Into<PathBuf>) -> Self {
104 self.config.store_path = Some(path.into());
105 self
106 }
107
108 pub fn with_moe_router<R: MoERouter + 'static>(mut self, router: R) -> Self {
110 self.router = Some(Arc::new(router));
111 self
112 }
113
114 pub fn with_store(mut self, store: BoxedStateStore) -> Self {
116 self.store = Some(store);
117 self
118 }
119
120 pub fn with_autoscaler(mut self, config: AutoscalerConfig) -> Self {
122 self.config.autoscaler_config = config;
123 self
124 }
125
126 pub fn with_http_config(mut self, config: HttpServerConfig) -> Self {
128 self.config.http_config = config;
129 self
130 }
131
132 pub fn with_http_addr(mut self, addr: &str) -> Result<Self> {
134 self.config.http_config = self.config.http_config.with_addr_str(addr)?;
135 Ok(self)
136 }
137
138 pub fn with_quic_config(mut self, config: QuicConfig) -> Self {
140 self.config.quic_config = config;
141 self
142 }
143
144 pub fn with_federation(mut self, regions: Vec<impl Into<Region>>) -> Self {
146 self.config.federation_regions = regions.into_iter().map(|r| r.into()).collect();
147 self
148 }
149
150 pub fn with_node_name(mut self, name: impl Into<String>) -> Self {
152 self.config.node_name = name.into();
153 self
154 }
155
156 pub fn with_datacenter(mut self, dc: impl Into<String>) -> Self {
158 self.config.datacenter = dc.into();
159 self
160 }
161
162 pub fn with_metrics(mut self, enabled: bool) -> Self {
164 self.config.metrics_enabled = enabled;
165 self
166 }
167
168 pub fn build(self) -> Result<Forge> {
170 info!(
171 node = %self.config.node_name,
172 dc = %self.config.datacenter,
173 "Building Forge instance"
174 );
175
176 let nomad = match &self.config.nomad_api {
178 Some(url) => {
179 let mut client = NomadClient::new(url)?;
180 if let Some(token) = &self.config.nomad_token {
181 client = client.with_token(token);
182 }
183 Some(client)
184 }
185 None => None,
186 };
187
188 let store: BoxedStateStore = match self.store {
190 Some(s) => s,
191 None => {
192 if let Some(path) = &self.config.store_path {
193 Arc::new(FileStore::open(path)?) as BoxedStateStore
194 } else {
195 Arc::new(MemoryStore::new()) as BoxedStateStore
196 }
197 }
198 };
199
200 let router = self.router.unwrap_or_else(|| Arc::new(DefaultMoERouter::new()));
202
203 let autoscaler = Autoscaler::new(self.config.autoscaler_config.clone())?;
205
206 let metrics = if self.config.metrics_enabled {
208 Some(Arc::new(ForgeMetrics::new()?))
209 } else {
210 None
211 };
212
213 Ok(Forge::new(
214 self.config,
215 nomad,
216 store,
217 router,
218 autoscaler,
219 metrics,
220 ))
221 }
222}
223
224impl Default for ForgeBuilder {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_builder_default() {
236 let forge = ForgeBuilder::new().build();
237 assert!(forge.is_ok());
238 }
239
240 #[test]
241 fn test_builder_with_nomad() {
242 let forge = ForgeBuilder::new()
243 .with_nomad_api("http://localhost:4646")
244 .with_nomad_token("secret-token")
245 .build();
246 assert!(forge.is_ok());
247 }
248
249 #[test]
250 fn test_builder_with_autoscaler() {
251 let config = AutoscalerConfig::default()
252 .upscale_threshold(0.9)
253 .downscale_threshold(0.2);
254
255 let forge = ForgeBuilder::new().with_autoscaler(config).build();
256 assert!(forge.is_ok());
257 }
258
259 #[test]
260 fn test_builder_with_custom_router() {
261 use crate::moe::RoundRobinMoERouter;
262
263 let forge = ForgeBuilder::new()
264 .with_moe_router(RoundRobinMoERouter::new())
265 .build();
266 assert!(forge.is_ok());
267 }
268}