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 use client::{Client, RoutingInstanceCounts};
71pub use endpoint::{StartedEndpoint, build_transport_type};
72
73#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
74#[serde(rename_all = "snake_case")]
75pub enum TransportType {
76 #[serde(rename = "nats_tcp")]
77 Nats(String),
78 Tcp(String),
79}
80
81impl TransportType {
82 pub fn address(&self) -> &str {
83 match self {
84 TransportType::Nats(address) | TransportType::Tcp(address) => address,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
90#[serde(rename_all = "snake_case")]
91pub enum DeviceType {
92 Cpu,
93 Cuda,
94}
95
96#[derive(Default)]
97pub struct RegistryInner {
98 pub(crate) services: HashMap<String, Service>,
99}
100
101#[derive(Clone)]
102pub struct Registry {
103 pub(crate) inner: Arc<tokio::sync::Mutex<RegistryInner>>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107pub struct Instance {
108 pub component: String,
109 pub endpoint: String,
110 pub namespace: String,
111 pub instance_id: u64,
112 pub transport: TransportType,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub device_type: Option<DeviceType>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub request_plane_codec: Option<RequestPlanePayloadCodec>,
119}
120
121impl Instance {
122 pub fn id(&self) -> u64 {
123 self.instance_id
124 }
125
126 pub fn endpoint_id(&self) -> EndpointId {
127 EndpointId {
128 namespace: self.namespace.clone(),
129 component: self.component.clone(),
130 name: self.endpoint.clone(),
131 }
132 }
133
134 pub fn endpoint_instance_id(&self) -> crate::discovery::EndpointInstanceId {
135 crate::discovery::EndpointInstanceId {
136 namespace: self.namespace.clone(),
137 component: self.component.clone(),
138 endpoint: self.endpoint.clone(),
139 instance_id: self.instance_id,
140 }
141 }
142}
143
144impl fmt::Display for Instance {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 write!(
147 f,
148 "{}/{}/{}/{}",
149 self.namespace, self.component, self.endpoint, self.instance_id
150 )
151 }
152}
153
154impl std::cmp::Ord for Instance {
156 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
157 self.to_string().cmp(&other.to_string())
158 }
159}
160
161impl PartialOrd for Instance {
162 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
163 Some(self.cmp(other))
165 }
166}
167
168#[derive(Educe, Builder, Clone, Validate)]
174#[educe(Debug)]
175#[builder(pattern = "owned", build_fn(private, name = "build_internal"))]
176pub struct Component {
177 #[builder(private)]
178 #[educe(Debug(ignore))]
179 drt: Arc<DistributedRuntime>,
180
181 #[builder(setter(into))]
183 #[validate(custom(function = "validate_allowed_chars"))]
184 name: String,
185
186 #[builder(default = "Vec::new()")]
188 labels: Vec<(String, String)>,
189
190 #[builder(setter(into))]
193 namespace: Namespace,
194
195 #[builder(default = "crate::MetricsRegistry::new()")]
197 metrics_registry: crate::MetricsRegistry,
198}
199
200impl Hash for Component {
201 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
202 self.namespace.name().hash(state);
203 self.name.hash(state);
204 }
205}
206
207impl PartialEq for Component {
208 fn eq(&self, other: &Self) -> bool {
209 self.namespace.name() == other.namespace.name() && self.name == other.name
210 }
211}
212
213impl Eq for Component {}
214
215impl std::fmt::Display for Component {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 write!(f, "{}.{}", self.namespace.name(), self.name)
218 }
219}
220
221impl DistributedRuntimeProvider for Component {
222 fn drt(&self) -> &DistributedRuntime {
223 &self.drt
224 }
225}
226
227impl RuntimeProvider for Component {
228 fn rt(&self) -> &Runtime {
229 self.drt.rt()
230 }
231}
232
233impl MetricsHierarchy for Component {
234 fn basename(&self) -> String {
235 self.name.clone()
236 }
237
238 fn parent_hierarchies(&self) -> Vec<&dyn MetricsHierarchy> {
239 let mut parents = vec![];
240
241 parents.extend(self.namespace.parent_hierarchies());
243
244 parents.push(&self.namespace as &dyn MetricsHierarchy);
246
247 parents
248 }
249
250 fn get_metrics_registry(&self) -> &MetricsRegistry {
251 &self.metrics_registry
252 }
253
254 fn connection_id(&self) -> Option<u64> {
255 Some(self.drt.connection_id())
256 }
257}
258
259impl Component {
260 pub fn service_name(&self) -> String {
261 let service_name = format!("{}_{}", self.namespace.name(), self.name);
262 Slug::slugify(&service_name).to_string()
263 }
264
265 pub fn namespace(&self) -> &Namespace {
266 &self.namespace
267 }
268
269 pub fn name(&self) -> &str {
270 &self.name
271 }
272
273 pub fn labels(&self) -> &[(String, String)] {
274 &self.labels
275 }
276
277 pub fn endpoint(&self, endpoint: impl Into<String>) -> Endpoint {
278 let endpoint = Endpoint {
279 component: self.clone(),
280 name: endpoint.into(),
281 labels: Vec::new(),
282 metrics_registry: crate::MetricsRegistry::new(),
283 };
284 self.get_metrics_registry()
286 .add_child_registry(endpoint.get_metrics_registry());
287 endpoint
288 }
289
290 pub async fn list_instances(&self) -> anyhow::Result<Vec<Instance>> {
291 let discovery = self.drt.discovery();
292
293 let discovery_query = crate::discovery::DiscoveryQuery::ComponentEndpoints {
294 namespace: self.namespace.name(),
295 component: self.name.clone(),
296 };
297
298 let discovery_instances = discovery.list(discovery_query).await?;
299
300 let mut instances: Vec<Instance> = discovery_instances
302 .into_iter()
303 .filter_map(|di| match di {
304 crate::discovery::DiscoveryInstance::Endpoint(instance) => Some(instance),
305 _ => None, })
307 .collect();
308
309 instances.sort();
310 Ok(instances)
311 }
312}
313
314impl ComponentBuilder {
315 pub fn from_runtime(drt: Arc<DistributedRuntime>) -> Self {
316 Self::default().drt(drt)
317 }
318
319 pub fn build(self) -> Result<Component, anyhow::Error> {
320 let component = self.build_internal()?;
321 let drt = component.drt();
325 if drt.request_plane().is_nats() {
326 let mut rx = drt.register_nats_service(component.clone());
327 let result = tokio::task::block_in_place(|| rx.blocking_recv());
332 match result {
333 Some(Ok(())) => {
334 tracing::debug!(
335 component = component.service_name(),
336 "NATS service registration completed"
337 );
338 }
339 Some(Err(e)) => {
340 return Err(anyhow::anyhow!(
341 "NATS service registration failed for component '{}': {}",
342 component.service_name(),
343 e
344 ));
345 }
346 None => {
347 return Err(anyhow::anyhow!(
348 "NATS service registration channel closed unexpectedly for component '{}'",
349 component.service_name()
350 ));
351 }
352 }
353 }
354 Ok(component)
355 }
356}
357
358#[derive(Debug, Clone)]
359pub struct Endpoint {
360 component: Component,
361
362 name: String,
365
366 labels: Vec<(String, String)>,
368
369 metrics_registry: crate::MetricsRegistry,
371}
372
373impl Hash for Endpoint {
374 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
375 self.component.hash(state);
376 self.name.hash(state);
377 }
378}
379
380impl PartialEq for Endpoint {
381 fn eq(&self, other: &Self) -> bool {
382 self.component == other.component && self.name == other.name
383 }
384}
385
386impl Eq for Endpoint {}
387
388impl DistributedRuntimeProvider for Endpoint {
389 fn drt(&self) -> &DistributedRuntime {
390 self.component.drt()
391 }
392}
393
394impl RuntimeProvider for Endpoint {
395 fn rt(&self) -> &Runtime {
396 self.component.rt()
397 }
398}
399
400impl MetricsHierarchy for Endpoint {
401 fn basename(&self) -> String {
402 self.name.clone()
403 }
404
405 fn parent_hierarchies(&self) -> Vec<&dyn MetricsHierarchy> {
406 let mut parents = vec![];
407
408 parents.extend(self.component.parent_hierarchies());
410
411 parents.push(&self.component as &dyn MetricsHierarchy);
413
414 parents
415 }
416
417 fn get_metrics_registry(&self) -> &MetricsRegistry {
418 &self.metrics_registry
419 }
420
421 fn connection_id(&self) -> Option<u64> {
422 Some(self.component.drt().connection_id())
423 }
424}
425
426impl Endpoint {
427 pub fn id(&self) -> EndpointId {
428 EndpointId {
429 namespace: self.component.namespace().name().to_string(),
430 component: self.component.name().to_string(),
431 name: self.name().to_string(),
432 }
433 }
434
435 pub fn name(&self) -> &str {
436 &self.name
437 }
438
439 pub fn component(&self) -> &Component {
440 &self.component
441 }
442
443 pub async fn client(&self) -> anyhow::Result<client::Client> {
444 client::Client::new(self.clone()).await
445 }
446
447 pub async fn client_with_cancellation(
455 &self,
456 cancel_token: tokio_util::sync::CancellationToken,
457 ) -> anyhow::Result<client::Client> {
458 client::Client::with_cancellation(self.clone(), cancel_token).await
459 }
460
461 pub fn endpoint_builder(&self) -> endpoint::EndpointConfigBuilder {
462 endpoint::EndpointConfigBuilder::from_endpoint(self.clone())
463 }
464}
465
466#[derive(Builder, Clone, Validate)]
467#[builder(pattern = "owned")]
468pub struct Namespace {
469 #[builder(private)]
470 runtime: Arc<DistributedRuntime>,
471
472 #[validate(custom(function = "validate_allowed_chars"))]
473 name: String,
474
475 #[builder(default = "None")]
476 parent: Option<Arc<Namespace>>,
477
478 #[builder(default = "Vec::new()")]
480 labels: Vec<(String, String)>,
481
482 #[builder(default = "crate::MetricsRegistry::new()")]
484 metrics_registry: crate::MetricsRegistry,
485
486 #[builder(default = "Arc::new(DashMap::new())")]
491 component_cache: Arc<DashMap<String, Component>>,
492}
493
494impl DistributedRuntimeProvider for Namespace {
495 fn drt(&self) -> &DistributedRuntime {
496 &self.runtime
497 }
498}
499
500impl std::fmt::Debug for Namespace {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 write!(
503 f,
504 "Namespace {{ name: {}; parent: {:?} }}",
505 self.name, self.parent
506 )
507 }
508}
509
510impl RuntimeProvider for Namespace {
511 fn rt(&self) -> &Runtime {
512 self.runtime.rt()
513 }
514}
515
516impl std::fmt::Display for Namespace {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 write!(f, "{}", self.name)
519 }
520}
521
522impl Namespace {
523 pub(crate) fn new(runtime: DistributedRuntime, name: String) -> anyhow::Result<Self> {
524 let ns = NamespaceBuilder::default()
525 .runtime(Arc::new(runtime))
526 .name(name)
527 .build()?;
528 ns.drt()
530 .get_metrics_registry()
531 .add_child_registry(ns.get_metrics_registry());
532 Ok(ns)
533 }
534
535 pub fn component(&self, name: impl Into<String>) -> anyhow::Result<Component> {
541 let name = name.into();
542
543 if let Some(cached) = self.component_cache.get(&name) {
546 return Ok(cached.value().clone());
547 }
548
549 let component = ComponentBuilder::from_runtime(self.runtime.clone())
551 .name(&name)
552 .namespace(self.clone())
553 .build()?;
554
555 self.get_metrics_registry()
557 .add_child_registry(component.get_metrics_registry());
558
559 self.component_cache.insert(name, component.clone());
563
564 Ok(component)
565 }
566
567 pub fn namespace(&self, name: impl Into<String>) -> anyhow::Result<Namespace> {
569 let child = NamespaceBuilder::default()
570 .runtime(self.runtime.clone())
571 .name(name.into())
572 .parent(Some(Arc::new(self.clone())))
573 .build()?;
574 self.get_metrics_registry()
576 .add_child_registry(child.get_metrics_registry());
577 Ok(child)
578 }
579
580 pub fn name(&self) -> String {
581 match &self.parent {
582 Some(parent) => format!("{}.{}", parent.name(), self.name),
583 None => self.name.clone(),
584 }
585 }
586}
587
588fn validate_allowed_chars(input: &str) -> Result<(), ValidationError> {
590 let regex = regex::Regex::new(r"^[a-z0-9-_]+$").unwrap();
592
593 if regex.is_match(input) {
594 Ok(())
595 } else {
596 Err(ValidationError::new("invalid_characters"))
597 }
598}