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