microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-run-vmm-tests-failure

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

hyperv/tools/hypestv/src/windows/vm.rs

581lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! VM command handling.
5
6use super::InspectArgs;
7use super::InspectTarget;
8use super::LogMode;
9use super::ParavisorCommand;
10use super::SerialMode;
11use super::VmCommand;
12use super::hyperv::hvc_output;
13use super::hyperv::powershell_script;
14use super::hyperv::run_hcsdiag;
15use super::hyperv::run_hvc;
16use super::rustyline_printer::Printer;
17use anyhow::Context as _;
18use console_relay::ConsoleLaunchOptions;
19use diag_client::DiagClient;
20use futures::AsyncBufReadExt;
21use futures::AsyncWriteExt;
22use futures::FutureExt;
23use futures::StreamExt;
24use futures::io::BufReader;
25use futures_concurrency::future::Race;
26use guid::Guid;
27use pal_async::DefaultDriver;
28use pal_async::pipe::PolledPipe;
29use pal_async::task::Spawn;
30use pal_async::task::Task;
31use std::sync::Arc;
32use std::time::Duration;
33
34pub struct Vm {
35 inner: Arc<VmInner>,
36 serial: Vec<Option<SerialTask>>,
37 pv_kmsg: Option<KmsgTask>,
38}
39
40struct SerialTask {
41 mode: SerialMode,
42 task: Task<()>,
43 req: mesh::Sender<IoRequest>,
44}
45
46struct KmsgTask {
47 mode: LogMode,
48 task: Task<()>,
49 req: mesh::Sender<IoRequest>,
50}
51
52struct VmInner {
53 driver: DefaultDriver,
54 paravisor_diag: DiagClient,
55 name: String,
56 id: Guid,
57 printer: Printer,
58}
59
60impl Vm {
61 pub fn new(driver: DefaultDriver, name: String, printer: Printer) -> anyhow::Result<Self> {
62 let id = diag_client::hyperv::vm_id_from_name(&name).context("failed to get vm id")?;
63 let inner = Arc::new(VmInner {
64 driver: driver.clone(),
65 paravisor_diag: DiagClient::from_hyperv_id(driver, id),
66 printer,
67 name,
68 id,
69 });
70 Ok(Self {
71 serial: (0..4).map(|_| None).collect(),
72 inner,
73 pv_kmsg: None,
74 })
75 }
76
77 fn delay(&self, f: impl 'static + FnOnce(&VmInner) -> anyhow::Result<()> + Send) {
78 let inner = self.inner.clone();
79 std::thread::spawn(move || {
80 if let Err(err) = f(&inner) {
81 writeln!(inner.printer.out(), "{:#}", err).ok();
82 };
83 });
84 }
85
86 pub async fn handle_inspect(
87 &mut self,
88 target: InspectTarget,
89 path: &str,
90 ) -> anyhow::Result<inspect::Node> {
91 match target {
92 InspectTarget::Paravisor => {
93 self.inner
94 .paravisor_diag
95 .inspect(path, Some(0), Some(Duration::from_secs(1)))
96 .await
97 }
98 }
99 }
100
101 pub async fn handle_command(&mut self, cmd: VmCommand) -> anyhow::Result<()> {
102 match cmd {
103 VmCommand::Start => self.delay(move |inner| {
104 run_hvc(|cmd| cmd.arg("start").arg(&inner.name))?;
105 writeln!(inner.printer.out(), "VM started")?;
106 Ok(())
107 }),
108 VmCommand::Kill { force } => self.delay(move |inner| {
109 if force {
110 run_hcsdiag(|cmd| cmd.arg("kill").arg(inner.id.to_string()))?;
111 } else {
112 run_hvc(|cmd| cmd.arg("kill").arg(&inner.name))?;
113 }
114 writeln!(inner.printer.out(), "VM killed")?;
115 Ok(())
116 }),
117 VmCommand::Reset => self.delay(move |inner| {
118 run_hvc(|cmd| cmd.arg("reset").arg(&inner.name))?;
119 writeln!(inner.printer.out(), "VM reset")?;
120 Ok(())
121 }),
122 VmCommand::Shutdown {
123 reboot,
124 hibernate,
125 force,
126 } => {
127 if hibernate {
128 anyhow::bail!("hibernate not supported");
129 }
130 self.delay(move |inner| {
131 run_hvc(|cmd| {
132 cmd.arg(if reboot { "restart" } else { "stop" });
133 if force {
134 cmd.arg("-f");
135 }
136 cmd.arg(&inner.name)
137 })?;
138 if reboot {
139 writeln!(inner.printer.out(), "VM restarted")?;
140 } else {
141 writeln!(inner.printer.out(), "VM shutdown")?;
142 }
143 Ok(())
144 });
145 }
146 VmCommand::Serial {
147 port: None,
148 mode: _,
149 } => {
150 for (i, port) in self.serial.iter().enumerate() {
151 println!(
152 "COM{}: {}",
153 i + 1,
154 port.as_ref().map_or(SerialMode::Off, |t| t.mode)
155 );
156 }
157 }
158 VmCommand::Serial {
159 port: Some(port),
160 mode: None,
161 } => {
162 let port_index = port.checked_sub(1).context("invalid port")? as usize;
163 let task = self.serial.get_mut(port_index).context("invalid port")?;
164 println!("{}", task.as_ref().map_or(SerialMode::Off, |t| t.mode));
165 }
166 VmCommand::Serial {
167 port: Some(port),
168 mode: Some(mode),
169 } => {
170 let port_index = port.checked_sub(1).context("invalid port")? as usize;
171 let task = self.serial.get_mut(port_index).context("invalid port")?;
172
173 let target = match mode {
174 SerialMode::Off => {
175 if let Some(task) = task.take() {
176 drop(task.req);
177 task.task.await;
178 }
179 None
180 }
181 SerialMode::Log => Some(IoTarget::Printer),
182 SerialMode::Term => Some(IoTarget::Console(
183 console_relay::Console::new(
184 self.inner.driver.clone(),
185 None,
186 Some(ConsoleLaunchOptions {
187 window_title: Some(format!("COM{} [Hypestv]", port)),
188 }),
189 )
190 .context("failed to launch console")?,
191 )),
192 };
193 if let Some(target) = target {
194 if task.as_ref().is_some_and(|task| task.task.is_finished()) {
195 *task = None;
196 }
197 if let Some(task) = task {
198 task.mode = mode;
199 task.req.send(IoRequest::NewTarget(target));
200 } else {
201 let (req, recv) = mesh::channel();
202 let inner = self.inner.clone();
203 let t = self.inner.driver.spawn("serial", async move {
204 if let Err(err) = inner.handle_serial(recv, target, port).await {
205 writeln!(inner.printer.out(), "com{port} failed: {:#}", err).ok();
206 }
207 });
208 *task = Some(SerialTask { task: t, mode, req });
209 }
210 }
211 }
212 VmCommand::Paravisor(cmd) => self.handle_paravisor_command(cmd).await?,
213 VmCommand::Nmi { vtl } => {
214 powershell_script(
215 r#"
216 param([string]$id, [int]$vtl)
217 $ErrorActionPreference = "Stop"
218 $vm = Get-CimInstance -namespace "root\virtualization\v2" -query "select * from Msvm_ComputerSystem where Name = '$id'"
219 $vm | Invoke-CimMethod -Name "InjectNonMaskableInterruptEx" -Arguments @{"Vtl" = $vtl}
220 "#,
221 &[&self.inner.id.to_string(), &vtl.to_string()],
222 )?;
223 }
224 }
225 Ok(())
226 }
227
228 async fn handle_paravisor_command(&mut self, cmd: ParavisorCommand) -> anyhow::Result<()> {
229 match cmd {
230 ParavisorCommand::Start => {
231 self.inner
232 .paravisor_diag
233 .start([], [])
234 .await
235 .context("start failed")?;
236
237 writeln!(self.inner.printer.out(), "guest started within paravisor")?;
238 }
239 ParavisorCommand::Kmsg { mode: None } => {
240 println!("{}", self.pv_kmsg.as_ref().map_or(LogMode::Off, |t| t.mode));
241 }
242 ParavisorCommand::Kmsg { mode: Some(mode) } => {
243 let target = match mode {
244 LogMode::Off => {
245 if let Some(task) = self.pv_kmsg.take() {
246 drop(task.req);
247 task.task.await;
248 }
249 None
250 }
251 LogMode::Log => Some(IoTarget::Printer),
252 LogMode::Term => Some(IoTarget::Console(
253 console_relay::Console::new(
254 self.inner.driver.clone(),
255 None,
256 Some(ConsoleLaunchOptions {
257 window_title: Some("KMSG [Hypestv]".to_owned()),
258 }),
259 )
260 .context("failed to launch console")?,
261 )),
262 };
263 if let Some(target) = target {
264 if self
265 .pv_kmsg
266 .as_ref()
267 .is_some_and(|task| task.task.is_finished())
268 {
269 self.pv_kmsg = None;
270 }
271 if let Some(task) = &mut self.pv_kmsg {
272 task.mode = mode;
273 task.req.send(IoRequest::NewTarget(target));
274 } else {
275 let (req, recv) = mesh::channel();
276 let inner = self.inner.clone();
277 let t = self.inner.driver.spawn("kmsg", async move {
278 if let Err(err) = inner.handle_kmsg(recv, target).await {
279 writeln!(inner.printer.out(), "kmsg failed: {:#}", err).ok();
280 }
281 });
282 self.pv_kmsg = Some(KmsgTask { task: t, mode, req });
283 }
284 }
285 }
286 ParavisorCommand::Inspect(InspectArgs {
287 recursive,
288 limit,
289 update,
290 element,
291 }) => {
292 if let Some(update) = update {
293 let value = self
294 .inner
295 .paravisor_diag
296 .update(element.unwrap_or_default(), update)
297 .await
298 .context("update failed")?;
299
300 println!("{:#}", value);
301 } else {
302 let node = self
303 .inner
304 .paravisor_diag
305 .inspect(
306 element.unwrap_or_default(),
307 if recursive { limit } else { Some(0) },
308 Some(Duration::from_secs(1)),
309 )
310 .await
311 .context("inspect failed")?;
312
313 println!("{:#}", node);
314 }
315 }
316 ParavisorCommand::CommandLine { command_line: None } => {
317 let output = powershell_script(
318 r#"
319 param([string]$id)
320 $ErrorActionPreference = "Stop"
321 $vm = Get-CimInstance -namespace "root\virtualization\v2" -query "select * from Msvm_ComputerSystem where Name = '$id'"
322 $vssd = $vm | Get-CimAssociatedInstance -ResultClass "Msvm_VirtualSystemSettingData" -Association "Msvm_SettingsDefineState"
323 [System.Text.Encoding]::Default.GetString($vssd.FirmwareParameters)
324 "#,
325 &[&self.inner.id.to_string()],
326 )
327 .context("failed to query vssd")?;
328 println!("{}", output.trim());
329 }
330 ParavisorCommand::CommandLine {
331 command_line: Some(command_line),
332 } => {
333 let output = powershell_script(
334 r#"
335 param([string]$id, [string]$command_line)
336 $ErrorActionPreference = "Stop"
337 $vm = Get-CimInstance -namespace "root\virtualization\v2" -query "select * from Msvm_ComputerSystem where Name = '$id'"
338 $vssd = $vm | Get-CimAssociatedInstance -ResultClass "Msvm_VirtualSystemSettingData" -Association "Msvm_SettingsDefineState"
339 $vssd.FirmwareParameters = [System.Text.Encoding]::UTF8.GetBytes($command_line)
340 $vmms = Get-CimInstance -Namespace "root\virtualization\v2" -Class "Msvm_VirtualSystemManagementService"
341 $vmms | Invoke-CimMethod -Name "ModifySystemSettings" -Arguments @{"SystemSettings" = ($vssd | ConvertTo-CimEmbeddedString)}
342 $command_line
343 "#,
344 &[&self.inner.id.to_string(), &command_line],
345 )
346 .context("failed to update vssd")?;
347 println!("{}", output.trim());
348 }
349 ParavisorCommand::Reload => {
350 let output = powershell_script(
351 r#"
352 param([string]$id)
353 $ErrorActionPreference = "Stop"
354 $guestManagementService = Get-CimInstance -namespace "root\virtualization\v2" -ClassName "Msvm_VirtualSystemGuestManagementService"
355 $options = 1; # Override version checks
356 $TimeoutHintSecs = 15; # Ends up as the deadline in GuestSaveRequest (see the handling of SaveGuestVtl2StateNotification in guest_emulation_transport). Keep O(15 seconds).
357 $result = $guestManagementService | Invoke-CimMethod -name "ReloadManagementVtl" -Arguments @{
358 "VmId" = $id
359 "Options" = $options
360 "TimeoutHintSecs" = $TimeoutHintSecs
361 }
362 "#,
363 &[&self.inner.id.to_string()],
364 )
365 .context("failed to reload paravisor")?;
366 // TODO: the result here is a Msvm_ConcreteJob, which this code should inspect to wait for completion and check for success.
367 // For now, we just print the output.
368 println!("{}", output.trim());
369 }
370 }
371 Ok(())
372 }
373
374 pub fn name(&self) -> &str {
375 &self.inner.name
376 }
377
378 pub fn state(&self) -> String {
379 hvc_output(|cmd| cmd.arg("state").arg(&self.inner.name)).map_or_else(
380 |_| "unknown".to_string(),
381 |mut s| {
382 s.truncate(s.trim_end().len());
383 s
384 },
385 )
386 }
387}
388
389enum IoRequest {
390 NewTarget(IoTarget),
391}
392
393enum IoTarget {
394 Printer,
395 Console(console_relay::Console),
396}
397
398impl VmInner {
399 async fn handle_serial(
400 &self,
401 mut req: mesh::Receiver<IoRequest>,
402 mut target: IoTarget,
403 port: u32,
404 ) -> anyhow::Result<()> {
405 let mut current_serial = None;
406
407 enum Event {
408 TaskDone(anyhow::Result<()>),
409 Request(Option<IoRequest>),
410 }
411
412 loop {
413 let task = async {
414 let serial = if let Some(serial) = &mut current_serial {
415 serial
416 } else {
417 let new_serial = diag_client::hyperv::open_serial_port(
418 &self.driver,
419 diag_client::hyperv::ComPortAccessInfo::NameAndPortNumber(&self.name, port),
420 )
421 .await
422 .context("failed to open serial port")?;
423
424 writeln!(self.printer.out(), "com{port} connected").ok();
425
426 current_serial.insert(BufReader::new(
427 PolledPipe::new(&self.driver, new_serial)
428 .context("failed to create polled pipe")?,
429 ))
430 };
431
432 match &mut target {
433 IoTarget::Printer => {
434 let mut line = String::new();
435 while let Ok(n) = serial.read_line(&mut line).await {
436 if n == 0 {
437 break;
438 }
439 write!(self.printer.out(), "[com{port}]: {}", line).ok();
440 line.clear();
441 }
442 }
443 IoTarget::Console(console) => {
444 console.relay(serial).await?;
445 }
446 }
447
448 writeln!(self.printer.out(), "com{port} disconnected").ok();
449 Ok(())
450 };
451
452 let event = (task.map(Event::TaskDone), req.next().map(Event::Request))
453 .race()
454 .await;
455 match event {
456 Event::TaskDone(r) => {
457 r?;
458 current_serial = None;
459 }
460 Event::Request(Some(y)) => match y {
461 IoRequest::NewTarget(new_target) => {
462 target = new_target;
463 }
464 },
465 Event::Request(None) => {
466 break;
467 }
468 }
469 }
470
471 if let Some(serial) = current_serial {
472 drop(serial);
473 writeln!(self.printer.out(), "com{port} disconnected").ok();
474 }
475
476 Ok(())
477 }
478
479 async fn handle_kmsg(
480 &self,
481 mut req: mesh::Receiver<IoRequest>,
482 mut target: IoTarget,
483 ) -> anyhow::Result<()> {
484 let mut current = None;
485
486 enum Event {
487 TaskDone(anyhow::Result<()>),
488 Request(Option<IoRequest>),
489 }
490
491 loop {
492 let task = async {
493 let kmsg = if let Some(kmsg) = &mut current {
494 kmsg
495 } else {
496 self.paravisor_diag.wait_for_server().await?;
497 let new_kmsg = self
498 .paravisor_diag
499 .kmsg(true)
500 .await
501 .context("failed to open kmsg stream")?;
502
503 writeln!(self.printer.out(), "kmsg connected").ok();
504
505 current.insert(new_kmsg)
506 };
507
508 while let Some(data) = kmsg.next().await {
509 match data {
510 Ok(data) => match kmsg::KmsgParsedEntry::new(&data) {
511 Ok(message) => match &mut target {
512 IoTarget::Printer => {
513 writeln!(
514 self.printer.out(),
515 "[kmsg]: {}",
516 message.display(true)
517 )
518 .ok();
519 }
520 IoTarget::Console(console) => {
521 let line = format!("{}\r\n", message.display(true));
522 console.write_all(line.as_bytes()).await?;
523 }
524 },
525 Err(e) => match &mut target {
526 IoTarget::Printer => {
527 writeln!(self.printer.out(), "[kmsg]: invalid entry: {:?}", e)
528 .ok();
529 }
530 IoTarget::Console(console) => {
531 let line = format!("invalid kmsg entry: {:?}\r\n", e);
532 console.write_all(line.as_bytes()).await?;
533 }
534 },
535 },
536 Err(err) if err.kind() == std::io::ErrorKind::ConnectionReset => {
537 break;
538 }
539 Err(err) => {
540 writeln!(
541 self.printer.out(),
542 "kmsg failure: {:#}",
543 anyhow::Error::from(err)
544 )
545 .ok();
546 return Ok(());
547 }
548 }
549 }
550
551 writeln!(self.printer.out(), "kmsg disconnected").ok();
552 Ok(())
553 };
554
555 let event = (task.map(Event::TaskDone), req.next().map(Event::Request))
556 .race()
557 .await;
558 match event {
559 Event::TaskDone(r) => {
560 current = None;
561 r?;
562 }
563 Event::Request(Some(y)) => match y {
564 IoRequest::NewTarget(new_target) => {
565 target = new_target;
566 }
567 },
568 Event::Request(None) => {
569 break;
570 }
571 }
572 }
573
574 if let Some(kmsg) = current {
575 drop(kmsg);
576 writeln!(self.printer.out(), "kmsg disconnected").ok();
577 }
578
579 Ok(())
580 }
581}
582