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