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 sh = xshell::Shell::new()?;
78 let mut commands = Vec::new();
79
80 if !matches!(rt.platform(), FlowPlatform::Windows)
81 && !flowey_lib_common::_util::running_in_wsl(rt)
82 {
83 anyhow::bail!("Must be on Windows or WSL2 to install Windows deps.")
84 }
85
86 let mut features_to_enable = BTreeSet::new();
90 if hyperv {
91 features_to_enable.append(&mut HYPERV_TESTS_REQUIRED_FEATURES.into());
92 }
93 if whp {
94 features_to_enable.append(&mut WHP_TESTS_REQUIRED_FEATURES.into());
95 }
96
97 if installing && !features_to_enable.is_empty() {
99 let features = xshell::cmd!(sh, "DISM.exe /Online /Get-Features").output()?;
100 assert!(features.status.success());
101 let features = String::from_utf8_lossy(&features.stdout).to_string();
102 let mut feature = None;
103 for line in features.lines() {
104 if let Some((k, v)) = line.split_once(":") {
105 if let Some(f) = feature {
106 assert_eq!(k.trim(), "State");
107 match v.trim() {
108 "Enabled" => {
109 assert!(features_to_enable.remove(f));
110 }
111 "Disabled" => {}
112 _ => anyhow::bail!("Unknown feature enablement state"),
113 }
114 feature = None;
115 } else if k.trim() == "Feature Name" {
116 let new_feature = v.trim();
117 feature = features_to_enable.contains(new_feature).then_some(new_feature);
118 }
119 }
120 }
121 }
122
123 if installing && !features_to_enable.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
125 let mut features_to_install_string = String::new();
126 for feature in features_to_enable.iter() {
127 features_to_install_string.push_str(feature);
128 features_to_install_string.push('\n');
129 }
130
131 log::warn!(
132 r#"
133================================================================================
134To run the VMM tests, the following features need to be enabled:
135{features_to_install_string}
136
137You may need to restart your system for the changes to take effect.
138
139If you're OK with installing these features, please press <enter>.
140Otherwise, press `ctrl-c` to cancel the run.
141================================================================================
142"#
143 );
144 let _ = std::io::stdin().read_line(&mut String::new());
145 }
146
147 for feature in features_to_enable {
149 if installing {
150 xshell::cmd!(sh, "DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}").run()?;
151 }
152 commands.push(format!("DISM.exe /Online /NoRestart /Enable-Feature /All /FeatureName:{feature}"));
153 }
154
155 let mut reg_keys_to_set = BTreeSet::new();
157 if hyperv {
158 reg_keys_to_set.insert("AllowFirmwareLoadFromFile");
159
160 if hardware_isolation {
161 reg_keys_to_set.insert("EnableHardwareIsolation");
162 }
163 }
164
165 if installing && !reg_keys_to_set.is_empty() {
167 let output = xshell::cmd!(sh, "reg.exe query {VIRT_REG_PATH}").output()?;
168 if output.status.success() {
169 let output = String::from_utf8_lossy(&output.stdout).to_string();
170 for line in output.lines() {
171 let components = line.split_whitespace().collect::<Vec<_>>();
172 if components.len() == 3
173 && reg_keys_to_set.contains(components[0])
174 && components[1] == "REG_DWORD"
175 && components[2] == "0x1"
176 {
177 assert!(reg_keys_to_set.remove(components[0]));
178 }
179 }
180 }
181 }
182
183 if installing && !reg_keys_to_set.is_empty() && matches!(rt.backend(), FlowBackend::Local) {
185 let mut reg_keys_to_set_string = String::new();
186 for feature in reg_keys_to_set.iter() {
187 reg_keys_to_set_string.push_str(feature);
188 reg_keys_to_set_string.push('\n');
189 }
190
191 log::warn!(
192 r#"
193================================================================================
194To run the VMM tests, the following registry keys need to be set to 1:
195{reg_keys_to_set_string}
196
197If you're OK with changing the registry, please press <enter>.
198Otherwise, press `ctrl-c` to cancel the run.
199================================================================================
200"#
201 );
202 let _ = std::io::stdin().read_line(&mut String::new());
203 }
204
205 for v in reg_keys_to_set {
207 if installing {
210 xshell::cmd!(sh, "reg.exe add {VIRT_REG_PATH} /v {v} /t REG_DWORD /d 1 /f").run()?;
211 }
212 commands.push(format!("reg.exe add \"{VIRT_REG_PATH}\" /v {v} /t REG_DWORD /d 1 /f"));
213 }
214
215 for write_cmds in write_commands {
216 rt.write(write_cmds, &commands);
217 }
218
219 Ok(())
220 }
221 });
222 }
223 VmmTestsDepSelections::Linux => {
224 ctx.emit_rust_step("install vmm tests deps (linux)", |ctx| {
225 installed.claim(ctx);
226 let write_commands = write_commands.claim(ctx);
227
228 |rt| {
229 for write_cmds in write_commands {
230 rt.write(write_cmds, &Vec::new());
231 }
232
233 Ok(())
234 }
235 });
236 }
237 }
238
239 Ok(())
240 }
241}