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 warn!("Detach is not implemented for {}", self.name());
166 }
167
168 async fn is_detached(&self) -> bool {
169 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 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 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 async fn ip(&self) -> Result<IpAddr, ProviderError> {
236 Ok(LOCALHOST)
237 }
238
239 async fn status(&self) -> Result<ProcessStatus, ProviderError> {
243 Ok(ProcessStatus::Running { ready: None })
244 }
245
246 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
297pub use docker::*;
299pub use kubernetes::*;
300pub use native::*;
301pub use shared::{constants, types};