Skip to main content

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);