1use std::fmt;
33
34use crate::{
35 config::HealthStatus,
36 distributed::RequestPlaneMode,
37 metrics::{MetricsHierarchy, MetricsRegistry, prometheus_names},
38 service::ServiceClient,
39 service::ServiceSet,
40};
41
42use super::{DistributedRuntime, Runtime, traits::*, transports::nats::Slug, utils::Duration};
43
44use crate::pipeline::network::{
45 PushWorkHandler, RequestPlanePayloadCodec, ingress::push_endpoint::PushEndpoint,
46};
47use crate::protocols::EndpointId;
48use async_nats::{
49 rustls::quic,
50 service::{Service, ServiceExt},
51};
52use dashmap::DashMap;
53use derive_builder::Builder;
54use derive_getters::Getters;
55use educe::Educe;
56use serde::{Deserialize, Serialize};
57use std::{collections::HashMap, hash::Hash, sync::Arc};
58use validator::{Validate, ValidationError};
59
60mod client;
61#[allow(clippy::module_inception)]
62mod component;
63mod endpoint;
64mod namespace;
65mod registry;
66pub mod service;
67
68pub(crate) use client::EndpointDiscoverySource;
69pub(crate) use client::RoutingInstances;
70pub(crate) use client::RoutingOccupancyState;
71pub(crate) use client::get_or_create_routing_occupancy_state;
72pub use client::{Client, RoutingInstanceCounts};
73pub use endpoint::{StartedEndpoint, build_transport_type};
74
75#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
76#[serde(rename_all = "snake_case")]
77pub enum TransportType {
78 #[serde(rename = "nats_tcp")]
79 Nats(String),
80 Tcp(String),
81}
82
83impl TransportType {
84 pub fn address(&self) -> &str {
85 match self {
86 TransportType::Nats(address) | TransportType::Tcp(address) => address,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
92#[serde(rename_all = "snake_case")]
93pub enum DeviceType {
94 Cpu,
95 Cuda,
96}
97
98#[derive(Default)]
99pub struct RegistryInner {
100 pub(crate) services: HashMap<String, Service>,
101}
102
103#[derive(Clone)]
104pub struct Registry {
105 pub(crate) inner: Arc<tokio::sync::Mutex<RegistryInner>>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109pub struct Instance {
110 pub component: String,
111 pub endpoint: String,
112 pub namespace: String,
113 pub instance_id: u64,
114 pub transport: TransportType,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub device_type: Option<DeviceType>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub request_plane_codec: Option<RequestPlanePayloadCodec>,
121}
122
123impl Instance {
124 pub fn id(&self) -> u64 {
125 self.instance_id
126 }
127
128 pub fn endpoint_id(&self) -> EndpointId {
129 EndpointId {
130 namespace: self.namespace.clone(),
131 component: self.component.clone(),
132 name: self.endpoint.clone(),
133 }
134 }
135
136 pub fn endpoint_instance_id(&self) -> crate::discovery::EndpointInstanceId {
137 crate::discovery::EndpointInstanceId {
138 namespace: self.namespace.clone(),
139 component: self.component.clone(),
140 endpoint: self.endpoint.clone(),
141 instance_id: self.instance_id,
142 }
143 }
144}
145
146impl fmt::Display for Instance {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 write!(
149 f,
150 "{}/{}/{}/{}",
151 self.namespace, self.component, self.endpoint, self.instance_id
152 )
153 }
154}
155
156impl std::cmp::Ord for Instance {
158 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
159 self.to_string().cmp(&other.to_string())
160 }
161}
162
163impl PartialOrd for Instance {
164 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
165 Some(self.cmp(other))
167 }
168}
169
170#[derive(Educe, Builder, Clone, Validate)]
176#[educe(Debug)]
177#[builder(pattern = "owned", build_fn(private, name = "build_internal"))]
178pub struct Component {
179 #[builder(private)]
180 #[educe(Debug(ignore))]
181 drt: Arc<DistributedRuntime>,
182
183 #[builder(setter(into))]
185 #[validate(custom(function = "validate_allowed_chars"))]
186 name: String,
187
188 #[builder(default = "Vec::new()")]
190 labels: Vec<(String, String)>,
191
192 #[builder(setter(into))]
195 namespace: Namespace,
196
197 #[builder(default = "crate::MetricsRegistry::new()")]
199 metrics_registry: crate::MetricsRegistry,
200}
201
202impl Hash for Component {
203 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
204 self.namespace.name().hash(state);
205 self.name.hash(state);
206 }
207}
208
209impl PartialEq for Component {
210 fn eq(&self, other: &Self) -> bool {
211 self.namespace.name() == other.namespace.name() && self.name == other.name
212 }
213}
214
215impl Eq for Component {}
216
217impl std::fmt::Display for Component {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 write!(f, "{}.{}", self.namespace.name(), self.name)
220 }
221}
222
223impl DistributedRuntimeProvider for Component {
224 fn drt(&self) -> &DistributedRuntime {
225 &self.drt
226 }
227}
228
229impl RuntimeProvider for Component {
230 fn rt(&self) -> &Runtime {
231 self.drt.rt()
232 }
233}
234
235impl MetricsHierarchy for Component {
236 fn basename(&self) -> String {
237 self.name.clone()
238 }
239
240 fn parent_hierarchies(&self) -> Vec<&dyn MetricsHierarchy> {
241 let mut parents = vec![];
242
243 parents.extend(self.namespace.parent_hierarchies());
245
246 parents.push(&self.namespace as &dyn MetricsHierarchy);
248
249 parents
250 }
251
252 fn get_metrics_registry(&self) -> &MetricsRegistry {
253 &self.metrics_registry
254 }
255
256 fn connection_id(&self) -> Option<u64> {
257 Some(self.drt.connection_id())
258 }
259}
260
261impl Component {
262 pub fn service_name(&self) -> String {
263 let service_name = format!("{}_{}", self.namespace.name(), self.name);
264 Slug::slugify(&service_name).to_string()
265 }
266
267 pub fn namespace(&self) -> &Namespace {
268 &self.namespace
269 }
270
271 pub fn name(&self) -> &str {
272 &self.name
273 }
274
275 pub fn labels(&self) -> &[(String, String)] {
276 &self.labels
277 }
278
279 pub fn endpoint(&self, endpoint: impl Into<String>) -> Endpoint {
280 let endpoint = Endpoint {
281 component: self.clone(),
282 name: endpoint.into(),
283 labels: Vec::new(),
284 metrics_registry: crate::MetricsRegistry::new(),
285 };
286 self.get_metrics_registry()
288 .add_child_registry(endpoint.get_metrics_registry());
289 endpoint
290 }
291
292 pub async fn list_instances(&self) -> anyhow::Result<Vec<Instance>> {
293 let discovery = self.drt.discovery();
294
295 let discovery_query = crate::discovery::DiscoveryQuery::ComponentEndpoints {
296 namespace: self.namespace.name(),
297 component: self.name.clone(),
298 };
299
300 let discovery_instances = discovery.list(discovery_query).await?;
301
302 let mut instances: Vec<Instance> = discovery_instances
304 .into_iter()
305 .filter_map(|di| match di {
306 crate::discovery::DiscoveryInstance::Endpoint(instance) => Some(instance),
307 _ => None, })
309 .collect();
310
311 instances.sort();
312 Ok(instances)
313 }
314}
315
316impl ComponentBuilder {
317 pub fn from_runtime(drt: Arc<DistributedRuntime>) -> Self {
318 Self::default().drt(drt)
319 }
320
321 pub fn build(self) -> Result<Component, anyhow::Error> {
322 let component = self.build_internal()?;
323 let drt = component.drt();
327 if drt.request_plane().is_nats() {
328 let mut rx = drt.register_nats_service(component.clone());
329 let result = tokio::task::block_in_place(|| rx.blocking_recv());
334 match result {
335 Some(Ok(())) => {
336 tracing::debug!(
337 component = component.service_name(),
338 "NATS service registration completed"
339 );
340 }
341 Some(Err(e)) => {
342 return Err(anyhow::anyhow!(
343 "NATS service registration failed for component '{}': {}",
344 component.service_name(),
345 e
346 ));
347 }
348 None => {
349 return Err(anyhow::anyhow!(
350 "NATS service registration channel closed unexpectedly for component '{}'",
351 component.service_name()
352 ));
353 }
354 }
355 }
356 Ok(component)
357 }
358}
359
360#[derive(Debug, Clone)]
361pub struct Endpoint {
362 component: Component,
363
364 name: String,
367
368 labels: Vec<(String, String)>,
370
371 metrics_registry: crate::MetricsRegistry,
373}
374
375impl Hash for Endpoint {
376 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
377 self.component.hash(state);
378 self.name.hash(state);
379 }
380}
381
382impl PartialEq for Endpoint {
383 fn eq(&self, other: &Self) -> bool {
384 self.component == other.component && self.name == other.name
385 }
386}
387
388impl Eq for Endpoint {}
389
390impl DistributedRuntimeProvider for Endpoint {
391 fn drt(&self) -> &DistributedRuntime {
392 self.component.drt()
393 }
394}
395
396impl RuntimeProvider for Endpoint {
397 fn rt(&self) -> &Runtime {
398 self.component.rt()
399 }
400}
401
402impl MetricsHierarchy for Endpoint {
403 fn basename(&self) -> String {
404 self.name.clone()
405 }
406
407 fn parent_hierarchies(&self) -> Vec<&dyn MetricsHierarchy> {
408 let mut parents = vec![];
409
410 parents.extend(self.component.parent_hierarchies());
412
413 parents.push(&self.component as &dyn MetricsHierarchy);
415
416 parents
417 }
418
419 fn get_metrics_registry(&self) -> &MetricsRegistry {
420 &self.metrics_registry
421 }
422
423 fn connection_id(&self) -> Option<u64> {
424 Some(self.component.drt().connection_id())
425 }
426}
427
428impl Endpoint {
429 pub fn id(&self) -> EndpointId {
430 EndpointId {
431 namespace: self.component.namespace().name().to_string(),
432 component: self.component.name().to_string(),
433 name: self.name().to_string(),
434 }
435 }
436
437 pub fn name(&self) -> &str {
438 &self.name
439 }
440
441 pub fn component(&self) -> &Component {
442 &self.component
443 }
444
445 pub async fn client(&self) -> anyhow::Result<client::Client> {
446 client::Client::new(self.clone()).await
447 }
448
449 pub fn endpoint_builder(&self) -> endpoint::EndpointConfigBuilder {
450 endpoint::EndpointConfigBuilder::from_endpoint(self.clone())
451 }
452}
453
454#[derive(Builder, Clone, Validate)]
455#[builder(pattern = "owned")]
456pub struct Namespace {
457 #[builder(private)]
458 runtime: Arc<DistributedRuntime>,
459
460 #[validate(custom(function = "validate_allowed_chars"))]
461 name: String,
462
463 #[builder(default = "None")]
464 parent: Option<Arc<Namespace>>,
465
466 #[builder(default = "Vec::new()")]
468 labels: Vec<(String, String)>,
469
470 #[builder(default = "crate::MetricsRegistry::new()")]
472 metrics_registry: crate::MetricsRegistry,
473
474 #[builder(default = "Arc::new(DashMap::new())")]
479 component_cache: Arc<DashMap<String, Component>>,
480}
481
482impl DistributedRuntimeProvider for Namespace {
483 fn drt(&self) -> &DistributedRuntime {
484 &self.runtime
485 }
486}
487
488impl std::fmt::Debug for Namespace {
489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490 write!(
491 f,
492 "Namespace {{ name: {}; parent: {:?} }}",
493 self.name, self.parent
494 )
495 }
496}
497
498impl RuntimeProvider for Namespace {
499 fn rt(&self) -> &Runtime {
500 self.runtime.rt()
501 }
502}
503
504impl std::fmt::Display for Namespace {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 write!(f, "{}", self.name)
507 }
508}
509
510impl Namespace {
511 pub(crate) fn new(runtime: DistributedRuntime, name: String) -> anyhow::Result<Self> {
512 let ns = NamespaceBuilder::default()
513 .runtime(Arc::new(runtime))
514 .name(name)
515 .build()?;
516 ns.drt()
518 .get_metrics_registry()
519 .add_child_registry(ns.get_metrics_registry());
520 Ok(ns)
521 }
522
523 pub fn component(&self, name: impl Into<String>) -> anyhow::Result<Component> {
529 let name = name.into();
530
531 if let Some(cached) = self.component_cache.get(&name) {
534 return Ok(cached.value().clone());
535 }
536
537 let component = ComponentBuilder::from_runtime(self.runtime.clone())
539 .name(&name)
540 .namespace(self.clone())
541 .build()?;
542
543 self.get_metrics_registry()
545 .add_child_registry(component.get_metrics_registry());
546
547 self.component_cache.insert(name, component.clone());
551
552 Ok(component)
553 }
554
555 pub fn namespace(&self, name: impl Into<String>) -> anyhow::Result<Namespace> {
557 let child = NamespaceBuilder::default()
558 .runtime(self.runtime.clone())
559 .name(name.into())
560 .parent(Some(Arc::new(self.clone())))
561 .build()?;
562 self.get_metrics_registry()
564 .add_child_registry(child.get_metrics_registry());
565 Ok(child)
566 }
567
568 pub fn name(&self) -> String {
569 match &self.parent {
570 Some(parent) => format!("{}.{}", parent.name(), self.name),
571 None => self.name.clone(),
572 }
573 }
574}
575
576fn validate_allowed_chars(input: &str) -> Result<(), ValidationError> {
578 let regex = regex::Regex::new(r"^[a-z0-9-_]+$").unwrap();
580
581 if regex.is_match(input) {
582 Ok(())
583 } else {
584 Err(ValidationError::new("invalid_characters"))
585 }
586}