Skip to main content

zombienet_provider/
lib.rs

1#![allow(clippy::expect_fun_call)]
2mod docker;
3mod kubernetes;
4mod native;
5pub mod shared;
6
7use std::{
8    collections::HashMap,
9    net::IpAddr,
10    path::{Path, PathBuf},
11    sync::Arc,
12    time::Duration,
13};
14
15use async_trait::async_trait;
16use configuration::types::AssetLocation;
17use shared::{
18    constants::LOCALHOST,
19    types::{
20        ExecutionResult, GenerateFilesOptions, ProcessStatus, ProviderCapabilities,
21        RunCommandOptions, RunScriptOptions, SpawnNodeOptions,
22    },
23};
24use support::fs::FileSystemError;
25use tracing::warn;
26use url::Url;
27
28use crate::shared::types::InnerSnapshotDb;
29
30#[derive(Debug, thiserror::Error)]
31#[allow(missing_docs)]
32pub enum ProviderError {
33    #[error("Failed to create client '{0}': {1}")]
34    CreateClientFailed(String, anyhow::Error),
35
36    #[error("Failed to create namespace '{0}': {1}")]
37    CreateNamespaceFailed(String, anyhow::Error),
38
39    #[error("Failed to spawn node '{0}': {1}")]
40    NodeSpawningFailed(String, anyhow::Error),
41
42    #[error("Error running command '{0}' {1}: {2}")]
43    RunCommandError(String, String, anyhow::Error),
44
45    #[error("Error running script'{0}': {1}")]
46    RunScriptError(String, anyhow::Error),
47
48    #[error("Invalid network configuration field {0}")]
49    InvalidConfig(String),
50
51    #[error("Failed to retrieve node available args using image {0} and command {1}: {2}")]
52    NodeAvailableArgsError(String, String, String),
53
54    #[error("Can not recover node: {0}")]
55    MissingNode(String),
56
57    #[error("Can not recover node: {0} info, field: {1}")]
58    MissingNodeInfo(String, String),
59
60    #[error("File generation failed: {0}")]
61    FileGenerationFailed(anyhow::Error),
62
63    #[error(transparent)]
64    FileSystemError(#[from] FileSystemError),
65
66    #[error("Invalid script path for {0}")]
67    InvalidScriptPath(anyhow::Error),
68
69    #[error("Script with path {0} not found")]
70    ScriptNotFound(PathBuf),
71
72    #[error("Failed to retrieve process ID for node '{0}'")]
73    ProcessIdRetrievalFailed(String),
74
75    #[error("Failed to pause node '{0}': {1}")]
76    PauseNodeFailed(String, anyhow::Error),
77
78    #[error("Failed to resume node '{0}': {1}")]
79    ResumeNodeFailed(String, anyhow::Error),
80
81    #[error("Failed to kill node '{0}': {1}")]
82    KillNodeFailed(String, anyhow::Error),
83
84    #[error("Failed to restart node '{0}': {1}")]
85    RestartNodeFailed(String, anyhow::Error),
86
87    #[error("Failed to destroy node '{0}': {1}")]
88    DestroyNodeFailed(String, anyhow::Error),
89
90    #[error("Failed to get logs for node '{0}': {1}")]
91    GetLogsFailed(String, anyhow::Error),
92
93    #[error("Failed to dump logs for node '{0}': {1}")]
94    DumpLogsFailed(String, anyhow::Error),
95
96    #[error("Failed to copy file from node '{0}': {1}")]
97    CopyFileFromNodeError(String, anyhow::Error),
98
99    #[error("Failed to setup fileserver: {0}")]
100    FileServerSetupError(anyhow::Error),
101
102    #[error("Error uploading file: '{0}': {1:#}")]
103    UploadFile(String, anyhow::Error),
104
105    #[error("Error downloading file: '{0}': {1:#}")]
106    DownloadFile(String, anyhow::Error),
107
108    #[error("Error sending file '{0}' to {1}: {2}")]
109    SendFile(String, String, anyhow::Error),
110
111    #[error("Error creating port-forward '{0}:{1}': {2}")]
112    PortForwardError(u16, u16, anyhow::Error),
113
114    #[error("Failed to delete namespace '{0}': {1}")]
115    DeleteNamespaceFailed(String, anyhow::Error),
116
117    #[error("Serialization error: {0}")]
118    SerializationError(#[from] serde_json::Error),
119
120    #[error("Failed to acquire lock: {0}")]
121    FailedToAcquireLock(String),
122
123    #[error("Failed to upgrade from weak ref of ns")]
124    FailedToUpgradeWeakRef,
125
126    #[error("Failed to generate the snapshot for node {0}:  {1}")]
127    SnapshotDb(String, anyhow::Error),
128}
129
130#[async_trait]
131pub trait Provider {
132    fn name(&self) -> &str;
133
134    fn capabilities(&self) -> &ProviderCapabilities;
135
136    async fn namespaces(&self) -> HashMap<String, DynNamespace>;
137
138    async fn create_namespace(&self) -> Result<DynNamespace, ProviderError>;
139
140    async fn create_namespace_with_base_dir(
141        &self,
142        base_dir: &Path,
143    ) -> Result<DynNamespace, ProviderError>;
144
145    async fn create_namespace_from_json(
146        &self,
147        json_value: &serde_json::Value,
148    ) -> Result<DynNamespace, ProviderError>;
149}
150
151pub type DynProvider = Arc<dyn Provider + Send + Sync>;
152
153#[async_trait]
154pub trait ProviderNamespace {
155    fn name(&self) -> &str;
156
157    fn base_dir(&self) -> &PathBuf;
158
159    fn capabilities(&self) -> &ProviderCapabilities;
160
161    fn provider_name(&self) -> &str;
162
163    async fn detach(&self) {
164        // noop by default
165        warn!("Detach is not implemented for {}", self.name());
166    }
167
168    async fn is_detached(&self) -> bool {
169        // false by default
170        false
171    }
172
173    async fn nodes(&self) -> HashMap<String, DynNode>;
174
175    async fn get_node_available_args(
176        &self,
177        options: (String, Option<String>),
178    ) -> Result<String, ProviderError>;
179
180    async fn spawn_node(&self, options: &SpawnNodeOptions) -> Result<DynNode, ProviderError>;
181
182    async fn spawn_node_from_json(
183        &self,
184        json_value: &serde_json::Value,
185    ) -> Result<DynNode, ProviderError>;
186
187    async fn generate_files(&self, options: GenerateFilesOptions) -> Result<(), ProviderError>;
188
189    async fn destroy(&self) -> Result<(), ProviderError>;
190
191    async fn static_setup(&self) -> Result<(), ProviderError>;
192
193    /// Upload a file to the namespace's file server, if the provider has one.
194    /// Returns the url and content hash of the uploaded file, or `None` for
195    /// providers without a file server.
196    async fn upload_to_fileserver(
197        &self,
198        _location: &Path,
199    ) -> Result<Option<(Url, String)>, ProviderError> {
200        Ok(None)
201    }
202}
203
204pub type DynNamespace = Arc<dyn ProviderNamespace + Send + Sync>;
205
206#[async_trait]
207pub trait ProviderNode: erased_serde::Serialize {
208    fn name(&self) -> &str;
209
210    fn args(&self) -> Vec<&str>;
211
212    fn base_dir(&self) -> &PathBuf;
213
214    fn config_dir(&self) -> &PathBuf;
215
216    fn data_dir(&self) -> &PathBuf;
217
218    fn relay_data_dir(&self) -> &PathBuf;
219
220    fn scripts_dir(&self) -> &PathBuf;
221
222    fn log_path(&self) -> &PathBuf;
223
224    fn log_cmd(&self) -> String;
225
226    // Return the absolute path to the file in the `node` perspective
227    // TODO: purpose?
228    fn path_in_node(&self, file: &Path) -> PathBuf;
229
230    async fn logs(&self) -> Result<String, ProviderError>;
231
232    async fn dump_logs(&self, local_dest: PathBuf) -> Result<(), ProviderError>;
233
234    // By default return localhost, should be overrided for k8s
235    async fn ip(&self) -> Result<IpAddr, ProviderError> {
236        Ok(LOCALHOST)
237    }
238
239    /// What the provider knows about the process: running (and, where the
240    /// provider checks readiness itself, ready or not) or exited. Providers
241    /// that cannot tell report running.
242    async fn status(&self) -> Result<ProcessStatus, ProviderError> {
243        Ok(ProcessStatus::Running { ready: None })
244    }
245
246    // Noop by default (native/docker provider)
247    async fn create_port_forward(
248        &self,
249        _local_port: u16,
250        _remote_port: u16,
251    ) -> Result<Option<u16>, ProviderError> {
252        Ok(None)
253    }
254
255    async fn run_command(
256        &self,
257        options: RunCommandOptions,
258    ) -> Result<ExecutionResult, ProviderError>;
259
260    async fn run_script(&self, options: RunScriptOptions)
261        -> Result<ExecutionResult, ProviderError>;
262
263    async fn send_file(
264        &self,
265        local_file_path: &Path,
266        remote_file_path: &Path,
267        mode: &str,
268    ) -> Result<(), ProviderError>;
269
270    async fn receive_file(
271        &self,
272        remote_file_path: &Path,
273        local_file_path: &Path,
274    ) -> Result<(), ProviderError>;
275
276    async fn pause(&self) -> Result<(), ProviderError>;
277
278    async fn resume(&self) -> Result<(), ProviderError>;
279
280    async fn restart(&self, after: Option<Duration>) -> Result<(), ProviderError>;
281
282    async fn restart_with(
283        &self,
284        assets: &[AssetLocation],
285        cmd: &str,
286        args: &[String],
287        after: Option<Duration>,
288    ) -> Result<(), ProviderError>;
289
290    async fn destroy(&self) -> Result<(), ProviderError>;
291
292    async fn snapshot_db(&self, is_cumulus_based: bool) -> Result<InnerSnapshotDb, ProviderError>;
293}
294
295pub type DynNode = Arc<dyn ProviderNode + Send + Sync>;
296
297// re-export
298pub use docker::*;
299pub use kubernetes::*;
300pub use native::*;
301pub use shared::{constants, types};