1use std::fmt::{self, Debug};
5use std::sync::{Arc, Mutex, MutexGuard};
6
7use serde_json::Value;
8use utils::time::{ClockType, get_time_us};
9
10use super::builder::build_and_boot_microvm;
11use super::persist::{create_snapshot, restore_from_snapshot};
12use super::resources::VmResources;
13use super::{Vmm, VmmError};
14use crate::EventManager;
15use crate::builder::StartMicrovmError;
16use crate::cpu_config::templates::{CustomCpuTemplate, GuestConfigError};
17use crate::devices::virtio::balloon::device::{HintingStatus, StartHintingCmd};
18use crate::devices::virtio::mem::VirtioMemStatus;
19use crate::logger::{LoggerConfig, info, warn, *};
20use crate::mmds::data_store::{self, Mmds};
21use crate::persist::{CreateSnapshotError, RestoreFromSnapshotError, VmInfo};
22use crate::resources::VmmConfig;
23use crate::seccomp::BpfThreadMap;
24use crate::vmm_config::balloon::{
25 BalloonConfigError, BalloonDeviceConfig, BalloonStats, BalloonUpdateConfig,
26 BalloonUpdateStatsConfig,
27};
28use crate::vmm_config::boot_source::{BootSourceConfig, BootSourceConfigError};
29use crate::vmm_config::drive::{BlockDeviceConfig, BlockDeviceUpdateConfig, DriveError};
30use crate::vmm_config::entropy::{EntropyDeviceConfig, EntropyDeviceError};
31use crate::vmm_config::instance_info::InstanceInfo;
32use crate::vmm_config::machine_config::{MachineConfig, MachineConfigError, MachineConfigUpdate};
33use crate::vmm_config::memory_hotplug::{
34 MemoryHotplugConfig, MemoryHotplugConfigError, MemoryHotplugSizeUpdate,
35};
36use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError};
37use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
38use crate::vmm_config::net::{
39 NetworkInterfaceConfig, NetworkInterfaceError, NetworkInterfaceUpdateConfig,
40};
41use crate::vmm_config::pmem::{PmemConfig, PmemConfigError};
42use crate::vmm_config::serial::SerialConfig;
43use crate::vmm_config::snapshot::{CreateSnapshotParams, LoadSnapshotParams, SnapshotType};
44use crate::vmm_config::vsock::{VsockConfigError, VsockDeviceConfig};
45use crate::vmm_config::{self, RateLimiterUpdate};
46
47#[derive(Debug, PartialEq, Eq)]
50pub enum VmmAction {
51 ConfigureBootSource(BootSourceConfig),
54 ConfigureLogger(LoggerConfig),
57 ConfigureMetrics(MetricsConfig),
60 ConfigureSerial(SerialConfig),
62 CreateSnapshot(CreateSnapshotParams),
65 GetBalloonConfig,
67 GetBalloonStats,
69 GetFullVmConfig,
71 GetMMDS,
73 GetVmMachineConfig,
75 GetVmInstanceInfo,
77 GetVmmVersion,
79 FlushMetrics,
81 InsertBlockDevice(BlockDeviceConfig),
84 InsertPmemDevice(PmemConfig),
86 InsertNetworkDevice(NetworkInterfaceConfig),
90 LoadSnapshot(LoadSnapshotParams),
94 PatchMMDS(Value),
96 Pause,
98 PutMMDS(Value),
100 PutCpuConfiguration(CustomCpuTemplate),
102 Resume,
104 SetBalloonDevice(BalloonDeviceConfig),
108 SetMmdsConfiguration(MmdsConfig),
110 SetVsockDevice(VsockDeviceConfig),
114 SetEntropyDevice(EntropyDeviceConfig),
117 GetMemoryHotplugStatus,
119 SetMemoryHotplugDevice(MemoryHotplugConfig),
122 UpdateMemoryHotplugSize(MemoryHotplugSizeUpdate),
125 StartMicroVm,
127 #[cfg(target_arch = "x86_64")]
130 SendCtrlAltDel,
131 UpdateBalloon(BalloonUpdateConfig),
133 UpdateBalloonStatistics(BalloonUpdateStatsConfig),
135 StartFreePageHinting(StartHintingCmd),
137 GetFreePageHintingStatus,
139 StopFreePageHinting,
141 UpdateBlockDevice(BlockDeviceUpdateConfig),
143 UpdateNetworkInterface(NetworkInterfaceUpdateConfig),
146 UpdateMachineConfiguration(MachineConfigUpdate),
149}
150
151#[derive(Debug, thiserror::Error, displaydoc::Display)]
153pub enum VmmActionError {
154 BalloonConfig(#[from] BalloonConfigError),
156 BalloonUpdate(VmmError),
158 BootSource(#[from] BootSourceConfigError),
160 CreateSnapshot(#[from] CreateSnapshotError),
162 ConfigureCpu(#[from] GuestConfigError),
164 DriveConfig(#[from] DriveError),
166 EntropyDevice(#[from] EntropyDeviceError),
168 PmemDevice(#[from] PmemConfigError),
170 MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
172 MemoryHotplugUpdate(VmmError),
174 InternalVmm(#[from] VmmError),
176 LoadSnapshot(#[from] LoadSnapshotError),
178 Logger(#[from] crate::logger::LoggerUpdateError),
180 MachineConfig(#[from] MachineConfigError),
182 Metrics(#[from] MetricsConfigError),
184 #[from(ignore)]
185 Mmds(#[from] data_store::MmdsDatastoreError),
187 MmdsConfig(#[from] MmdsConfigError),
189 #[from(ignore)]
190 MmdsLimitExceeded(data_store::MmdsDatastoreError),
192 NetworkConfig(#[from] NetworkInterfaceError),
194 NotSupported(String),
196 OperationNotSupportedPostBoot,
198 OperationNotSupportedPreBoot,
200 StartMicrovm(#[from] StartMicrovmError),
202 VsockConfig(#[from] VsockConfigError),
204}
205
206#[allow(clippy::large_enum_variant)]
209#[derive(Debug, PartialEq, Eq)]
210pub enum VmmData {
211 BalloonConfig(BalloonDeviceConfig),
213 BalloonStats(BalloonStats),
215 Empty,
217 FullVmConfig(VmmConfig),
219 MachineConfiguration(MachineConfig),
221 MmdsValue(serde_json::Value),
223 InstanceInformation(InstanceInfo),
225 VmmVersion(String),
227 VirtioMemStatus(VirtioMemStatus),
229 HintingStatus(HintingStatus),
231}
232
233trait MmdsRequestHandler {
237 fn mmds(&mut self) -> Result<MutexGuard<'_, Mmds>, VmmActionError>;
238
239 fn get_mmds(&mut self) -> Result<VmmData, VmmActionError> {
240 Ok(VmmData::MmdsValue(self.mmds()?.data_store_value()))
241 }
242
243 fn patch_mmds(&mut self, value: serde_json::Value) -> Result<VmmData, VmmActionError> {
244 self.mmds()?
245 .patch_data(value)
246 .map(|()| VmmData::Empty)
247 .map_err(|err| match err {
248 data_store::MmdsDatastoreError::DataStoreLimitExceeded => {
249 VmmActionError::MmdsLimitExceeded(
250 data_store::MmdsDatastoreError::DataStoreLimitExceeded,
251 )
252 }
253 _ => VmmActionError::Mmds(err),
254 })
255 }
256
257 fn put_mmds(&mut self, value: serde_json::Value) -> Result<VmmData, VmmActionError> {
258 self.mmds()?
259 .put_data(value)
260 .map(|()| VmmData::Empty)
261 .map_err(|err| match err {
262 data_store::MmdsDatastoreError::DataStoreLimitExceeded => {
263 VmmActionError::MmdsLimitExceeded(
264 data_store::MmdsDatastoreError::DataStoreLimitExceeded,
265 )
266 }
267 _ => VmmActionError::Mmds(err),
268 })
269 }
270}
271
272pub struct PrebootApiController<'a> {
274 seccomp_filters: &'a BpfThreadMap,
275 instance_info: InstanceInfo,
276 vm_resources: &'a mut VmResources,
277 event_manager: &'a mut EventManager,
278 pub built_vmm: Option<Arc<Mutex<Vmm>>>,
280 boot_path: bool,
283 fatal_error: Option<BuildMicrovmFromRequestsError>,
286}
287
288impl fmt::Debug for PrebootApiController<'_> {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 f.debug_struct("PrebootApiController")
292 .field("seccomp_filters", &self.seccomp_filters)
293 .field("instance_info", &self.instance_info)
294 .field("vm_resources", &self.vm_resources)
295 .field("event_manager", &"?")
296 .field("built_vmm", &self.built_vmm)
297 .field("boot_path", &self.boot_path)
298 .field("fatal_error", &self.fatal_error)
299 .finish()
300 }
301}
302
303impl MmdsRequestHandler for PrebootApiController<'_> {
304 fn mmds(&mut self) -> Result<MutexGuard<'_, Mmds>, VmmActionError> {
305 self.vm_resources
306 .locked_mmds_or_default()
307 .map_err(VmmActionError::MmdsConfig)
308 }
309}
310
311#[derive(Debug, thiserror::Error, displaydoc::Display)]
313pub enum LoadSnapshotError {
314 LoadSnapshotNotAllowed,
316 RestoreFromSnapshot(#[from] RestoreFromSnapshotError),
318 ResumeMicrovm(#[from] VmmError),
320}
321
322pub type ApiRequest = Box<VmmAction>;
324pub type ApiResponse = Box<Result<VmmData, VmmActionError>>;
326
327#[derive(Debug, thiserror::Error, displaydoc::Display)]
329pub enum BuildMicrovmFromRequestsError {
330 ConfigureMmds(#[from] MmdsConfigError),
332 PopulateMmds(#[from] data_store::MmdsDatastoreError),
334 Restore,
336 Resume,
338}
339
340impl<'a> PrebootApiController<'a> {
341 pub fn new(
343 seccomp_filters: &'a BpfThreadMap,
344 instance_info: InstanceInfo,
345 vm_resources: &'a mut VmResources,
346 event_manager: &'a mut EventManager,
347 ) -> Self {
348 Self {
349 seccomp_filters,
350 instance_info,
351 vm_resources,
352 event_manager,
353 built_vmm: None,
354 boot_path: false,
355 fatal_error: None,
356 }
357 }
358
359 #[allow(clippy::too_many_arguments)]
363 pub fn build_microvm_from_requests(
364 seccomp_filters: &BpfThreadMap,
365 event_manager: &mut EventManager,
366 instance_info: InstanceInfo,
367 from_api: &std::sync::mpsc::Receiver<ApiRequest>,
368 to_api: &std::sync::mpsc::Sender<ApiResponse>,
369 api_event_fd: &vmm_sys_util::eventfd::EventFd,
370 boot_timer_enabled: bool,
371 pci_enabled: bool,
372 mmds_size_limit: usize,
373 metadata_json: Option<&str>,
374 ) -> Result<(VmResources, Arc<Mutex<Vmm>>), BuildMicrovmFromRequestsError> {
375 let mut vm_resources = VmResources {
376 boot_timer: boot_timer_enabled,
377 mmds_size_limit,
378 pci_enabled,
379 ..Default::default()
380 };
381
382 if let Some(data) = metadata_json {
384 vm_resources.locked_mmds_or_default()?.put_data(
385 serde_json::from_str(data).expect("MMDS error: metadata provided not valid json"),
386 )?;
387
388 info!("Successfully added metadata to mmds from file");
389 }
390
391 let mut preboot_controller = PrebootApiController::new(
392 seccomp_filters,
393 instance_info,
394 &mut vm_resources,
395 event_manager,
396 );
397
398 while preboot_controller.built_vmm.is_none() {
402 let req = from_api
404 .recv()
405 .expect("The channel's sending half was disconnected. Cannot receive data.");
406
407 api_event_fd
410 .read()
411 .expect("VMM: Failed to read the API event_fd");
412
413 let res = preboot_controller.handle_preboot_request(*req);
415
416 to_api.send(Box::new(res)).expect("one-shot channel closed");
418
419 if let Some(preboot_error) = preboot_controller.fatal_error {
421 return Err(preboot_error);
422 }
423 }
424
425 let vmm = preboot_controller.built_vmm.unwrap();
427 Ok((vm_resources, vmm))
428 }
429
430 pub fn handle_preboot_request(
433 &mut self,
434 request: VmmAction,
435 ) -> Result<VmmData, VmmActionError> {
436 use self::VmmAction::*;
437
438 match request {
439 ConfigureBootSource(config) => self.set_boot_source(config),
441 ConfigureLogger(logger_cfg) => crate::logger::LOGGER
442 .update(logger_cfg)
443 .map(|()| VmmData::Empty)
444 .map_err(VmmActionError::Logger),
445 ConfigureMetrics(metrics_cfg) => vmm_config::metrics::init_metrics(metrics_cfg)
446 .map(|()| VmmData::Empty)
447 .map_err(VmmActionError::Metrics),
448 ConfigureSerial(serial_cfg) => {
449 self.vm_resources.serial_out_path = serial_cfg.serial_out_path;
450 Ok(VmmData::Empty)
451 }
452 GetBalloonConfig => self.balloon_config(),
453 GetFullVmConfig => {
454 warn!(
455 "If the VM was restored from snapshot, boot-source, machine-config.smt, and \
456 machine-config.cpu_template will all be empty."
457 );
458 Ok(VmmData::FullVmConfig((&*self.vm_resources).into()))
459 }
460 GetMMDS => self.get_mmds(),
461 GetVmMachineConfig => Ok(VmmData::MachineConfiguration(
462 self.vm_resources.machine_config.clone(),
463 )),
464 GetVmInstanceInfo => Ok(VmmData::InstanceInformation(self.instance_info.clone())),
465 GetVmmVersion => Ok(VmmData::VmmVersion(self.instance_info.vmm_version.clone())),
466 InsertBlockDevice(config) => self.insert_block_device(config),
467 InsertPmemDevice(config) => self.insert_pmem_device(config),
468 InsertNetworkDevice(config) => self.insert_net_device(config),
469 LoadSnapshot(config) => self
470 .load_snapshot(&config)
471 .map_err(VmmActionError::LoadSnapshot),
472 PatchMMDS(value) => self.patch_mmds(value),
473 PutCpuConfiguration(custom_cpu_template) => {
474 self.set_custom_cpu_template(custom_cpu_template)
475 }
476 PutMMDS(value) => self.put_mmds(value),
477 SetBalloonDevice(config) => self.set_balloon_device(config),
478 SetVsockDevice(config) => self.set_vsock_device(config),
479 SetMmdsConfiguration(config) => self.set_mmds_config(config),
480 StartMicroVm => self.start_microvm(),
481 UpdateMachineConfiguration(config) => self.update_machine_config(config),
482 SetEntropyDevice(config) => self.set_entropy_device(config),
483 SetMemoryHotplugDevice(config) => self.set_memory_hotplug_device(config),
484 CreateSnapshot(_)
486 | FlushMetrics
487 | Pause
488 | Resume
489 | GetBalloonStats
490 | GetMemoryHotplugStatus
491 | UpdateBalloon(_)
492 | UpdateBalloonStatistics(_)
493 | UpdateBlockDevice(_)
494 | UpdateMemoryHotplugSize(_)
495 | UpdateNetworkInterface(_)
496 | StartFreePageHinting(_)
497 | GetFreePageHintingStatus
498 | StopFreePageHinting => Err(VmmActionError::OperationNotSupportedPreBoot),
499 #[cfg(target_arch = "x86_64")]
500 SendCtrlAltDel => Err(VmmActionError::OperationNotSupportedPreBoot),
501 }
502 }
503
504 fn balloon_config(&mut self) -> Result<VmmData, VmmActionError> {
505 self.vm_resources
506 .balloon
507 .get_config()
508 .map(VmmData::BalloonConfig)
509 .map_err(VmmActionError::BalloonConfig)
510 }
511
512 fn insert_block_device(&mut self, cfg: BlockDeviceConfig) -> Result<VmmData, VmmActionError> {
513 self.boot_path = true;
514 self.vm_resources
515 .set_block_device(cfg)
516 .map(|()| VmmData::Empty)
517 .map_err(VmmActionError::DriveConfig)
518 }
519
520 fn insert_net_device(
521 &mut self,
522 cfg: NetworkInterfaceConfig,
523 ) -> Result<VmmData, VmmActionError> {
524 self.boot_path = true;
525 self.vm_resources
526 .build_net_device(cfg)
527 .map(|()| VmmData::Empty)
528 .map_err(VmmActionError::NetworkConfig)
529 }
530
531 fn insert_pmem_device(&mut self, cfg: PmemConfig) -> Result<VmmData, VmmActionError> {
532 self.boot_path = true;
533 self.vm_resources
534 .build_pmem_device(cfg)
535 .map(|()| VmmData::Empty)
536 .map_err(VmmActionError::PmemDevice)
537 }
538
539 fn set_balloon_device(&mut self, cfg: BalloonDeviceConfig) -> Result<VmmData, VmmActionError> {
540 self.boot_path = true;
541 self.vm_resources
542 .set_balloon_device(cfg)
543 .map(|()| VmmData::Empty)
544 .map_err(VmmActionError::BalloonConfig)
545 }
546
547 fn set_boot_source(&mut self, cfg: BootSourceConfig) -> Result<VmmData, VmmActionError> {
548 self.boot_path = true;
549 self.vm_resources
550 .build_boot_source(cfg)
551 .map(|()| VmmData::Empty)
552 .map_err(VmmActionError::BootSource)
553 }
554
555 fn set_mmds_config(&mut self, cfg: MmdsConfig) -> Result<VmmData, VmmActionError> {
556 self.boot_path = true;
557 self.vm_resources
558 .set_mmds_config(cfg, &self.instance_info.id)
559 .map(|()| VmmData::Empty)
560 .map_err(VmmActionError::MmdsConfig)
561 }
562
563 fn update_machine_config(
564 &mut self,
565 cfg: MachineConfigUpdate,
566 ) -> Result<VmmData, VmmActionError> {
567 self.boot_path = true;
568 self.vm_resources
569 .update_machine_config(&cfg)
570 .map(|()| VmmData::Empty)
571 .map_err(VmmActionError::MachineConfig)
572 }
573
574 fn set_custom_cpu_template(
575 &mut self,
576 cpu_template: CustomCpuTemplate,
577 ) -> Result<VmmData, VmmActionError> {
578 self.vm_resources.set_custom_cpu_template(cpu_template);
579 Ok(VmmData::Empty)
580 }
581
582 fn set_vsock_device(&mut self, cfg: VsockDeviceConfig) -> Result<VmmData, VmmActionError> {
583 self.boot_path = true;
584 self.vm_resources
585 .set_vsock_device(cfg)
586 .map(|()| VmmData::Empty)
587 .map_err(VmmActionError::VsockConfig)
588 }
589
590 fn set_entropy_device(&mut self, cfg: EntropyDeviceConfig) -> Result<VmmData, VmmActionError> {
591 self.boot_path = true;
592 self.vm_resources.build_entropy_device(cfg)?;
593 Ok(VmmData::Empty)
594 }
595
596 fn set_memory_hotplug_device(
597 &mut self,
598 cfg: MemoryHotplugConfig,
599 ) -> Result<VmmData, VmmActionError> {
600 self.boot_path = true;
601 self.vm_resources.set_memory_hotplug_config(cfg)?;
602 Ok(VmmData::Empty)
603 }
604
605 fn start_microvm(&mut self) -> Result<VmmData, VmmActionError> {
608 build_and_boot_microvm(
609 &self.instance_info,
610 self.vm_resources,
611 self.event_manager,
612 self.seccomp_filters,
613 )
614 .map(|vmm| {
615 self.built_vmm = Some(vmm);
616 VmmData::Empty
617 })
618 .map_err(VmmActionError::StartMicrovm)
619 }
620
621 fn load_snapshot(
624 &mut self,
625 load_params: &LoadSnapshotParams,
626 ) -> Result<VmmData, LoadSnapshotError> {
627 let load_start_us = get_time_us(ClockType::Monotonic);
628
629 if self.boot_path {
630 let err = LoadSnapshotError::LoadSnapshotNotAllowed;
631 info!("{}", err);
632 return Err(err);
633 }
634
635 let vmm = restore_from_snapshot(
637 &self.instance_info,
638 self.event_manager,
639 self.seccomp_filters,
640 load_params,
641 self.vm_resources,
642 )
643 .inspect_err(|_| {
644 self.fatal_error = Some(BuildMicrovmFromRequestsError::Restore);
646 })?;
647 if load_params.resume_vm {
649 vmm.lock()
650 .expect("Poisoned lock")
651 .resume_vm()
652 .inspect_err(|_| {
653 self.fatal_error = Some(BuildMicrovmFromRequestsError::Resume);
655 })?;
656 }
657 self.built_vmm = Some(vmm);
659
660 debug!(
661 "'load snapshot' VMM action took {} us.",
662 update_metric_with_elapsed_time(&METRICS.latencies_us.vmm_load_snapshot, load_start_us)
663 );
664
665 Ok(VmmData::Empty)
666 }
667}
668
669#[derive(Debug)]
671pub struct RuntimeApiController {
672 vmm: Arc<Mutex<Vmm>>,
673 vm_resources: VmResources,
674}
675
676impl MmdsRequestHandler for RuntimeApiController {
677 fn mmds(&mut self) -> Result<MutexGuard<'_, Mmds>, VmmActionError> {
678 self.vm_resources
679 .locked_mmds_or_default()
680 .map_err(VmmActionError::MmdsConfig)
681 }
682}
683
684impl RuntimeApiController {
685 pub fn handle_request(&mut self, request: VmmAction) -> Result<VmmData, VmmActionError> {
687 use self::VmmAction::*;
688 match request {
689 CreateSnapshot(snapshot_create_cfg) => self.create_snapshot(&snapshot_create_cfg),
691 FlushMetrics => self.flush_metrics(),
692 GetBalloonConfig => self
693 .vmm
694 .lock()
695 .expect("Poisoned lock")
696 .balloon_config()
697 .map(|state| VmmData::BalloonConfig(BalloonDeviceConfig::from(state)))
698 .map_err(VmmActionError::InternalVmm),
699 GetBalloonStats => self
700 .vmm
701 .lock()
702 .expect("Poisoned lock")
703 .latest_balloon_stats()
704 .map(VmmData::BalloonStats)
705 .map_err(VmmActionError::InternalVmm),
706 GetFullVmConfig => Ok(VmmData::FullVmConfig((&self.vm_resources).into())),
707 GetMemoryHotplugStatus => self
708 .vmm
709 .lock()
710 .expect("Poisoned lock")
711 .memory_hotplug_status()
712 .map(VmmData::VirtioMemStatus)
713 .map_err(VmmActionError::InternalVmm),
714 GetMMDS => self.get_mmds(),
715 GetVmMachineConfig => Ok(VmmData::MachineConfiguration(
716 self.vm_resources.machine_config.clone(),
717 )),
718 GetVmInstanceInfo => Ok(VmmData::InstanceInformation(
719 self.vmm.lock().expect("Poisoned lock").instance_info(),
720 )),
721 GetVmmVersion => Ok(VmmData::VmmVersion(
722 self.vmm.lock().expect("Poisoned lock").version(),
723 )),
724 PatchMMDS(value) => self.patch_mmds(value),
725 Pause => self.pause(),
726 PutMMDS(value) => self.put_mmds(value),
727 Resume => self.resume(),
728 #[cfg(target_arch = "x86_64")]
729 SendCtrlAltDel => self.send_ctrl_alt_del(),
730 UpdateBalloon(balloon_update) => self
731 .vmm
732 .lock()
733 .expect("Poisoned lock")
734 .update_balloon_config(balloon_update.amount_mib)
735 .map(|_| VmmData::Empty)
736 .map_err(VmmActionError::BalloonUpdate),
737 UpdateBalloonStatistics(balloon_stats_update) => self
738 .vmm
739 .lock()
740 .expect("Poisoned lock")
741 .update_balloon_stats_config(balloon_stats_update.stats_polling_interval_s)
742 .map(|_| VmmData::Empty)
743 .map_err(VmmActionError::BalloonUpdate),
744 StartFreePageHinting(cmd) => self
745 .vmm
746 .lock()
747 .expect("Poisoned lock")
748 .start_balloon_hinting(cmd)
749 .map(|_| VmmData::Empty)
750 .map_err(VmmActionError::BalloonUpdate),
751 GetFreePageHintingStatus => self
752 .vmm
753 .lock()
754 .expect("Poisoned lock")
755 .get_balloon_hinting_status()
756 .map(VmmData::HintingStatus)
757 .map_err(VmmActionError::BalloonUpdate),
758 StopFreePageHinting => self
759 .vmm
760 .lock()
761 .expect("Poisoned lock")
762 .stop_balloon_hinting()
763 .map(|_| VmmData::Empty)
764 .map_err(VmmActionError::BalloonUpdate),
765 UpdateBlockDevice(new_cfg) => self.update_block_device(new_cfg),
766 UpdateNetworkInterface(netif_update) => self.update_net_rate_limiters(netif_update),
767 UpdateMemoryHotplugSize(cfg) => self
768 .vmm
769 .lock()
770 .expect("Poisoned lock")
771 .update_memory_hotplug_size(cfg.requested_size_mib)
772 .map(|_| VmmData::Empty)
773 .map_err(VmmActionError::MemoryHotplugUpdate),
774 ConfigureBootSource(_)
776 | ConfigureLogger(_)
777 | ConfigureMetrics(_)
778 | ConfigureSerial(_)
779 | InsertBlockDevice(_)
780 | InsertPmemDevice(_)
781 | InsertNetworkDevice(_)
782 | LoadSnapshot(_)
783 | PutCpuConfiguration(_)
784 | SetBalloonDevice(_)
785 | SetVsockDevice(_)
786 | SetMmdsConfiguration(_)
787 | SetEntropyDevice(_)
788 | SetMemoryHotplugDevice(_)
789 | StartMicroVm
790 | UpdateMachineConfiguration(_) => Err(VmmActionError::OperationNotSupportedPostBoot),
791 }
792 }
793
794 pub fn new(vm_resources: VmResources, vmm: Arc<Mutex<Vmm>>) -> Self {
796 Self { vmm, vm_resources }
797 }
798
799 pub fn pause(&mut self) -> Result<VmmData, VmmActionError> {
801 let pause_start_us = get_time_us(ClockType::Monotonic);
802
803 self.vmm.lock().expect("Poisoned lock").pause_vm()?;
804
805 let elapsed_time_us =
806 update_metric_with_elapsed_time(&METRICS.latencies_us.vmm_pause_vm, pause_start_us);
807 info!("'pause vm' VMM action took {} us.", elapsed_time_us);
808
809 Ok(VmmData::Empty)
810 }
811
812 pub fn resume(&mut self) -> Result<VmmData, VmmActionError> {
814 let resume_start_us = get_time_us(ClockType::Monotonic);
815
816 self.vmm.lock().expect("Poisoned lock").resume_vm()?;
817
818 let elapsed_time_us =
819 update_metric_with_elapsed_time(&METRICS.latencies_us.vmm_resume_vm, resume_start_us);
820 info!("'resume vm' VMM action took {} us.", elapsed_time_us);
821
822 Ok(VmmData::Empty)
823 }
824
825 fn flush_metrics(&mut self) -> Result<VmmData, VmmActionError> {
831 METRICS
833 .write()
834 .map(|_| VmmData::Empty)
835 .map_err(super::VmmError::Metrics)
836 .map_err(VmmActionError::InternalVmm)
837 }
838
839 #[cfg(target_arch = "x86_64")]
841 fn send_ctrl_alt_del(&mut self) -> Result<VmmData, VmmActionError> {
842 self.vmm
843 .lock()
844 .expect("Poisoned lock")
845 .send_ctrl_alt_del()
846 .map(|()| VmmData::Empty)
847 .map_err(VmmActionError::InternalVmm)
848 }
849
850 fn create_snapshot(
851 &mut self,
852 create_params: &CreateSnapshotParams,
853 ) -> Result<VmmData, VmmActionError> {
854 if create_params.snapshot_type == SnapshotType::Diff {
855 log_dev_preview_warning("Virtual machine diff snapshots", None);
856 }
857
858 let mut locked_vmm = self.vmm.lock().unwrap();
859 let vm_info = VmInfo::from(&self.vm_resources);
860 let create_start_us = get_time_us(ClockType::Monotonic);
861
862 create_snapshot(&mut locked_vmm, &vm_info, create_params)?;
863
864 match create_params.snapshot_type {
865 SnapshotType::Full => {
866 let elapsed_time_us = update_metric_with_elapsed_time(
867 &METRICS.latencies_us.vmm_full_create_snapshot,
868 create_start_us,
869 );
870 info!(
871 "'create full snapshot' VMM action took {} us.",
872 elapsed_time_us
873 );
874 }
875 SnapshotType::Diff => {
876 let elapsed_time_us = update_metric_with_elapsed_time(
877 &METRICS.latencies_us.vmm_diff_create_snapshot,
878 create_start_us,
879 );
880 info!(
881 "'create diff snapshot' VMM action took {} us.",
882 elapsed_time_us
883 );
884 }
885 }
886 Ok(VmmData::Empty)
887 }
888
889 fn update_block_device(
894 &mut self,
895 new_cfg: BlockDeviceUpdateConfig,
896 ) -> Result<VmmData, VmmActionError> {
897 let mut vmm = self.vmm.lock().expect("Poisoned lock");
898
899 if new_cfg.path_on_host.is_none() && new_cfg.rate_limiter.is_none() {
901 vmm.update_vhost_user_block_config(&new_cfg.drive_id)
902 .map_err(DriveError::DeviceUpdate)?;
903 }
904
905 if let Some(new_path) = new_cfg.path_on_host {
907 vmm.update_block_device_path(&new_cfg.drive_id, new_path)
908 .map_err(DriveError::DeviceUpdate)?;
909 }
910 if new_cfg.rate_limiter.is_some() {
911 vmm.update_block_rate_limiter(
912 &new_cfg.drive_id,
913 RateLimiterUpdate::from(new_cfg.rate_limiter).bandwidth,
914 RateLimiterUpdate::from(new_cfg.rate_limiter).ops,
915 )
916 .map_err(DriveError::DeviceUpdate)?;
917 }
918 Ok(VmmData::Empty)
919 }
920
921 fn update_net_rate_limiters(
923 &mut self,
924 new_cfg: NetworkInterfaceUpdateConfig,
925 ) -> Result<VmmData, VmmActionError> {
926 self.vmm
927 .lock()
928 .expect("Poisoned lock")
929 .update_net_rate_limiters(
930 &new_cfg.iface_id,
931 RateLimiterUpdate::from(new_cfg.rx_rate_limiter).bandwidth,
932 RateLimiterUpdate::from(new_cfg.rx_rate_limiter).ops,
933 RateLimiterUpdate::from(new_cfg.tx_rate_limiter).bandwidth,
934 RateLimiterUpdate::from(new_cfg.tx_rate_limiter).ops,
935 )
936 .map(|()| VmmData::Empty)
937 .map_err(NetworkInterfaceError::DeviceUpdate)
938 .map_err(VmmActionError::NetworkConfig)
939 }
940}
941
942#[cfg(test)]
943mod tests {
944 use std::path::PathBuf;
945
946 use super::*;
947 use crate::HTTP_MAX_PAYLOAD_SIZE;
948 use crate::builder::tests::default_vmm;
949 use crate::devices::virtio::block::CacheType;
950 use crate::mmds::data_store::MmdsVersion;
951 use crate::seccomp::BpfThreadMap;
952 use crate::vmm_config::snapshot::{MemBackendConfig, MemBackendType};
953
954 fn default_preboot<'a>(
955 vm_resources: &'a mut VmResources,
956 event_manager: &'a mut EventManager,
957 seccomp_filters: &'a BpfThreadMap,
958 ) -> PrebootApiController<'a> {
959 let instance_info = InstanceInfo::default();
960 PrebootApiController::new(seccomp_filters, instance_info, vm_resources, event_manager)
961 }
962
963 fn preboot_request(request: VmmAction) -> Result<VmmData, VmmActionError> {
964 let mut vm_resources = VmResources::default();
965 let mut evmgr = EventManager::new().unwrap();
966 let seccomp_filters = BpfThreadMap::new();
967 let mut preboot = default_preboot(&mut vm_resources, &mut evmgr, &seccomp_filters);
968 preboot.handle_preboot_request(request)
969 }
970
971 fn preboot_request_with_mmds(
972 request: VmmAction,
973 mmds: Arc<Mutex<Mmds>>,
974 ) -> Result<VmmData, VmmActionError> {
975 let mut vm_resources = VmResources {
976 mmds: Some(mmds),
977 mmds_size_limit: HTTP_MAX_PAYLOAD_SIZE,
978 ..Default::default()
979 };
980 let mut evmgr = EventManager::new().unwrap();
981 let seccomp_filters = BpfThreadMap::new();
982 let mut preboot = default_preboot(&mut vm_resources, &mut evmgr, &seccomp_filters);
983 preboot.handle_preboot_request(request)
984 }
985
986 #[test]
987 fn test_preboot_get_vm_config() {
988 assert_eq!(
989 preboot_request(VmmAction::GetVmMachineConfig).unwrap(),
990 VmmData::MachineConfiguration(MachineConfig::default())
991 );
992 }
993
994 #[test]
995 fn test_preboot_get_mmds() {
996 assert_eq!(
997 preboot_request(VmmAction::GetMMDS).unwrap(),
998 VmmData::MmdsValue(Value::Null)
999 );
1000 }
1001
1002 #[test]
1003 fn test_runtime_get_mmds() {
1004 assert_eq!(
1005 runtime_request(VmmAction::GetMMDS).unwrap(),
1006 VmmData::MmdsValue(Value::Null)
1007 );
1008 }
1009
1010 #[test]
1011 fn test_preboot_put_mmds() {
1012 let mmds = Arc::new(Mutex::new(Mmds::default()));
1013
1014 assert_eq!(
1015 preboot_request_with_mmds(
1016 VmmAction::PutMMDS(Value::String("string".to_string())),
1017 mmds.clone()
1018 )
1019 .unwrap(),
1020 VmmData::Empty
1021 );
1022 assert_eq!(
1023 preboot_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1024 VmmData::MmdsValue(Value::String("string".to_string()))
1025 );
1026
1027 let filling = (0..51300).map(|_| "X").collect::<String>();
1028 let data = "{\"key\": \"".to_string() + &filling + "\"}";
1029
1030 assert!(matches!(
1031 preboot_request_with_mmds(
1032 VmmAction::PutMMDS(serde_json::from_str(&data).unwrap()),
1033 mmds.clone()
1034 ),
1035 Err(VmmActionError::MmdsLimitExceeded(_))
1036 ));
1037 assert_eq!(
1038 preboot_request_with_mmds(VmmAction::GetMMDS, mmds).unwrap(),
1039 VmmData::MmdsValue(Value::String("string".to_string()))
1040 );
1041 }
1042
1043 #[test]
1044 fn test_runtime_put_mmds() {
1045 let mmds = Arc::new(Mutex::new(Mmds::default()));
1046
1047 assert_eq!(
1048 runtime_request_with_mmds(
1049 VmmAction::PutMMDS(Value::String("string".to_string())),
1050 mmds.clone()
1051 )
1052 .unwrap(),
1053 VmmData::Empty
1054 );
1055 assert_eq!(
1056 runtime_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1057 VmmData::MmdsValue(Value::String("string".to_string()))
1058 );
1059
1060 let filling = (0..51300).map(|_| "X").collect::<String>();
1061 let data = "{\"key\": \"".to_string() + &filling + "\"}";
1062
1063 assert!(matches!(
1064 runtime_request_with_mmds(
1065 VmmAction::PutMMDS(serde_json::from_str(&data).unwrap()),
1066 mmds.clone()
1067 ),
1068 Err(VmmActionError::MmdsLimitExceeded(_))
1069 ));
1070 assert_eq!(
1071 runtime_request_with_mmds(VmmAction::GetMMDS, mmds).unwrap(),
1072 VmmData::MmdsValue(Value::String("string".to_string()))
1073 );
1074 }
1075
1076 #[test]
1077 fn test_preboot_patch_mmds() {
1078 let mmds = Arc::new(Mutex::new(Mmds::default()));
1079 let res = preboot_request(VmmAction::PatchMMDS(Value::String("string".to_string())));
1081 assert!(
1082 matches!(
1083 res,
1084 Err(VmmActionError::Mmds(
1085 data_store::MmdsDatastoreError::NotInitialized
1086 ))
1087 ),
1088 "{:?}",
1089 res
1090 );
1091
1092 assert_eq!(
1093 preboot_request_with_mmds(
1094 VmmAction::PutMMDS(
1095 serde_json::from_str(r#"{"key1": "value1", "key2": "val2"}"#).unwrap(),
1096 ),
1097 mmds.clone()
1098 )
1099 .unwrap(),
1100 VmmData::Empty
1101 );
1102 assert_eq!(
1103 preboot_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1104 VmmData::MmdsValue(
1105 serde_json::from_str(r#"{"key1": "value1", "key2": "val2"}"#).unwrap()
1106 )
1107 );
1108
1109 assert_eq!(
1110 preboot_request_with_mmds(
1111 VmmAction::PatchMMDS(
1112 serde_json::from_str(r#"{"key1": null, "key2": "value2"}"#).unwrap(),
1113 ),
1114 mmds.clone()
1115 )
1116 .unwrap(),
1117 VmmData::Empty
1118 );
1119
1120 assert_eq!(
1121 preboot_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1122 VmmData::MmdsValue(serde_json::from_str(r#"{"key2": "value2"}"#).unwrap())
1123 );
1124
1125 let filling = (0..HTTP_MAX_PAYLOAD_SIZE).map(|_| "X").collect::<String>();
1126 let data = "{\"key\": \"".to_string() + &filling + "\"}";
1127
1128 assert!(matches!(
1129 preboot_request_with_mmds(
1130 VmmAction::PatchMMDS(serde_json::from_str(&data).unwrap()),
1131 mmds.clone()
1132 ),
1133 Err(VmmActionError::MmdsLimitExceeded(_))
1134 ));
1135 assert_eq!(
1136 preboot_request_with_mmds(VmmAction::GetMMDS, mmds).unwrap(),
1137 VmmData::MmdsValue(serde_json::from_str(r#"{"key2": "value2"}"#).unwrap())
1138 );
1139 }
1140
1141 #[test]
1142 fn test_runtime_patch_mmds() {
1143 let mmds = Arc::new(Mutex::new(Mmds::default()));
1144 let res = runtime_request(VmmAction::PatchMMDS(Value::String("string".to_string())));
1146 assert!(
1147 matches!(
1148 res,
1149 Err(VmmActionError::Mmds(
1150 data_store::MmdsDatastoreError::NotInitialized
1151 ))
1152 ),
1153 "{:?}",
1154 res
1155 );
1156
1157 assert_eq!(
1158 runtime_request_with_mmds(
1159 VmmAction::PutMMDS(
1160 serde_json::from_str(r#"{"key1": "value1", "key2": "val2"}"#).unwrap(),
1161 ),
1162 mmds.clone()
1163 )
1164 .unwrap(),
1165 VmmData::Empty
1166 );
1167 assert_eq!(
1168 runtime_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1169 VmmData::MmdsValue(
1170 serde_json::from_str(r#"{"key1": "value1", "key2": "val2"}"#).unwrap()
1171 )
1172 );
1173 assert_eq!(
1174 runtime_request_with_mmds(
1175 VmmAction::PatchMMDS(
1176 serde_json::from_str(r#"{"key1": null, "key2": "value2"}"#).unwrap(),
1177 ),
1178 mmds.clone()
1179 )
1180 .unwrap(),
1181 VmmData::Empty
1182 );
1183
1184 assert_eq!(
1185 runtime_request_with_mmds(VmmAction::GetMMDS, mmds.clone()).unwrap(),
1186 VmmData::MmdsValue(serde_json::from_str(r#"{"key2": "value2"}"#).unwrap())
1187 );
1188
1189 let filling = (0..HTTP_MAX_PAYLOAD_SIZE).map(|_| "X").collect::<String>();
1190 let data = "{\"key\": \"".to_string() + &filling + "\"}";
1191
1192 assert!(matches!(
1193 runtime_request_with_mmds(
1194 VmmAction::PatchMMDS(serde_json::from_str(&data).unwrap()),
1195 mmds.clone()
1196 ),
1197 Err(VmmActionError::MmdsLimitExceeded(_))
1198 ));
1199 assert_eq!(
1200 runtime_request_with_mmds(VmmAction::GetMMDS, mmds).unwrap(),
1201 VmmData::MmdsValue(serde_json::from_str(r#"{"key2": "value2"}"#).unwrap())
1202 );
1203 }
1204
1205 #[test]
1206 fn test_preboot_disallowed() {
1207 fn check_unsupported(res: Result<VmmData, VmmActionError>) {
1208 assert!(
1209 matches!(res, Err(VmmActionError::OperationNotSupportedPreBoot)),
1210 "{:?}",
1211 res
1212 );
1213 }
1214
1215 check_unsupported(preboot_request(VmmAction::FlushMetrics));
1216 check_unsupported(preboot_request(VmmAction::Pause));
1217 check_unsupported(preboot_request(VmmAction::Resume));
1218 check_unsupported(preboot_request(VmmAction::GetBalloonStats));
1219 check_unsupported(preboot_request(VmmAction::UpdateBalloon(
1220 BalloonUpdateConfig { amount_mib: 0 },
1221 )));
1222 check_unsupported(preboot_request(VmmAction::StartFreePageHinting(
1223 Default::default(),
1224 )));
1225 check_unsupported(preboot_request(VmmAction::GetFreePageHintingStatus));
1226 check_unsupported(preboot_request(VmmAction::StopFreePageHinting));
1227 check_unsupported(preboot_request(VmmAction::UpdateBalloonStatistics(
1228 BalloonUpdateStatsConfig {
1229 stats_polling_interval_s: 0,
1230 },
1231 )));
1232 check_unsupported(preboot_request(VmmAction::UpdateBlockDevice(
1233 BlockDeviceUpdateConfig::default(),
1234 )));
1235 check_unsupported(preboot_request(VmmAction::UpdateNetworkInterface(
1236 NetworkInterfaceUpdateConfig {
1237 iface_id: String::new(),
1238 rx_rate_limiter: None,
1239 tx_rate_limiter: None,
1240 },
1241 )));
1242 check_unsupported(preboot_request(VmmAction::CreateSnapshot(
1243 CreateSnapshotParams {
1244 snapshot_type: SnapshotType::Full,
1245 snapshot_path: PathBuf::new(),
1246 mem_file_path: PathBuf::new(),
1247 },
1248 )));
1249 #[cfg(target_arch = "x86_64")]
1250 check_unsupported(preboot_request(VmmAction::SendCtrlAltDel));
1251 check_unsupported(preboot_request(VmmAction::UpdateMemoryHotplugSize(
1252 MemoryHotplugSizeUpdate {
1253 requested_size_mib: 0,
1254 },
1255 )));
1256 }
1257
1258 fn runtime_request(request: VmmAction) -> Result<VmmData, VmmActionError> {
1259 let vmm = Arc::new(Mutex::new(default_vmm()));
1260 let mut runtime = RuntimeApiController::new(VmResources::default(), vmm.clone());
1261 runtime.handle_request(request)
1262 }
1263
1264 fn runtime_request_with_mmds(
1265 request: VmmAction,
1266 mmds: Arc<Mutex<Mmds>>,
1267 ) -> Result<VmmData, VmmActionError> {
1268 let vm_res = VmResources {
1269 mmds: Some(mmds),
1270 ..Default::default()
1271 };
1272 let vmm = Arc::new(Mutex::new(default_vmm()));
1273 let mut runtime = RuntimeApiController::new(vm_res, vmm.clone());
1274 runtime.handle_request(request)
1275 }
1276
1277 #[test]
1278 fn test_runtime_get_vm_config() {
1279 assert_eq!(
1280 runtime_request(VmmAction::GetVmMachineConfig).unwrap(),
1281 VmmData::MachineConfiguration(MachineConfig::default())
1282 );
1283 }
1284
1285 #[test]
1286 fn test_runtime_disallowed() {
1287 fn check_unsupported(res: Result<VmmData, VmmActionError>) {
1288 assert!(
1289 matches!(res, Err(VmmActionError::OperationNotSupportedPostBoot)),
1290 "{:?}",
1291 res
1292 );
1293 }
1294
1295 check_unsupported(runtime_request(VmmAction::ConfigureBootSource(
1296 BootSourceConfig::default(),
1297 )));
1298 check_unsupported(runtime_request(VmmAction::ConfigureLogger(LoggerConfig {
1299 log_path: Some(PathBuf::new()),
1300 level: Some(crate::logger::LevelFilter::Debug),
1301 show_level: Some(false),
1302 show_log_origin: Some(false),
1303 module: None,
1304 })));
1305 check_unsupported(runtime_request(VmmAction::ConfigureMetrics(
1306 MetricsConfig {
1307 metrics_path: PathBuf::new(),
1308 },
1309 )));
1310 check_unsupported(runtime_request(VmmAction::InsertBlockDevice(
1311 BlockDeviceConfig {
1312 drive_id: String::new(),
1313 partuuid: None,
1314 is_root_device: false,
1315 cache_type: CacheType::Unsafe,
1316
1317 is_read_only: Some(false),
1318 path_on_host: Some(String::new()),
1319 rate_limiter: None,
1320 file_engine_type: None,
1321
1322 socket: None,
1323 },
1324 )));
1325 check_unsupported(runtime_request(VmmAction::InsertNetworkDevice(
1326 NetworkInterfaceConfig {
1327 iface_id: String::new(),
1328 host_dev_name: String::new(),
1329 guest_mac: None,
1330 rx_rate_limiter: None,
1331 tx_rate_limiter: None,
1332 },
1333 )));
1334 check_unsupported(runtime_request(VmmAction::SetVsockDevice(
1335 VsockDeviceConfig {
1336 vsock_id: Some(String::new()),
1337 guest_cid: 0,
1338 uds_path: String::new(),
1339 },
1340 )));
1341 check_unsupported(runtime_request(VmmAction::SetBalloonDevice(
1342 BalloonDeviceConfig::default(),
1343 )));
1344 check_unsupported(runtime_request(VmmAction::SetVsockDevice(
1345 VsockDeviceConfig {
1346 vsock_id: Some(String::new()),
1347 guest_cid: 0,
1348 uds_path: String::new(),
1349 },
1350 )));
1351 check_unsupported(runtime_request(VmmAction::SetMmdsConfiguration(
1352 MmdsConfig {
1353 ipv4_address: None,
1354 version: MmdsVersion::default(),
1355 network_interfaces: Vec::new(),
1356 imds_compat: false,
1357 },
1358 )));
1359 check_unsupported(runtime_request(VmmAction::UpdateMachineConfiguration(
1360 MachineConfigUpdate::from(MachineConfig::default()),
1361 )));
1362 check_unsupported(runtime_request(VmmAction::LoadSnapshot(
1363 LoadSnapshotParams {
1364 snapshot_path: PathBuf::new(),
1365 mem_backend: MemBackendConfig {
1366 backend_type: MemBackendType::File,
1367 backend_path: PathBuf::new(),
1368 },
1369 track_dirty_pages: false,
1370 resume_vm: false,
1371 network_overrides: vec![],
1372 },
1373 )));
1374 check_unsupported(runtime_request(VmmAction::SetEntropyDevice(
1375 EntropyDeviceConfig::default(),
1376 )));
1377 check_unsupported(runtime_request(VmmAction::InsertPmemDevice(PmemConfig {
1378 id: String::new(),
1379 path_on_host: String::new(),
1380 root_device: false,
1381 read_only: false,
1382 })));
1383 check_unsupported(runtime_request(VmmAction::SetMemoryHotplugDevice(
1384 MemoryHotplugConfig::default(),
1385 )));
1386 }
1387}