Skip to main content

xianyun_deploy/diagnostics/
mod.rs

1//! Runs a set of diagnostics and reports the results.
2//!
3//! Ported from `internal/diagnostics/diagnostics.go`.
4
5#![allow(dead_code)]
6
7pub mod base;
8pub mod builtin;
9
10use std::sync::Arc;
11use std::sync::mpsc::{self, Receiver};
12
13use base::{Diagnostic, Meta};
14
15/// Holds the result of a single diagnostic run.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct DiagnosticResult {
18    pub name: String,
19    pub output: String,
20    pub err: Option<String>,
21    pub skipped: bool,
22}
23
24/// Runs a set of diagnostics and reports the results.
25pub struct Runner {
26    diagnostics: Arc<Vec<Arc<dyn Diagnostic>>>,
27}
28
29impl Runner {
30    /// Creates a new [`Runner`] with the given diagnostics.
31    #[must_use]
32    pub fn new(diagnostics: Vec<Arc<dyn Diagnostic>>) -> Self {
33        Self {
34            diagnostics: Arc::new(diagnostics),
35        }
36    }
37
38    /// Executes all diagnostics and returns a channel of their results.
39    /// Diagnostics that need a running cluster are skipped (without
40    /// being invoked) when `is_cluster_up` is `false`.
41    #[must_use]
42    pub fn run(&self, meta: Meta, is_cluster_up: bool) -> Receiver<DiagnosticResult> {
43        let (tx, rx) = mpsc::channel();
44        let diagnostics = Arc::clone(&self.diagnostics);
45        let meta = Arc::new(meta);
46
47        std::thread::spawn(move || {
48            let handles: Vec<_> = diagnostics
49                .iter()
50                .cloned()
51                .map(|d| {
52                    let tx = tx.clone();
53                    let meta = Arc::clone(&meta);
54
55                    std::thread::spawn(move || {
56                        if d.needs_cluster() && !is_cluster_up {
57                            let _ = tx.send(DiagnosticResult {
58                                name: d.name().to_string(),
59                                output: String::new(),
60                                err: None,
61                                skipped: true,
62                            });
63
64                            return;
65                        }
66
67                        let (output, err) = d.run(&meta);
68                        let _ = tx.send(DiagnosticResult {
69                            name: d.name().to_string(),
70                            output,
71                            err: err.map(|e| e.to_string()),
72                            skipped: false,
73                        });
74                    })
75                })
76                .collect();
77
78            for handle in handles {
79                let _ = handle.join();
80            }
81        });
82
83        rx
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use std::sync::Arc;
90    use std::sync::atomic::{AtomicI64, Ordering};
91    use std::time::Duration;
92
93    use super::*;
94    use base::{Meta, SimpleDiagnostic};
95
96    // Ported from internal/diagnostics/diagnostics_test.go:16 (TestRunner)
97    #[test]
98    fn test_runner() {
99        let skipped_calls = Arc::new(AtomicI64::new(0));
100
101        let example_diagnostics: Vec<Arc<dyn Diagnostic>> = vec![
102            Arc::new(SimpleDiagnostic::new("simple diag", false, |_meta| {
103                (String::new(), None)
104            })),
105            Arc::new({
106                let skipped_calls = Arc::clone(&skipped_calls);
107                SimpleDiagnostic::new("needs cluster!!", true, move |_meta| {
108                    skipped_calls.fetch_add(1, Ordering::SeqCst);
109                    (String::new(), None)
110                })
111            }),
112            Arc::new(SimpleDiagnostic::new("omg warning", false, |_meta| {
113                (
114                    "woof, something is wrong, here is how you fix it".to_string(),
115                    None,
116                )
117            })),
118            Arc::new(SimpleDiagnostic::new("ah well", false, |_meta| {
119                (
120                    String::new(),
121                    Some(
122                        Box::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
123                            as Box<dyn std::error::Error + Send + Sync>,
124                    ),
125                )
126            })),
127        ];
128
129        let runner = Runner::new(example_diagnostics);
130        let rx = runner.run(Meta::default(), false);
131
132        let mut results = Vec::new();
133        let deadline = std::time::Instant::now() + Duration::from_millis(500);
134        while std::time::Instant::now() < deadline {
135            match rx.recv_timeout(Duration::from_millis(10)) {
136                Ok(r) => results.push(r),
137                Err(mpsc::RecvTimeoutError::Timeout) => continue,
138                Err(mpsc::RecvTimeoutError::Disconnected) => break,
139            }
140        }
141
142        results.sort_by(|a, b| a.name.cmp(&b.name));
143
144        let mut expected = vec![
145            DiagnosticResult {
146                name: "simple diag".to_string(),
147                output: String::new(),
148                err: None,
149                skipped: false,
150            },
151            DiagnosticResult {
152                name: "needs cluster!!".to_string(),
153                output: String::new(),
154                err: None,
155                skipped: true,
156            },
157            DiagnosticResult {
158                name: "omg warning".to_string(),
159                output: "woof, something is wrong, here is how you fix it".to_string(),
160                err: None,
161                skipped: false,
162            },
163            DiagnosticResult {
164                name: "ah well".to_string(),
165                output: String::new(),
166                err: Some("permission denied".to_string()),
167                skipped: false,
168            },
169        ];
170        expected.sort_by(|a, b| a.name.cmp(&b.name));
171
172        assert_eq!(results, expected);
173        assert_eq!(
174            skipped_calls.load(Ordering::SeqCst),
175            0,
176            "we must not have called the skipped diagnostic"
177        );
178    }
179}