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