zombienet_orchestrator/network/node/spawned.rs
1//! The erased node interface.
2//!
3//! [`SpawnedNode`] is what lets a [`Network`](crate::network::Network) keep
4//! every node it spawned — substrate and JAM alike — in a single registry,
5//! while still handing back the concrete type on request.
6//!
7//! Downcasting relies on trait upcasting (`&dyn SpawnedNode -> &dyn Any`),
8//! stable since Rust 1.86, which is the workspace MSRV. That's why there is
9//! no `as_any()` method here.
10
11use std::{path::PathBuf, time::Duration};
12
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15
16use super::core::NodeCore;
17
18/// Which flavour of node a [`SpawnedNode`] is.
19///
20/// `Any` gives no type information on a failed downcast, so nodes carry this
21/// tag for readable errors and for filtering the registry.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "lowercase")]
24pub enum NodeKind {
25 /// A substrate node: relaychain node or collator.
26 Substrate,
27 /// A JAM node.
28 Jam,
29 /// A custom process declared in the network config, running next to the nodes.
30 #[serde(rename = "custom-process")]
31 CustomProcess,
32}
33
34impl std::fmt::Display for NodeKind {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 NodeKind::Substrate => write!(f, "substrate"),
38 NodeKind::Jam => write!(f, "jam"),
39 NodeKind::CustomProcess => write!(f, "custom-process"),
40 }
41 }
42}
43
44/// Behaviour shared by every node zombienet spawns, regardless of what it runs.
45///
46/// Implementors only need to provide [`core`](SpawnedNode::core) and
47/// [`wait_until_is_up`](SpawnedNode::wait_until_is_up); everything else is
48/// delegated to the [`NodeCore`].
49///
50/// NOTE: this trait is deliberately dyn-compatible. Generic methods (e.g the
51/// subxt `client::<Config>()`) stay inherent on the concrete types — downcast
52/// with [`Network::get_node`](crate::network::Network::get_node) or
53/// [`Network::get_jam_node`](crate::network::Network::get_jam_node) to reach them.
54#[async_trait]
55pub trait SpawnedNode: std::any::Any + erased_serde::Serialize + Send + Sync + 'static {
56 /// The provider handle and runtime state of this node.
57 fn core(&self) -> &NodeCore;
58
59 /// Wait until the node reports it finished booting.
60 ///
61 /// How readiness is established is up to each node flavour (Prometheus
62 /// scrape for substrate, log/rpc probe for JAM), which is why this has no
63 /// default implementation.
64 async fn wait_until_is_up(&self, timeout_secs: u64) -> Result<(), anyhow::Error>;
65
66 /// What flavour of node this is.
67 fn kind(&self) -> NodeKind {
68 self.core().kind()
69 }
70
71 fn name(&self) -> &str {
72 self.core().name()
73 }
74
75 fn is_running(&self) -> bool {
76 self.core().is_running()
77 }
78
79 fn last_start_ts(&self) -> u64 {
80 self.core().last_start_ts()
81 }
82
83 fn base_dir(&self) -> &PathBuf {
84 self.core().base_dir()
85 }
86
87 fn args(&self) -> Vec<&str> {
88 self.core().args()
89 }
90
91 async fn logs(&self) -> Result<String, anyhow::Error> {
92 self.core().logs().await
93 }
94
95 async fn pause(&self) -> Result<(), anyhow::Error> {
96 self.core().pause().await
97 }
98
99 async fn resume(&self) -> Result<(), anyhow::Error> {
100 self.core().resume().await
101 }
102
103 async fn restart(&self, after: Option<Duration>) -> Result<(), anyhow::Error> {
104 self.core().restart(after).await
105 }
106}
107
108erased_serde::serialize_trait_object!(SpawnedNode);