1use std::convert::From;
5use std::path::PathBuf;
6use std::sync::{Arc, Mutex, MutexGuard};
7
8use serde::{Deserialize, Serialize};
9use vm_memory::GuestAddress;
10
11use crate::cpu_config::templates::CustomCpuTemplate;
12use crate::logger::info;
13use crate::mmds;
14use crate::mmds::data_store::{Mmds, MmdsVersion};
15use crate::mmds::ns::MmdsNetworkStack;
16use crate::utils::mib_to_bytes;
17use crate::utils::net::ipv4addr::is_link_local_valid;
18use crate::vmm_config::balloon::*;
19use crate::vmm_config::boot_source::{
20 BootConfig, BootSource, BootSourceConfig, BootSourceConfigError,
21};
22use crate::vmm_config::drive::*;
23use crate::vmm_config::entropy::*;
24use crate::vmm_config::instance_info::InstanceInfo;
25use crate::vmm_config::machine_config::{MachineConfig, MachineConfigError, MachineConfigUpdate};
26use crate::vmm_config::memory_hotplug::{MemoryHotplugConfig, MemoryHotplugConfigError};
27use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError, init_metrics};
28use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError};
29use crate::vmm_config::net::*;
30use crate::vmm_config::pmem::{PmemBuilder, PmemConfig, PmemConfigError};
31use crate::vmm_config::serial::SerialConfig;
32use crate::vmm_config::vsock::*;
33use crate::vstate::memory;
34use crate::vstate::memory::{GuestRegionMmap, MemoryError};
35
36#[derive(Debug, thiserror::Error, displaydoc::Display)]
38pub enum ResourcesError {
39 BalloonDevice(#[from] BalloonConfigError),
41 BlockDevice(#[from] DriveError),
43 BootSource(#[from] BootSourceConfigError),
45 File(#[from] std::io::Error),
47 InvalidJson(#[from] serde_json::Error),
49 Logger(#[from] crate::logger::LoggerUpdateError),
51 Metrics(#[from] MetricsConfigError),
53 Mmds(#[from] mmds::data_store::MmdsDatastoreError),
55 MmdsConfig(#[from] MmdsConfigError),
57 NetDevice(#[from] NetworkInterfaceError),
59 MachineConfig(#[from] MachineConfigError),
61 VsockDevice(#[from] VsockConfigError),
63 EntropyDevice(#[from] EntropyDeviceError),
65 PmemDevice(#[from] PmemConfigError),
67 MemoryHotplugConfig(#[from] MemoryHotplugConfigError),
69}
70
71#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
72#[serde(untagged)]
73enum CustomCpuTemplateOrPath {
74 Path(PathBuf),
75 Template(CustomCpuTemplate),
76}
77
78#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
80#[serde(rename_all = "kebab-case")]
81pub struct VmmConfig {
82 balloon: Option<BalloonDeviceConfig>,
83 drives: Vec<BlockDeviceConfig>,
84 boot_source: BootSourceConfig,
85 cpu_config: Option<CustomCpuTemplateOrPath>,
86 logger: Option<crate::logger::LoggerConfig>,
87 machine_config: Option<MachineConfig>,
88 metrics: Option<MetricsConfig>,
89 mmds_config: Option<MmdsConfig>,
90 #[serde(default)]
91 network_interfaces: Vec<NetworkInterfaceConfig>,
92 vsock: Option<VsockDeviceConfig>,
93 entropy: Option<EntropyDeviceConfig>,
94 #[serde(default, rename = "pmem")]
95 pmem_devices: Vec<PmemConfig>,
96 #[serde(skip)]
97 serial_config: Option<SerialConfig>,
98 memory_hotplug: Option<MemoryHotplugConfig>,
99}
100
101#[derive(Debug, Default)]
104pub struct VmResources {
105 pub machine_config: MachineConfig,
107 pub boot_source: BootSource,
109 pub block: BlockBuilder,
111 pub vsock: VsockBuilder,
113 pub balloon: BalloonBuilder,
115 pub net_builder: NetBuilder,
117 pub entropy: EntropyDeviceBuilder,
119 pub pmem: PmemBuilder,
121 pub memory_hotplug: Option<MemoryHotplugConfig>,
123 pub mmds: Option<Arc<Mutex<Mmds>>>,
127 pub mmds_size_limit: usize,
129 pub boot_timer: bool,
131 pub pci_enabled: bool,
133 pub serial_out_path: Option<PathBuf>,
135}
136
137impl VmResources {
138 pub fn from_json(
140 config_json: &str,
141 instance_info: &InstanceInfo,
142 mmds_size_limit: usize,
143 metadata_json: Option<&str>,
144 ) -> Result<Self, ResourcesError> {
145 let vmm_config = serde_json::from_str::<VmmConfig>(config_json)?;
146
147 if let Some(logger_config) = vmm_config.logger {
148 crate::logger::LOGGER.update(logger_config)?;
149 }
150
151 if let Some(metrics) = vmm_config.metrics {
152 init_metrics(metrics)?;
153 }
154
155 let mut resources: Self = Self {
156 mmds_size_limit,
157 ..Default::default()
158 };
159 if let Some(machine_config) = vmm_config.machine_config {
160 let machine_config = MachineConfigUpdate::from(machine_config);
161 resources.update_machine_config(&machine_config)?;
162 }
163
164 if let Some(either) = vmm_config.cpu_config {
165 match either {
166 CustomCpuTemplateOrPath::Path(path) => {
167 let cpu_config_json =
168 std::fs::read_to_string(path).map_err(ResourcesError::File)?;
169 let cpu_template = CustomCpuTemplate::try_from(cpu_config_json.as_str())?;
170 resources.set_custom_cpu_template(cpu_template);
171 }
172 CustomCpuTemplateOrPath::Template(template) => {
173 resources.set_custom_cpu_template(template)
174 }
175 }
176 }
177
178 resources.build_boot_source(vmm_config.boot_source)?;
179
180 for drive_config in vmm_config.drives.into_iter() {
181 resources.set_block_device(drive_config)?;
182 }
183
184 for net_config in vmm_config.network_interfaces.into_iter() {
185 resources.build_net_device(net_config)?;
186 }
187
188 if let Some(vsock_config) = vmm_config.vsock {
189 resources.set_vsock_device(vsock_config)?;
190 }
191
192 if let Some(balloon_config) = vmm_config.balloon {
193 resources.set_balloon_device(balloon_config)?;
194 }
195
196 if let Some(data) = metadata_json {
198 resources.locked_mmds_or_default()?.put_data(
199 serde_json::from_str(data).expect("MMDS error: metadata provided not valid json"),
200 )?;
201 info!("Successfully added metadata to mmds from file");
202 }
203
204 if let Some(mmds_config) = vmm_config.mmds_config {
205 resources.set_mmds_config(mmds_config, &instance_info.id)?;
206 }
207
208 if let Some(entropy_device_config) = vmm_config.entropy {
209 resources.build_entropy_device(entropy_device_config)?;
210 }
211
212 for pmem_config in vmm_config.pmem_devices.into_iter() {
213 resources.build_pmem_device(pmem_config)?;
214 }
215
216 if let Some(serial_cfg) = vmm_config.serial_config {
217 resources.serial_out_path = serial_cfg.serial_out_path;
218 }
219
220 if let Some(memory_hotplug_config) = vmm_config.memory_hotplug {
221 resources.set_memory_hotplug_config(memory_hotplug_config)?;
222 }
223
224 Ok(resources)
225 }
226
227 pub fn mmds_or_default(&mut self) -> Result<&Arc<Mutex<Mmds>>, MmdsConfigError> {
229 Ok(self
230 .mmds
231 .get_or_insert(Arc::new(Mutex::new(Mmds::try_new(self.mmds_size_limit)?))))
232 }
233
234 pub fn locked_mmds_or_default(&mut self) -> Result<MutexGuard<'_, Mmds>, MmdsConfigError> {
236 let mmds = self.mmds_or_default()?;
237 Ok(mmds.lock().expect("Poisoned lock"))
238 }
239
240 pub fn set_custom_cpu_template(&mut self, cpu_template: CustomCpuTemplate) {
243 self.machine_config.set_custom_cpu_template(cpu_template);
244 }
245
246 pub fn update_machine_config(
248 &mut self,
249 update: &MachineConfigUpdate,
250 ) -> Result<(), MachineConfigError> {
251 let updated = self.machine_config.update(update)?;
252
253 if self.balloon.get().is_some()
256 && updated.mem_size_mib
257 < self
258 .balloon
259 .get_config()
260 .map_err(|_| MachineConfigError::InvalidVmState)?
261 .amount_mib as usize
262 {
263 return Err(MachineConfigError::IncompatibleBalloonSize);
264 }
265
266 self.machine_config = updated;
267
268 Ok(())
269 }
270
271 fn mmds_config(&self) -> Option<MmdsConfig> {
274 let mmds = self.mmds.as_ref()?;
277
278 let mut mmds_config = None;
279 let net_devs_with_mmds: Vec<_> = self
280 .net_builder
281 .iter()
282 .filter(|net| net.lock().expect("Poisoned lock").mmds_ns().is_some())
283 .collect();
284
285 if !net_devs_with_mmds.is_empty() {
286 let mmds_guard = mmds.lock().expect("Poisoned lock");
287 let mut inner_mmds_config = MmdsConfig {
288 version: mmds_guard.version(),
289 network_interfaces: vec![],
290 ipv4_address: None,
291 imds_compat: mmds_guard.imds_compat(),
292 };
293
294 for net_dev in net_devs_with_mmds {
295 let net = net_dev.lock().unwrap();
296 inner_mmds_config.network_interfaces.push(net.id().clone());
297 if inner_mmds_config.ipv4_address.is_none() {
299 inner_mmds_config.ipv4_address = Some(net.mmds_ns().unwrap().ipv4_addr());
302 }
303 }
304
305 mmds_config = Some(inner_mmds_config);
306 }
307
308 mmds_config
309 }
310
311 pub fn set_balloon_device(
313 &mut self,
314 config: BalloonDeviceConfig,
315 ) -> Result<(), BalloonConfigError> {
316 if config.amount_mib as usize > self.machine_config.mem_size_mib {
319 return Err(BalloonConfigError::TooManyPagesRequested);
320 }
321
322 self.balloon.set(config)
323 }
324
325 pub fn build_boot_source(
327 &mut self,
328 boot_source_cfg: BootSourceConfig,
329 ) -> Result<(), BootSourceConfigError> {
330 self.boot_source = BootSource {
331 builder: Some(BootConfig::new(&boot_source_cfg)?),
332 config: boot_source_cfg,
333 };
334
335 Ok(())
336 }
337
338 pub fn set_block_device(
342 &mut self,
343 block_device_config: BlockDeviceConfig,
344 ) -> Result<(), DriveError> {
345 let has_pmem_root = self.pmem.has_root_device();
346 self.block.insert(block_device_config, has_pmem_root)
347 }
348
349 pub fn build_net_device(
351 &mut self,
352 body: NetworkInterfaceConfig,
353 ) -> Result<(), NetworkInterfaceError> {
354 let _ = self.net_builder.build(body)?;
355 Ok(())
356 }
357
358 pub fn set_vsock_device(&mut self, config: VsockDeviceConfig) -> Result<(), VsockConfigError> {
360 self.vsock.insert(config)
361 }
362
363 pub fn build_entropy_device(
365 &mut self,
366 body: EntropyDeviceConfig,
367 ) -> Result<(), EntropyDeviceError> {
368 self.entropy.insert(body)
369 }
370
371 pub fn build_pmem_device(&mut self, body: PmemConfig) -> Result<(), PmemConfigError> {
373 let has_block_root = self.block.has_root_device();
374 self.pmem.build(body, has_block_root)
375 }
376
377 pub fn set_memory_hotplug_config(
379 &mut self,
380 config: MemoryHotplugConfig,
381 ) -> Result<(), MemoryHotplugConfigError> {
382 config.validate()?;
383 self.memory_hotplug = Some(config);
384 Ok(())
385 }
386
387 pub fn set_mmds_config(
389 &mut self,
390 config: MmdsConfig,
391 instance_id: &str,
392 ) -> Result<(), MmdsConfigError> {
393 self.set_mmds_network_stack_config(&config)?;
394 self.set_mmds_basic_config(config.version, config.imds_compat, instance_id)?;
395
396 Ok(())
397 }
398
399 pub fn set_mmds_basic_config(
401 &mut self,
402 version: MmdsVersion,
403 imds_compat: bool,
404 instance_id: &str,
405 ) -> Result<(), MmdsConfigError> {
406 let mut mmds_guard = self.locked_mmds_or_default()?;
407 mmds_guard.set_version(version);
408 mmds_guard.set_imds_compat(imds_compat);
409 mmds_guard.set_aad(instance_id);
410
411 Ok(())
412 }
413
414 fn set_mmds_network_stack_config(
417 &mut self,
418 config: &MmdsConfig,
419 ) -> Result<(), MmdsConfigError> {
420 let ipv4_addr = match config.ipv4_addr() {
422 Some(ipv4_addr) if is_link_local_valid(ipv4_addr) => Ok(ipv4_addr),
423 None => Ok(MmdsNetworkStack::default_ipv4_addr()),
424 _ => Err(MmdsConfigError::InvalidIpv4Addr),
425 }?;
426
427 let network_interfaces = config.network_interfaces();
428 if network_interfaces.is_empty() {
430 return Err(MmdsConfigError::EmptyNetworkIfaceList);
431 }
432
433 if !network_interfaces.iter().all(|id| {
435 self.net_builder
436 .iter()
437 .map(|device| device.lock().expect("Poisoned lock").id().clone())
438 .any(|x| &x == id)
439 }) {
440 return Err(MmdsConfigError::InvalidNetworkInterfaceId);
441 }
442
443 let mmds = self.mmds_or_default()?.clone();
445
446 for net_device in self.net_builder.iter() {
450 let mut net_device_lock = net_device.lock().expect("Poisoned lock");
451 if network_interfaces.contains(net_device_lock.id()) {
452 net_device_lock.configure_mmds_network_stack(ipv4_addr, mmds.clone());
453 } else {
454 net_device_lock.disable_mmds_network_stack();
455 }
456 }
457
458 Ok(())
459 }
460
461 fn allocate_memory_regions(
466 &self,
467 regions: &[(GuestAddress, usize)],
468 ) -> Result<Vec<GuestRegionMmap>, MemoryError> {
469 let vhost_user_device_used = self
470 .block
471 .devices
472 .iter()
473 .any(|b| b.lock().expect("Poisoned lock").is_vhost_user());
474
475 if vhost_user_device_used {
485 memory::memfd_backed(
486 regions,
487 self.machine_config.track_dirty_pages,
488 self.machine_config.huge_pages,
489 )
490 } else {
491 memory::anonymous(
492 regions.iter().copied(),
493 self.machine_config.track_dirty_pages,
494 self.machine_config.huge_pages,
495 )
496 }
497 }
498
499 pub fn allocate_guest_memory(&self) -> Result<Vec<GuestRegionMmap>, MemoryError> {
501 let regions =
502 crate::arch::arch_memory_regions(mib_to_bytes(self.machine_config.mem_size_mib));
503 self.allocate_memory_regions(®ions)
504 }
505
506 pub fn allocate_memory_region(
508 &self,
509 start: GuestAddress,
510 size: usize,
511 ) -> Result<GuestRegionMmap, MemoryError> {
512 Ok(self
513 .allocate_memory_regions(&[(start, size)])?
514 .pop()
515 .unwrap())
516 }
517}
518
519impl From<&VmResources> for VmmConfig {
520 fn from(resources: &VmResources) -> Self {
521 VmmConfig {
522 balloon: resources.balloon.get_config().ok(),
523 drives: resources.block.configs(),
524 boot_source: resources.boot_source.config.clone(),
525 cpu_config: None,
526 logger: None,
527 machine_config: Some(resources.machine_config.clone()),
528 metrics: None,
529 mmds_config: resources.mmds_config(),
530 network_interfaces: resources.net_builder.configs(),
531 vsock: resources.vsock.config(),
532 entropy: resources.entropy.config(),
533 pmem_devices: resources.pmem.configs(),
534 serial_config: None,
536 memory_hotplug: resources.memory_hotplug.clone(),
537 }
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use std::fs::File;
544 use std::io::Write;
545 use std::os::linux::fs::MetadataExt;
546 use std::str::FromStr;
547
548 use serde_json::{Map, Value};
549 use vmm_sys_util::tempfile::TempFile;
550
551 use super::*;
552 use crate::HTTP_MAX_PAYLOAD_SIZE;
553 use crate::cpu_config::templates::test_utils::TEST_TEMPLATE_JSON;
554 use crate::cpu_config::templates::{CpuTemplateType, StaticCpuTemplate};
555 use crate::devices::virtio::block::virtio::VirtioBlockError;
556 use crate::devices::virtio::block::{BlockError, CacheType};
557 use crate::devices::virtio::vsock::VSOCK_DEV_ID;
558 use crate::resources::VmResources;
559 use crate::utils::net::mac::MacAddr;
560 use crate::vmm_config::RateLimiterConfig;
561 use crate::vmm_config::boot_source::{
562 BootConfig, BootSource, BootSourceConfig, DEFAULT_KERNEL_CMDLINE,
563 };
564 use crate::vmm_config::drive::{BlockBuilder, BlockDeviceConfig};
565 use crate::vmm_config::machine_config::{HugePageConfig, MachineConfig, MachineConfigError};
566 use crate::vmm_config::net::{NetBuilder, NetworkInterfaceConfig};
567 use crate::vmm_config::vsock::tests::default_config;
568
569 fn default_net_cfg() -> NetworkInterfaceConfig {
570 NetworkInterfaceConfig {
571 iface_id: "net_if1".to_string(),
572 host_dev_name: TempFile::new_with_prefix("")
575 .unwrap()
576 .as_path()
577 .to_str()
578 .unwrap()
579 .to_string(),
580 guest_mac: Some(MacAddr::from_str("01:23:45:67:89:0a").unwrap()),
581 rx_rate_limiter: Some(RateLimiterConfig::default()),
582 tx_rate_limiter: Some(RateLimiterConfig::default()),
583 }
584 }
585
586 fn default_net_builder() -> NetBuilder {
587 let mut net_builder = NetBuilder::new();
588 net_builder.build(default_net_cfg()).unwrap();
589
590 net_builder
591 }
592
593 fn default_block_cfg() -> (BlockDeviceConfig, TempFile) {
594 let tmp_file = TempFile::new().unwrap();
595 (
596 BlockDeviceConfig {
597 drive_id: "block1".to_string(),
598 partuuid: Some("0eaa91a0-01".to_string()),
599 is_root_device: false,
600 cache_type: CacheType::Unsafe,
601
602 is_read_only: Some(false),
603 path_on_host: Some(tmp_file.as_path().to_str().unwrap().to_string()),
604 rate_limiter: Some(RateLimiterConfig::default()),
605 file_engine_type: None,
606
607 socket: None,
608 },
609 tmp_file,
610 )
611 }
612
613 fn default_blocks() -> BlockBuilder {
614 let mut blocks = BlockBuilder::new();
615 let (cfg, _file) = default_block_cfg();
616 blocks.insert(cfg, false).unwrap();
617 blocks
618 }
619
620 fn default_boot_cfg() -> BootSource {
621 let kernel_cmdline =
622 linux_loader::cmdline::Cmdline::try_from(DEFAULT_KERNEL_CMDLINE, 4096).unwrap();
623 let tmp_file = TempFile::new().unwrap();
624 BootSource {
625 config: BootSourceConfig::default(),
626 builder: Some(BootConfig {
627 cmdline: kernel_cmdline,
628 kernel_file: File::open(tmp_file.as_path()).unwrap(),
629 initrd_file: Some(File::open(tmp_file.as_path()).unwrap()),
630 }),
631 }
632 }
633
634 fn default_vm_resources() -> VmResources {
635 VmResources {
636 machine_config: MachineConfig::default(),
637 boot_source: default_boot_cfg(),
638 block: default_blocks(),
639 vsock: Default::default(),
640 balloon: Default::default(),
641 net_builder: default_net_builder(),
642 mmds: None,
643 boot_timer: false,
644 mmds_size_limit: HTTP_MAX_PAYLOAD_SIZE,
645 entropy: Default::default(),
646 pmem: Default::default(),
647 pci_enabled: false,
648 serial_out_path: None,
649 memory_hotplug: Default::default(),
650 }
651 }
652
653 #[test]
654 fn test_from_json() {
655 let kernel_file = TempFile::new().unwrap();
656 let rootfs_file = TempFile::new().unwrap();
657 let scratch_file = TempFile::new().unwrap();
658 scratch_file.as_file().set_len(0x1000).unwrap();
659 let default_instance_info = InstanceInfo::default();
660
661 let error =
668 VmResources::from_json(r#"}"#, &default_instance_info, HTTP_MAX_PAYLOAD_SIZE, None)
669 .unwrap_err();
670 assert!(
671 matches!(error, ResourcesError::InvalidJson(_)),
672 "{:?}",
673 error
674 );
675
676 let error =
679 VmResources::from_json(r#"{}"#, &default_instance_info, HTTP_MAX_PAYLOAD_SIZE, None)
680 .unwrap_err();
681 assert!(
682 matches!(error, ResourcesError::InvalidJson(_)),
683 "{:?}",
684 error
685 );
686
687 let mut json = format!(
689 r#"{{
690 "boot-source": {{
691 "kernel_image_path": "/invalid/path",
692 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
693 }},
694 "drives": [
695 {{
696 "drive_id": "rootfs",
697 "path_on_host": "{}",
698 "is_root_device": true,
699 "is_read_only": false
700 }}
701 ]
702 }}"#,
703 rootfs_file.as_path().to_str().unwrap()
704 );
705
706 let error = VmResources::from_json(
707 json.as_str(),
708 &default_instance_info,
709 HTTP_MAX_PAYLOAD_SIZE,
710 None,
711 )
712 .unwrap_err();
713 assert!(
714 matches!(
715 error,
716 ResourcesError::BootSource(BootSourceConfigError::InvalidKernelPath(_))
717 ),
718 "{:?}",
719 error
720 );
721
722 json = format!(
724 r#"{{
725 "boot-source": {{
726 "kernel_image_path": "{}",
727 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
728 }},
729 "drives": [
730 {{
731 "drive_id": "rootfs",
732 "path_on_host": "/invalid/path",
733 "is_root_device": true,
734 "is_read_only": false
735 }}
736 ]
737 }}"#,
738 kernel_file.as_path().to_str().unwrap()
739 );
740
741 let error = VmResources::from_json(
742 json.as_str(),
743 &default_instance_info,
744 HTTP_MAX_PAYLOAD_SIZE,
745 None,
746 )
747 .unwrap_err();
748 assert!(
749 matches!(
750 error,
751 ResourcesError::BlockDevice(DriveError::CreateBlockDevice(
752 BlockError::VirtioBackend(VirtioBlockError::BackingFile(_, _)),
753 ))
754 ),
755 "{:?}",
756 error
757 );
758 json = format!(
760 r#"{{
761 "boot-source": {{
762 "kernel_image_path": "{}",
763 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
764 }},
765 "drives": [
766 {{
767 "drive_id": "rootfs",
768 "path_on_host": "{}",
769 "is_root_device": true,
770 "is_read_only": false
771 }}
772 ],
773 "machine-config": {{
774 "vcpu_count": 2,
775 "mem_size_mib": 1024,
776 "cpu_template": "C3"
777 }}
778 }}"#,
779 kernel_file.as_path().to_str().unwrap(),
780 rootfs_file.as_path().to_str().unwrap()
781 );
782 #[cfg(target_arch = "x86_64")]
783 VmResources::from_json(
784 json.as_str(),
785 &default_instance_info,
786 HTTP_MAX_PAYLOAD_SIZE,
787 None,
788 )
789 .unwrap();
790 #[cfg(target_arch = "aarch64")]
791 VmResources::from_json(
792 json.as_str(),
793 &default_instance_info,
794 HTTP_MAX_PAYLOAD_SIZE,
795 None,
796 )
797 .unwrap_err();
798
799 json = format!(
801 r#"{{
802 "boot-source": {{
803 "kernel_image_path": "{}",
804 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
805 }},
806 "drives": [
807 {{
808 "drive_id": "rootfs",
809 "path_on_host": "{}",
810 "is_root_device": true,
811 "is_read_only": false
812 }}
813 ],
814 "machine-config": {{
815 "vcpu_count": 2,
816 "mem_size_mib": 0
817 }}
818 }}"#,
819 kernel_file.as_path().to_str().unwrap(),
820 rootfs_file.as_path().to_str().unwrap()
821 );
822
823 let error = VmResources::from_json(
824 json.as_str(),
825 &default_instance_info,
826 HTTP_MAX_PAYLOAD_SIZE,
827 None,
828 )
829 .unwrap_err();
830 assert!(
831 matches!(
832 error,
833 ResourcesError::MachineConfig(MachineConfigError::InvalidMemorySize)
834 ),
835 "{:?}",
836 error
837 );
838
839 json = format!(
841 r#"{{
842 "boot-source": {{
843 "kernel_image_path": "{}",
844 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
845 }},
846 "drives": [
847 {{
848 "drive_id": "rootfs",
849 "path_on_host": "{}",
850 "is_root_device": true,
851 "is_read_only": false
852 }}
853 ],
854 "logger": {{
855 "log_path": "/invalid/path"
856 }}
857 }}"#,
858 kernel_file.as_path().to_str().unwrap(),
859 rootfs_file.as_path().to_str().unwrap()
860 );
861
862 let error = VmResources::from_json(
863 json.as_str(),
864 &default_instance_info,
865 HTTP_MAX_PAYLOAD_SIZE,
866 None,
867 )
868 .unwrap_err();
869 assert!(
870 matches!(
871 error,
872 ResourcesError::Logger(crate::logger::LoggerUpdateError(_))
873 ),
874 "{:?}",
875 error
876 );
877
878 json = format!(
880 r#"{{
881 "boot-source": {{
882 "kernel_image_path": "{}",
883 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
884 }},
885 "drives": [
886 {{
887 "drive_id": "rootfs",
888 "path_on_host": "{}",
889 "is_root_device": true,
890 "is_read_only": false
891 }}
892 ],
893 "metrics": {{
894 "metrics_path": "/invalid/path"
895 }}
896 }}"#,
897 kernel_file.as_path().to_str().unwrap(),
898 rootfs_file.as_path().to_str().unwrap()
899 );
900
901 let error = VmResources::from_json(
902 json.as_str(),
903 &default_instance_info,
904 HTTP_MAX_PAYLOAD_SIZE,
905 None,
906 )
907 .unwrap_err();
908 assert!(
909 matches!(
910 error,
911 ResourcesError::Metrics(MetricsConfigError::InitializationFailure { .. })
912 ),
913 "{:?}",
914 error
915 );
916
917 json = format!(
919 r#"{{
920 "boot-source": {{
921 "kernel_image_path": "{}",
922 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
923 }},
924 "drives": [
925 {{
926 "drive_id": "rootfs",
927 "path_on_host": "{}",
928 "is_root_device": true,
929 "is_read_only": false
930 }}
931 ],
932 "network-interfaces": [
933 {{
934 "iface_id": "netif1",
935 "host_dev_name": "hostname7"
936 }},
937 {{
938 "iface_id": "netif2",
939 "host_dev_name": "hostname7"
940 }}
941 ]
942 }}"#,
943 kernel_file.as_path().to_str().unwrap(),
944 rootfs_file.as_path().to_str().unwrap()
945 );
946
947 let error = VmResources::from_json(
948 json.as_str(),
949 &default_instance_info,
950 HTTP_MAX_PAYLOAD_SIZE,
951 None,
952 )
953 .unwrap_err();
954
955 assert!(
956 matches!(
957 error,
958 ResourcesError::NetDevice(NetworkInterfaceError::CreateNetworkDevice(
959 crate::devices::virtio::net::NetError::TapOpen { .. },
960 ))
961 ),
962 "{:?}",
963 error
964 );
965
966 json = format!(
970 r#"{{
971 "boot-source": {{
972 "kernel_image_path": "{}",
973 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
974 }},
975 "drives": [
976 {{
977 "drive_id": "rootfs",
978 "path_on_host": "{}",
979 "is_root_device": true,
980 "is_read_only": false
981 }}
982 ],
983 "network-interfaces": [
984 {{
985 "iface_id": "netif",
986 "host_dev_name": "hostname8"
987 }}
988 ],
989 "machine-config": {{
990 "vcpu_count": 2,
991 "mem_size_mib": 1024,
992 "smt": false
993 }},
994 "mmds-config": {{
995 "version": "V2",
996 "ipv4_address": "169.254.170.2",
997 "network_interfaces": ["netif"]
998 }}
999 }}"#,
1000 kernel_file.as_path().to_str().unwrap(),
1001 rootfs_file.as_path().to_str().unwrap(),
1002 );
1003 VmResources::from_json(
1004 json.as_str(),
1005 &default_instance_info,
1006 HTTP_MAX_PAYLOAD_SIZE,
1007 None,
1008 )
1009 .unwrap();
1010
1011 let kernel_file = TempFile::new().unwrap();
1014 json = format!(
1015 r#"{{
1016 "balloon": {{
1017 "amount_mib": 0,
1018 "deflate_on_oom": false,
1019 "stats_polling_interval_s": 0
1020 }},
1021 "boot-source": {{
1022 "kernel_image_path": "{}",
1023 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1024 }},
1025 "drives": [
1026 {{
1027 "drive_id": "rootfs",
1028 "path_on_host": "{}",
1029 "is_root_device": true,
1030 "is_read_only": false
1031 }}
1032 ],
1033 "pmem": [
1034 {{
1035 "id": "pmem",
1036 "path_on_host": "{}",
1037 "root_device": false,
1038 "read_only": false
1039 }}
1040 ],
1041 "network-interfaces": [
1042 {{
1043 "iface_id": "netif",
1044 "host_dev_name": "hostname9"
1045 }}
1046 ],
1047 "machine-config": {{
1048 "vcpu_count": 2,
1049 "mem_size_mib": 1024,
1050 "smt": false
1051 }},
1052 "mmds-config": {{
1053 "network_interfaces": ["netif"],
1054 "ipv4_address": "169.254.1.1"
1055 }}
1056 }}"#,
1057 kernel_file.as_path().to_str().unwrap(),
1058 rootfs_file.as_path().to_str().unwrap(),
1059 scratch_file.as_path().to_str().unwrap(),
1060 );
1061 let resources = VmResources::from_json(
1062 json.as_str(),
1063 &default_instance_info,
1064 1200,
1065 Some(r#"{"key": "value"}"#),
1066 )
1067 .unwrap();
1068 let mut map = Map::new();
1069 map.insert("key".to_string(), Value::String("value".to_string()));
1070 assert_eq!(
1071 resources.mmds.unwrap().lock().unwrap().data_store_value(),
1072 Value::Object(map)
1073 );
1074 }
1075
1076 #[test]
1077 fn test_cpu_config_from_invalid_json() {
1078 let kernel_file = TempFile::new().unwrap();
1081 let rootfs_file = TempFile::new().unwrap();
1082 let default_instance_info = InstanceInfo::default();
1083
1084 let json = format!(
1085 r#"{{
1086 "boot-source": {{
1087 "kernel_image_path": "{}",
1088 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1089 }},
1090 "cpu-config": "/invalid/path",
1091 "drives": [
1092 {{
1093 "drive_id": "rootfs",
1094 "path_on_host": "{}",
1095 "is_root_device": true,
1096 "is_read_only": false
1097 }}
1098 ]
1099 }}"#,
1100 kernel_file.as_path().to_str().unwrap(),
1101 rootfs_file.as_path().to_str().unwrap(),
1102 );
1103
1104 let error = VmResources::from_json(
1105 json.as_str(),
1106 &default_instance_info,
1107 HTTP_MAX_PAYLOAD_SIZE,
1108 None,
1109 )
1110 .unwrap_err();
1111 assert!(matches!(error, ResourcesError::File(_)), "{:?}", error);
1112 }
1113
1114 #[test]
1115 fn test_cpu_config_inline() {
1116 let kernel_file = TempFile::new().unwrap();
1118 let rootfs_file = TempFile::new().unwrap();
1119 let default_instance_info = InstanceInfo::default();
1120
1121 let json = format!(
1122 r#"{{
1123 "boot-source": {{
1124 "kernel_image_path": "{}",
1125 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1126 }},
1127 "cpu-config": {},
1128 "drives": [
1129 {{
1130 "drive_id": "rootfs",
1131 "path_on_host": "{}",
1132 "is_root_device": true,
1133 "is_read_only": false
1134 }}
1135 ]
1136 }}"#,
1137 kernel_file.as_path().to_str().unwrap(),
1138 TEST_TEMPLATE_JSON,
1139 rootfs_file.as_path().to_str().unwrap(),
1140 );
1141
1142 VmResources::from_json(
1143 json.as_str(),
1144 &default_instance_info,
1145 HTTP_MAX_PAYLOAD_SIZE,
1146 None,
1147 )
1148 .unwrap();
1149 }
1150
1151 #[test]
1152 fn test_cpu_config_from_valid_json() {
1153 let kernel_file = TempFile::new().unwrap();
1156 let rootfs_file = TempFile::new().unwrap();
1157 let default_instance_info = InstanceInfo::default();
1158 let cpu_config_file = TempFile::new().unwrap();
1159 cpu_config_file
1160 .as_file()
1161 .write_all("{}".as_bytes())
1162 .unwrap();
1163
1164 let json = format!(
1165 r#"{{
1166 "boot-source": {{
1167 "kernel_image_path": "{}",
1168 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1169 }},
1170 "cpu-config": "{}",
1171 "drives": [
1172 {{
1173 "drive_id": "rootfs",
1174 "path_on_host": "{}",
1175 "is_root_device": true,
1176 "is_read_only": false
1177 }}
1178 ]
1179 }}"#,
1180 kernel_file.as_path().to_str().unwrap(),
1181 cpu_config_file.as_path().to_str().unwrap(),
1182 rootfs_file.as_path().to_str().unwrap(),
1183 );
1184
1185 let vm_resources = VmResources::from_json(
1186 json.as_str(),
1187 &default_instance_info,
1188 HTTP_MAX_PAYLOAD_SIZE,
1189 None,
1190 )
1191 .unwrap();
1192 assert_eq!(
1193 vm_resources.machine_config.cpu_template,
1194 Some(CpuTemplateType::Custom(CustomCpuTemplate::default()))
1195 );
1196 }
1197
1198 #[test]
1199 fn test_cast_to_vmm_config() {
1200 {
1202 let kernel_file = TempFile::new().unwrap();
1203 let rootfs_file = TempFile::new().unwrap();
1204 let json = format!(
1205 r#"{{
1206 "balloon": {{
1207 "amount_mib": 0,
1208 "deflate_on_oom": false,
1209 "stats_polling_interval_s": 0
1210 }},
1211 "boot-source": {{
1212 "kernel_image_path": "{}",
1213 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1214 }},
1215 "drives": [
1216 {{
1217 "drive_id": "rootfs",
1218 "path_on_host": "{}",
1219 "is_root_device": true,
1220 "is_read_only": false,
1221 "io_engine": "Sync"
1222 }}
1223 ],
1224 "network-interfaces": [
1225 {{
1226 "iface_id": "netif1",
1227 "host_dev_name": "hostname9"
1228 }},
1229 {{
1230 "iface_id": "netif2",
1231 "host_dev_name": "hostname10"
1232 }}
1233 ],
1234 "machine-config": {{
1235 "vcpu_count": 2,
1236 "mem_size_mib": 1024,
1237 "smt": false
1238 }},
1239 "entropy": {{}}
1240 }}"#,
1241 kernel_file.as_path().to_str().unwrap(),
1242 rootfs_file.as_path().to_str().unwrap(),
1243 );
1244
1245 {
1246 let resources = VmResources::from_json(
1247 json.as_str(),
1248 &InstanceInfo::default(),
1249 HTTP_MAX_PAYLOAD_SIZE,
1250 None,
1251 )
1252 .unwrap();
1253
1254 let initial_vmm_config = serde_json::from_str::<VmmConfig>(&json).unwrap();
1255 let vmm_config: VmmConfig = (&resources).into();
1256 assert_eq!(initial_vmm_config, vmm_config);
1257 }
1258
1259 {
1260 let resources = VmResources::from_json(
1262 json.as_str(),
1263 &InstanceInfo::default(),
1264 HTTP_MAX_PAYLOAD_SIZE,
1265 Some(r#"{"key": "value"}"#),
1266 )
1267 .unwrap();
1268
1269 let initial_vmm_config = serde_json::from_str::<VmmConfig>(&json).unwrap();
1270 let vmm_config: VmmConfig = (&resources).into();
1271 assert_eq!(initial_vmm_config, vmm_config);
1272 }
1273 }
1274
1275 {
1277 let kernel_file = TempFile::new().unwrap();
1278 let rootfs_file = TempFile::new().unwrap();
1279 let json = format!(
1280 r#"{{
1281 "balloon": {{
1282 "amount_mib": 0,
1283 "deflate_on_oom": false,
1284 "stats_polling_interval_s": 0
1285 }},
1286 "boot-source": {{
1287 "kernel_image_path": "{}",
1288 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1289 }},
1290 "drives": [
1291 {{
1292 "drive_id": "rootfs",
1293 "path_on_host": "{}",
1294 "is_root_device": true,
1295 "is_read_only": false,
1296 "io_engine": "Sync"
1297 }}
1298 ],
1299 "network-interfaces": [
1300 {{
1301 "iface_id": "netif1",
1302 "host_dev_name": "hostname9"
1303 }},
1304 {{
1305 "iface_id": "netif2",
1306 "host_dev_name": "hostname10"
1307 }}
1308 ],
1309 "machine-config": {{
1310 "vcpu_count": 2,
1311 "mem_size_mib": 1024,
1312 "smt": false
1313 }},
1314 "mmds-config": {{
1315 "network_interfaces": ["netif1"],
1316 "ipv4_address": "169.254.1.1"
1317 }}
1318 }}"#,
1319 kernel_file.as_path().to_str().unwrap(),
1320 rootfs_file.as_path().to_str().unwrap(),
1321 );
1322 let resources = VmResources::from_json(
1323 json.as_str(),
1324 &InstanceInfo::default(),
1325 HTTP_MAX_PAYLOAD_SIZE,
1326 None,
1327 )
1328 .unwrap();
1329
1330 let initial_vmm_config = serde_json::from_str::<VmmConfig>(&json).unwrap();
1331 let vmm_config: VmmConfig = (&resources).into();
1332 assert_eq!(initial_vmm_config, vmm_config);
1333 }
1334
1335 {
1337 let kernel_file = TempFile::new().unwrap();
1338 let rootfs_file = TempFile::new().unwrap();
1339 let json = format!(
1340 r#"{{
1341 "balloon": {{
1342 "amount_mib": 0,
1343 "deflate_on_oom": false,
1344 "stats_polling_interval_s": 0
1345 }},
1346 "boot-source": {{
1347 "kernel_image_path": "{}",
1348 "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
1349 }},
1350 "drives": [
1351 {{
1352 "drive_id": "rootfs",
1353 "path_on_host": "{}",
1354 "is_root_device": true,
1355 "is_read_only": false,
1356 "io_engine": "Sync"
1357 }}
1358 ],
1359 "network-interfaces": [
1360 {{
1361 "iface_id": "netif1",
1362 "host_dev_name": "hostname9"
1363 }},
1364 {{
1365 "iface_id": "netif2",
1366 "host_dev_name": "hostname10"
1367 }}
1368 ],
1369 "machine-config": {{
1370 "vcpu_count": 2,
1371 "mem_size_mib": 1024,
1372 "smt": false
1373 }},
1374 "mmds-config": {{
1375 "network_interfaces": ["netif1", "netif2"],
1376 "ipv4_address": "169.254.1.1"
1377 }}
1378 }}"#,
1379 kernel_file.as_path().to_str().unwrap(),
1380 rootfs_file.as_path().to_str().unwrap(),
1381 );
1382 let resources = VmResources::from_json(
1383 json.as_str(),
1384 &InstanceInfo::default(),
1385 HTTP_MAX_PAYLOAD_SIZE,
1386 None,
1387 )
1388 .unwrap();
1389
1390 let initial_vmm_config = serde_json::from_str::<VmmConfig>(&json).unwrap();
1391 let vmm_config: VmmConfig = (&resources).into();
1392 assert_eq!(initial_vmm_config, vmm_config);
1393 }
1394 }
1395
1396 #[test]
1397 fn test_update_machine_config() {
1398 let mut vm_resources = default_vm_resources();
1399 let mut aux_vm_config = MachineConfigUpdate {
1400 vcpu_count: Some(32),
1401 mem_size_mib: Some(512),
1402 smt: Some(false),
1403 #[cfg(target_arch = "x86_64")]
1404 cpu_template: Some(StaticCpuTemplate::T2),
1405 #[cfg(target_arch = "aarch64")]
1406 cpu_template: Some(StaticCpuTemplate::V1N1),
1407 track_dirty_pages: Some(false),
1408 huge_pages: Some(HugePageConfig::None),
1409 #[cfg(feature = "gdb")]
1410 gdb_socket_path: None,
1411 };
1412
1413 assert_ne!(
1414 MachineConfigUpdate::from(vm_resources.machine_config.clone()),
1415 aux_vm_config
1416 );
1417 vm_resources.update_machine_config(&aux_vm_config).unwrap();
1418 assert_eq!(
1419 MachineConfigUpdate::from(vm_resources.machine_config.clone()),
1420 aux_vm_config
1421 );
1422
1423 aux_vm_config.vcpu_count = Some(0);
1425 assert_eq!(
1426 vm_resources.update_machine_config(&aux_vm_config),
1427 Err(MachineConfigError::InvalidVcpuCount)
1428 );
1429 aux_vm_config.vcpu_count = Some(33);
1430 assert_eq!(
1431 vm_resources.update_machine_config(&aux_vm_config),
1432 Err(MachineConfigError::InvalidVcpuCount)
1433 );
1434
1435 aux_vm_config.smt = Some(true);
1438 #[cfg(target_arch = "aarch64")]
1439 assert_eq!(
1440 vm_resources.update_machine_config(&aux_vm_config),
1441 Err(MachineConfigError::SmtNotSupported)
1442 );
1443 aux_vm_config.vcpu_count = Some(3);
1444 #[cfg(target_arch = "x86_64")]
1445 assert_eq!(
1446 vm_resources.update_machine_config(&aux_vm_config),
1447 Err(MachineConfigError::InvalidVcpuCount)
1448 );
1449 aux_vm_config.vcpu_count = Some(32);
1450 #[cfg(target_arch = "x86_64")]
1451 vm_resources.update_machine_config(&aux_vm_config).unwrap();
1452 aux_vm_config.smt = Some(false);
1453
1454 aux_vm_config.mem_size_mib = Some(0);
1456 assert_eq!(
1457 vm_resources.update_machine_config(&aux_vm_config),
1458 Err(MachineConfigError::InvalidMemorySize)
1459 );
1460
1461 vm_resources.machine_config.mem_size_mib = 128;
1463 vm_resources
1464 .set_balloon_device(BalloonDeviceConfig {
1465 amount_mib: 100,
1466 deflate_on_oom: false,
1467 stats_polling_interval_s: 0,
1468 free_page_hinting: false,
1469 free_page_reporting: false,
1470 })
1471 .unwrap();
1472 aux_vm_config.mem_size_mib = Some(90);
1473 assert_eq!(
1474 vm_resources.update_machine_config(&aux_vm_config),
1475 Err(MachineConfigError::IncompatibleBalloonSize)
1476 );
1477
1478 aux_vm_config.mem_size_mib = Some(256);
1480 vm_resources.update_machine_config(&aux_vm_config).unwrap();
1481
1482 aux_vm_config.mem_size_mib = Some(129);
1484 aux_vm_config.huge_pages = Some(HugePageConfig::Hugetlbfs2M);
1485 assert_eq!(
1486 vm_resources
1487 .update_machine_config(&aux_vm_config)
1488 .unwrap_err(),
1489 MachineConfigError::InvalidMemorySize
1490 );
1491
1492 aux_vm_config.mem_size_mib = Some(2048);
1494 vm_resources.balloon = BalloonBuilder::new();
1497 vm_resources.update_machine_config(&aux_vm_config).unwrap();
1498 }
1499
1500 #[test]
1501 fn test_set_balloon_device() {
1502 let mut vm_resources = default_vm_resources();
1503 vm_resources.balloon = BalloonBuilder::new();
1504 let mut new_balloon_cfg = BalloonDeviceConfig {
1505 amount_mib: 100,
1506 deflate_on_oom: false,
1507 stats_polling_interval_s: 0,
1508 free_page_hinting: false,
1509 free_page_reporting: false,
1510 };
1511 assert!(vm_resources.balloon.get().is_none());
1512 vm_resources
1513 .set_balloon_device(new_balloon_cfg.clone())
1514 .unwrap();
1515
1516 let actual_balloon_cfg = vm_resources.balloon.get_config().unwrap();
1517 assert_eq!(actual_balloon_cfg.amount_mib, new_balloon_cfg.amount_mib);
1518 assert_eq!(
1519 actual_balloon_cfg.deflate_on_oom,
1520 new_balloon_cfg.deflate_on_oom
1521 );
1522 assert_eq!(
1523 actual_balloon_cfg.stats_polling_interval_s,
1524 new_balloon_cfg.stats_polling_interval_s
1525 );
1526
1527 let mut vm_resources = default_vm_resources();
1528 vm_resources.balloon = BalloonBuilder::new();
1529 new_balloon_cfg.amount_mib = 256;
1530 vm_resources
1531 .set_balloon_device(new_balloon_cfg)
1532 .unwrap_err();
1533 }
1534
1535 #[test]
1536 fn test_set_entropy_device() {
1537 let mut vm_resources = default_vm_resources();
1538 vm_resources.entropy = EntropyDeviceBuilder::new();
1539 let entropy_device_cfg = EntropyDeviceConfig::default();
1540
1541 assert!(vm_resources.entropy.get().is_none());
1542 vm_resources
1543 .build_entropy_device(entropy_device_cfg.clone())
1544 .unwrap();
1545
1546 let actual_entropy_cfg = vm_resources.entropy.config().unwrap();
1547 assert_eq!(actual_entropy_cfg, entropy_device_cfg);
1548 }
1549
1550 #[test]
1551 fn test_set_boot_source() {
1552 let tmp_file = TempFile::new().unwrap();
1553 let cmdline = "reboot=k panic=1 pci=off nomodule 8250.nr_uarts=0";
1554 let expected_boot_cfg = BootSourceConfig {
1555 kernel_image_path: String::from(tmp_file.as_path().to_str().unwrap()),
1556 initrd_path: Some(String::from(tmp_file.as_path().to_str().unwrap())),
1557 boot_args: Some(cmdline.to_string()),
1558 };
1559
1560 let mut vm_resources = default_vm_resources();
1561 let boot_builder = vm_resources.boot_source.builder.as_ref().unwrap();
1562 let tmp_ino = tmp_file.as_file().metadata().unwrap().st_ino();
1563
1564 assert_ne!(
1565 boot_builder
1566 .cmdline
1567 .as_cstring()
1568 .unwrap()
1569 .as_bytes_with_nul(),
1570 [cmdline.as_bytes(), b"\0"].concat()
1571 );
1572 assert_ne!(
1573 boot_builder.kernel_file.metadata().unwrap().st_ino(),
1574 tmp_ino
1575 );
1576 assert_ne!(
1577 boot_builder
1578 .initrd_file
1579 .as_ref()
1580 .unwrap()
1581 .metadata()
1582 .unwrap()
1583 .st_ino(),
1584 tmp_ino
1585 );
1586
1587 vm_resources.build_boot_source(expected_boot_cfg).unwrap();
1588 let boot_source_builder = vm_resources.boot_source.builder.unwrap();
1589 assert_eq!(
1590 boot_source_builder
1591 .cmdline
1592 .as_cstring()
1593 .unwrap()
1594 .as_bytes_with_nul(),
1595 [cmdline.as_bytes(), b"\0"].concat()
1596 );
1597 assert_eq!(
1598 boot_source_builder.kernel_file.metadata().unwrap().st_ino(),
1599 tmp_ino
1600 );
1601 assert_eq!(
1602 boot_source_builder
1603 .initrd_file
1604 .as_ref()
1605 .unwrap()
1606 .metadata()
1607 .unwrap()
1608 .st_ino(),
1609 tmp_ino
1610 );
1611 }
1612
1613 #[test]
1614 fn test_set_block_device() {
1615 let mut vm_resources = default_vm_resources();
1616 let (mut new_block_device_cfg, _file) = default_block_cfg();
1617 let tmp_file = TempFile::new().unwrap();
1618 new_block_device_cfg.drive_id = "block2".to_string();
1619 new_block_device_cfg.path_on_host = Some(tmp_file.as_path().to_str().unwrap().to_string());
1620 assert_eq!(vm_resources.block.devices.len(), 1);
1621 vm_resources.set_block_device(new_block_device_cfg).unwrap();
1622 assert_eq!(vm_resources.block.devices.len(), 2);
1623 }
1624
1625 #[test]
1626 fn test_set_vsock_device() {
1627 let mut vm_resources = default_vm_resources();
1628 let mut tmp_sock_file = TempFile::new().unwrap();
1629 tmp_sock_file.remove().unwrap();
1630 let new_vsock_cfg = default_config(&tmp_sock_file);
1631 assert!(vm_resources.vsock.get().is_none());
1632 vm_resources.set_vsock_device(new_vsock_cfg).unwrap();
1633 let actual_vsock_cfg = vm_resources.vsock.get().unwrap();
1634 assert_eq!(actual_vsock_cfg.lock().unwrap().id(), VSOCK_DEV_ID);
1635 }
1636
1637 #[test]
1638 fn test_set_net_device() {
1639 let mut vm_resources = default_vm_resources();
1640
1641 let mut new_net_device_cfg = default_net_cfg();
1643 new_net_device_cfg.iface_id = "new_net_if".to_string();
1644 new_net_device_cfg.guest_mac = Some(MacAddr::from_str("01:23:45:67:89:0c").unwrap());
1645 new_net_device_cfg.host_dev_name = "dummy_path2".to_string();
1646 assert_eq!(vm_resources.net_builder.len(), 1);
1647
1648 vm_resources.build_net_device(new_net_device_cfg).unwrap();
1649 assert_eq!(vm_resources.net_builder.len(), 2);
1650 }
1651
1652 #[test]
1653 fn test_set_pmem_device() {
1654 let mut vm_resources = default_vm_resources();
1655
1656 let tmp_file = TempFile::new().unwrap();
1657 tmp_file.as_file().set_len(0x1000).unwrap();
1658 let cfg = PmemConfig {
1659 id: "pmem".to_string(),
1660 path_on_host: tmp_file.as_path().to_str().unwrap().to_string(),
1661 ..Default::default()
1662 };
1663 assert_eq!(vm_resources.pmem.devices.len(), 0);
1664 vm_resources.build_pmem_device(cfg).unwrap();
1665 assert_eq!(vm_resources.pmem.devices.len(), 1);
1666 }
1667}