Skip to main content

microvms_domain/
preflight.rs

1// SPDX-License-Identifier: Apache-2.0
2//! The lines of a `preflight` or `doctor` report, and the region line's rule (#223).
3//!
4//! The checks that call something (credentials, the service listing) and `preflight` itself
5//! are in `microvms_core::preflight`, which also documents the outcome rules: a check whose
6//! precondition failed is reported as not run ([`Check::ran`] false), fatal, and makes no
7//! call, and [`PreflightReport::ok`] is true exactly when no fatal check failed or was
8//! skipped. The Stateright model in `crates/model/src/preflight.rs` specifies this (BIND-15,
9//! BIND-16).
10
11use crate::error::Error;
12use crate::region::{MEASURED_REGIONS, Region};
13
14/// One line of a preflight or `doctor` report.
15#[derive(Clone, Debug, PartialEq)]
16pub struct Check {
17    pub name: &'static str,
18    pub ok: bool,
19    /// Whether a failure decides the outcome. An advisory failure is reported and ignored.
20    pub fatal: bool,
21    /// False when an earlier check's failure kept this one from running.
22    pub ran: bool,
23    pub detail: String,
24    /// What to do about a failure; empty on a pass.
25    pub remedy: String,
26}
27
28impl Check {
29    pub fn pass(name: &'static str, detail: impl Into<String>) -> Self {
30        Self {
31            name,
32            ok: true,
33            fatal: true,
34            ran: true,
35            detail: detail.into(),
36            remedy: String::new(),
37        }
38    }
39
40    pub fn fail(name: &'static str, detail: impl Into<String>, remedy: impl Into<String>) -> Self {
41        Self {
42            name,
43            ok: false,
44            fatal: true,
45            ran: true,
46            detail: detail.into(),
47            remedy: remedy.into(),
48        }
49    }
50
51    /// A check that did not run because `reason`; it fails, fatally, and made no call.
52    pub fn not_run(name: &'static str, reason: impl Into<String>) -> Self {
53        Self {
54            ran: false,
55            ..Self::fail(
56                name,
57                format!("not run: {}", reason.into()),
58                "fix the check above",
59            )
60        }
61    }
62
63    /// A non-fatal finding: reported, but it does not decide the outcome.
64    #[must_use]
65    pub fn advisory(mut self) -> Self {
66        self.fatal = false;
67        self
68    }
69}
70
71/// Whether every fatal check passed.
72pub fn healthy(checks: &[Check]) -> bool {
73    checks
74        .iter()
75        .filter(|check| check.fatal)
76        .all(|check| check.ok)
77}
78
79/// What `microvms_core::preflight::preflight` found.
80#[derive(Clone, Debug)]
81pub struct PreflightReport {
82    /// The region checked, or `None` when none resolved.
83    pub region: Option<Region>,
84    /// `region`, `credentials`, and `service`, in that order, always all three.
85    pub checks: Vec<Check>,
86}
87
88impl PreflightReport {
89    /// True exactly when no fatal check failed or was skipped: a launch could proceed.
90    pub fn ok(&self) -> bool {
91        healthy(&self.checks)
92    }
93
94    /// The check named `name`, if the report has one.
95    pub fn check(&self, name: &str) -> Option<&Check> {
96        self.checks.iter().find(|check| check.name == name)
97    }
98}
99
100fn known_regions() -> String {
101    MEASURED_REGIONS.join(", ")
102}
103
104/// The region line: pass for a region AWS was measured running MicroVMs in, advisory for any
105/// other, fatal when none resolved. `doctor` marks the last advisory too, because it must run
106/// in a broken environment and report everything else.
107pub fn region_check(resolved: &Result<Region, Error>) -> Check {
108    match resolved {
109        Ok(region) if region.is_measured() => {
110            Check::pass("region", format!("{region} is a known MicroVMs region"))
111        }
112        Ok(region) => Check::fail(
113            "region",
114            format!("{region} is not a region AWS was measured running MicroVMs in"),
115            format!(
116                "known: {} (measured 2026-10-24). If AWS has not launched MicroVMs here, a call \
117                 can fail with an AccessDeniedException and a null message, and the service \
118                 check below can still pass",
119                known_regions()
120            ),
121        )
122        .advisory(),
123        Err(error) => Check::fail(
124            "region",
125            error.to_string(),
126            format!("known: {}", known_regions()),
127        ),
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn only_a_fatal_failure_is_unhealthy() {
137        let advisory = Check::fail("region", "unlisted", "see below").advisory();
138        assert!(healthy(&[Check::pass("a", "fine"), advisory.clone()]));
139        assert!(!healthy(&[
140            advisory,
141            Check::not_run("service", "no region")
142        ]));
143        assert!(!Check::not_run("service", "no region").ran);
144    }
145
146    #[test]
147    fn the_region_line_passes_a_measured_region_and_advises_on_any_other() {
148        assert!(region_check(&Ok(Region::US_EAST_1)).ok);
149        assert!(region_check(&Ok("eu-central-1".parse().unwrap())).ok);
150        let unlisted = region_check(&Ok("xx-new-1".parse().unwrap()));
151        assert!(!unlisted.ok && !unlisted.fatal, "{unlisted:?}");
152        let refused = region_check(&Err(Error::invalid_arg("no region")));
153        assert!(!refused.ok && refused.fatal, "{refused:?}");
154        assert!(refused.remedy.contains("us-east-1"), "{refused:?}");
155    }
156}