Skip to main content

microvms_app/
defaults.rs

1// SPDX-License-Identifier: Apache-2.0
2//! The client's defaults, by name: what every surface uses when its caller leaves a value out
3//! (#300).
4//!
5//! The values live where the rules that use them do, in the domain and the app, as named
6//! constants and as the `Default` of the request types. [`client_defaults`] reads them back into
7//! one JSON object, so a surface that restates one (a clap `default_value`, a PyO3 `signature`
8//! literal) can be compared with core's figure. `microvm manifest` publishes it as
9//! `data.clientDefaults`, and `tools/check-parity.py` holds each surface default to it through
10//! the `[[default]]` rows of `verify/parity/capabilities.toml`.
11//!
12//! A value is read from the constant or the request's `Default`, never typed here: a copy in
13//! this file would be one more restatement for the check to trust.
14
15use std::time::Duration;
16
17use serde_json::{Value, json};
18
19use crate::agents::{DEFAULT_PROMPT_TIMEOUT, DEFAULT_SIZE, PromptOptions, bedrock};
20use crate::control::artifact::WrapOptions;
21use crate::control::ensure::EnsureImageRequest;
22use crate::control::{CreateImageRequest, DEFAULT_AGENT_PORT};
23use crate::cost::{DEFAULT_REPORT_CYCLES, DEFAULT_RESIDENCY_CYCLES};
24use crate::sandbox::{
25    DEFAULT_LIFECYCLE_TIMEOUT, LIFECYCLE_POLL_INTERVAL, RunRequest, TeardownOpts,
26};
27use crate::session::{DEFAULT_CLIENT_GRACE, DEFAULT_EXEC_WAIT, StreamOptions};
28use crate::sizing::SizeClass;
29
30/// Every default a surface may restate, keyed as the `[[default]]` rows name them.
31///
32/// Durations are seconds and sizes MiB, the units the surfaces take them in. The nested objects
33/// are the request types' own defaults: what a launch, an exec, a stream, a teardown and a
34/// prompt do when a caller sets nothing.
35pub fn client_defaults() -> Value {
36    let launch = RunRequest::default();
37    let start = protocol::exec::StartRequest::new(String::new(), Vec::new());
38    let stream = StreamOptions::default();
39    let teardown = TeardownOpts::default();
40    let prompt = PromptOptions::default();
41    let image = CreateImageRequest::new(String::new(), Vec::new(), String::new(), String::new());
42    let ensured = EnsureImageRequest::new(
43        String::new(),
44        Vec::new(),
45        String::new(),
46        String::new(),
47        String::new(),
48    );
49    json!({
50        "sizeMib": SizeClass::DEFAULT.baseline_mib(),
51        "agentSizeMib": DEFAULT_SIZE.baseline_mib(),
52        "agentPort": DEFAULT_AGENT_PORT,
53        "execWaitSeconds": seconds(DEFAULT_EXEC_WAIT),
54        // The daemon wait a launch makes after RUNNING, and the RUNNING wait before it (#254).
55        "readyTimeoutSeconds": seconds(crate::session::DEFAULT_BOOTSTRAP_TIMEOUT),
56        "launchTimeoutSeconds": seconds(crate::sandbox::DEFAULT_RUNNING_TIMEOUT),
57        "lifecycleTimeoutSeconds": seconds(DEFAULT_LIFECYCLE_TIMEOUT),
58        "lifecyclePollIntervalSeconds": seconds(LIFECYCLE_POLL_INTERVAL),
59        "promptTimeoutSeconds": seconds(DEFAULT_PROMPT_TIMEOUT),
60        "clientGraceSeconds": seconds(DEFAULT_CLIENT_GRACE),
61        "bedrockTokenTtlHours": bedrock::MAX_LIFETIME.as_secs() / 3600,
62        "residencyCycles": DEFAULT_RESIDENCY_CYCLES,
63        "reportCycles": DEFAULT_REPORT_CYCLES,
64        "keepAwakeToleratedErrors": crate::session::keepalive::DEFAULT_TOLERATED_ERRORS,
65        "syncDeleteTimeoutSeconds": seconds(crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT),
66        "launch": {
67            "maxIdleSeconds": launch.max_idle_sec,
68            "suspendedSeconds": launch.suspended_sec,
69            "maxDurationSeconds": launch.max_duration_sec,
70            "autoResume": launch.auto_resume,
71            "egress": launch.egress,
72            "denyEgress": launch.deny_egress,
73            "identity": launch.identity,
74            "shell": launch.shell,
75            "wait": launch.wait,
76            // `None` keeps the service's default destination; logging is off only when a
77            // caller turns it off.
78            "loggingDisabled": matches!(
79                launch.logging,
80                Some(crate::control::ops::Logging::Disabled { .. })
81            ),
82        },
83        "exec": {
84            "shell": matches!(start.shell, protocol::exec::Shell::Flag(true)),
85            "stdin": start.stdin,
86            "reapGroupOnExit": start.reap_group_on_exit,
87            "inheritImageEnv": start.inherit_image_env,
88        },
89        "stream": {
90            "reconnect": stream.reconnect,
91            "errorOnGap": stream.error_on_gap,
92        },
93        "teardown": {
94            "deleteImage": teardown.delete_image,
95            "deleteLogGroup": teardown.delete_log_group,
96            "waitForTerminated": teardown.wait_for_terminated.is_some(),
97        },
98        "prompt": {
99            "reapGroupOnExit": prompt.reap_group_on_exit,
100        },
101        "image": {
102            "inheritWorkdir": WrapOptions::default().inherit_workdir,
103            "repairGuestIdentity": image.repair_guest_identity,
104            "force": ensured.force,
105        },
106    })
107}
108
109/// A duration as the float seconds every surface takes one in.
110fn seconds(duration: Duration) -> f64 {
111    duration.as_secs_f64()
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    /// The object reads core's values, not copies: each figure a surface restates is the one
119    /// the rule that uses it holds (#300).
120    ///
121    /// **Falsification**: `verify/guards/faults/parity-defaults.toml` entry
122    /// `core-client-defaults-copy` types the exec wait here as a literal 301, and this goes
123    /// red.
124    #[test]
125    fn the_client_defaults_are_cores_own_values() {
126        let defaults = client_defaults();
127        assert_eq!(
128            defaults["execWaitSeconds"],
129            DEFAULT_EXEC_WAIT.as_secs_f64(),
130            "execWaitSeconds"
131        );
132        assert_eq!(defaults["agentPort"], DEFAULT_AGENT_PORT);
133        assert_eq!(defaults["sizeMib"], SizeClass::DEFAULT.baseline_mib());
134        let launch = RunRequest::default();
135        assert_eq!(defaults["launch"]["maxIdleSeconds"], launch.max_idle_sec);
136        assert_eq!(defaults["launch"]["wait"], launch.wait);
137        let start = protocol::exec::StartRequest::new("x", vec!["true".into()]);
138        assert_eq!(defaults["exec"]["stdin"], start.stdin);
139        assert_eq!(defaults["exec"]["shell"], false);
140        assert_eq!(
141            defaults["syncDeleteTimeoutSeconds"],
142            crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT.as_secs_f64()
143        );
144    }
145}