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