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, Debug, PartialEq)]
20pub enum VmmTestsDepSelections {
21 Windows {
22 hyperv: bool,
23 whp: bool,
24 hardware_isolation: bool,
25 },
26 Linux,
27}
28
29flowey_config! {
30 pub struct Config {
32 pub selections: Option<VmmTestsDepSelections>,
34 pub auto_install: Option<bool>,
41 }
42}
43
44flowey_request! {
45 pub enum Request {
46 Install(WriteVar<SideEffect>),
48 GetCommands(WriteVar<Vec<String>>),
50 }
51}
52
53new_flow_node_with_config!(struct Node);
54
55impl FlowNodeWithConfig for Node {
56 type Request = Request;
57 type Config = Config;
58
59 fn imports(_ctx: &mut ImportCtx<'_>) {}
60
61 fn emit(
62 config: Config,
63 requests: Vec<Self::Request>,
64 ctx: &mut NodeCtx<'_>,
65 ) -> anyhow::Result<()> {
66 let mut installed = Vec::new();
67 let mut write_commands = Vec::new();
68 for req in requests {
69 match req {
70 Request::Install(v) => installed.push(v),
71 Request::GetCommands(v) => write_commands.push(v),
72 }
73 }
74
75 let installed = installed;
76 let write_commands = write_commands;
77
78 if installed.is_empty() && write_commands.is_empty() {
80 return Ok(());
81 }
82
83 let selections = config
84 .selections
85 .ok_or(anyhow::anyhow!("missing config: selections"))?;
86 let auto_install = config.auto_install;
87 let installing = !installed.is_empty();
88
89 match selections {
90 VmmTestsDepSelections::Windows {
91 hyperv,
92 whp,
93 hardware_isolation,
94 } => {
95 ctx.emit_rust_step("install vmm tests deps (windows)", move |ctx| {
96 installed.claim(ctx);
97 let write_commands = write_commands.claim(ctx);
98
99 move |rt| {
100 let mut commands = Vec::new();
101
102 if !matches!(rt.platform(), FlowPlatform::Windows)
103 && !flowey_lib_common::_util::running_in_wsl(rt)
104 {
105 anyhow::bail!("Must be on Windows or WSL2 to install Windows deps.")
106 }
107
108 let auto_install = match rt.backend() {
110 FlowBackend::Local => auto_install.ok_or_else(|| {
111 anyhow::anyhow!("Missing essential request: AutoInstall")
112 })?,
113 FlowBackend::Ado | FlowBackend::Github => true,
115 };
116
117 let mut features_to_enable = BTreeSet::new();
121 if hyperv {
122 features_to_enable.append(&mut HYPERV_TESTS_REQUIRED_FEATURES.into());
123 }
124 if whp {
125 features_to_enable.append(&mut WHP_TESTS_REQUIRED_FEATURES.into());
126 }
127
128 if installing && auto_install && !features_to_enable.is_empty() {
130 let features = flowey::shell_cmd!(rt, "DISM.exe /Online /Get-Features").output()?;
131 assert!(features.status.success());
132 let features = String::from_utf8_lossy(&features.stdout).to_string();
133 let mut feature = None;
134 for line in features.lines() {
135 if let Some((k, v)) = line.split_once(":") {
136 if let Some(f) = feature {
137 assert_eq!(k.trim(), "State");
138 match v.trim() {
139 "Enabled" => {
140 assert!(features_to_enable.remove(f));
141 }
142 "Disabled" => {}
143 _ => anyhow::bail!("Unknown feature enablement state"),
144 }
145 feature = None;
146 } else if k.trim() == "Feature Name" {
147 let new_feature = v.trim();
148 feature = features_to_enable.contains(new_feature).then_some(new_feature);
149 }
150 }
151 }
152 } else if installing && !auto_install && !features_to_enable.is_empty() {
153 log::info!("Skipping Windows feature check (requires admin). Assuming features are already enabled.");
155 features_to_enable.clear();
156 }
157
158 if installing && auto_install && !features_to_enable.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
160 let mut features_to_install_string = String::new();
161 for feature in features_to_enable.iter() {
162 features_to_install_string.push_str(feature);
163 features_to_install_string.push('\n');
164 }
165
166 log::warn!(
167 r#"
168================================================================================
169To run the VMM tests, the following features need to be enabled:
170{features_to_install_string}
171
172You may need to restart your system for the changes to take effect.
173
174If you're OK with installing these features, please press <enter>.
175Otherwise, press `ctrl-c` to cancel the run.
176================================================================================
177"#
178 );
179 let _ = std::io::stdin().read_line(&mut String::new());
180 }
181
182 for feature in features_to_enable {
184 if installing && auto_install {
185 flowey::shell_cmd!(rt, "DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}").run()?;
186 }
187 commands.push(format!("DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}"));
188 }
189
190 let mut reg_keys_to_set = BTreeSet::new();
192 if hyperv {
193 reg_keys_to_set.insert("AllowFirmwareLoadFromFile");
195 reg_keys_to_set.insert("EnableAdditionalComPorts");
197
198 if hardware_isolation {
199 reg_keys_to_set.insert("EnableHardwareIsolation");
200 }
201 }
202
203 if installing && auto_install && !reg_keys_to_set.is_empty() {
205 let output = flowey::shell_cmd!(rt, "reg.exe query {VIRT_REG_PATH}").output()?;
206 if output.status.success() {
207 let output = String::from_utf8_lossy(&output.stdout).to_string();
208 for line in output.lines() {
209 let components = line.split_whitespace().collect::<Vec<_>>();
210 if components.len() == 3
211 && reg_keys_to_set.contains(components[0])
212 && components[1] == "REG_DWORD"
213 && components[2] == "0x1"
214 {
215 assert!(reg_keys_to_set.remove(components[0]));
216 }
217 }
218 }
219 } else if installing && !auto_install && !reg_keys_to_set.is_empty() {
220 log::info!("Skipping registry key check. Assuming keys are already set.");
222 reg_keys_to_set.clear();
223 }
224
225 if installing && auto_install && !reg_keys_to_set.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
227 let mut reg_keys_to_set_string = String::new();
228 for feature in reg_keys_to_set.iter() {
229 reg_keys_to_set_string.push_str(feature);
230 reg_keys_to_set_string.push('\n');
231 }
232
233 log::warn!(
234 r#"
235================================================================================
236To run the VMM tests, the following registry keys need to be set to 1:
237{reg_keys_to_set_string}
238
239If you're OK with changing the registry, please press <enter>.
240Otherwise, press `ctrl-c` to cancel the run.
241================================================================================
242"#
243 );
244 let _ = std::io::stdin().read_line(&mut String::new());
245 }
246
247 for v in reg_keys_to_set {
249 if installing && auto_install {
252 flowey::shell_cmd!(rt, "reg.exe add {VIRT_REG_PATH} /v {v} /t REG_DWORD /d 1 /f").run()?;
253 }
254 commands.push(format!("reg.exe add \"{VIRT_REG_PATH}\" /v {v} /t REG_DWORD /d 1 /f"));
255 }
256
257 for write_cmds in write_commands {
258 rt.write(write_cmds, &commands);
259 }
260
261 Ok(())
262 }
263 });
264 }
265 VmmTestsDepSelections::Linux => {
266 ctx.emit_rust_step("install vmm tests deps (linux)", |ctx| {
267 installed.claim(ctx);
268 let write_commands = write_commands.claim(ctx);
269
270 |rt| {
271 for write_cmds in write_commands {
272 rt.write(write_cmds, &Vec::new());
273 }
274
275 Ok(())
276 }
277 });
278 }
279 }
280
281 Ok(())
282 }
283}