Skip to main content

disk_striped/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements the [`DiskIo`] trait for virtual disks backed by multiple raw
5//! block devices.
6
7#![expect(missing_docs)]
8#![forbid(unsafe_code)]
9
10use async_trait::async_trait;
11use disk_backend::Disk;
12use disk_backend::DiskError;
13use disk_backend::DiskIo;
14use disk_backend::UnmapBehavior;
15use disk_backend::resolve::ResolveDiskParameters;
16use disk_backend::resolve::ResolvedDisk;
17use disk_backend_resources::StripedDiskHandle;
18use futures::future::try_join_all;
19use inspect::Inspect;
20use scsi_buffers::RequestBuffers;
21use std::fmt::Debug;
22use thiserror::Error;
23use vm_resource::AsyncResolveResource;
24use vm_resource::ResourceResolver;
25use vm_resource::declare_static_async_resolver;
26use vm_resource::kind::DiskHandleKind;
27
28pub struct StripedDiskResolver;
29declare_static_async_resolver!(StripedDiskResolver, (DiskHandleKind, StripedDiskHandle));
30
31#[async_trait]
32impl AsyncResolveResource<DiskHandleKind, StripedDiskHandle> for StripedDiskResolver {
33    type Output = ResolvedDisk;
34    type Error = anyhow::Error;
35
36    async fn resolve(
37        &self,
38        resolver: &ResourceResolver,
39        rsrc: StripedDiskHandle,
40        input: ResolveDiskParameters<'_>,
41    ) -> Result<Self::Output, Self::Error> {
42        let disks = try_join_all(
43            rsrc.devices
44                .into_iter()
45                .map(async |device| resolver.resolve(device, input).await.map(|r| r.0)),
46        )
47        .await?;
48        Ok(ResolvedDisk::new(StripedDisk::new(
49            disks,
50            rsrc.chunk_size_in_bytes,
51            rsrc.logic_sector_count,
52        )?)?)
53    }
54}
55
56#[derive(Debug, Inspect)]
57pub struct StripedDisk {
58    #[inspect(iter_by_index)]
59    block_devices: Vec<Disk>,
60    sector_size: u32,
61    sector_shift: u32,
62    sector_count: u64,
63    read_only: bool,
64    sector_count_per_chunk: u32,
65    unmap_behavior: UnmapBehavior,
66}
67
68const CHUNK_SIZE_128K: u32 = 128 * 1024;
69
70#[derive(Error, Debug)]
71pub enum NewDeviceError {
72    #[error("Can't create a striping disk since the input device list is empty")]
73    EmptyDeviceList,
74    #[error(
75        "The files are not compatible to form a striping disk: sector_size-{sector_size} != cur_sector_size-{cur_sector_size} OR sector_count-{sector_count} != cur_sector_count-{cur_sector_count}"
76    )]
77    DeviceNotCompatible {
78        sector_size: u32,
79        cur_sector_size: u32,
80        sector_count: u64,
81        cur_sector_count: u64,
82    },
83    #[error(
84        "Invalid chunk size: chunk_size_in_bytes-{0} is not multiple of logical_sector_size-{1}"
85    )]
86    InvalidChunkSize(u32, u32),
87    #[error(
88        "logic_sector_count is out of range: logic_sector_count.unwrap_or(total_sector_count)-{0} > total_sector_count-{1}"
89    )]
90    InvalidLogicSectorCount(u64, u64),
91    #[error(
92        "The striping disk size must be multiple of chunk size * number of disks. logic_sector_count-{0} != {1}."
93    )]
94    InvalidStripingDiskSize(u64, u64),
95}
96
97#[derive(Debug, Error)]
98#[error("error in lower disk {index}")]
99struct LowerError {
100    index: usize,
101    #[source]
102    err: DiskError,
103}
104
105impl From<LowerError> for DiskError {
106    fn from(err: LowerError) -> Self {
107        // Treat all lower disk errors as IO errors--we don't currently handle
108        // specific errors from lower disks in a striped configuration.
109        DiskError::Io(std::io::Error::other(err))
110    }
111}
112
113struct Chunk {
114    // The index of the disk where the chunk is in.
115    disk_index: usize,
116    // The chunk starting sector and offset on the disk.
117    disk_sector_index: u64,
118    // The chunk length. It can be less than the sector_count_per_chunk for the
119    // first and last chunk.
120    chunk_length_in_sectors: u32,
121}
122
123impl StripedDisk {
124    fn get_chunk_iter(
125        &self,
126        start_sector: u64,
127        end_sector: u64,
128    ) -> Result<impl 'static + Iterator<Item = Chunk>, DiskError> {
129        if end_sector > self.sector_count {
130            return Err(DiskError::IllegalBlock);
131        }
132
133        let start_chunk_index = start_sector / self.sector_count_per_chunk as u64;
134        let end_chunk_index = end_sector.div_ceil(self.sector_count_per_chunk as u64);
135
136        // Use `Iterator::map` on `Range` so that the iterator implements
137        // `TrustedLen`, which ensures `Vec::from_iter` (i.e., `.collect()`) is
138        // optimized to use a single allocation and in-place construction.
139        let sector_count_per_chunk = self.sector_count_per_chunk;
140        let disk_count = self.block_devices.len();
141        let iter = (start_chunk_index..end_chunk_index).map(move |i| {
142            // The sector can be in middle of a chunk for the first chunk.
143            let sector_offset_in_chunk = if i == start_chunk_index {
144                start_sector % sector_count_per_chunk as u64
145            } else {
146                0
147            };
148
149            let disk_index = (i % (disk_count as u64)) as usize;
150            let disk_sector_index =
151                (i / disk_count as u64) * sector_count_per_chunk as u64 + sector_offset_in_chunk;
152
153            // The disk end offset can be in middle of the chunk for the last
154            // chunk.
155            let disk_end_offset_in_sectors = (i / disk_count as u64)
156                * sector_count_per_chunk as u64
157                + if i == end_chunk_index - 1 {
158                    end_sector - sector_count_per_chunk as u64 * i
159                } else {
160                    sector_count_per_chunk as u64
161                };
162
163            // The chunk length can be less than the sector_count_per_chunk for
164            // the first and last chunk.
165            let chunk_length_in_sectors = (disk_end_offset_in_sectors - disk_sector_index) as u32;
166
167            Chunk {
168                disk_index,
169                disk_sector_index,
170                chunk_length_in_sectors,
171            }
172        });
173
174        Ok(iter)
175    }
176}
177
178impl StripedDisk {
179    /// Constructs a new `StripedDisk` backed by the vector of file.
180    ///
181    /// # Arguments
182    /// * `devices` - The backing devices opened for raw access.
183    /// * 'chunk_size_in_bytes' - The chunk size of the striped disk, and the default value is 128K.
184    /// * 'logic_sector_count' - The sector count of the striped disk, and the default value is the sum of the sector count of the backing devices.
185    ///
186    pub fn new(
187        devices: Vec<Disk>,
188        chunk_size_in_bytes: Option<u32>,
189        logic_sector_count: Option<u64>,
190    ) -> Result<Self, NewDeviceError> {
191        if devices.is_empty() {
192            return Err(NewDeviceError::EmptyDeviceList);
193        }
194
195        let mut total_sector_count = 0;
196        let sector_size = devices[0].sector_size();
197        let sector_count = devices[0].sector_count();
198        let read_only = devices[0].is_read_only();
199        let chunk_size_in_bytes = chunk_size_in_bytes.unwrap_or(CHUNK_SIZE_128K);
200        if chunk_size_in_bytes == 0 || !chunk_size_in_bytes.is_multiple_of(sector_size) {
201            return Err(NewDeviceError::InvalidChunkSize(
202                chunk_size_in_bytes,
203                sector_size,
204            ));
205        }
206
207        let sector_count_per_chunk = (chunk_size_in_bytes / sector_size) as u64;
208
209        for device in &devices {
210            let cur_sector_size = device.sector_size();
211            let cur_sector_count = device.sector_count();
212            let cur_read_only = device.is_read_only();
213
214            if sector_size != cur_sector_size
215                || sector_count != cur_sector_count
216                || read_only != cur_read_only
217            {
218                return Err(NewDeviceError::DeviceNotCompatible {
219                    sector_size,
220                    cur_sector_size,
221                    sector_count,
222                    cur_sector_count,
223                });
224            }
225
226            total_sector_count +=
227                (cur_sector_count / sector_count_per_chunk) * sector_count_per_chunk;
228        }
229
230        if total_sector_count % (devices.len() as u64 * sector_count_per_chunk) != 0 {
231            return Err(NewDeviceError::InvalidStripingDiskSize(
232                total_sector_count,
233                devices.len() as u64 * sector_count_per_chunk,
234            ));
235        }
236
237        let logic_sector_count = logic_sector_count.unwrap_or(total_sector_count);
238        if logic_sector_count > total_sector_count {
239            return Err(NewDeviceError::InvalidLogicSectorCount(
240                logic_sector_count,
241                total_sector_count,
242            ));
243        }
244
245        if !logic_sector_count.is_multiple_of(devices.len() as u64 * sector_count_per_chunk) {
246            return Err(NewDeviceError::InvalidStripingDiskSize(
247                logic_sector_count,
248                devices.len() as u64 * sector_count_per_chunk,
249            ));
250        }
251
252        // Unify the unmap behavior of all devices. If all disks specify the
253        // same behavior, use it. Otherwise, report unspecified behavior and
254        // send unmap to all disks.
255        let unmap_behavior = devices.iter().fold(UnmapBehavior::Zeroes, |rest, d| {
256            match (rest, d.unmap_behavior()) {
257                (UnmapBehavior::Zeroes, UnmapBehavior::Zeroes) => UnmapBehavior::Zeroes,
258                (UnmapBehavior::Ignored, UnmapBehavior::Ignored) => UnmapBehavior::Ignored,
259                _ => UnmapBehavior::Unspecified,
260            }
261        });
262
263        let stripped_block_device = StripedDisk {
264            block_devices: devices,
265            sector_size,
266            sector_shift: sector_size.trailing_zeros(),
267            sector_count: logic_sector_count,
268            read_only,
269            sector_count_per_chunk: (sector_count_per_chunk as u32),
270            unmap_behavior,
271        };
272
273        tracing::info!("stripped block device start completed.");
274        Ok(stripped_block_device)
275    }
276}
277
278impl DiskIo for StripedDisk {
279    fn disk_type(&self) -> &str {
280        "striped"
281    }
282
283    fn sector_count(&self) -> u64 {
284        self.sector_count
285    }
286
287    fn sector_size(&self) -> u32 {
288        self.sector_size
289    }
290
291    fn is_read_only(&self) -> bool {
292        self.read_only
293    }
294
295    fn disk_id(&self) -> Option<[u8; 16]> {
296        None
297    }
298
299    fn physical_sector_size(&self) -> u32 {
300        self.block_devices
301            .iter()
302            .map(|d| d.physical_sector_size())
303            .max()
304            .unwrap()
305    }
306
307    fn is_fua_respected(&self) -> bool {
308        self.block_devices.iter().all(|d| d.is_fua_respected())
309    }
310
311    async fn eject(&self) -> Result<(), DiskError> {
312        let futures = self.block_devices.iter().map(|disk| disk.eject()).collect();
313        await_all_and_check(futures).await?;
314        Ok(())
315    }
316
317    async fn read_vectored(
318        &self,
319        buffers: &RequestBuffers<'_>,
320        start_sector: u64,
321    ) -> Result<(), DiskError> {
322        let buf_total_size = buffers.len();
323        let end_sector = start_sector + ((buf_total_size as u64) >> self.sector_shift);
324        let chunk_iter = self.get_chunk_iter(start_sector, end_sector)?;
325
326        let mut cur_buf_offset: usize = 0;
327        let all_futures = chunk_iter
328            .map(|chunk| {
329                let disk = &self.block_devices[chunk.disk_index];
330
331                let buf_len = (chunk.chunk_length_in_sectors as usize) << self.sector_shift;
332
333                let sub_buffers = buffers.subrange(cur_buf_offset, buf_len);
334                cur_buf_offset += buf_len;
335
336                async move {
337                    disk.read_vectored(&sub_buffers, chunk.disk_sector_index)
338                        .await
339                        .map_err(|err| LowerError {
340                            index: chunk.disk_index,
341                            err,
342                        })
343                }
344            })
345            .collect();
346
347        assert_eq!(cur_buf_offset, buf_total_size);
348
349        await_all_and_check(all_futures).await?;
350        Ok(())
351    }
352
353    async fn write_vectored(
354        &self,
355        buffers: &RequestBuffers<'_>,
356        start_sector: u64,
357        fua: bool,
358    ) -> Result<(), DiskError> {
359        let buf_total_size = buffers.len();
360        let end_sector = start_sector + ((buf_total_size as u64) >> self.sector_shift);
361        let chunk_iter = self.get_chunk_iter(start_sector, end_sector)?;
362
363        let mut cur_buf_offset: usize = 0;
364        let all_futures = chunk_iter
365            .map(|chunk| {
366                let disk = &self.block_devices[chunk.disk_index];
367
368                let buf_len = (chunk.chunk_length_in_sectors as usize) << self.sector_shift;
369
370                let sub_buffers = buffers.subrange(cur_buf_offset, buf_len);
371                cur_buf_offset += buf_len;
372
373                async move {
374                    disk.write_vectored(&sub_buffers, chunk.disk_sector_index, fua)
375                        .await
376                        .map_err(|err| LowerError {
377                            index: chunk.disk_index,
378                            err,
379                        })
380                }
381            })
382            .collect();
383
384        assert_eq!(cur_buf_offset, buf_total_size);
385
386        await_all_and_check(all_futures).await?;
387        Ok(())
388    }
389
390    async fn sync_cache(&self) -> Result<(), DiskError> {
391        let all_futures = self
392            .block_devices
393            .iter()
394            .enumerate()
395            .map(|(disk_index, disk)| async move {
396                disk.sync_cache().await.map_err(|err| LowerError {
397                    index: disk_index,
398                    err,
399                })
400            })
401            .collect();
402        await_all_and_check(all_futures).await?;
403        Ok(())
404    }
405
406    async fn unmap(
407        &self,
408        start_sector: u64,
409        sector_count: u64,
410        block_level_only: bool,
411    ) -> Result<(), DiskError> {
412        let end_sector = start_sector + sector_count;
413        let chunk_iter = match self.get_chunk_iter(start_sector, end_sector) {
414            Ok(iter) => iter,
415            Err(err) => {
416                return Err(err);
417            }
418        };
419
420        // Create a vector to group chunks by disk index
421        let mut disk_sectors: Vec<(u64, u64)> = vec![(0, 0); self.block_devices.len()];
422        let mut trimmed_sectors: u64 = 0;
423
424        for chunk in chunk_iter {
425            let start = chunk.disk_sector_index;
426            let length = chunk.chunk_length_in_sectors as u64;
427            let (disk_start, disk_len) = &mut disk_sectors[chunk.disk_index];
428            if *disk_len == 0 {
429                *disk_start = start; // set the start of the unmap operation
430            }
431            *disk_len += length; // add the length to the total
432
433            trimmed_sectors += length;
434        }
435
436        assert_eq!(trimmed_sectors, sector_count);
437
438        // Create a future for each disk's combined unmap operations
439        let all_futures = disk_sectors
440            .iter()
441            .enumerate()
442            .map(|(disk_index, &(start, length))| {
443                let disk = &self.block_devices[disk_index];
444                async move {
445                    if length > 0 {
446                        disk.unmap(start, length, block_level_only).await
447                    } else {
448                        Ok(())
449                    }
450                }
451            })
452            .collect();
453
454        await_all_and_check(all_futures).await?;
455        Ok(())
456    }
457
458    fn unmap_behavior(&self) -> UnmapBehavior {
459        self.unmap_behavior
460    }
461
462    fn optimal_unmap_sectors(&self) -> u32 {
463        self.block_devices
464            .iter()
465            .map(|disk| disk.optimal_unmap_sectors())
466            .max()
467            .unwrap_or(1)
468    }
469}
470
471/// Waits for all IOs to complete and checks for errors.
472///
473/// Use `JoinAll` to wait for all IOs even if one fails. This is necessary to
474/// avoid dropping IOs while they are in flight.
475async fn await_all_and_check<F, E>(futures: futures::future::JoinAll<F>) -> Result<(), E>
476where
477    F: Future<Output = Result<(), E>>,
478{
479    for result in futures.await {
480        result?;
481    }
482    Ok(())
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use guestmem::GuestMemory;
489    use hvdef::HV_PAGE_SIZE;
490    use pal_async::async_test;
491    use scsi_buffers::OwnedRequestBuffers;
492
493    const CONFORMANCE_DISK_SIZE: u64 = 1024 * 1024;
494
495    fn conformance_disk() -> Disk {
496        let devices = (0..2)
497            .map(|_| disklayer_ram::ram_disk(CONFORMANCE_DISK_SIZE, false).unwrap())
498            .collect();
499        Disk::new(StripedDisk::new(devices, None, None).unwrap()).unwrap()
500    }
501
502    #[async_test]
503    async fn sector_range_conformance() {
504        storage_tests::sector_range::test_disk_sector_range_conformance(&conformance_disk()).await;
505    }
506
507    /// `StripedDisk` computes `end_sector = start_sector + (len >> sector_shift)`
508    /// before handing it to `get_chunk_iter`, which is the function that
509    /// actually range checks. The addition is unchecked, so a request near
510    /// `u64::MAX` either panics or wraps to a small in-range `end_sector` that
511    /// passes the check.
512    #[async_test]
513    async fn end_sector_does_not_wrap() {
514        let disk = conformance_disk();
515        let mem = GuestMemory::allocate(1024);
516        let r = disk
517            .read_vectored(
518                &OwnedRequestBuffers::linear(0, 1024, true).buffer(&mem),
519                u64::MAX - 1,
520            )
521            .await;
522        assert!(matches!(r, Err(DiskError::IllegalBlock)), "{r:?}");
523    }
524
525    fn new_strip_device(
526        disk_count: u8,
527        disk_size_in_bytes: Option<u64>,
528        chunk_size_in_bytes: Option<u32>,
529        logic_sector_count: Option<u64>,
530    ) -> StripedDisk {
531        let mut devices = Vec::new();
532
533        for _i in 0..disk_count {
534            let ramdisk =
535                disklayer_ram::ram_disk(disk_size_in_bytes.unwrap_or(1024 * 1024 * 64), false)
536                    .unwrap();
537            devices.push(ramdisk);
538        }
539
540        StripedDisk::new(devices, chunk_size_in_bytes, logic_sector_count).unwrap()
541    }
542
543    fn create_guest_mem(size: usize) -> GuestMemory {
544        let mem = GuestMemory::allocate(size);
545
546        let mut index: usize = 0;
547        while index < size - 3 {
548            mem.write_at(
549                index as u64,
550                &[
551                    (index % 255) as u8,
552                    ((index >> 8) % 255) as u8,
553                    ((index >> 16) % 255) as u8,
554                    ((index >> 24) % 255) as u8,
555                ],
556            )
557            .unwrap();
558
559            index += 4;
560        }
561
562        mem
563    }
564
565    async fn validate_async_striping_disk_ios(
566        disk: &StripedDisk,
567        start_sectors: &[u64],
568        offset: &[usize],
569        length: usize,
570        write_gpns: &[u64],
571        read_gpns: &[u64],
572    ) {
573        for (start_sector, offset) in start_sectors.iter().zip(offset) {
574            validate_async_striping_disk_io(
575                disk,
576                *start_sector,
577                *offset,
578                length,
579                write_gpns,
580                read_gpns,
581            )
582            .await;
583        }
584    }
585
586    /// Validate the async strip disk I/O.
587    ///
588    /// # Arguments
589    /// * `disk` - The strip block device.
590    /// * `start_sector` - The sector index where the I/O shall start.
591    /// * `offset` - The I/O buffer offset.
592    /// * `length` - The total I/O length.
593    /// * `write_gpns` - The write GPN index.
594    /// * `read_gpns` - The read GPN index.
595    ///
596    async fn validate_async_striping_disk_io(
597        disk: &StripedDisk,
598        start_sector: u64,
599        offset: usize,
600        length: usize,
601        write_gpns: &[u64],
602        read_gpns: &[u64],
603    ) {
604        let page_count = (offset + length).div_ceil(HV_PAGE_SIZE as usize);
605        // Create continuous guest memory pages and initialize them with random data.
606        let guest_mem = create_guest_mem(page_count * 2 * HV_PAGE_SIZE as usize);
607        assert_eq!(write_gpns.len(), page_count);
608        assert_eq!(read_gpns.len(), page_count);
609
610        // Get the write buffer from guest memory, which has random data.
611        let write_buffers = OwnedRequestBuffers::new_unaligned(write_gpns, offset, length);
612        // Write the random data to disk.
613        disk.write_vectored(&write_buffers.buffer(&guest_mem), start_sector, false)
614            .await
615            .unwrap();
616
617        disk.sync_cache().await.unwrap();
618
619        // Get the read buffer from guest memory, which has random data.
620        let read_buffers = OwnedRequestBuffers::new_unaligned(read_gpns, offset, length);
621        // Read the data from disk back to read buffers.
622        disk.read_vectored(&read_buffers.buffer(&guest_mem), start_sector)
623            .await
624            .unwrap();
625
626        // Validate if the source and target match.
627        let mut source = vec![0u8; page_count * HV_PAGE_SIZE as usize];
628        guest_mem.read_at(0, &mut source).unwrap();
629
630        let mut target = vec![255u8; page_count * HV_PAGE_SIZE as usize];
631        guest_mem
632            .read_at(page_count as u64 * HV_PAGE_SIZE, &mut target)
633            .unwrap();
634
635        assert_eq!(
636            source[offset..(offset + length - 1)],
637            target[offset..(offset + length - 1)]
638        );
639
640        // async_trim test
641        // Since the discard function doesn't trim the file content, the test doesn't check if the file content is ZERO after the trim.
642        disk.unmap(
643            start_sector,
644            (length / disk.sector_size() as usize) as u64,
645            true,
646        )
647        .await
648        .unwrap();
649    }
650
651    #[async_test]
652    async fn run_async_striping_disk_io() {
653        // Create a striping disk with two disks, set the chunk size to 4K and total size to 256K.
654        let disk = new_strip_device(2, Some(128 * 1024), Some(4096), None);
655        assert_eq!(disk.sector_size, 512);
656        assert_eq!(disk.sector_count_per_chunk, 4096 / 512);
657        assert_eq!(disk.sector_count(), 128 * 1024 * 2 / 512);
658
659        // Read 1K data from the beginning, middle, and end of the disk using paged aligned buffers.
660        validate_async_striping_disk_ios(
661            &disk,
662            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 2],
663            &[0, 0, 0],
664            1024,
665            &[0],
666            &[1],
667        )
668        .await;
669
670        // Read 512 bytes data from the beginning, middle, and end of the disk using aligned buffers.
671        validate_async_striping_disk_ios(
672            &disk,
673            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 2],
674            &[0, 0, 0],
675            512,
676            &[0],
677            &[1],
678        )
679        .await;
680
681        // Read 16K data from the beginning, middle, and end of the disk using paged aligned buffers.
682        validate_async_striping_disk_ios(
683            &disk,
684            &[0, disk.sector_count() / 2 - 16, disk.sector_count() - 32],
685            &[0, 0, 0],
686            16 * 1024,
687            &[0, 1, 2, 3],
688            &[4, 5, 6, 7],
689        )
690        .await;
691
692        // Read 512 bytes data from the beginning, middle, and end of the disk using un-aligned buffers.
693        validate_async_striping_disk_ios(
694            &disk,
695            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 4],
696            &[512, 513, 1028],
697            512,
698            &[0],
699            &[1],
700        )
701        .await;
702
703        // Read 5K data from the beginning, middle, and end of the disk using un-aligned buffers.
704        validate_async_striping_disk_ios(
705            &disk,
706            &[0, disk.sector_count() / 2 - 5, disk.sector_count() - 10],
707            &[512, 513, 1028],
708            5 * 1024,
709            &[0, 1],
710            &[2, 3],
711        )
712        .await;
713    }
714
715    #[async_test]
716    async fn run_async_128k_striping_disk_io() {
717        // Create a striping disk with four disks, set the chunk size to 128K and total size to 4M.
718        let disk = new_strip_device(4, Some(1024 * 1024), Some(128 * 1024), None);
719        assert_eq!(disk.sector_size, 512);
720        assert_eq!(disk.sector_count_per_chunk, 128 * 1024 / 512);
721        assert_eq!(disk.sector_count(), 1024 * 1024 * 4 / 512);
722
723        // Read 1K data from the beginning, middle, and end of the disk using paged aligned buffers.
724        validate_async_striping_disk_ios(
725            &disk,
726            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 2],
727            &[0, 0, 0],
728            1024,
729            &[0],
730            &[1],
731        )
732        .await;
733
734        // Read 512 bytes data from the beginning, middle, and end of the disk using aligned buffers.
735        validate_async_striping_disk_ios(
736            &disk,
737            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 2],
738            &[0, 0, 0],
739            512,
740            &[0],
741            &[1],
742        )
743        .await;
744
745        // Read 256K data from the beginning, middle, and end of the disk using paged aligned buffers.
746        let mut write_gpns: [u64; 256 * 1024 / HV_PAGE_SIZE as usize] =
747            [0; 256 * 1024 / HV_PAGE_SIZE as usize];
748        for (i, write_gpn) in write_gpns.iter_mut().enumerate() {
749            *write_gpn = i as u64;
750        }
751
752        let mut read_gpns: [u64; 256 * 1024 / HV_PAGE_SIZE as usize] =
753            [0; 256 * 1024 / HV_PAGE_SIZE as usize];
754        for (i, read_gpn) in read_gpns.iter_mut().enumerate() {
755            *read_gpn = (i + write_gpns.len()) as u64;
756        }
757
758        validate_async_striping_disk_ios(
759            &disk,
760            &[0, disk.sector_count() / 2 - 256, disk.sector_count() - 512],
761            &[0, 0, 0],
762            256 * 1024,
763            &write_gpns,
764            &read_gpns,
765        )
766        .await;
767
768        // Read 9K data from the beginning, middle, and end of the disk using un-aligned buffers.
769        validate_async_striping_disk_ios(
770            &disk,
771            &[0, disk.sector_count() / 2 - 9, disk.sector_count() - 18],
772            &[512, 513, 1028],
773            9 * 1024,
774            &[0, 1, 2],
775            &[3, 4, 5],
776        )
777        .await;
778
779        // Read 512 bytes data from the beginning, middle, and end of the disk using un-aligned buffers.
780        validate_async_striping_disk_ios(
781            &disk,
782            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 4],
783            &[512, 513, 1028],
784            512,
785            &[0],
786            &[1],
787        )
788        .await;
789    }
790
791    #[async_test]
792    async fn run_async_64k_striping_disk_io() {
793        // Create a striping disk with thirty two disks, set the chunk size to 64K and total size to 32M.
794        let disk = new_strip_device(32, Some(1024 * 1024), Some(64 * 1024), None);
795        assert_eq!(disk.sector_size, 512);
796        assert_eq!(disk.sector_count_per_chunk, 64 * 1024 / 512);
797        assert_eq!(disk.sector_count(), 1024 * 1024 * 32 / 512);
798
799        // Read 1K data from the beginning, middle, and end of the disk using paged aligned buffers.
800        validate_async_striping_disk_ios(
801            &disk,
802            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 2],
803            &[0, 0, 0],
804            1024,
805            &[0],
806            &[1],
807        )
808        .await;
809
810        // Read 512 bytes data from the beginning, middle, and end of the disk using aligned buffers.
811        validate_async_striping_disk_ios(
812            &disk,
813            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 1],
814            &[0, 0, 0],
815            512,
816            &[0],
817            &[1],
818        )
819        .await;
820
821        // Read 256K data from the beginning, middle, and end of the disk using paged aligned buffers.
822        let mut write_gpns: [u64; 256 * 1024 / HV_PAGE_SIZE as usize] =
823            [0; 256 * 1024 / HV_PAGE_SIZE as usize];
824        for (i, write_gpn) in write_gpns.iter_mut().enumerate() {
825            *write_gpn = i as u64;
826        }
827
828        let mut read_gpns: [u64; 256 * 1024 / HV_PAGE_SIZE as usize] =
829            [0; 256 * 1024 / HV_PAGE_SIZE as usize];
830        for (i, read_gpn) in read_gpns.iter_mut().enumerate() {
831            *read_gpn = (i + write_gpns.len()) as u64;
832        }
833
834        validate_async_striping_disk_ios(
835            &disk,
836            &[0, disk.sector_count() / 2 - 256, disk.sector_count() - 512],
837            &[0, 0, 0],
838            256 * 1024,
839            &write_gpns,
840            &read_gpns,
841        )
842        .await;
843
844        // Read 9K data from the beginning, middle, and end of the disk using un-aligned buffers.
845        validate_async_striping_disk_ios(
846            &disk,
847            &[0, disk.sector_count() / 2 - 9, disk.sector_count() - 18],
848            &[512, 513, 1028],
849            9 * 1024,
850            &[0, 1, 2],
851            &[3, 4, 5],
852        )
853        .await;
854
855        // Read 512 bytes data from the beginning, middle, and end of the disk using un-aligned buffers.
856        validate_async_striping_disk_ios(
857            &disk,
858            &[0, disk.sector_count() / 2 - 1, disk.sector_count() - 4],
859            &[512, 513, 1028],
860            512,
861            &[0],
862            &[1],
863        )
864        .await;
865    }
866
867    #[async_test]
868    async fn run_async_striping_disk_negative() {
869        // Creating striping disk using incompatible files shall fail.
870        let mut devices = Vec::new();
871        for i in 0..2 {
872            let ramdisk = disklayer_ram::ram_disk(1024 * 1024 + i * 64 * 1024, false).unwrap();
873            devices.push(ramdisk);
874        }
875
876        StripedDisk::new(devices, None, None)
877            .expect_err("Expected failure because of incompatible files");
878
879        // Creating striping disk using invalid chunk size shall fail.
880        let mut block_devices = Vec::new();
881        for _ in 0..2 {
882            let ramdisk = disklayer_ram::ram_disk(1024 * 1024, false).unwrap();
883            block_devices.push(ramdisk);
884        }
885
886        StripedDisk::new(block_devices, Some(4 * 1024 + 1), None)
887            .expect_err("Expected failure since chunk size is invalid");
888
889        // Creating striping disk using invalid logic sector count shall fail.
890        let mut block_devices = Vec::new();
891        for _ in 0..2 {
892            let ramdisk = disklayer_ram::ram_disk(1024 * 1024, false).unwrap();
893            block_devices.push(ramdisk);
894        }
895
896        StripedDisk::new(
897            block_devices,
898            Some(4 * 1024),
899            Some(1024 * 1024 * 2 / 512 + 1),
900        )
901        .expect_err("Expected failure since logic sector count is invalid");
902
903        // Create a simple striping disk.
904        let mut block_devices = Vec::new();
905        for _ in 0..2 {
906            let ramdisk = disklayer_ram::ram_disk(1024 * 1024, false).unwrap();
907            block_devices.push(ramdisk);
908        }
909
910        let disk = StripedDisk::new(block_devices, Some(8 * 1024), None)
911            .expect("Failed to create striping disk");
912
913        assert_eq!(disk.sector_size, 512);
914        assert_eq!(disk.sector_count_per_chunk, 8 * 1024 / 512);
915        assert_eq!(disk.sector_count(), 1024 * 1024 * 2 / 512);
916
917        // write 1 sector off shall be caught.
918        let guest_mem = create_guest_mem(2 * HV_PAGE_SIZE as usize);
919        let write_buffers = OwnedRequestBuffers::new(&[0]);
920        let buf_sector_count = write_buffers.len().div_ceil(disk.sector_size as usize);
921        disk.write_vectored(
922            &write_buffers.buffer(&guest_mem),
923            disk.sector_count() - buf_sector_count as u64 + 1,
924            false,
925        )
926        .await
927        .expect_err("Expected write failure because of 1 sector off");
928
929        // read 1 sector off shall be caught.
930        let guest_mem = create_guest_mem(2 * HV_PAGE_SIZE as usize);
931        let read_buffers = OwnedRequestBuffers::new(&[1]);
932        let buf_sector_count = read_buffers.len().div_ceil(disk.sector_size as usize);
933        disk.read_vectored(
934            &read_buffers.buffer(&guest_mem),
935            disk.sector_count() - buf_sector_count as u64 + 1,
936        )
937        .await
938        .expect_err("Expected read failure because of 1 sector off");
939
940        disk.unmap(disk.sector_count() - 2, 3, true)
941            .await
942            .expect_err("Expected unmap failure because of 1 sector off");
943    }
944
945    #[async_test]
946    async fn run_async_striping_disk_unmap() {
947        let disk = new_strip_device(2, Some(128 * 1024 * 1024), Some(4096), None);
948        assert_eq!(disk.sector_size, 512);
949        assert_eq!(disk.sector_count_per_chunk, 4096 / 512);
950        assert_eq!(disk.sector_count(), 128 * 1024 * 1024 * 2 / 512); //sector_count =  524288
951        disk.unmap(0, 1, false).await.unwrap();
952        disk.unmap(0, 524288, false).await.unwrap();
953        disk.unmap(8, 524280, false).await.unwrap();
954        disk.unmap(disk.sector_count() / 2 - 512, 1024, false)
955            .await
956            .unwrap();
957        disk.unmap(disk.sector_count() - 1024, 1024, false)
958            .await
959            .unwrap();
960        disk.unmap(0, disk.sector_count() / 2, false).await.unwrap();
961        disk.unmap(disk.sector_count() / 2, disk.sector_count() / 2, false)
962            .await
963            .unwrap();
964        disk.unmap(disk.sector_count() / 2 - 500, 1000, false)
965            .await
966            .unwrap();
967        //this one should fail, out of bounds
968        assert!(disk.unmap(disk.sector_count(), 100, false).await.is_err());
969        //unmap zero sector
970        disk.unmap(1000, 0, false).await.unwrap();
971    }
972}