1use crate::component::{
5 self, Component, ComponentBuilder, Endpoint, EndpointDiscoverySource, Instance, Namespace,
6 RoutingOccupancyState,
7};
8use crate::config::environment_names::tcp_response_stream;
9use crate::pipeline::PipelineError;
10use crate::pipeline::network::manager::NetworkManager;
11use crate::service::{ServiceClient, ServiceSet};
12use crate::storage::kv;
13use crate::{discovery, system_status_server, transports};
14use crate::{
15 discovery::{Discovery, DiscoverySpec, EndpointRegistrationLease, EndpointRegistrationManager},
16 metrics::PrometheusUpdateCallback,
17 metrics::{MetricsHierarchy, MetricsRegistry},
18 transports::{etcd, nats, tcp},
19};
20
21use super::utils::GracefulShutdownTracker;
22use crate::SystemHealth;
23use crate::runtime::Runtime;
24
25use async_once_cell::OnceCell;
27
28use std::fmt;
29use std::sync::{Arc, OnceLock, Weak};
30use std::time::Duration;
31use tokio::sync::watch::Receiver;
32
33use anyhow::Result;
34use derive_getters::Dissolve;
35use figment::error;
36use std::collections::HashMap;
37use tokio::sync::Mutex;
38use tokio_util::sync::CancellationToken;
39
40type EndpointDiscoverySourceMap = HashMap<Endpoint, Weak<EndpointDiscoverySource>>;
41type RoutingOccupancyMap = HashMap<Endpoint, Weak<RoutingOccupancyState>>;
42
43#[derive(Clone)]
53pub struct DistributedRuntime {
54 runtime: Runtime,
56
57 nats_client: Option<transports::nats::Client>,
58 network_manager: Arc<NetworkManager>,
59 tcp_server: Arc<OnceCell<Arc<transports::tcp::server::TcpStreamServer>>>,
60 system_status_server: Arc<OnceLock<Arc<system_status_server::SystemStatusServerInfo>>>,
61 request_plane: RequestPlaneMode,
62
63 discovery_client: Arc<dyn discovery::Discovery>,
65 endpoint_registrations: Arc<EndpointRegistrationManager>,
66
67 discovery_metadata: Option<Arc<tokio::sync::RwLock<discovery::DiscoveryMetadata>>>,
70
71 component_registry: component::Registry,
77
78 endpoint_discovery_sources: Arc<tokio::sync::Mutex<EndpointDiscoverySourceMap>>,
79 routing_occupancy_states: Arc<tokio::sync::Mutex<RoutingOccupancyMap>>,
80
81 system_health: Arc<parking_lot::Mutex<SystemHealth>>,
83
84 local_endpoint_registry: crate::local_endpoint_registry::LocalEndpointRegistry,
86
87 metrics_registry: MetricsRegistry,
89
90 engine_routes: crate::engine_routes::EngineRouteRegistry,
92
93 metadata_artifacts: crate::metadata_registry::MetadataArtifactRegistry,
95
96 event_transport_kind: crate::discovery::EventTransportKind,
99}
100
101impl MetricsHierarchy for DistributedRuntime {
102 fn basename(&self) -> String {
103 "".to_string() }
105
106 fn parent_hierarchies(&self) -> Vec<&dyn MetricsHierarchy> {
107 vec![] }
109
110 fn get_metrics_registry(&self) -> &MetricsRegistry {
111 &self.metrics_registry
112 }
113
114 fn connection_id(&self) -> Option<u64> {
115 Some(self.discovery_client.instance_id())
116 }
117}
118
119impl std::fmt::Debug for DistributedRuntime {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "DistributedRuntime")
122 }
123}
124
125impl DistributedRuntime {
126 pub async fn new(runtime: Runtime, config: DistributedConfig) -> Result<Self> {
127 let (discovery_backend, nats_config, request_plane, event_transport_kind) =
128 config.dissolve();
129
130 let nats_client = match nats_config {
131 Some(nc) => Some(nc.connect().await?),
132 None => None,
133 };
134
135 let config = crate::config::RuntimeConfig::from_settings().unwrap_or_default();
137 let cancel_token = if config.system_server_enabled() {
140 Some(runtime.clone().child_token())
141 } else {
142 None
143 };
144 let starting_health_status = config.starting_health_status.clone();
145 let use_endpoint_health_status = config.use_endpoint_health_status.clone();
146 let health_endpoint_path = config.system_health_path.clone();
147 let live_endpoint_path = config.system_live_path.clone();
148 let system_health = Arc::new(parking_lot::Mutex::new(SystemHealth::new(
149 starting_health_status,
150 use_endpoint_health_status,
151 config.health_check_enabled,
152 health_endpoint_path,
153 live_endpoint_path,
154 )));
155
156 let (discovery_client, discovery_metadata) = match discovery_backend {
158 DiscoveryBackend::Kubernetes => {
159 tracing::info!("Initializing Kubernetes discovery backend");
160 let metadata = Arc::new(tokio::sync::RwLock::new(
161 crate::discovery::DiscoveryMetadata::new(),
162 ));
163 let client = crate::discovery::KubeDiscoveryClient::new(
164 metadata.clone(),
165 runtime.primary_token(),
166 )
167 .await
168 .inspect_err(
169 |err| tracing::error!(%err, "Failed to initialize Kubernetes discovery client"),
170 )?;
171 (Arc::new(client) as Arc<dyn Discovery>, Some(metadata))
172 }
173 DiscoveryBackend::KvStore(kv_selector) => {
174 tracing::info!("Initializing KV store discovery backend: {kv_selector}");
175 let runtime_clone = runtime.clone();
176 let store = match kv_selector {
177 kv::Selector::Etcd(etcd_config) => {
178 let etcd_client = etcd::Client::new(*etcd_config, runtime_clone).await.inspect_err(|err|
179 tracing::error!(%err, "Could not connect to etcd. Pass `--discovery-backend ..` to use a different backend or start etcd."))?;
180 kv::Manager::etcd(etcd_client)
181 }
182 kv::Selector::File(root) => kv::Manager::file(runtime.primary_token(), root),
183 kv::Selector::Memory => kv::Manager::memory(),
184 };
185 use crate::discovery::KVStoreDiscovery;
186 (
187 Arc::new(KVStoreDiscovery::new(store, runtime.primary_token()))
188 as Arc<dyn Discovery>,
189 None,
190 )
191 }
192 };
193
194 let component_registry = component::Registry::new();
195
196 let network_manager = NetworkManager::new(
198 runtime.child_token(),
199 nats_client.clone().map(|c| c.client().clone()),
200 component_registry.clone(),
201 request_plane,
202 );
203
204 let endpoint_registrations = EndpointRegistrationManager::new(
205 discovery_client.clone(),
206 runtime.secondary(),
207 runtime.primary_token(),
208 );
209 let distributed_runtime = Self {
210 runtime,
211 network_manager: Arc::new(network_manager),
212 nats_client,
213 tcp_server: Arc::new(OnceCell::new()),
214 system_status_server: Arc::new(OnceLock::new()),
215 discovery_client,
216 endpoint_registrations,
217 discovery_metadata,
218 component_registry,
219 endpoint_discovery_sources: Arc::new(Mutex::new(HashMap::new())),
220 routing_occupancy_states: Arc::new(Mutex::new(HashMap::new())),
221 metrics_registry: crate::MetricsRegistry::new(),
222 system_health,
223 request_plane,
224 local_endpoint_registry: crate::local_endpoint_registry::LocalEndpointRegistry::new(),
225 engine_routes: crate::engine_routes::EngineRouteRegistry::new(),
226 metadata_artifacts: crate::metadata_registry::MetadataArtifactRegistry::new(),
227 event_transport_kind,
228 };
229
230 distributed_runtime
232 .system_health
233 .lock()
234 .initialize_uptime_gauge(&distributed_runtime)?;
235
236 {
239 let system_health = distributed_runtime.system_health.clone();
240 distributed_runtime
241 .metrics_registry
242 .add_update_callback(std::sync::Arc::new(move || {
243 system_health.lock().update_uptime_gauge();
244 Ok(())
245 }));
246 }
247
248 if let Some(cancel_token) = cancel_token {
250 let host = config.system_host.clone();
252 let port = config.system_port as u16;
253
254 match crate::system_status_server::spawn_system_status_server(
256 &host,
257 port,
258 cancel_token,
259 Arc::new(distributed_runtime.clone()),
260 distributed_runtime.discovery_metadata.clone(),
261 )
262 .await
263 {
264 Ok((addr, handle)) => {
265 tracing::info!("System status server started successfully on {addr}");
266
267 let system_status_server_info =
269 crate::system_status_server::SystemStatusServerInfo::new(
270 addr,
271 Some(handle),
272 );
273
274 distributed_runtime
276 .system_status_server
277 .set(Arc::new(system_status_server_info))
278 .expect("System status server info should only be set once");
279 }
280 Err(e) => {
281 tracing::error!("System status server startup failed: {e}");
282 }
283 }
284 } else {
285 tracing::debug!(
287 "System status server HTTP endpoints disabled, but uptime metrics are being tracked"
288 );
289 }
290
291 if config.health_check_enabled {
293 let health_check_config = crate::health_check::HealthCheckConfig {
294 canary_wait_time: std::time::Duration::from_secs(config.canary_wait_time_secs),
295 request_timeout: std::time::Duration::from_secs(
296 config.health_check_request_timeout_secs,
297 ),
298 };
299
300 match crate::health_check::start_health_check_manager(
302 distributed_runtime.clone(),
303 Some(health_check_config),
304 )
305 .await
306 {
307 Ok(()) => tracing::info!(
308 "Health check manager started (canary_wait_time: {}s, request_timeout: {}s)",
309 config.canary_wait_time_secs,
310 config.health_check_request_timeout_secs
311 ),
312 Err(e) => tracing::error!("Health check manager failed to start: {e}"),
313 }
314 }
315
316 Ok(distributed_runtime)
317 }
318
319 pub async fn from_settings(runtime: Runtime) -> Result<Self> {
320 let config = DistributedConfig::from_settings();
321 Self::new(runtime, config).await
322 }
323
324 pub fn runtime(&self) -> &Runtime {
325 &self.runtime
326 }
327
328 pub fn primary_token(&self) -> CancellationToken {
329 self.runtime.primary_token()
330 }
331
332 pub fn component_registry(&self) -> &component::Registry {
335 &self.component_registry
336 }
337
338 pub fn system_health(&self) -> Arc<parking_lot::Mutex<SystemHealth>> {
340 self.system_health.clone()
341 }
342
343 pub fn local_endpoint_registry(
345 &self,
346 ) -> &crate::local_endpoint_registry::LocalEndpointRegistry {
347 &self.local_endpoint_registry
348 }
349
350 pub fn engine_routes(&self) -> &crate::engine_routes::EngineRouteRegistry {
352 &self.engine_routes
353 }
354
355 pub fn metadata_artifacts(&self) -> &crate::metadata_registry::MetadataArtifactRegistry {
356 &self.metadata_artifacts
357 }
358
359 pub fn connection_id(&self) -> u64 {
364 self.discovery_client.instance_id()
365 }
366
367 pub fn shutdown(&self) {
368 self.runtime.shutdown();
369 self.discovery_client.shutdown();
370 }
371
372 pub fn namespace(&self, name: impl Into<String>) -> Result<Namespace> {
374 Namespace::new(self.clone(), name.into())
375 }
376
377 pub fn discovery(&self) -> Arc<dyn Discovery> {
379 self.discovery_client.clone()
380 }
381
382 pub async fn register_endpoint_lease(
384 &self,
385 spec: DiscoverySpec,
386 ) -> Result<EndpointRegistrationLease> {
387 self.endpoint_registrations.register(spec).await
388 }
389
390 pub async fn tcp_server(&self) -> Result<Arc<tcp::server::TcpStreamServer>> {
391 Ok(self
392 .tcp_server
393 .get_or_try_init(async move {
394 let port = match std::env::var(tcp_response_stream::DYN_TCP_RESPONSE_STREAM_PORT) {
395 Ok(p) => p.parse::<u16>().map_err(|_| {
396 PipelineError::Generic(format!(
397 "invalid {}: '{}' is not a valid port number",
398 tcp_response_stream::DYN_TCP_RESPONSE_STREAM_PORT,
399 p
400 ))
401 })?,
402 Err(_) => 0,
403 };
404 let interface = std::env::var(tcp_response_stream::DYN_TCP_RESPONSE_STREAM_HOST)
405 .ok()
406 .filter(|h| !h.is_empty());
407
408 let host_suffix = interface
409 .as_ref()
410 .map_or(String::new(), |h| format!(" on host {h}"));
411 if port == 0 {
412 tracing::info!(
413 "TCP response stream server using OS-assigned port{host_suffix}"
414 );
415 } else {
416 tracing::info!(
417 "TCP response stream server using fixed port {port}{host_suffix}"
418 );
419 }
420
421 let options = tcp::server::ServerOptions { port, interface };
422 let server = tcp::server::TcpStreamServer::new(options).await?;
423 Ok::<_, PipelineError>(server)
424 })
425 .await?
426 .clone())
427 }
428
429 pub fn network_manager(&self) -> Arc<NetworkManager> {
434 self.network_manager.clone()
435 }
436
437 pub async fn request_plane_server(
441 &self,
442 ) -> Result<Arc<dyn crate::pipeline::network::ingress::unified_server::RequestPlaneServer>>
443 {
444 self.network_manager().server().await
445 }
446
447 pub fn system_status_server_info(
449 &self,
450 ) -> Option<Arc<crate::system_status_server::SystemStatusServerInfo>> {
451 self.system_status_server.get().cloned()
452 }
453
454 pub fn request_plane(&self) -> RequestPlaneMode {
456 self.request_plane
457 }
458
459 pub fn default_event_transport_kind(&self) -> crate::discovery::EventTransportKind {
467 self.event_transport_kind
468 }
469
470 pub fn child_token(&self) -> CancellationToken {
471 self.runtime.child_token()
472 }
473
474 pub(crate) fn graceful_shutdown_tracker(&self) -> Arc<GracefulShutdownTracker> {
475 self.runtime.graceful_shutdown_tracker()
476 }
477
478 pub(crate) fn endpoint_discovery_sources(&self) -> Arc<Mutex<EndpointDiscoverySourceMap>> {
479 self.endpoint_discovery_sources.clone()
480 }
481
482 pub fn register_graceful_task(&self) -> crate::utils::GracefulTaskGuard {
488 self.runtime.graceful_shutdown_tracker().register_task()
489 }
490
491 pub(crate) fn routing_occupancy_states(&self) -> Arc<Mutex<RoutingOccupancyMap>> {
492 self.routing_occupancy_states.clone()
493 }
494
495 pub async fn kv_router_nats_publish(
501 &self,
502 subject: String,
503 payload: bytes::Bytes,
504 ) -> anyhow::Result<()> {
505 self.kv_router_nats_publish_subject(subject.into(), payload)
506 .await
507 }
508
509 pub(crate) async fn kv_router_nats_publish_subject(
510 &self,
511 subject: async_nats::Subject,
512 payload: bytes::Bytes,
513 ) -> anyhow::Result<()> {
514 let Some(nats_client) = self.nats_client.as_ref() else {
515 tracing::trace!("Skipping NATS publish (NATS not configured): {subject}");
517 return Ok(());
518 };
519 Ok(nats_client.client().publish(subject, payload).await?)
520 }
521
522 pub(crate) async fn kv_router_nats_subscribe(
525 &self,
526 subject: String,
527 ) -> Result<async_nats::Subscriber> {
528 let Some(nats_client) = self.nats_client.as_ref() else {
529 anyhow::bail!("KV router's EventSubscriber requires NATS");
530 };
531 Ok(nats_client.client().subscribe(subject).await?)
532 }
533
534 pub async fn kv_router_nats_request(
538 &self,
539 subject: String,
540 payload: bytes::Bytes,
541 timeout: std::time::Duration,
542 ) -> anyhow::Result<async_nats::Message> {
543 let Some(nats_client) = self.nats_client.as_ref() else {
544 anyhow::bail!("KV router's request requires NATS");
545 };
546 let response =
547 tokio::time::timeout(timeout, nats_client.client().request(subject, payload))
548 .await
549 .map_err(|_| anyhow::anyhow!("Request timed out after {:?}", timeout))??;
550 Ok(response)
551 }
552
553 pub fn register_nats_service(
560 &self,
561 component: Component,
562 ) -> tokio::sync::mpsc::Receiver<Result<(), String>> {
563 let (tx, rx) = tokio::sync::mpsc::channel::<Result<(), String>>(1);
565
566 let drt = self.clone();
567 self.runtime().secondary().spawn(async move {
568 let service_name = component.service_name();
569
570 if drt
572 .component_registry()
573 .inner
574 .lock()
575 .await
576 .services
577 .contains_key(&service_name)
578 {
579 tracing::trace!("Service {service_name} already exists");
582 let _ = tx.send(Ok(())).await;
584 return;
585 }
586
587 let Some(nats_client) = drt.nats_client.as_ref() else {
588 tracing::error!("Cannot create NATS service without NATS.");
589 let _ = tx
590 .send(Err("Cannot create NATS service without NATS".to_string()))
591 .await;
592 return;
593 };
594 let description = None;
595 let nats_service = match crate::component::service::build_nats_service(
596 nats_client,
597 &component,
598 description,
599 )
600 .await
601 {
602 Ok(service) => service,
603 Err(err) => {
604 tracing::error!(error = %err, component = service_name, "Failed to build NATS service");
605 let _ = tx.send(Err(format!("Failed to build NATS service: {err}"))).await;
606 return;
607 }
608 };
609
610 let mut guard = drt.component_registry().inner.lock().await;
611 if !guard.services.contains_key(&service_name) {
612 guard.services.insert(service_name.clone(), nats_service);
614
615 tracing::info!("Added NATS service {service_name}");
616
617 drop(guard);
618 } else {
619 drop(guard);
620 let _ = nats_service.stop().await;
621 }
625
626 let _ = tx.send(Ok(())).await;
628 });
629
630 rx
631 }
632}
633
634#[derive(Clone, Debug)]
636pub enum DiscoveryBackend {
637 Kubernetes,
639 KvStore(kv::Selector),
641}
642
643impl DiscoveryBackend {
644 pub fn is_local(&self) -> bool {
648 matches!(
649 self,
650 DiscoveryBackend::KvStore(kv::Selector::File(_))
651 | DiscoveryBackend::KvStore(kv::Selector::Memory)
652 )
653 }
654
655 pub fn resolve_event_transport_kind(&self) -> crate::discovery::EventTransportKind {
664 use crate::config::environment_names::event_plane::DYN_EVENT_PLANE;
665 use crate::discovery::EventTransportKind;
666 match std::env::var(DYN_EVENT_PLANE).as_deref() {
667 Ok("nats") => EventTransportKind::Nats,
668 Ok("zmq") => EventTransportKind::Zmq,
669 Ok("") | Err(_) => EventTransportKind::Zmq,
671 Ok(other) => {
672 tracing::warn!(
673 "Invalid DYN_EVENT_PLANE value '{}'. Valid values: 'nats', 'zmq'. \
674 Defaulting to ZMQ.",
675 other
676 );
677 EventTransportKind::Zmq
678 }
679 }
680 }
681}
682
683#[derive(Dissolve)]
684pub struct DistributedConfig {
685 pub discovery_backend: DiscoveryBackend,
686 pub nats_config: Option<nats::ClientOptions>,
687 pub request_plane: RequestPlaneMode,
688 pub event_transport_kind: crate::discovery::EventTransportKind,
693}
694
695impl DistributedConfig {
696 pub fn from_settings() -> DistributedConfig {
697 let request_plane = RequestPlaneMode::from_env();
698
699 let backend_str =
702 std::env::var("DYN_DISCOVERY_BACKEND").unwrap_or_else(|_| "etcd".to_string());
703
704 let discovery_backend = match backend_str.as_str() {
705 "kubernetes" => {
706 tracing::info!("Using Kubernetes discovery backend");
707 DiscoveryBackend::Kubernetes
708 }
709 other => {
710 let selector: kv::Selector = other.parse().unwrap_or_else(|_| {
711 panic!(
712 "Unknown DYN_DISCOVERY_BACKEND value: '{other}'. \
713 Valid options: kubernetes, etcd, file, mem"
714 )
715 });
716 DiscoveryBackend::KvStore(selector)
717 }
718 };
719
720 let event_transport_kind = discovery_backend.resolve_event_transport_kind();
724
725 let nats_enabled = request_plane.is_nats()
734 || std::env::var(crate::config::environment_names::nats::NATS_SERVER).is_ok()
735 || matches!(
736 event_transport_kind,
737 crate::discovery::EventTransportKind::Nats
738 );
739
740 DistributedConfig {
741 discovery_backend,
742 nats_config: if nats_enabled {
743 Some(nats::ClientOptions::default())
744 } else {
745 None
746 },
747 request_plane,
748 event_transport_kind,
749 }
750 }
751
752 pub fn for_cli() -> DistributedConfig {
753 let etcd_config = etcd::ClientOptions {
754 attach_lease: false,
755 ..Default::default()
756 };
757 let request_plane = RequestPlaneMode::from_env();
758 let discovery_backend =
759 DiscoveryBackend::KvStore(kv::Selector::Etcd(Box::new(etcd_config)));
760 let event_transport_kind = discovery_backend.resolve_event_transport_kind();
761 let nats_enabled = request_plane.is_nats()
762 || std::env::var(crate::config::environment_names::nats::NATS_SERVER).is_ok()
763 || matches!(
764 event_transport_kind,
765 crate::discovery::EventTransportKind::Nats
766 );
767 DistributedConfig {
768 discovery_backend,
769 nats_config: if nats_enabled {
770 Some(nats::ClientOptions::default())
771 } else {
772 None
773 },
774 request_plane,
775 event_transport_kind,
776 }
777 }
778
779 pub fn process_local() -> DistributedConfig {
782 DistributedConfig {
783 discovery_backend: DiscoveryBackend::KvStore(kv::Selector::Memory),
784 nats_config: None,
785 request_plane: RequestPlaneMode::Tcp,
788 event_transport_kind: crate::discovery::EventTransportKind::Zmq,
789 }
790 }
791}
792
793#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
799pub enum RequestPlaneMode {
800 Nats,
802 #[default]
804 Tcp,
805}
806
807impl fmt::Display for RequestPlaneMode {
808 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
809 match self {
810 Self::Nats => write!(f, "nats"),
811 Self::Tcp => write!(f, "tcp"),
812 }
813 }
814}
815
816impl std::str::FromStr for RequestPlaneMode {
817 type Err = anyhow::Error;
818
819 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
820 match s.to_lowercase().as_str() {
821 "nats" => Ok(Self::Nats),
822 "tcp" => Ok(Self::Tcp),
823 _ => Err(anyhow::anyhow!(
824 "Invalid request plane mode: '{}'. Valid options are: 'nats', 'tcp'",
825 s
826 )),
827 }
828 }
829}
830
831impl RequestPlaneMode {
832 fn from_env() -> Self {
835 std::env::var("DYN_REQUEST_PLANE")
836 .ok()
837 .and_then(|s| s.parse().ok())
838 .unwrap_or_default()
839 }
840
841 pub fn is_nats(&self) -> bool {
842 matches!(self, RequestPlaneMode::Nats)
843 }
844}
845
846pub mod distributed_test_utils {
847 #[cfg(feature = "integration")]
853 pub async fn create_test_drt_async() -> super::DistributedRuntime {
854 use crate::transports::nats;
855
856 let rt = crate::Runtime::from_current().unwrap();
857 let config = super::DistributedConfig {
858 discovery_backend: super::DiscoveryBackend::KvStore(
859 crate::storage::kv::Selector::Memory,
860 ),
861 nats_config: Some(nats::ClientOptions::default()),
862 request_plane: crate::distributed::RequestPlaneMode::default(),
863 event_transport_kind: crate::discovery::EventTransportKind::Nats,
864 };
865 super::DistributedRuntime::new(rt, config).await.unwrap()
866 }
867
868 pub async fn create_test_shared_drt_async(
875 store_path: &std::path::Path,
876 ) -> super::DistributedRuntime {
877 use crate::transports::nats;
878
879 let rt = crate::Runtime::from_current().unwrap();
880 let config = super::DistributedConfig {
881 discovery_backend: super::DiscoveryBackend::KvStore(
882 crate::storage::kv::Selector::File(store_path.to_path_buf()),
883 ),
884 nats_config: Some(nats::ClientOptions::default()),
885 request_plane: crate::distributed::RequestPlaneMode::default(),
886 event_transport_kind: crate::discovery::EventTransportKind::Nats,
887 };
888 super::DistributedRuntime::new(rt, config).await.unwrap()
889 }
890}
891
892#[cfg(all(test, feature = "integration"))]
893mod tests {
894 use super::RequestPlaneMode;
895 use super::distributed_test_utils::create_test_drt_async;
896
897 #[tokio::test]
898 async fn test_drt_uptime_after_delay_system_disabled() {
899 use crate::config::environment_names::runtime::system as env_system;
900 temp_env::async_with_vars([(env_system::DYN_SYSTEM_PORT, None::<&str>)], async {
902 let drt = create_test_drt_async().await;
904
905 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
907
908 let uptime = drt.system_health.lock().uptime();
910 assert!(
911 uptime >= std::time::Duration::from_millis(50),
912 "Expected uptime to be at least 50ms, but got {:?}",
913 uptime
914 );
915
916 println!(
917 "✓ DRT uptime test passed (system disabled): uptime = {:?}",
918 uptime
919 );
920 })
921 .await;
922 }
923
924 #[tokio::test]
925 async fn test_drt_uptime_after_delay_system_enabled() {
926 use crate::config::environment_names::runtime::system as env_system;
927 temp_env::async_with_vars([(env_system::DYN_SYSTEM_PORT, Some("8081"))], async {
929 let drt = create_test_drt_async().await;
931
932 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
934
935 let uptime = drt.system_health.lock().uptime();
937 assert!(
938 uptime >= std::time::Duration::from_millis(50),
939 "Expected uptime to be at least 50ms, but got {:?}",
940 uptime
941 );
942
943 println!(
944 "✓ DRT uptime test passed (system enabled): uptime = {:?}",
945 uptime
946 );
947 })
948 .await;
949 }
950
951 #[test]
952 fn test_request_plane_mode_from_str() {
953 assert_eq!(
954 "nats".parse::<RequestPlaneMode>().unwrap(),
955 RequestPlaneMode::Nats
956 );
957 assert_eq!(
958 "tcp".parse::<RequestPlaneMode>().unwrap(),
959 RequestPlaneMode::Tcp
960 );
961 assert_eq!(
962 "NATS".parse::<RequestPlaneMode>().unwrap(),
963 RequestPlaneMode::Nats
964 );
965 assert_eq!(
966 "TCP".parse::<RequestPlaneMode>().unwrap(),
967 RequestPlaneMode::Tcp
968 );
969 assert!("invalid".parse::<RequestPlaneMode>().is_err());
970 }
971}