flowey_lib_hvlite/
install_vmm_tests_deps.rs1use flowey::node::prelude::*;
7use std::collections::BTreeSet;
8
9const HYPERV_TESTS_REQUIRED_FEATURES: [&str; 3] = [
10 "Microsoft-Hyper-V",
11 "Microsoft-Hyper-V-Management-PowerShell",
12 "Microsoft-Hyper-V-Management-Clients",
13];
14
15const WHP_TESTS_REQUIRED_FEATURES: [&str; 1] = ["HypervisorPlatform"];
16
17const VIRT_REG_PATH: &str = r#"HKLM\Software\Microsoft\Windows NT\CurrentVersion\Virtualization"#;
18
19#[derive(Serialize, Deserialize, PartialEq)]
20pub enum VmmTestsDepSelections {
21 Windows {
22 hyperv: bool,
23 whp: bool,
24 hardware_isolation: bool,
25 },
26 Linux,
27}
28
29flowey_request! {
30 pub enum Request {
31 Select(VmmTestsDepSelections),
33 Install(WriteVar<SideEffect>),
35 GetCommands(WriteVar<Vec<String>>),
37 }
38}
39
40new_flow_node!(struct Node);
41
42impl FlowNode for Node {
43 type Request = Request;
44
45 fn imports(_ctx: &mut ImportCtx<'_>) {}
46
47 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
48 let mut selections = None;
49 let mut installed = Vec::new();
50 let mut write_commands = Vec::new();
51 for req in requests {
52 match req {
53 Request::Select(v) => same_across_all_reqs("Select", &mut selections, v)?,
54 Request::Install(v) => installed.push(v),
55 Request::GetCommands(v) => write_commands.push(v),
56 }
57 }
58 let selections = selections.ok_or(anyhow::anyhow!("Missing essential request: Select"))?;
59 let installed = installed;
60 let write_commands = write_commands;
61 if installed.is_empty() && write_commands.is_empty() {
62 return Ok(());
63 }
64 let installing = !installed.is_empty();
65
66 match selections {
67 VmmTestsDepSelections::Windows {
68 hyperv,
69 whp,
70 hardware_isolation,
71 } => {
72 ctx.emit_rust_step("install vmm tests deps (windows)", move |ctx| {
73 installed.claim(ctx);
74 let write_commands = write_commands.claim(ctx);
75
76 move |rt| {
77 let mut commands = Vec::new();
78
79 if !matches!(rt.platform(), FlowPlatform::Windows)
80 && !flowey_lib_common::_util::running_in_wsl(rt)
81 {
82 anyhow::bail!("Must be on Windows or WSL2 to install Windows deps.")
83 }
84
85 let mut features_to_enable = BTreeSet::new();
89 if hyperv {
90 features_to_enable.append(&mut HYPERV_TESTS_REQUIRED_FEATURES.into());
91 }
92 if whp {
93 features_to_enable.append(&mut WHP_TESTS_REQUIRED_FEATURES.into());
94 }
95
96 if installing && !features_to_enable.is_empty() {
98 let features = flowey::shell_cmd!(rt, "DISM.exe /Online /Get-Features").output()?;
99 assert!(features.status.success());
100 let features = String::from_utf8_lossy(&features.stdout).to_string();
101 let mut feature = None;
102 for line in features.lines() {
103 if let Some((k, v)) = line.split_once(":") {
104 if let Some(f) = feature {
105 assert_eq!(k.trim(), "State");
106 match v.trim() {
107 "Enabled" => {
108 assert!(features_to_enable.remove(f));
109 }
110 "Disabled" => {}
111 _ => anyhow::bail!("Unknown feature enablement state"),
112 }
113 feature = None;
114 } else if k.trim() == "Feature Name" {
115 let new_feature = v.trim();
116 feature = features_to_enable.contains(new_feature).then_some(new_feature);
117 }
118 }
119 }
120 }
121
122 if installing && !features_to_enable.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
124 let mut features_to_install_string = String::new();
125 for feature in features_to_enable.iter() {
126 features_to_install_string.push_str(feature);
127 features_to_install_string.push('\n');
128 }
129
130 log::warn!(
131 r#"
132================================================================================
133To run the VMM tests, the following features need to be enabled:
134{features_to_install_string}
135
136You may need to restart your system for the changes to take effect.
137
138If you're OK with installing these features, please press <enter>.
139Otherwise, press `ctrl-c` to cancel the run.
140================================================================================
141"#
142 );
143 let _ = std::io::stdin().read_line(&mut String::new());
144 }
145
146 for feature in features_to_enable {
148 if installing {
149 flowey::shell_cmd!(rt, "DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}").run()?;
150 }
151 commands.push(format!("DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}"));
152 }
153
154 let mut reg_keys_to_set = BTreeSet::new();
156 if hyperv {
157 reg_keys_to_set.insert("AllowFirmwareLoadFromFile");
159 reg_keys_to_set.insert("EnableAdditionalComPorts");
161
162 if hardware_isolation {
163 reg_keys_to_set.insert("EnableHardwareIsolation");
164 }
165 }
166
167 if installing && !reg_keys_to_set.is_empty() {
169 let output = flowey::shell_cmd!(rt, "reg.exe query {VIRT_REG_PATH}").output()?;
170 if output.status.success() {
171 let output = String::from_utf8_lossy(&output.stdout).to_string();
172 for line in output.lines() {
173 let components = line.split_whitespace().collect::<Vec<_>>();
174 if components.len() == 3
175 && reg_keys_to_set.contains(components[0])
176 && components[1] == "REG_DWORD"
177 && components[2] == "0x1"
178 {
179 assert!(reg_keys_to_set.remove(components[0]));
180 }
181 }
182 }
183 }
184
185 if installing && !reg_keys_to_set.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
187 let mut reg_keys_to_set_string = String::new();
188 for feature in reg_keys_to_set.iter() {
189 reg_keys_to_set_string.push_str(feature);
190 reg_keys_to_set_string.push('\n');
191 }
192
193 log::warn!(
194 r#"
195================================================================================
196To run the VMM tests, the following registry keys need to be set to 1:
197{reg_keys_to_set_string}
198
199If you're OK with changing the registry, please press <enter>.
200Otherwise, press `ctrl-c` to cancel the run.
201================================================================================
202"#
203 );
204 let _ = std::io::stdin().read_line(&mut String::new());
205 }
206
207 for v in reg_keys_to_set {
209 if installing {
212 flowey::shell_cmd!(rt, "reg.exe add {VIRT_REG_PATH} /v {v} /t REG_DWORD /d 1 /f").run()?;
213 }
214 commands.push(format!("reg.exe add \"{VIRT_REG_PATH}\" /v {v} /t REG_DWORD /d 1 /f"));
215 }
216
217 for write_cmds in write_commands {
218 rt.write(write_cmds, &commands);
219 }
220
221 Ok(())
222 }
223 });
224 }
225 VmmTestsDepSelections::Linux => {
226 ctx.emit_rust_step("install vmm tests deps (linux)", |ctx| {
227 installed.claim(ctx);
228 let write_commands = write_commands.claim(ctx);
229
230 |rt| {
231 for write_cmds in write_commands {
232 rt.write(write_cmds, &Vec::new());
233 }
234
235 Ok(())
236 }
237 });
238 }
239 }
240
241 Ok(())
242 }
243}