1mod clickhouse_service;
16mod compactor_service;
17mod compute_node_service;
18mod configure_tmux_service;
19mod docker_service;
20mod dummy_service;
21mod elasticsearch_service;
22mod ensure_stop_service;
23mod frontend_service;
24mod grafana_service;
25mod kafka_service;
26mod lakekeeper_service;
27mod meta_node_service;
28mod minio_service;
29mod moat_service;
30mod mongodb_service;
31mod moto_service;
32mod mqtt_service;
33mod mysql_service;
34mod nats_service;
35mod opensearch_service;
36mod postgres_service;
37mod prometheus_service;
38mod pubsub_service;
39mod pulsar_service;
40mod redis_service;
41mod schema_registry_service;
42mod sql_server_service;
43mod task_clickhouse_ready_check;
44mod task_configure_minio;
45mod task_db_ready_check;
46mod task_kafka_ready_check;
47mod task_log_ready_check;
48mod task_pubsub_emu_ready_check;
49mod task_redis_ready_check;
50mod task_tcp_ready_check;
51mod tempo_service;
52mod utils;
53
54use std::collections::HashMap;
55use std::env;
56use std::io::Write;
57use std::net::{TcpStream, ToSocketAddrs};
58use std::path::{Path, PathBuf};
59use std::process::{Command, Output};
60use std::sync::Arc;
61use std::time::Duration;
62
63use anyhow::{Context, Result, anyhow};
64use indicatif::ProgressBar;
65use reqwest::blocking::{Client, Response};
66use tempfile::TempDir;
67pub use utils::*;
68
69pub use self::clickhouse_service::*;
70pub use self::compactor_service::*;
71pub use self::compute_node_service::*;
72pub use self::configure_tmux_service::*;
73pub use self::dummy_service::DummyService;
74pub use self::elasticsearch_service::*;
75pub use self::ensure_stop_service::*;
76pub use self::frontend_service::*;
77pub use self::grafana_service::*;
78pub use self::kafka_service::*;
79pub use self::lakekeeper_service::*;
80pub use self::meta_node_service::*;
81pub use self::minio_service::*;
82pub use self::moat_service::*;
83pub use self::mongodb_service::*;
84pub use self::moto_service::*;
85pub use self::mqtt_service::*;
86pub use self::mysql_service::*;
87pub use self::nats_service::*;
88pub use self::opensearch_service::*;
89pub use self::postgres_service::*;
90pub use self::prometheus_service::*;
91pub use self::pubsub_service::*;
92pub use self::pulsar_service::*;
93pub use self::redis_service::*;
94pub use self::schema_registry_service::SchemaRegistryService;
95pub use self::sql_server_service::*;
96pub use self::task_clickhouse_ready_check::*;
97pub use self::task_configure_minio::*;
98pub use self::task_db_ready_check::*;
99pub use self::task_kafka_ready_check::*;
100pub use self::task_log_ready_check::*;
101pub use self::task_pubsub_emu_ready_check::*;
102pub use self::task_redis_ready_check::*;
103pub use self::task_tcp_ready_check::*;
104pub use self::tempo_service::*;
105use crate::util::{begin_spin, complete_spin, get_program_args, get_program_name};
106use crate::wait::{wait, wait_tcp_available};
107
108pub trait Task: 'static + Send {
109 fn execute(&mut self, ctx: &mut ExecuteContext<impl std::io::Write>) -> anyhow::Result<()>;
111
112 fn id(&self) -> String {
114 "<task>".into()
115 }
116}
117
118pub struct ExecuteContext<W>
120where
121 W: std::io::Write,
122{
123 pub log: W,
125
126 pub pb: ProgressBar,
128
129 pub status_dir: Arc<TempDir>,
134
135 pub id: Option<String>,
137
138 pub status_file: Option<PathBuf>,
140
141 pub log_file: Option<PathBuf>,
143}
144
145impl<W> ExecuteContext<W>
146where
147 W: std::io::Write,
148{
149 pub fn new(log: W, pb: ProgressBar, status_dir: Arc<TempDir>) -> Self {
150 Self {
151 log,
152 pb,
153 status_dir,
154 status_file: None,
155 log_file: None,
156 id: None,
157 }
158 }
159
160 pub fn service(&mut self, task: &impl Task) {
161 let id = task.id();
162 if !id.is_empty() {
163 begin_spin(&self.pb);
164 self.pb.set_prefix(id.clone());
165 self.id = Some(id.clone());
166 self.status_file = Some(self.status_dir.path().join(format!("{}.status", id)));
167
168 let log_file = Path::new(&env::var("PREFIX_LOG").unwrap())
170 .join(format!("{}.log", self.id.as_ref().unwrap()));
171 fs_err::remove_file(&log_file).ok();
172 self.log_file = Some(log_file);
173 }
174 }
175
176 pub fn run_command(&mut self, mut cmd: Command) -> Result<Output> {
177 let program_name = get_program_name(&cmd);
178
179 writeln!(self.log, "> {} {}", program_name, get_program_args(&cmd))?;
180
181 let node_id = self.id.as_ref();
183 if program_name == "tmux"
184 && let Some(node_id) = node_id
185 && node_id != "tmux-configure"
186 {
187 let cmd: HashMap<String, String> = HashMap::from_iter([(
188 node_id.clone(),
189 format!("{} {}", program_name, get_program_args(&cmd)),
190 )]);
191 let prefix_config = env::var("PREFIX_CONFIG").unwrap();
192 let path = Path::new(&prefix_config).join("risedev_commands.yaml");
193 let content = serde_yaml::to_string(&cmd)?;
194 fs_err::OpenOptions::new()
195 .create(true)
196 .append(true)
197 .open(path)?
198 .write_all(content.as_bytes())?;
199 }
200
201 let output = cmd.output()?;
202
203 let mut full_output = String::from_utf8_lossy(&output.stdout).to_string();
204 full_output.extend(String::from_utf8_lossy(&output.stderr).chars());
205
206 write!(self.log, "{}", full_output)?;
207
208 writeln!(
209 self.log,
210 "({} exited with {:?})",
211 program_name,
212 output.status.code()
213 )?;
214
215 writeln!(self.log, "---")?;
216
217 output.status.exit_ok().context(full_output)?;
218
219 Ok(output)
220 }
221
222 pub fn complete_spin(&mut self) {
223 complete_spin(&self.pb);
224 }
225
226 pub fn status_path(&self) -> PathBuf {
227 self.status_file.clone().unwrap()
228 }
229
230 pub fn log_path(&self) -> &Path {
231 self.log_file.as_ref().unwrap().as_path()
232 }
233
234 pub fn wait_tcp(&mut self, server: impl AsRef<str>) -> anyhow::Result<()> {
235 let addr = server
236 .as_ref()
237 .to_socket_addrs()?
238 .next()
239 .with_context(|| format!("failed to resolve {}", server.as_ref()))?;
240 wait(
241 || {
242 TcpStream::connect_timeout(&addr, Duration::from_secs(1)).with_context(|| {
243 format!("failed to establish tcp connection to {}", server.as_ref())
244 })?;
245 Ok(())
246 },
247 &mut self.log,
248 self.status_file.as_ref().unwrap(),
249 self.id.as_ref().unwrap(),
250 Some(Duration::from_secs(30)),
251 true,
252 )?;
253 Ok(())
254 }
255
256 fn wait_http_with_response_cb(
257 &mut self,
258 server: impl AsRef<str>,
259 cb: impl Fn(Response) -> anyhow::Result<()>,
260 ) -> anyhow::Result<()> {
261 let server = server.as_ref();
262 wait(
263 || {
264 let resp = Client::new()
265 .get(server)
266 .timeout(Duration::from_secs(1))
267 .body("")
268 .send()?
269 .error_for_status()
270 .with_context(|| {
271 format!("failed to establish http connection to {}", server)
272 })?;
273
274 cb(resp)
275 },
276 &mut self.log,
277 self.status_file.as_ref().unwrap(),
278 self.id.as_ref().unwrap(),
279 Some(Duration::from_secs(30)),
280 true,
281 )
282 }
283
284 pub fn wait_http(&mut self, server: impl AsRef<str>) -> anyhow::Result<()> {
285 self.wait_http_with_response_cb(server, |_| Ok(()))
286 }
287
288 pub fn wait_http_with_text_cb(
289 &mut self,
290 server: impl AsRef<str>,
291 cb: impl Fn(&str) -> bool,
292 ) -> anyhow::Result<()> {
293 self.wait_http_with_response_cb(server, |resp| {
294 let data = resp.text()?;
295 if cb(&data) {
296 Ok(())
297 } else {
298 Err(anyhow!(
299 "http health check callback failed with body: {:?}",
300 data
301 ))
302 }
303 })
304 }
305
306 pub fn wait(&mut self, wait_func: impl FnMut() -> Result<()>) -> anyhow::Result<()> {
307 wait(
308 wait_func,
309 &mut self.log,
310 self.status_file.as_ref().unwrap(),
311 self.id.as_ref().unwrap(),
312 Some(Duration::from_secs(30)),
313 true,
314 )
315 }
316
317 pub fn wait_tcp_close(&mut self, server: impl AsRef<str>) -> anyhow::Result<()> {
319 wait_tcp_available(server, Some(Duration::from_secs(30)))?;
320 Ok(())
321 }
322
323 pub fn wait_tcp_user(&mut self, server: impl AsRef<str>) -> anyhow::Result<()> {
325 let addr = server
326 .as_ref()
327 .to_socket_addrs()?
328 .next()
329 .unwrap_or_else(|| panic!("failed to resolve {}", server.as_ref()));
330 wait(
331 || {
332 TcpStream::connect_timeout(&addr, Duration::from_secs(1))?;
333 Ok(())
334 },
335 &mut self.log,
336 self.status_file.as_ref().unwrap(),
337 self.id.as_ref().unwrap(),
338 None,
339 false,
340 )?;
341 Ok(())
342 }
343
344 pub fn tmux_run(&self, user_cmd: Command) -> anyhow::Result<Command> {
345 let prefix_path = env::var("PREFIX_BIN")?;
346 let mut cmd = new_tmux_command();
347 cmd.arg("new-window")
348 .arg("-t")
350 .arg(RISEDEV_NAME)
351 .arg("-d")
353 .arg("-n")
355 .arg(self.id.as_ref().unwrap());
356
357 if let Some(dir) = user_cmd.get_current_dir() {
358 cmd.arg("-c").arg(dir);
359 }
360 for (k, v) in user_cmd.get_envs() {
361 cmd.arg("-e");
362 if let Some(v) = v {
363 cmd.arg(format!("{}={}", k.to_string_lossy(), v.to_string_lossy()));
364 } else {
365 cmd.arg(k);
366 }
367 }
368 cmd.arg(Path::new(&prefix_path).join("run_command.sh"));
369 cmd.arg(self.log_path());
370 cmd.arg(self.status_path());
371 cmd.arg(user_cmd.get_program());
372 for arg in user_cmd.get_args() {
373 cmd.arg(arg);
374 }
375
376 Ok(cmd)
377 }
378}