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}
30
31impl std::fmt::Display for NodeKind {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 NodeKind::Substrate => write!(f, "substrate"),
35 NodeKind::Jam => write!(f, "jam"),
36 }
37 }
38}
39
40/// Behaviour shared by every node zombienet spawns, regardless of what it runs.
41///
42/// Implementors only need to provide [`core`](SpawnedNode::core) and
43/// [`wait_until_is_up`](SpawnedNode::wait_until_is_up); everything else is
44/// delegated to the [`NodeCore`].
45///
46/// NOTE: this trait is deliberately dyn-compatible. Generic methods (e.g the
47/// subxt `client::<Config>()`) stay inherent on the concrete types — downcast
48/// with [`Network::get_node`](crate::network::Network::get_node) or
49/// [`Network::get_jam_node`](crate::network::Network::get_jam_node) to reach them.
50#[async_trait]
51pub trait SpawnedNode: std::any::Any + erased_serde::Serialize + Send + Sync + 'static {
52 /// The provider handle and runtime state of this node.
53 fn core(&self) -> &NodeCore;
54
55 /// Wait until the node reports it finished booting.
56 ///
57 /// How readiness is established is up to each node flavour (Prometheus
58 /// scrape for substrate, log/rpc probe for JAM), which is why this has no
59 /// default implementation.
60 async fn wait_until_is_up(&self, timeout_secs: u64) -> Result<(), anyhow::Error>;
61
62 /// What flavour of node this is.
63 fn kind(&self) -> NodeKind {
64 self.core().kind()
65 }
66
67 fn name(&self) -> &str {
68 self.core().name()
69 }
70
71 fn is_running(&self) -> bool {
72 self.core().is_running()
73 }
74
75 fn last_start_ts(&self) -> u64 {
76 self.core().last_start_ts()
77 }
78
79 fn base_dir(&self) -> &PathBuf {
80 self.core().base_dir()
81 }
82
83 fn args(&self) -> Vec<&str> {
84 self.core().args()
85 }
86
87 async fn logs(&self) -> Result<String, anyhow::Error> {
88 self.core().logs().await
89 }
90
91 async fn pause(&self) -> Result<(), anyhow::Error> {
92 self.core().pause().await
93 }
94
95 async fn resume(&self) -> Result<(), anyhow::Error> {
96 self.core().resume().await
97 }
98
99 async fn restart(&self, after: Option<Duration>) -> Result<(), anyhow::Error> {
100 self.core().restart(after).await
101 }
102}
103
104erased_serde::serialize_trait_object!(SpawnedNode);