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