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 plan = crate::cost::PlanUsage::default();
42    let usage = crate::cost::RunUsage::default();
43    let image = CreateImageRequest::new(String::new(), Vec::new(), String::new(), String::new());
44    let ensured = EnsureImageRequest::new(
45        String::new(),
46        Vec::new(),
47        String::new(),
48        String::new(),
49        String::new(),
50    );
51    json!({
52        "sizeMib": SizeClass::DEFAULT.baseline_mib(),
53        "agentSizeMib": DEFAULT_SIZE.baseline_mib(),
54        "agentPort": DEFAULT_AGENT_PORT,
55        "listener": {
56            "bindAddress": crate::session::DEFAULT_BIND.to_string(),
57            "bindIp": crate::session::DEFAULT_BIND.ip().to_string(),
58        },
59        "execWaitSeconds": seconds(DEFAULT_EXEC_WAIT),
60        // The daemon wait a launch makes after RUNNING, and the RUNNING wait before it (#254).
61        "readyTimeoutSeconds": seconds(crate::session::DEFAULT_BOOTSTRAP_TIMEOUT),
62        "launchTimeoutSeconds": seconds(crate::sandbox::DEFAULT_RUNNING_TIMEOUT),
63        "lifecycleTimeoutSeconds": seconds(DEFAULT_LIFECYCLE_TIMEOUT),
64        "lifecyclePollIntervalSeconds": seconds(LIFECYCLE_POLL_INTERVAL),
65        "promptTimeoutSeconds": seconds(DEFAULT_PROMPT_TIMEOUT),
66        "clientGraceSeconds": seconds(DEFAULT_CLIENT_GRACE),
67        "bedrockTokenTtlHours": bedrock::MAX_LIFETIME.as_secs() / 3600,
68        "residencyCycles": DEFAULT_RESIDENCY_CYCLES,
69        "reportCycles": DEFAULT_REPORT_CYCLES,
70        "keepAwakeToleratedErrors": crate::session::keepalive::DEFAULT_TOLERATED_ERRORS,
71        "syncDeleteTimeoutSeconds": seconds(crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT),
72        "launch": {
73            "maxIdleSeconds": launch.max_idle_sec,
74            "suspendedSeconds": launch.suspended_sec,
75            "maxDurationSeconds": launch.max_duration_sec,
76            "autoResume": launch.auto_resume,
77            "egress": launch.egress,
78            "denyEgress": launch.deny_egress,
79            "identity": launch.identity,
80            "shell": launch.shell,
81            "wait": launch.wait,
82            // `None` keeps the service's default destination; logging is off only when a
83            // caller turns it off.
84            "loggingDisabled": matches!(
85                launch.logging,
86                Some(crate::control::ops::Logging::Disabled { .. })
87            ),
88        },
89        "exec": {
90            "cwd": start.cwd,
91            "user": start.user,
92            "group": start.group,
93            "timeoutSeconds": start.timeout_sec,
94            "shell": matches!(start.shell, protocol::exec::Shell::Flag(true)),
95            "stdin": start.stdin,
96            "reapGroupOnExit": start.reap_group_on_exit,
97            "poweroffOnExit": start.poweroff_on_exit,
98            "inheritImageEnv": start.inherit_image_env,
99        },
100        "stream": {
101            "offset": stream.offset,
102            "reconnect": stream.reconnect,
103            "errorOnGap": stream.error_on_gap,
104        },
105        "teardown": {
106            "deleteImage": teardown.delete_image,
107            "deleteLogGroup": teardown.delete_log_group,
108            "waitForTerminated": teardown.wait_for_terminated.is_some(),
109        },
110        "prompt": {
111            "permissionMode": prompt.permission_mode.as_str(),
112            "execId": prompt.exec_id,
113            "timeoutSeconds": prompt.timeout.map(seconds),
114            "reapGroupOnExit": prompt.reap_group_on_exit,
115        },
116        "cost": {
117            "runningSeconds": plan.running_seconds,
118            "suspendedSeconds": plan.suspended_seconds,
119            "running": usage.running.map(crate::cost::DurationP::seconds_f64),
120            "suspended": usage.suspended.map(crate::cost::DurationP::seconds_f64),
121            "imageBuild": usage.image_build.map(crate::cost::DurationP::seconds_f64),
122            "imageGb": usage.image_gb,
123            "snapshotGb": usage.snapshot_gb,
124        },
125        "image": {
126            "workingDirectory": crate::control::artifact::BaseImage::al2023().working_dir,
127            "workdir": WrapOptions::default().workdir,
128            "inheritWorkdir": WrapOptions::default().inherit_workdir,
129            "repairGuestIdentity": image.repair_guest_identity,
130            "force": ensured.force,
131        },
132    })
133}
134
135/// A duration as the float seconds every surface takes one in.
136fn seconds(duration: Duration) -> f64 {
137    duration.as_secs_f64()
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    /// The object reads core's values, not copies: each figure a surface restates is the one
145    /// the rule that uses it holds (#300).
146    ///
147    /// **Falsification**: `verify/guards/faults/parity-defaults.toml` entry
148    /// `core-client-defaults-copy` types the exec wait here as a literal 301, and this goes
149    /// red.
150    #[test]
151    fn the_client_defaults_are_cores_own_values() {
152        let defaults = client_defaults();
153        assert_eq!(
154            defaults["execWaitSeconds"],
155            DEFAULT_EXEC_WAIT.as_secs_f64(),
156            "execWaitSeconds"
157        );
158        assert_eq!(defaults["agentPort"], DEFAULT_AGENT_PORT);
159        assert_eq!(defaults["sizeMib"], SizeClass::DEFAULT.baseline_mib());
160        let launch = RunRequest::default();
161        assert_eq!(defaults["launch"]["maxIdleSeconds"], launch.max_idle_sec);
162        assert_eq!(defaults["launch"]["wait"], launch.wait);
163        let start = protocol::exec::StartRequest::new("x", vec!["true".into()]);
164        assert_eq!(defaults["exec"]["cwd"], json!(start.cwd));
165        assert_eq!(defaults["exec"]["user"], json!(start.user));
166        assert_eq!(defaults["exec"]["group"], json!(start.group));
167        assert_eq!(defaults["exec"]["timeoutSeconds"], json!(start.timeout_sec));
168        assert_eq!(defaults["exec"]["stdin"], start.stdin);
169        assert_eq!(defaults["exec"]["shell"], false);
170        assert_eq!(
171            defaults["stream"]["offset"],
172            StreamOptions::default().offset
173        );
174        assert_eq!(
175            defaults["prompt"]["permissionMode"],
176            PromptOptions::default().permission_mode.as_str()
177        );
178        assert_eq!(
179            defaults["image"]["workingDirectory"],
180            crate::control::artifact::BaseImage::al2023().working_dir
181        );
182        assert_eq!(
183            defaults["syncDeleteTimeoutSeconds"],
184            crate::workspace::DEFAULT_SYNC_DELETE_TIMEOUT.as_secs_f64()
185        );
186    }
187}