vmm/
rpc_interface.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// This enum represents the public interface of the VMM. Each action contains various
48/// bits of information (ids, paths, etc.).
49#[derive(Debug, PartialEq, Eq)]
50pub enum VmmAction {
51    /// Configure the boot source of the microVM using as input the `ConfigureBootSource`. This
52    /// action can only be called before the microVM has booted.
53    ConfigureBootSource(BootSourceConfig),
54    /// Configure the logger using as input the `LoggerConfig`. This action can only be called
55    /// before the microVM has booted.
56    ConfigureLogger(LoggerConfig),
57    /// Configure the metrics using as input the `MetricsConfig`. This action can only be called
58    /// before the microVM has booted.
59    ConfigureMetrics(MetricsConfig),
60    /// Configure the serial device. This action can only be called before the microVM has booted.
61    ConfigureSerial(SerialConfig),
62    /// Create a snapshot using as input the `CreateSnapshotParams`. This action can only be called
63    /// after the microVM has booted and only when the microVM is in `Paused` state.
64    CreateSnapshot(CreateSnapshotParams),
65    /// Get the balloon device configuration.
66    GetBalloonConfig,
67    /// Get the ballon device latest statistics.
68    GetBalloonStats,
69    /// Get complete microVM configuration in JSON format.
70    GetFullVmConfig,
71    /// Get MMDS contents.
72    GetMMDS,
73    /// Get the machine configuration of the microVM.
74    GetVmMachineConfig,
75    /// Get microVM instance information.
76    GetVmInstanceInfo,
77    /// Get microVM version.
78    GetVmmVersion,
79    /// Flush the metrics. This action can only be called after the logger has been configured.
80    FlushMetrics,
81    /// Add a new block device or update one that already exists using the `BlockDeviceConfig` as
82    /// input. This action can only be called before the microVM has booted.
83    InsertBlockDevice(BlockDeviceConfig),
84    /// Add a virtio-pmem device.
85    InsertPmemDevice(PmemConfig),
86    /// Add a new network interface config or update one that already exists using the
87    /// `NetworkInterfaceConfig` as input. This action can only be called before the microVM has
88    /// booted.
89    InsertNetworkDevice(NetworkInterfaceConfig),
90    /// Load the microVM state using as input the `LoadSnapshotParams`. This action can only be
91    /// called before the microVM has booted. If this action is successful, the loaded microVM will
92    /// be in `Paused` state. Should change this state to `Resumed` for the microVM to run.
93    LoadSnapshot(LoadSnapshotParams),
94    /// Partial update of the MMDS contents.
95    PatchMMDS(Value),
96    /// Pause the guest, by pausing the microVM VCPUs.
97    Pause,
98    /// Repopulate the MMDS contents.
99    PutMMDS(Value),
100    /// Configure the guest vCPU features.
101    PutCpuConfiguration(CustomCpuTemplate),
102    /// Resume the guest, by resuming the microVM VCPUs.
103    Resume,
104    /// Set the balloon device or update the one that already exists using the
105    /// `BalloonDeviceConfig` as input. This action can only be called before the microVM
106    /// has booted.
107    SetBalloonDevice(BalloonDeviceConfig),
108    /// Set the MMDS configuration.
109    SetMmdsConfiguration(MmdsConfig),
110    /// Set the vsock device or update the one that already exists using the
111    /// `VsockDeviceConfig` as input. This action can only be called before the microVM has
112    /// booted.
113    SetVsockDevice(VsockDeviceConfig),
114    /// Set the entropy device using `EntropyDeviceConfig` as input. This action can only be called
115    /// before the microVM has booted.
116    SetEntropyDevice(EntropyDeviceConfig),
117    /// Get the memory hotplug device configuration and status.
118    GetMemoryHotplugStatus,
119    /// Set the memory hotplug device using `MemoryHotplugConfig` as input. This action can only be
120    /// called before the microVM has booted.
121    SetMemoryHotplugDevice(MemoryHotplugConfig),
122    /// Updates the memory hotplug device using `MemoryHotplugConfigUpdate` as input. This action
123    /// can only be called after the microVM has booted.
124    UpdateMemoryHotplugSize(MemoryHotplugSizeUpdate),
125    /// Launch the microVM. This action can only be called before the microVM has booted.
126    StartMicroVm,
127    /// Send CTRL+ALT+DEL to the microVM, using the i8042 keyboard function. If an AT-keyboard
128    /// driver is listening on the guest end, this can be used to shut down the microVM gracefully.
129    #[cfg(target_arch = "x86_64")]
130    SendCtrlAltDel,
131    /// Update the balloon size, after microVM start.
132    UpdateBalloon(BalloonUpdateConfig),
133    /// Update the balloon statistics polling interval, after microVM start.
134    UpdateBalloonStatistics(BalloonUpdateStatsConfig),
135    /// Start a free page hinting run
136    StartFreePageHinting(StartHintingCmd),
137    /// Retrieve the status of the hinting run
138    GetFreePageHintingStatus,
139    /// Stops a free page hinting run
140    StopFreePageHinting,
141    /// Update existing block device properties such as `path_on_host` or `rate_limiter`.
142    UpdateBlockDevice(BlockDeviceUpdateConfig),
143    /// Update a network interface, after microVM start. Currently, the only updatable properties
144    /// are the RX and TX rate limiters.
145    UpdateNetworkInterface(NetworkInterfaceUpdateConfig),
146    /// Update the microVM configuration (memory & vcpu) using `VmUpdateConfig` as input. This
147    /// action can only be called before the microVM has booted.
148    UpdateMachineConfiguration(MachineConfigUpdate),
149}
150
151/// Wrapper for all errors associated with VMM actions.
152#[derive(Debug, thiserror::Error, displaydoc::Display)]
153pub enum VmmActionError {
154    /// Balloon config error: {0}
155    BalloonConfig(#[from] BalloonConfigError),
156    /// Balloon update error: {0}
157    BalloonUpdate(VmmError),
158    /// Boot source error: {0}
159    BootSource(#[from] BootSourceConfigError),
160    /// Create snapshot error: {0}
161    CreateSnapshot(#[from] CreateSnapshotError),
162    /// Configure CPU error: {0}
163    ConfigureCpu(#[from] GuestConfigError),
164    /// Drive config error: {0}
165    DriveConfig(#[from] DriveError),
166    /// Entropy device error: {0}
167    EntropyDevice(#[from] EntropyDeviceError),
168    /// Pmem device error: {0}
169    PmemDevice(#[from] PmemConfigError),
170    /// Memory hotplug config error: {0}
171    MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
172    /// Memory hotplug update error: {0}
173    MemoryHotplugUpdate(VmmError),
174    /// Internal VMM error: {0}
175    InternalVmm(#[from] VmmError),
176    /// Load snapshot error: {0}
177    LoadSnapshot(#[from] LoadSnapshotError),
178    /// Logger error: {0}
179    Logger(#[from] crate::logger::LoggerUpdateError),
180    /// Machine config error: {0}
181    MachineConfig(#[from] MachineConfigError),
182    /// Metrics error: {0}
183    Metrics(#[from] MetricsConfigError),
184    #[from(ignore)]
185    /// MMDS error: {0}
186    Mmds(#[from] data_store::MmdsDatastoreError),
187    /// MMMDS config error: {0}
188    MmdsConfig(#[from] MmdsConfigError),
189    #[from(ignore)]
190    /// MMDS limit exceeded error: {0}
191    MmdsLimitExceeded(data_store::MmdsDatastoreError),
192    /// Network config error: {0}
193    NetworkConfig(#[from] NetworkInterfaceError),
194    /// The requested operation is not supported: {0}
195    NotSupported(String),
196    /// The requested operation is not supported after starting the microVM.
197    OperationNotSupportedPostBoot,
198    /// The requested operation is not supported before starting the microVM.
199    OperationNotSupportedPreBoot,
200    /// Start microvm error: {0}
201    StartMicrovm(#[from] StartMicrovmError),
202    /// Vsock config error: {0}
203    VsockConfig(#[from] VsockConfigError),
204}
205
206/// The enum represents the response sent by the VMM in case of success. The response is either
207/// empty, when no data needs to be sent, or an internal VMM structure.
208#[allow(clippy::large_enum_variant)]
209#[derive(Debug, PartialEq, Eq)]
210pub enum VmmData {
211    /// The balloon device configuration.
212    BalloonConfig(BalloonDeviceConfig),
213    /// The latest balloon device statistics.
214    BalloonStats(BalloonStats),
215    /// No data is sent on the channel.
216    Empty,
217    /// The complete microVM configuration in JSON format.
218    FullVmConfig(VmmConfig),
219    /// The microVM configuration represented by `VmConfig`.
220    MachineConfiguration(MachineConfig),
221    /// Mmds contents.
222    MmdsValue(serde_json::Value),
223    /// The microVM instance information.
224    InstanceInformation(InstanceInfo),
225    /// The microVM version.
226    VmmVersion(String),
227    /// The status of the memory hotplug device.
228    VirtioMemStatus(VirtioMemStatus),
229    /// The status of the virtio-balloon hinting run
230    HintingStatus(HintingStatus),
231}
232
233/// Trait used for deduplicating the MMDS request handling across the two ApiControllers.
234/// The methods get a mutable reference to self because the methods should initialise the data
235/// store with the defaults if it's not already initialised.
236trait 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
272/// Enables pre-boot setup and instantiation of a Firecracker VMM.
273pub 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    /// The [`Vmm`] object constructed through requests
279    pub built_vmm: Option<Arc<Mutex<Vmm>>>,
280    // Configuring boot specific resources will set this to true.
281    // Loading from snapshot will not be allowed once this is true.
282    boot_path: bool,
283    // Some PrebootApiRequest errors are irrecoverable and Firecracker
284    // should cleanly teardown if they occur.
285    fatal_error: Option<BuildMicrovmFromRequestsError>,
286}
287
288// TODO Remove when `EventManager` implements `std::fmt::Debug`.
289impl 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/// Error type for [`PrebootApiController::load_snapshot`]
312#[derive(Debug, thiserror::Error, displaydoc::Display)]
313pub enum LoadSnapshotError {
314    /// Loading a microVM snapshot not allowed after configuring boot-specific resources.
315    LoadSnapshotNotAllowed,
316    /// Failed to restore from snapshot: {0}
317    RestoreFromSnapshot(#[from] RestoreFromSnapshotError),
318    /// Failed to resume microVM: {0}
319    ResumeMicrovm(#[from] VmmError),
320}
321
322/// Shorthand type for a request containing a boxed VmmAction.
323pub type ApiRequest = Box<VmmAction>;
324/// Shorthand type for a response containing a boxed Result.
325pub type ApiResponse = Box<Result<VmmData, VmmActionError>>;
326
327/// Error type for `PrebootApiController::build_microvm_from_requests`.
328#[derive(Debug, thiserror::Error, displaydoc::Display)]
329pub enum BuildMicrovmFromRequestsError {
330    /// Configuring MMDS failed: {0}.
331    ConfigureMmds(#[from] MmdsConfigError),
332    /// Populating MMDS from file failed: {0}.
333    PopulateMmds(#[from] data_store::MmdsDatastoreError),
334    /// Loading snapshot failed.
335    Restore,
336    /// Resuming MicroVM after loading snapshot failed.
337    Resume,
338}
339
340impl<'a> PrebootApiController<'a> {
341    /// Constructor for the PrebootApiController.
342    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    /// Default implementation for the function that builds and starts a microVM.
360    ///
361    /// Returns a populated `VmResources` object and a running `Vmm` object.
362    #[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        // Init the data store from file, if present.
383        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        // Configure and start microVM through successive API calls.
399        // Iterate through API calls to configure microVm.
400        // The loop breaks when a microVM is successfully started, and a running Vmm is built.
401        while preboot_controller.built_vmm.is_none() {
402            // Get request
403            let req = from_api
404                .recv()
405                .expect("The channel's sending half was disconnected. Cannot receive data.");
406
407            // Also consume the API event along with the message. It is safe to unwrap()
408            // because this event_fd is blocking.
409            api_event_fd
410                .read()
411                .expect("VMM: Failed to read the API event_fd");
412
413            // Process the request.
414            let res = preboot_controller.handle_preboot_request(*req);
415
416            // Send back the response.
417            to_api.send(Box::new(res)).expect("one-shot channel closed");
418
419            // If any fatal errors were encountered, break the loop.
420            if let Some(preboot_error) = preboot_controller.fatal_error {
421                return Err(preboot_error);
422            }
423        }
424
425        // Safe to unwrap because previous loop cannot end on None.
426        let vmm = preboot_controller.built_vmm.unwrap();
427        Ok((vm_resources, vmm))
428    }
429
430    /// Handles the incoming preboot request and provides a response for it.
431    /// Returns a built/running `Vmm` after handling a successful `StartMicroVm` request.
432    pub fn handle_preboot_request(
433        &mut self,
434        request: VmmAction,
435    ) -> Result<VmmData, VmmActionError> {
436        use self::VmmAction::*;
437
438        match request {
439            // Supported operations allowed pre-boot.
440            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            // Operations not allowed pre-boot.
485            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    // On success, this command will end the pre-boot stage and this controller
606    // will be replaced by a runtime controller.
607    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    // On success, this command will end the pre-boot stage and this controller
622    // will be replaced by a runtime controller.
623    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        // Restore VM from snapshot
636        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            // If restore fails, we consider the process is too dirty to recover.
645            self.fatal_error = Some(BuildMicrovmFromRequestsError::Restore);
646        })?;
647        // Resume VM
648        if load_params.resume_vm {
649            vmm.lock()
650                .expect("Poisoned lock")
651                .resume_vm()
652                .inspect_err(|_| {
653                    // If resume fails, we consider the process is too dirty to recover.
654                    self.fatal_error = Some(BuildMicrovmFromRequestsError::Resume);
655                })?;
656        }
657        // Set the VM
658        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/// Enables RPC interaction with a running Firecracker VMM.
670#[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    /// Handles the incoming runtime `VmmAction` request and provides a response for it.
686    pub fn handle_request(&mut self, request: VmmAction) -> Result<VmmData, VmmActionError> {
687        use self::VmmAction::*;
688        match request {
689            // Supported operations allowed post-boot.
690            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            // Operations not allowed post-boot.
775            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    /// Creates a new `RuntimeApiController`.
795    pub fn new(vm_resources: VmResources, vmm: Arc<Mutex<Vmm>>) -> Self {
796        Self { vmm, vm_resources }
797    }
798
799    /// Pauses the microVM by pausing the vCPUs.
800    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    /// Resumes the microVM by resuming the vCPUs.
813    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    /// Write the metrics on user demand (flush). We use the word `flush` here to highlight the fact
826    /// that the metrics will be written immediately.
827    /// Defer to inner Vmm. We'll move to a variant where the Vmm simply exposes functionality like
828    /// getting the dirty pages, and then we'll have the metrics flushing logic entirely on the
829    /// outside.
830    fn flush_metrics(&mut self) -> Result<VmmData, VmmActionError> {
831        // FIXME: we're losing the bool saying whether metrics were actually written.
832        METRICS
833            .write()
834            .map(|_| VmmData::Empty)
835            .map_err(super::VmmError::Metrics)
836            .map_err(VmmActionError::InternalVmm)
837    }
838
839    /// Injects CTRL+ALT+DEL keystroke combo to the inner Vmm (if present).
840    #[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    /// Updates block device properties:
890    ///  - path of the host file backing the emulated block device, update the disk image on the
891    ///    device and its virtio configuration
892    ///  - rate limiter configuration.
893    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        // vhost-user-block updates
900        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        // virtio-block updates
906        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    /// Updates configuration for an emulated net device as described in `new_cfg`.
922    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        // MMDS data store is not yet initialized.
1080        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        // MMDS data store is not yet initialized.
1145        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}