vmm/vmm_config/
drive.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::VecDeque;
5use std::io;
6use std::sync::{Arc, Mutex};
7
8use serde::{Deserialize, Serialize};
9
10use super::RateLimiterConfig;
11use crate::VmmError;
12use crate::devices::virtio::block::device::Block;
13pub use crate::devices::virtio::block::virtio::device::FileEngineType;
14use crate::devices::virtio::block::{BlockError, CacheType};
15
16/// Errors associated with the operations allowed on a drive.
17#[derive(Debug, thiserror::Error, displaydoc::Display)]
18pub enum DriveError {
19    /// Attempt to add block as a root device while the root device defined as a pmem device
20    AddingSecondRootDevice,
21    /// Unable to create the virtio block device: {0}
22    CreateBlockDevice(BlockError),
23    /// Cannot create RateLimiter: {0}
24    CreateRateLimiter(io::Error),
25    /// Unable to patch the block device: {0} Please verify the request arguments.
26    DeviceUpdate(VmmError),
27    /// A root block device already exists!
28    RootBlockDeviceAlreadyAdded,
29}
30
31/// Use this structure to set up the Block Device before booting the kernel.
32#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
33#[serde(deny_unknown_fields)]
34pub struct BlockDeviceConfig {
35    /// Unique identifier of the drive.
36    pub drive_id: String,
37    /// Part-UUID. Represents the unique id of the boot partition of this device. It is
38    /// optional and it will be used only if the `is_root_device` field is true.
39    pub partuuid: Option<String>,
40    /// If set to true, it makes the current device the root block device.
41    /// Setting this flag to true will mount the block device in the
42    /// guest under /dev/vda unless the partuuid is present.
43    pub is_root_device: bool,
44    /// If set to true, the drive will ignore flush requests coming from
45    /// the guest driver.
46    #[serde(default)]
47    pub cache_type: CacheType,
48
49    // VirtioBlock specific fields
50    /// If set to true, the drive is opened in read-only mode. Otherwise, the
51    /// drive is opened as read-write.
52    pub is_read_only: Option<bool>,
53    /// Path of the drive.
54    pub path_on_host: Option<String>,
55    /// Rate Limiter for I/O operations.
56    pub rate_limiter: Option<RateLimiterConfig>,
57    /// The type of IO engine used by the device.
58    // #[serde(default)]
59    // #[serde(rename = "io_engine")]
60    // pub file_engine_type: FileEngineType,
61    #[serde(rename = "io_engine")]
62    pub file_engine_type: Option<FileEngineType>,
63
64    // VhostUserBlock specific fields
65    /// Path to the vhost-user socket.
66    pub socket: Option<String>,
67}
68
69/// Only provided fields will be updated. I.e. if any optional fields
70/// are missing, they will not be updated.
71#[derive(Debug, Default, PartialEq, Eq, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct BlockDeviceUpdateConfig {
74    /// The drive ID, as provided by the user at creation time.
75    pub drive_id: String,
76
77    // VirtioBlock sepcific fields
78    /// New block file path on the host. Only provided data will be updated.
79    pub path_on_host: Option<String>,
80    /// New rate limiter config.
81    pub rate_limiter: Option<RateLimiterConfig>,
82}
83
84/// Wrapper for the collection that holds all the Block Devices
85#[derive(Debug, Default)]
86pub struct BlockBuilder {
87    /// The list of block devices.
88    /// There can be at most one root block device and it would be the first in the list.
89    // Root Device should be the first in the list whether or not PARTUUID is
90    // specified in order to avoid bugs in case of switching from partuuid boot
91    // scenarios to /dev/vda boot type.
92    pub devices: VecDeque<Arc<Mutex<Block>>>,
93}
94
95impl BlockBuilder {
96    /// Constructor for BlockDevices. It initializes an empty LinkedList.
97    pub fn new() -> Self {
98        Self {
99            devices: Default::default(),
100        }
101    }
102
103    /// Specifies whether there is a root block device already present in the list.
104    pub fn has_root_device(&self) -> bool {
105        // If there is a root device, it would be at the top of the list.
106        if let Some(block) = self.devices.front() {
107            block.lock().expect("Poisoned lock").root_device()
108        } else {
109            false
110        }
111    }
112
113    /// Gets the index of the device with the specified `drive_id` if it exists in the list.
114    fn get_index_of_drive_id(&self, drive_id: &str) -> Option<usize> {
115        self.devices
116            .iter()
117            .position(|b| b.lock().expect("Poisoned lock").id().eq(drive_id))
118    }
119
120    /// Inserts an existing block device.
121    pub fn add_virtio_device(&mut self, block_device: Arc<Mutex<Block>>) {
122        if block_device.lock().expect("Poisoned lock").root_device() {
123            self.devices.push_front(block_device);
124        } else {
125            self.devices.push_back(block_device);
126        }
127    }
128
129    /// Inserts a `Block` in the block devices list using the specified configuration.
130    /// If a block with the same id already exists, it will overwrite it.
131    /// Inserting a secondary root block device will fail.
132    pub fn insert(
133        &mut self,
134        config: BlockDeviceConfig,
135        has_pmem_root: bool,
136    ) -> Result<(), DriveError> {
137        let position = self.get_index_of_drive_id(&config.drive_id);
138        let has_root_device = self.has_root_device();
139        let configured_as_root = config.is_root_device;
140
141        if configured_as_root && has_pmem_root {
142            return Err(DriveError::AddingSecondRootDevice);
143        }
144
145        // Don't allow adding a second root block device.
146        // If the new device cfg is root and not an update to the existing root, fail fast.
147        if configured_as_root && has_root_device && position != Some(0) {
148            return Err(DriveError::RootBlockDeviceAlreadyAdded);
149        }
150
151        let block_dev = Arc::new(Mutex::new(
152            Block::new(config).map_err(DriveError::CreateBlockDevice)?,
153        ));
154
155        // If the id of the drive already exists in the list, the operation is update/overwrite.
156        match position {
157            // New block device.
158            None => {
159                if configured_as_root {
160                    self.devices.push_front(block_dev);
161                } else {
162                    self.devices.push_back(block_dev);
163                }
164            }
165            // Update existing block device.
166            Some(index) => {
167                // Update the slot with the new block.
168                self.devices[index] = block_dev;
169                // Check if the root block device is being updated.
170                if index != 0 && configured_as_root {
171                    // Make sure the root device is on the first position.
172                    self.devices.swap(0, index);
173                }
174            }
175        }
176        Ok(())
177    }
178
179    /// Returns a vec with the structures used to configure the devices.
180    pub fn configs(&self) -> Vec<BlockDeviceConfig> {
181        self.devices
182            .iter()
183            .map(|b| b.lock().unwrap().config())
184            .collect()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use vmm_sys_util::tempfile::TempFile;
191
192    use super::*;
193    use crate::devices::virtio::block::virtio::VirtioBlockError;
194
195    impl PartialEq for DriveError {
196        fn eq(&self, other: &DriveError) -> bool {
197            self.to_string() == other.to_string()
198        }
199    }
200
201    // This implementation is used only in tests.
202    // We cannot directly derive clone because RateLimiter does not implement clone.
203    impl Clone for BlockDeviceConfig {
204        fn clone(&self) -> Self {
205            BlockDeviceConfig {
206                drive_id: self.drive_id.clone(),
207                partuuid: self.partuuid.clone(),
208                is_root_device: self.is_root_device,
209                is_read_only: self.is_read_only,
210                cache_type: self.cache_type,
211
212                path_on_host: self.path_on_host.clone(),
213                rate_limiter: self.rate_limiter,
214                file_engine_type: self.file_engine_type,
215
216                socket: self.socket.clone(),
217            }
218        }
219    }
220
221    #[test]
222    fn test_create_block_devs() {
223        let block_devs = BlockBuilder::new();
224        assert_eq!(block_devs.devices.len(), 0);
225    }
226
227    #[test]
228    fn test_add_non_root_block_device() {
229        let dummy_file = TempFile::new().unwrap();
230        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
231        let dummy_id = String::from("1");
232        let dummy_block_device = BlockDeviceConfig {
233            drive_id: dummy_id.clone(),
234            partuuid: None,
235            is_root_device: false,
236            cache_type: CacheType::Writeback,
237
238            is_read_only: Some(false),
239            path_on_host: Some(dummy_path),
240            rate_limiter: None,
241            file_engine_type: None,
242
243            socket: None,
244        };
245
246        let mut block_devs = BlockBuilder::new();
247        block_devs
248            .insert(dummy_block_device.clone(), false)
249            .unwrap();
250
251        assert!(!block_devs.has_root_device());
252        assert_eq!(block_devs.devices.len(), 1);
253        assert_eq!(block_devs.get_index_of_drive_id(&dummy_id), Some(0));
254
255        let block = block_devs.devices[0].lock().unwrap();
256        assert_eq!(block.id(), dummy_block_device.drive_id);
257        assert_eq!(block.partuuid(), &dummy_block_device.partuuid);
258        assert_eq!(block.read_only(), dummy_block_device.is_read_only.unwrap());
259    }
260
261    #[test]
262    fn test_add_one_root_block_device() {
263        let dummy_file = TempFile::new().unwrap();
264        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
265
266        let dummy_block_device = BlockDeviceConfig {
267            drive_id: String::from("1"),
268            partuuid: None,
269            is_root_device: true,
270            cache_type: CacheType::Unsafe,
271
272            is_read_only: Some(true),
273            path_on_host: Some(dummy_path),
274            rate_limiter: None,
275            file_engine_type: None,
276
277            socket: None,
278        };
279
280        let mut block_devs = BlockBuilder::new();
281        block_devs
282            .insert(dummy_block_device.clone(), false)
283            .unwrap();
284
285        assert!(block_devs.has_root_device());
286        assert_eq!(block_devs.devices.len(), 1);
287        let block = block_devs.devices[0].lock().unwrap();
288        assert_eq!(block.id(), dummy_block_device.drive_id);
289        assert_eq!(block.partuuid(), &dummy_block_device.partuuid);
290        assert_eq!(block.read_only(), dummy_block_device.is_read_only.unwrap());
291    }
292
293    #[test]
294    fn test_add_one_root_block_device_with_pmem_already_as_root() {
295        let dummy_file = TempFile::new().unwrap();
296        let dummy_path = dummy_file.as_path().to_str().unwrap().to_string();
297
298        let dummy_block_device = BlockDeviceConfig {
299            drive_id: String::from("1"),
300            partuuid: None,
301            is_root_device: true,
302            cache_type: CacheType::Unsafe,
303
304            is_read_only: Some(true),
305            path_on_host: Some(dummy_path),
306            rate_limiter: None,
307            file_engine_type: None,
308
309            socket: None,
310        };
311
312        let mut block_devs = BlockBuilder::new();
313        assert!(matches!(
314            block_devs
315                .insert(dummy_block_device.clone(), true)
316                .unwrap_err(),
317            DriveError::AddingSecondRootDevice,
318        ));
319        assert!(!block_devs.has_root_device());
320        assert_eq!(block_devs.devices.len(), 0);
321    }
322
323    #[test]
324    fn test_add_two_root_block_devs() {
325        let dummy_file_1 = TempFile::new().unwrap();
326        let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
327        let root_block_device_1 = BlockDeviceConfig {
328            drive_id: String::from("1"),
329            partuuid: None,
330            is_root_device: true,
331            cache_type: CacheType::Unsafe,
332
333            is_read_only: Some(false),
334            path_on_host: Some(dummy_path_1),
335            rate_limiter: None,
336            file_engine_type: None,
337
338            socket: None,
339        };
340
341        let dummy_file_2 = TempFile::new().unwrap();
342        let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
343        let root_block_device_2 = BlockDeviceConfig {
344            drive_id: String::from("2"),
345            partuuid: None,
346            is_root_device: true,
347            cache_type: CacheType::Unsafe,
348
349            is_read_only: Some(false),
350            path_on_host: Some(dummy_path_2),
351            rate_limiter: None,
352            file_engine_type: None,
353
354            socket: None,
355        };
356
357        let mut block_devs = BlockBuilder::new();
358        block_devs.insert(root_block_device_1, false).unwrap();
359        assert_eq!(
360            block_devs.insert(root_block_device_2, false).unwrap_err(),
361            DriveError::RootBlockDeviceAlreadyAdded
362        );
363    }
364
365    #[test]
366    // Test BlockDevicesConfigs::add when you first add the root device and then the other devices.
367    fn test_add_root_block_device_first() {
368        let dummy_file_1 = TempFile::new().unwrap();
369        let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
370        let root_block_device = BlockDeviceConfig {
371            drive_id: String::from("1"),
372            partuuid: None,
373            is_root_device: true,
374            cache_type: CacheType::Unsafe,
375
376            is_read_only: Some(false),
377            path_on_host: Some(dummy_path_1),
378            rate_limiter: None,
379            file_engine_type: None,
380
381            socket: None,
382        };
383
384        let dummy_file_2 = TempFile::new().unwrap();
385        let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
386        let dummy_block_dev_2 = BlockDeviceConfig {
387            drive_id: String::from("2"),
388            partuuid: None,
389            is_root_device: false,
390            cache_type: CacheType::Unsafe,
391
392            is_read_only: Some(false),
393            path_on_host: Some(dummy_path_2),
394            rate_limiter: None,
395            file_engine_type: None,
396
397            socket: None,
398        };
399
400        let dummy_file_3 = TempFile::new().unwrap();
401        let dummy_path_3 = dummy_file_3.as_path().to_str().unwrap().to_string();
402        let dummy_block_dev_3 = BlockDeviceConfig {
403            drive_id: String::from("3"),
404            partuuid: None,
405            is_root_device: false,
406            cache_type: CacheType::Unsafe,
407
408            is_read_only: Some(false),
409            path_on_host: Some(dummy_path_3),
410            rate_limiter: None,
411            file_engine_type: None,
412
413            socket: None,
414        };
415
416        let mut block_devs = BlockBuilder::new();
417        block_devs.insert(dummy_block_dev_2.clone(), false).unwrap();
418        block_devs.insert(dummy_block_dev_3.clone(), false).unwrap();
419        block_devs.insert(root_block_device.clone(), false).unwrap();
420
421        assert_eq!(block_devs.devices.len(), 3);
422
423        let mut block_iter = block_devs.devices.iter();
424        assert_eq!(
425            block_iter.next().unwrap().lock().unwrap().id(),
426            root_block_device.drive_id
427        );
428        assert_eq!(
429            block_iter.next().unwrap().lock().unwrap().id(),
430            dummy_block_dev_2.drive_id
431        );
432        assert_eq!(
433            block_iter.next().unwrap().lock().unwrap().id(),
434            dummy_block_dev_3.drive_id
435        );
436    }
437
438    #[test]
439    // Test BlockDevicesConfigs::add when you add other devices first and then the root device.
440    fn test_root_block_device_add_last() {
441        let dummy_file_1 = TempFile::new().unwrap();
442        let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
443        let root_block_device = BlockDeviceConfig {
444            drive_id: String::from("1"),
445            partuuid: None,
446            is_root_device: true,
447            cache_type: CacheType::Unsafe,
448
449            is_read_only: Some(false),
450            path_on_host: Some(dummy_path_1),
451            rate_limiter: None,
452            file_engine_type: None,
453
454            socket: None,
455        };
456
457        let dummy_file_2 = TempFile::new().unwrap();
458        let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
459        let dummy_block_dev_2 = BlockDeviceConfig {
460            drive_id: String::from("2"),
461            partuuid: None,
462            is_root_device: false,
463            cache_type: CacheType::Unsafe,
464
465            is_read_only: Some(false),
466            path_on_host: Some(dummy_path_2),
467            rate_limiter: None,
468            file_engine_type: None,
469
470            socket: None,
471        };
472
473        let dummy_file_3 = TempFile::new().unwrap();
474        let dummy_path_3 = dummy_file_3.as_path().to_str().unwrap().to_string();
475        let dummy_block_dev_3 = BlockDeviceConfig {
476            drive_id: String::from("3"),
477            partuuid: None,
478            is_root_device: false,
479            cache_type: CacheType::Unsafe,
480
481            is_read_only: Some(false),
482            path_on_host: Some(dummy_path_3),
483            rate_limiter: None,
484            file_engine_type: None,
485
486            socket: None,
487        };
488
489        let mut block_devs = BlockBuilder::new();
490        block_devs.insert(dummy_block_dev_2.clone(), false).unwrap();
491        block_devs.insert(dummy_block_dev_3.clone(), false).unwrap();
492        block_devs.insert(root_block_device.clone(), false).unwrap();
493
494        assert_eq!(block_devs.devices.len(), 3);
495
496        let mut block_iter = block_devs.devices.iter();
497        // The root device should be first in the list no matter of the order in
498        // which the devices were added.
499        assert_eq!(
500            block_iter.next().unwrap().lock().unwrap().id(),
501            root_block_device.drive_id
502        );
503        assert_eq!(
504            block_iter.next().unwrap().lock().unwrap().id(),
505            dummy_block_dev_2.drive_id
506        );
507        assert_eq!(
508            block_iter.next().unwrap().lock().unwrap().id(),
509            dummy_block_dev_3.drive_id
510        );
511    }
512
513    #[test]
514    fn test_update() {
515        let dummy_file_1 = TempFile::new().unwrap();
516        let dummy_path_1 = dummy_file_1.as_path().to_str().unwrap().to_string();
517        let root_block_device = BlockDeviceConfig {
518            drive_id: String::from("1"),
519            partuuid: None,
520            is_root_device: true,
521            cache_type: CacheType::Unsafe,
522
523            is_read_only: Some(false),
524            path_on_host: Some(dummy_path_1.clone()),
525            rate_limiter: None,
526            file_engine_type: None,
527
528            socket: None,
529        };
530
531        let dummy_file_2 = TempFile::new().unwrap();
532        let dummy_path_2 = dummy_file_2.as_path().to_str().unwrap().to_string();
533        let mut dummy_block_device_2 = BlockDeviceConfig {
534            drive_id: String::from("2"),
535            partuuid: None,
536            is_root_device: false,
537            cache_type: CacheType::Unsafe,
538
539            is_read_only: Some(false),
540            path_on_host: Some(dummy_path_2.clone()),
541            rate_limiter: None,
542            file_engine_type: None,
543
544            socket: None,
545        };
546
547        let mut block_devs = BlockBuilder::new();
548
549        // Add 2 block devices.
550        block_devs.insert(root_block_device, false).unwrap();
551        block_devs
552            .insert(dummy_block_device_2.clone(), false)
553            .unwrap();
554
555        // Get index zero.
556        assert_eq!(
557            block_devs.get_index_of_drive_id(&String::from("1")),
558            Some(0)
559        );
560
561        // Get None.
562        assert!(
563            block_devs
564                .get_index_of_drive_id(&String::from("foo"))
565                .is_none()
566        );
567
568        // Test several update cases using dummy_block_device_2.
569        // Validate `dummy_block_device_2` is already in the list
570        assert!(
571            block_devs
572                .get_index_of_drive_id(&dummy_block_device_2.drive_id)
573                .is_some()
574        );
575        // Update OK.
576        dummy_block_device_2.is_read_only = Some(true);
577        block_devs
578            .insert(dummy_block_device_2.clone(), false)
579            .unwrap();
580
581        let index = block_devs
582            .get_index_of_drive_id(&dummy_block_device_2.drive_id)
583            .unwrap();
584        // Validate update was successful.
585        assert!(block_devs.devices[index].lock().unwrap().read_only());
586
587        // Update with invalid path.
588        let dummy_path_3 = String::from("test_update_3");
589        dummy_block_device_2.path_on_host = Some(dummy_path_3);
590        assert!(matches!(
591            block_devs.insert(dummy_block_device_2.clone(), false),
592            Err(DriveError::CreateBlockDevice(BlockError::VirtioBackend(
593                VirtioBlockError::BackingFile(_, _)
594            )))
595        ));
596
597        // Update with 2 root block devices.
598        dummy_block_device_2.path_on_host = Some(dummy_path_2.clone());
599        dummy_block_device_2.is_root_device = true;
600        assert_eq!(
601            block_devs.insert(dummy_block_device_2, false),
602            Err(DriveError::RootBlockDeviceAlreadyAdded)
603        );
604
605        let root_block_device = BlockDeviceConfig {
606            drive_id: String::from("1"),
607            partuuid: None,
608            is_root_device: true,
609            cache_type: CacheType::Unsafe,
610
611            is_read_only: Some(false),
612            path_on_host: Some(dummy_path_1),
613            rate_limiter: None,
614            file_engine_type: None,
615
616            socket: None,
617        };
618        // Switch roots and add a PARTUUID for the new one.
619        let mut root_block_device_old = root_block_device;
620        root_block_device_old.is_root_device = false;
621        let root_block_device_new = BlockDeviceConfig {
622            drive_id: String::from("2"),
623            partuuid: Some("0eaa91a0-01".to_string()),
624            is_root_device: true,
625            cache_type: CacheType::Unsafe,
626
627            is_read_only: Some(false),
628            path_on_host: Some(dummy_path_2),
629            rate_limiter: None,
630            file_engine_type: None,
631
632            socket: None,
633        };
634
635        block_devs.insert(root_block_device_old, false).unwrap();
636        let root_block_id = root_block_device_new.drive_id.clone();
637        block_devs.insert(root_block_device_new, false).unwrap();
638        assert!(block_devs.has_root_device());
639        // Verify it's been moved to the first position.
640        assert_eq!(block_devs.devices[0].lock().unwrap().id(), root_block_id);
641    }
642
643    #[test]
644    fn test_block_config() {
645        let dummy_file = TempFile::new().unwrap();
646
647        let dummy_block_device = BlockDeviceConfig {
648            drive_id: String::from("1"),
649            partuuid: None,
650            is_root_device: true,
651            cache_type: CacheType::Unsafe,
652
653            is_read_only: Some(true),
654            path_on_host: Some(dummy_file.as_path().to_str().unwrap().to_string()),
655            rate_limiter: None,
656            file_engine_type: Some(FileEngineType::Sync),
657
658            socket: None,
659        };
660
661        let mut block_devs = BlockBuilder::new();
662        block_devs
663            .insert(dummy_block_device.clone(), false)
664            .unwrap();
665
666        let configs = block_devs.configs();
667        assert_eq!(configs.len(), 1);
668        assert_eq!(configs.first().unwrap(), &dummy_block_device);
669    }
670
671    #[test]
672    fn test_add_device() {
673        let mut block_devs = BlockBuilder::new();
674        let backing_file = TempFile::new().unwrap();
675
676        let block_id = "test_id";
677        let config = BlockDeviceConfig {
678            drive_id: block_id.to_string(),
679            partuuid: None,
680            is_root_device: true,
681            cache_type: CacheType::default(),
682
683            is_read_only: Some(true),
684            path_on_host: Some(backing_file.as_path().to_str().unwrap().to_string()),
685            rate_limiter: None,
686            file_engine_type: None,
687
688            socket: None,
689        };
690
691        let block = Block::new(config).unwrap();
692
693        block_devs.add_virtio_device(Arc::new(Mutex::new(block)));
694        assert_eq!(block_devs.devices.len(), 1);
695        assert_eq!(
696            block_devs.devices.pop_back().unwrap().lock().unwrap().id(),
697            block_id
698        );
699    }
700}